@trishchuk/coolors-mcp 1.0.0 → 1.0.1
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 +2 -6
- package/.github/ISSUE_TEMPLATE/bug_report.md +20 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -8
- package/.github/pull_request_template.md +33 -8
- package/.github/workflows/ci.yml +97 -97
- package/.github/workflows/deploy-docs.yml +9 -9
- package/.github/workflows/release.yml +15 -15
- package/README.md +26 -1
- package/TOOLS_UK.md +233 -0
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +30 -12
- package/docs/.vitepress/cache/deps/_metadata.json +1 -1
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -6
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +2543 -1612
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +3508 -2529
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +1902 -1003
- package/docs/.vitepress/cache/deps/cytoscape.js +13303 -7347
- package/docs/.vitepress/cache/deps/dayjs.js +494 -272
- package/docs/.vitepress/cache/deps/debug.js +82 -38
- package/docs/.vitepress/cache/deps/prismjs.js +444 -272
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +80 -73
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +93 -62
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +13 -13
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +34 -27
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +20 -17
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +75 -41
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +2005 -1438
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +2 -2
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +566 -229
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +382 -270
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js +334 -125
- package/docs/.vitepress/cache/deps/vue.js +2 -2
- package/docs/.vitepress/components/ClientGrid.vue +9 -3
- package/docs/.vitepress/components/CodeBlock.vue +51 -44
- package/docs/.vitepress/components/ConfigModal.vue +151 -67
- package/docs/.vitepress/components/DiagramModal.vue +186 -154
- package/docs/.vitepress/components/TroubleshootingModal.vue +101 -96
- package/docs/.vitepress/config.js +171 -141
- package/docs/.vitepress/theme/FundingLayout.vue +65 -54
- package/docs/.vitepress/theme/Layout.vue +21 -21
- package/docs/.vitepress/theme/components/AdBanner.vue +73 -52
- package/docs/.vitepress/theme/components/AdPlaceholder.vue +3 -3
- package/docs/.vitepress/theme/components/FundingEffects.vue +77 -53
- package/docs/.vitepress/theme/components/FundingHero.vue +78 -63
- package/docs/.vitepress/theme/components/SupportSection.vue +106 -89
- package/docs/.vitepress/theme/custom-app.css +19 -12
- package/docs/.vitepress/theme/custom.css +33 -25
- package/docs/.vitepress/theme/index.js +19 -16
- package/docs/concepts/accessibility.md +59 -47
- package/docs/concepts/color-spaces.md +28 -6
- package/docs/concepts/distance-metrics.md +45 -30
- package/docs/concepts/hct.md +30 -27
- package/docs/concepts/image-analysis.md +52 -21
- package/docs/concepts/material-design.md +43 -17
- package/docs/concepts/theme-matching.md +64 -40
- package/docs/examples/basic-colors.md +92 -108
- package/docs/examples/creating-themes.md +104 -108
- package/docs/examples/css-refactoring.md +33 -29
- package/docs/examples/image-extraction.md +145 -138
- package/docs/getting-started.md +45 -34
- package/docs/index.md +5 -1
- package/docs/installation.md +15 -1
- package/docs/tools/accessibility.md +74 -68
- package/docs/tools/image-extraction.md +62 -54
- package/docs/tools/theme-matching.md +45 -42
- package/note.md +1 -2
- package/package.json +2 -2
|
@@ -8,7 +8,7 @@ Learn how to extract colors from images and generate dynamic themes using Coolor
|
|
|
8
8
|
|
|
9
9
|
```javascript
|
|
10
10
|
// Extract dominant colors
|
|
11
|
-
"Extract the main colors from this image"
|
|
11
|
+
"Extract the main colors from this image";
|
|
12
12
|
|
|
13
13
|
// Process:
|
|
14
14
|
// 1. Quantizes image using Celebi algorithm (Wu + WSMeans)
|
|
@@ -21,7 +21,7 @@ Learn how to extract colors from images and generate dynamic themes using Coolor
|
|
|
21
21
|
|
|
22
22
|
```javascript
|
|
23
23
|
// Create Material Design theme from image
|
|
24
|
-
"Generate a theme from this album artwork"
|
|
24
|
+
"Generate a theme from this album artwork";
|
|
25
25
|
|
|
26
26
|
// Result:
|
|
27
27
|
// - Extracts dominant colors
|
|
@@ -63,12 +63,12 @@ img.src = 'path/to/image.jpg';
|
|
|
63
63
|
### Node.js with Sharp
|
|
64
64
|
|
|
65
65
|
```javascript
|
|
66
|
-
const sharp = require(
|
|
66
|
+
const sharp = require("sharp");
|
|
67
67
|
|
|
68
68
|
async function extractColorsFromFile(imagePath) {
|
|
69
69
|
// Load and prepare image
|
|
70
70
|
const { data, info } = await sharp(imagePath)
|
|
71
|
-
.resize(300, 300, { fit:
|
|
71
|
+
.resize(300, 300, { fit: "inside" }) // Resize for performance
|
|
72
72
|
.raw()
|
|
73
73
|
.toBuffer({ resolveWithObject: true });
|
|
74
74
|
|
|
@@ -79,14 +79,14 @@ async function extractColorsFromFile(imagePath) {
|
|
|
79
79
|
const result = await extractImageColors({
|
|
80
80
|
imageData: pixels,
|
|
81
81
|
maxColors: 8,
|
|
82
|
-
minPopulation: 0.01
|
|
82
|
+
minPopulation: 0.01, // Include colors with 1%+ coverage
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
return result;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
// Usage
|
|
89
|
-
const colors = await extractColorsFromFile(
|
|
89
|
+
const colors = await extractColorsFromFile("logo.png");
|
|
90
90
|
console.log(`Found ${colors.colors.length} dominant colors`);
|
|
91
91
|
```
|
|
92
92
|
|
|
@@ -94,53 +94,57 @@ console.log(`Found ${colors.colors.length} dominant colors`);
|
|
|
94
94
|
|
|
95
95
|
```html
|
|
96
96
|
<!-- HTML -->
|
|
97
|
-
<input type="file" id="imageUpload" accept="image/*"
|
|
97
|
+
<input type="file" id="imageUpload" accept="image/*" />
|
|
98
98
|
<div id="colorPalette"></div>
|
|
99
99
|
|
|
100
100
|
<script>
|
|
101
|
-
document
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const img = new Image();
|
|
107
|
-
const reader = new FileReader();
|
|
108
|
-
|
|
109
|
-
reader.onload = async function(event) {
|
|
110
|
-
img.src = event.target.result;
|
|
111
|
-
|
|
112
|
-
img.onload = async function() {
|
|
113
|
-
// Create canvas
|
|
114
|
-
const canvas = document.createElement('canvas');
|
|
115
|
-
const ctx = canvas.getContext('2d');
|
|
116
|
-
|
|
117
|
-
// Resize for performance
|
|
118
|
-
const maxSize = 400;
|
|
119
|
-
const scale = Math.min(maxSize / img.width, maxSize / img.height, 1);
|
|
120
|
-
canvas.width = img.width * scale;
|
|
121
|
-
canvas.height = img.height * scale;
|
|
122
|
-
|
|
123
|
-
// Draw and extract
|
|
124
|
-
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
125
|
-
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
126
|
-
|
|
127
|
-
// Extract colors
|
|
128
|
-
const colors = await extractImageColors({
|
|
129
|
-
imageData: Array.from(imageData.data),
|
|
130
|
-
maxColors: 6
|
|
131
|
-
});
|
|
101
|
+
document
|
|
102
|
+
.getElementById("imageUpload")
|
|
103
|
+
.addEventListener("change", async (e) => {
|
|
104
|
+
const file = e.target.files[0];
|
|
105
|
+
if (!file) return;
|
|
132
106
|
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
};
|
|
107
|
+
// Read file as image
|
|
108
|
+
const img = new Image();
|
|
109
|
+
const reader = new FileReader();
|
|
137
110
|
|
|
138
|
-
|
|
139
|
-
|
|
111
|
+
reader.onload = async function (event) {
|
|
112
|
+
img.src = event.target.result;
|
|
113
|
+
|
|
114
|
+
img.onload = async function () {
|
|
115
|
+
// Create canvas
|
|
116
|
+
const canvas = document.createElement("canvas");
|
|
117
|
+
const ctx = canvas.getContext("2d");
|
|
118
|
+
|
|
119
|
+
// Resize for performance
|
|
120
|
+
const maxSize = 400;
|
|
121
|
+
const scale = Math.min(maxSize / img.width, maxSize / img.height, 1);
|
|
122
|
+
canvas.width = img.width * scale;
|
|
123
|
+
canvas.height = img.height * scale;
|
|
124
|
+
|
|
125
|
+
// Draw and extract
|
|
126
|
+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
127
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
128
|
+
|
|
129
|
+
// Extract colors
|
|
130
|
+
const colors = await extractImageColors({
|
|
131
|
+
imageData: Array.from(imageData.data),
|
|
132
|
+
maxColors: 6,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Display palette
|
|
136
|
+
displayPalette(colors.colors);
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
reader.readAsDataURL(file);
|
|
141
|
+
});
|
|
140
142
|
|
|
141
|
-
function displayPalette(colors) {
|
|
142
|
-
|
|
143
|
-
|
|
143
|
+
function displayPalette(colors) {
|
|
144
|
+
const palette = document.getElementById("colorPalette");
|
|
145
|
+
palette.innerHTML = colors
|
|
146
|
+
.map(
|
|
147
|
+
(color) => `
|
|
144
148
|
<div style="
|
|
145
149
|
background: ${color.hex};
|
|
146
150
|
width: 100px;
|
|
@@ -156,8 +160,10 @@ function displayPalette(colors) {
|
|
|
156
160
|
font-size: 12px;
|
|
157
161
|
">${color.hex}</span>
|
|
158
162
|
</div>
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
`,
|
|
164
|
+
)
|
|
165
|
+
.join("");
|
|
166
|
+
}
|
|
161
167
|
</script>
|
|
162
168
|
```
|
|
163
169
|
|
|
@@ -176,14 +182,14 @@ class AlbumThemeGenerator {
|
|
|
176
182
|
const extraction = await extractImageColors({
|
|
177
183
|
imageData: pixels,
|
|
178
184
|
maxColors: 5,
|
|
179
|
-
targetChroma: 48
|
|
185
|
+
targetChroma: 48, // Optimal for UI
|
|
180
186
|
});
|
|
181
187
|
|
|
182
188
|
// Generate Material theme
|
|
183
189
|
const theme = await generateThemeFromImage({
|
|
184
190
|
imageData: pixels,
|
|
185
|
-
variant:
|
|
186
|
-
contrastLevel: 0
|
|
191
|
+
variant: "expressive", // Creative for music
|
|
192
|
+
contrastLevel: 0,
|
|
187
193
|
});
|
|
188
194
|
|
|
189
195
|
// Create player theme
|
|
@@ -201,18 +207,18 @@ class AlbumThemeGenerator {
|
|
|
201
207
|
textSecondary: theme.schemes.dark.onSurfaceVariant,
|
|
202
208
|
|
|
203
209
|
// Visualizer colors
|
|
204
|
-
visualizerColors: extraction.colors.map(c => c.hex)
|
|
210
|
+
visualizerColors: extraction.colors.map((c) => c.hex),
|
|
205
211
|
};
|
|
206
212
|
}
|
|
207
213
|
}
|
|
208
214
|
|
|
209
215
|
// Usage
|
|
210
216
|
const player = new AlbumThemeGenerator();
|
|
211
|
-
const theme = await player.generateFromArtwork(
|
|
217
|
+
const theme = await player.generateFromArtwork("album.jpg");
|
|
212
218
|
|
|
213
219
|
// Apply theme
|
|
214
|
-
document.querySelector(
|
|
215
|
-
document.querySelector(
|
|
220
|
+
document.querySelector(".player").style.background = theme.backgroundGradient;
|
|
221
|
+
document.querySelector(".controls").style.color = theme.controlsColor;
|
|
216
222
|
```
|
|
217
223
|
|
|
218
224
|
### Logo Brand Color Extraction
|
|
@@ -225,35 +231,35 @@ async function extractBrandColors(logoPath) {
|
|
|
225
231
|
// Extract with focus on distinct colors
|
|
226
232
|
const result = await extractImageColors({
|
|
227
233
|
imageData: pixels,
|
|
228
|
-
maxColors: 3,
|
|
229
|
-
minPopulation: 0.05
|
|
234
|
+
maxColors: 3, // Most logos have 1-3 colors
|
|
235
|
+
minPopulation: 0.05, // 5% minimum for significance
|
|
230
236
|
});
|
|
231
237
|
|
|
232
238
|
// Check for disliked colors and fix
|
|
233
239
|
const fixedColors = await fixDislikedColorsBatch({
|
|
234
|
-
colors: result.colors.map(c => c.hex)
|
|
240
|
+
colors: result.colors.map((c) => c.hex),
|
|
235
241
|
});
|
|
236
242
|
|
|
237
243
|
// Build brand palette
|
|
238
244
|
const brandPalette = {
|
|
239
245
|
primary: fixedColors.fixedColors[0],
|
|
240
246
|
secondary: fixedColors.fixedColors[1] || null,
|
|
241
|
-
accent: fixedColors.fixedColors[2] || null
|
|
247
|
+
accent: fixedColors.fixedColors[2] || null,
|
|
242
248
|
};
|
|
243
249
|
|
|
244
250
|
// Generate full theme from primary
|
|
245
251
|
const fullTheme = await generateMaterialTheme({
|
|
246
252
|
sourceColor: brandPalette.primary,
|
|
247
|
-
customColors: brandPalette.secondary
|
|
248
|
-
{ name:
|
|
249
|
-
|
|
253
|
+
customColors: brandPalette.secondary
|
|
254
|
+
? [{ name: "brand", color: brandPalette.secondary }]
|
|
255
|
+
: [],
|
|
250
256
|
});
|
|
251
257
|
|
|
252
258
|
return {
|
|
253
259
|
extractedColors: result.colors,
|
|
254
260
|
brandPalette,
|
|
255
261
|
fullTheme,
|
|
256
|
-
cssVariables: generateBrandCSS(brandPalette)
|
|
262
|
+
cssVariables: generateBrandCSS(brandPalette),
|
|
257
263
|
};
|
|
258
264
|
}
|
|
259
265
|
|
|
@@ -265,7 +271,7 @@ function generateBrandCSS(palette) {
|
|
|
265
271
|
--brand-accent: ${palette.accent || palette.primary};
|
|
266
272
|
|
|
267
273
|
/* Generate shades */
|
|
268
|
-
${generateShades(
|
|
274
|
+
${generateShades("brand-primary", palette.primary)}
|
|
269
275
|
}
|
|
270
276
|
`;
|
|
271
277
|
}
|
|
@@ -283,7 +289,7 @@ class ProductColorAnalyzer {
|
|
|
283
289
|
const extracted = await extractImageColors({
|
|
284
290
|
imageData: pixels,
|
|
285
291
|
maxColors: 10,
|
|
286
|
-
minPopulation: 0.02
|
|
292
|
+
minPopulation: 0.02, // 2% threshold
|
|
287
293
|
});
|
|
288
294
|
|
|
289
295
|
// Categorize colors
|
|
@@ -297,7 +303,7 @@ class ProductColorAnalyzer {
|
|
|
297
303
|
accentColors: categorized.accents,
|
|
298
304
|
availableVariants: variants,
|
|
299
305
|
colorDescription: this.generateDescription(categorized),
|
|
300
|
-
searchTags: this.generateSearchTags(categorized)
|
|
306
|
+
searchTags: this.generateSearchTags(categorized),
|
|
301
307
|
};
|
|
302
308
|
}
|
|
303
309
|
|
|
@@ -306,9 +312,9 @@ class ProductColorAnalyzer {
|
|
|
306
312
|
const sorted = colors.sort((a, b) => b.population - a.population);
|
|
307
313
|
|
|
308
314
|
return {
|
|
309
|
-
primary: sorted[0],
|
|
310
|
-
accents: sorted.slice(1, 4),
|
|
311
|
-
details: sorted.slice(4)
|
|
315
|
+
primary: sorted[0], // Most dominant
|
|
316
|
+
accents: sorted.slice(1, 4), // Next 3 colors
|
|
317
|
+
details: sorted.slice(4), // Remaining colors
|
|
312
318
|
};
|
|
313
319
|
}
|
|
314
320
|
|
|
@@ -317,10 +323,10 @@ class ProductColorAnalyzer {
|
|
|
317
323
|
const hue = categorized.primary.hct.hue;
|
|
318
324
|
|
|
319
325
|
return [
|
|
320
|
-
{ name:
|
|
321
|
-
{ name:
|
|
322
|
-
{ name:
|
|
323
|
-
{ name:
|
|
326
|
+
{ name: "Original", color: categorized.primary.hex },
|
|
327
|
+
{ name: "Darker", color: adjustTone(categorized.primary, -20) },
|
|
328
|
+
{ name: "Lighter", color: adjustTone(categorized.primary, +20) },
|
|
329
|
+
{ name: "Vibrant", color: adjustChroma(categorized.primary, +30) },
|
|
324
330
|
];
|
|
325
331
|
}
|
|
326
332
|
|
|
@@ -331,21 +337,21 @@ class ProductColorAnalyzer {
|
|
|
331
337
|
const tone = primary.hct.tone;
|
|
332
338
|
|
|
333
339
|
// Generate human-readable description
|
|
334
|
-
let description =
|
|
340
|
+
let description = "";
|
|
335
341
|
|
|
336
342
|
// Hue description
|
|
337
|
-
if (hue >= 0 && hue < 20) description +=
|
|
338
|
-
else if (hue >= 20 && hue < 40) description +=
|
|
339
|
-
else if (hue >= 40 && hue < 60) description +=
|
|
343
|
+
if (hue >= 0 && hue < 20) description += "Red";
|
|
344
|
+
else if (hue >= 20 && hue < 40) description += "Orange";
|
|
345
|
+
else if (hue >= 40 && hue < 60) description += "Yellow";
|
|
340
346
|
// ... etc
|
|
341
347
|
|
|
342
348
|
// Saturation
|
|
343
|
-
if (chroma < 20) description +=
|
|
344
|
-
else if (chroma > 60) description +=
|
|
349
|
+
if (chroma < 20) description += ", muted";
|
|
350
|
+
else if (chroma > 60) description += ", vibrant";
|
|
345
351
|
|
|
346
352
|
// Lightness
|
|
347
|
-
if (tone < 30) description +=
|
|
348
|
-
else if (tone > 70) description +=
|
|
353
|
+
if (tone < 30) description += ", dark";
|
|
354
|
+
else if (tone > 70) description += ", light";
|
|
349
355
|
|
|
350
356
|
return description;
|
|
351
357
|
}
|
|
@@ -363,7 +369,7 @@ async function extractWebsiteTheme(screenshotUrl) {
|
|
|
363
369
|
const colors = await extractImageColors({
|
|
364
370
|
imageData: pixels,
|
|
365
371
|
maxColors: 8,
|
|
366
|
-
targetChroma: 40
|
|
372
|
+
targetChroma: 40, // Lower chroma for web UI
|
|
367
373
|
});
|
|
368
374
|
|
|
369
375
|
// Identify UI elements
|
|
@@ -375,34 +381,34 @@ async function extractWebsiteTheme(screenshotUrl) {
|
|
|
375
381
|
primary: identified.brand || colors.colors[0].hex,
|
|
376
382
|
|
|
377
383
|
// Background (lightest color)
|
|
378
|
-
background: identified.background ||
|
|
384
|
+
background: identified.background || "#ffffff",
|
|
379
385
|
|
|
380
386
|
// Surface (cards, modals)
|
|
381
|
-
surface: identified.surface ||
|
|
387
|
+
surface: identified.surface || "#ffffff",
|
|
382
388
|
|
|
383
389
|
// Text colors
|
|
384
|
-
textPrimary: identified.textPrimary ||
|
|
385
|
-
textSecondary: identified.textSecondary ||
|
|
390
|
+
textPrimary: identified.textPrimary || "#1f2937",
|
|
391
|
+
textSecondary: identified.textSecondary || "#6b7280",
|
|
386
392
|
|
|
387
393
|
// Borders and dividers
|
|
388
|
-
border: identified.border ||
|
|
394
|
+
border: identified.border || "#e5e7eb",
|
|
389
395
|
|
|
390
396
|
// Interactive elements
|
|
391
397
|
link: identified.link || colors.colors[0].hex,
|
|
392
|
-
button: identified.button || colors.colors[0].hex
|
|
398
|
+
button: identified.button || colors.colors[0].hex,
|
|
393
399
|
};
|
|
394
400
|
|
|
395
401
|
return {
|
|
396
402
|
extractedColors: colors,
|
|
397
403
|
identifiedTheme: theme,
|
|
398
|
-
css: generateThemeCSS(theme)
|
|
404
|
+
css: generateThemeCSS(theme),
|
|
399
405
|
};
|
|
400
406
|
}
|
|
401
407
|
|
|
402
408
|
function identifyUIColors(colors) {
|
|
403
409
|
const identified = {};
|
|
404
410
|
|
|
405
|
-
colors.forEach(color => {
|
|
411
|
+
colors.forEach((color) => {
|
|
406
412
|
const { hue, chroma, tone } = color.hct;
|
|
407
413
|
|
|
408
414
|
// Very light colors are likely backgrounds
|
|
@@ -451,10 +457,10 @@ function extractFromRegion(imageData, region) {
|
|
|
451
457
|
for (let col = x; col < x + width; col++) {
|
|
452
458
|
const idx = (row * fullWidth + col) * 4;
|
|
453
459
|
regionPixels.push(
|
|
454
|
-
imageData[idx],
|
|
460
|
+
imageData[idx], // R
|
|
455
461
|
imageData[idx + 1], // G
|
|
456
462
|
imageData[idx + 2], // B
|
|
457
|
-
imageData[idx + 3]
|
|
463
|
+
imageData[idx + 3], // A
|
|
458
464
|
);
|
|
459
465
|
}
|
|
460
466
|
}
|
|
@@ -462,7 +468,7 @@ function extractFromRegion(imageData, region) {
|
|
|
462
468
|
// Extract colors from region
|
|
463
469
|
return extractImageColors({
|
|
464
470
|
imageData: regionPixels,
|
|
465
|
-
maxColors: 5
|
|
471
|
+
maxColors: 5,
|
|
466
472
|
});
|
|
467
473
|
}
|
|
468
474
|
|
|
@@ -479,20 +485,20 @@ async function extractForegroundColors(imageData, backgroundColor) {
|
|
|
479
485
|
// Extract all colors
|
|
480
486
|
const allColors = await extractImageColors({
|
|
481
487
|
imageData,
|
|
482
|
-
maxColors: 10
|
|
488
|
+
maxColors: 10,
|
|
483
489
|
});
|
|
484
490
|
|
|
485
491
|
// Filter out colors similar to background
|
|
486
|
-
const foregroundColors = allColors.colors.filter(color => {
|
|
492
|
+
const foregroundColors = allColors.colors.filter((color) => {
|
|
487
493
|
const distance = colorDistance(color.hex, backgroundColor);
|
|
488
|
-
return distance > 10;
|
|
494
|
+
return distance > 10; // Significant difference
|
|
489
495
|
});
|
|
490
496
|
|
|
491
497
|
return foregroundColors;
|
|
492
498
|
}
|
|
493
499
|
|
|
494
500
|
// Usage: Extract product colors without white background
|
|
495
|
-
const productColors = await extractForegroundColors(pixels,
|
|
501
|
+
const productColors = await extractForegroundColors(pixels, "#ffffff");
|
|
496
502
|
```
|
|
497
503
|
|
|
498
504
|
### Weighted Extraction
|
|
@@ -511,10 +517,9 @@ function createWeightedPixelArray(imageData, width, height) {
|
|
|
511
517
|
|
|
512
518
|
// Calculate weight based on distance from center
|
|
513
519
|
const dist = Math.sqrt(
|
|
514
|
-
Math.pow(x - centerX, 2) +
|
|
515
|
-
Math.pow(y - centerY, 2)
|
|
520
|
+
Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2),
|
|
516
521
|
);
|
|
517
|
-
const weight = 1 - (dist / maxDist) * 0.5;
|
|
522
|
+
const weight = 1 - (dist / maxDist) * 0.5; // 50-100% weight
|
|
518
523
|
|
|
519
524
|
// Add pixel multiple times based on weight
|
|
520
525
|
const copies = Math.ceil(weight * 3);
|
|
@@ -523,7 +528,7 @@ function createWeightedPixelArray(imageData, width, height) {
|
|
|
523
528
|
imageData[idx],
|
|
524
529
|
imageData[idx + 1],
|
|
525
530
|
imageData[idx + 2],
|
|
526
|
-
imageData[idx + 3]
|
|
531
|
+
imageData[idx + 3],
|
|
527
532
|
);
|
|
528
533
|
}
|
|
529
534
|
}
|
|
@@ -536,7 +541,7 @@ function createWeightedPixelArray(imageData, width, height) {
|
|
|
536
541
|
const weighted = createWeightedPixelArray(pixels, 300, 300);
|
|
537
542
|
const centralColors = await extractImageColors({
|
|
538
543
|
imageData: weighted,
|
|
539
|
-
maxColors: 5
|
|
544
|
+
maxColors: 5,
|
|
540
545
|
});
|
|
541
546
|
```
|
|
542
547
|
|
|
@@ -550,14 +555,14 @@ async function extractLikableColors(imageData) {
|
|
|
550
555
|
// Extract colors
|
|
551
556
|
const extracted = await extractImageColors({
|
|
552
557
|
imageData,
|
|
553
|
-
maxColors: 8
|
|
558
|
+
maxColors: 8,
|
|
554
559
|
});
|
|
555
560
|
|
|
556
561
|
// Check and fix disliked colors
|
|
557
|
-
const colorHexes = extracted.colors.map(c => c.hex);
|
|
562
|
+
const colorHexes = extracted.colors.map((c) => c.hex);
|
|
558
563
|
const fixed = await fixDislikedColorsBatch({
|
|
559
564
|
colors: colorHexes,
|
|
560
|
-
strategy:
|
|
565
|
+
strategy: "both", // Shift hue and adjust tone
|
|
561
566
|
});
|
|
562
567
|
|
|
563
568
|
// Map back to full color objects
|
|
@@ -567,7 +572,7 @@ async function extractLikableColors(imageData) {
|
|
|
567
572
|
...color,
|
|
568
573
|
hex: fixed.results[i].fixed,
|
|
569
574
|
wasFixed: true,
|
|
570
|
-
originalHex: color.hex
|
|
575
|
+
originalHex: color.hex,
|
|
571
576
|
};
|
|
572
577
|
}
|
|
573
578
|
return color;
|
|
@@ -576,7 +581,7 @@ async function extractLikableColors(imageData) {
|
|
|
576
581
|
return {
|
|
577
582
|
colors: likableColors,
|
|
578
583
|
fixedCount: fixed.summary.fixed,
|
|
579
|
-
originalColors: extracted.colors
|
|
584
|
+
originalColors: extracted.colors,
|
|
580
585
|
};
|
|
581
586
|
}
|
|
582
587
|
```
|
|
@@ -588,17 +593,23 @@ async function extractLikableColors(imageData) {
|
|
|
588
593
|
function detectBileZoneColors(colors) {
|
|
589
594
|
const problematic = [];
|
|
590
595
|
|
|
591
|
-
colors.forEach(color => {
|
|
596
|
+
colors.forEach((color) => {
|
|
592
597
|
const { hue, chroma, tone } = color.hct;
|
|
593
598
|
|
|
594
599
|
// Bile zone: dark yellow-greens
|
|
595
|
-
if (
|
|
596
|
-
|
|
597
|
-
|
|
600
|
+
if (
|
|
601
|
+
hue >= 50 &&
|
|
602
|
+
hue <= 120 && // Yellow-green range
|
|
603
|
+
chroma >= 20 &&
|
|
604
|
+
chroma <= 50 && // Moderate saturation
|
|
605
|
+
tone >= 20 &&
|
|
606
|
+
tone <= 50
|
|
607
|
+
) {
|
|
608
|
+
// Dark to medium
|
|
598
609
|
problematic.push({
|
|
599
610
|
color: color.hex,
|
|
600
611
|
reason: 'Falls in universally disliked "bile zone"',
|
|
601
|
-
suggestion: adjustToLikable(color)
|
|
612
|
+
suggestion: adjustToLikable(color),
|
|
602
613
|
});
|
|
603
614
|
}
|
|
604
615
|
});
|
|
@@ -610,7 +621,7 @@ function adjustToLikable(color) {
|
|
|
610
621
|
// Shift hue away from problematic range
|
|
611
622
|
let newHue = color.hct.hue;
|
|
612
623
|
if (newHue >= 50 && newHue <= 120) {
|
|
613
|
-
newHue = newHue < 85 ? 40 : 130;
|
|
624
|
+
newHue = newHue < 85 ? 40 : 130; // Shift to yellow or green
|
|
614
625
|
}
|
|
615
626
|
|
|
616
627
|
// Increase tone for lighter appearance
|
|
@@ -634,24 +645,24 @@ async function preprocessImage(imageUrl) {
|
|
|
634
645
|
const scale = Math.min(
|
|
635
646
|
maxDimension / img.width,
|
|
636
647
|
maxDimension / img.height,
|
|
637
|
-
1
|
|
648
|
+
1,
|
|
638
649
|
);
|
|
639
650
|
|
|
640
651
|
// Apply slight blur to reduce noise
|
|
641
|
-
const canvas = document.createElement(
|
|
642
|
-
const ctx = canvas.getContext(
|
|
652
|
+
const canvas = document.createElement("canvas");
|
|
653
|
+
const ctx = canvas.getContext("2d");
|
|
643
654
|
canvas.width = img.width * scale;
|
|
644
655
|
canvas.height = img.height * scale;
|
|
645
656
|
|
|
646
657
|
// Enable image smoothing
|
|
647
658
|
ctx.imageSmoothingEnabled = true;
|
|
648
|
-
ctx.imageSmoothingQuality =
|
|
659
|
+
ctx.imageSmoothingQuality = "high";
|
|
649
660
|
|
|
650
661
|
// Draw scaled image
|
|
651
662
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
652
663
|
|
|
653
664
|
// Optional: Apply slight blur
|
|
654
|
-
ctx.filter =
|
|
665
|
+
ctx.filter = "blur(0.5px)";
|
|
655
666
|
ctx.drawImage(canvas, 0, 0);
|
|
656
667
|
|
|
657
668
|
return ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
@@ -680,7 +691,7 @@ class ColorExtractionCache {
|
|
|
680
691
|
const pixels = await loadImage(imageUrl);
|
|
681
692
|
const result = await extractImageColors({
|
|
682
693
|
imageData: pixels,
|
|
683
|
-
...options
|
|
694
|
+
...options,
|
|
684
695
|
});
|
|
685
696
|
|
|
686
697
|
// Cache result
|
|
@@ -706,7 +717,7 @@ class ColorExtractionCache {
|
|
|
706
717
|
```javascript
|
|
707
718
|
// Process multiple images efficiently
|
|
708
719
|
async function batchExtractColors(imageUrls, options = {}) {
|
|
709
|
-
const batchSize = 5;
|
|
720
|
+
const batchSize = 5; // Process 5 at a time
|
|
710
721
|
const results = [];
|
|
711
722
|
|
|
712
723
|
for (let i = 0; i < imageUrls.length; i += batchSize) {
|
|
@@ -714,18 +725,18 @@ async function batchExtractColors(imageUrls, options = {}) {
|
|
|
714
725
|
|
|
715
726
|
// Process batch in parallel
|
|
716
727
|
const batchResults = await Promise.all(
|
|
717
|
-
batch.map(async url => {
|
|
728
|
+
batch.map(async (url) => {
|
|
718
729
|
try {
|
|
719
730
|
const pixels = await loadImage(url);
|
|
720
731
|
return await extractImageColors({
|
|
721
732
|
imageData: pixels,
|
|
722
|
-
...options
|
|
733
|
+
...options,
|
|
723
734
|
});
|
|
724
735
|
} catch (error) {
|
|
725
736
|
console.error(`Failed to process ${url}:`, error);
|
|
726
737
|
return null;
|
|
727
738
|
}
|
|
728
|
-
})
|
|
739
|
+
}),
|
|
729
740
|
);
|
|
730
741
|
|
|
731
742
|
results.push(...batchResults);
|
|
@@ -734,12 +745,12 @@ async function batchExtractColors(imageUrls, options = {}) {
|
|
|
734
745
|
if (options.onProgress) {
|
|
735
746
|
options.onProgress({
|
|
736
747
|
processed: Math.min(i + batchSize, imageUrls.length),
|
|
737
|
-
total: imageUrls.length
|
|
748
|
+
total: imageUrls.length,
|
|
738
749
|
});
|
|
739
750
|
}
|
|
740
751
|
}
|
|
741
752
|
|
|
742
|
-
return results.filter(r => r !== null);
|
|
753
|
+
return results.filter((r) => r !== null);
|
|
743
754
|
}
|
|
744
755
|
```
|
|
745
756
|
|
|
@@ -765,7 +776,7 @@ function useImageColors(imageUrl) {
|
|
|
765
776
|
const pixels = await loadImageFromUrl(imageUrl);
|
|
766
777
|
const result = await extractImageColors({
|
|
767
778
|
imageData: pixels,
|
|
768
|
-
maxColors: 5
|
|
779
|
+
maxColors: 5,
|
|
769
780
|
});
|
|
770
781
|
setColors(result.colors);
|
|
771
782
|
} catch (err) {
|
|
@@ -811,11 +822,7 @@ function ImagePalette({ imageUrl }) {
|
|
|
811
822
|
```vue
|
|
812
823
|
<template>
|
|
813
824
|
<div class="image-theme-generator">
|
|
814
|
-
<input
|
|
815
|
-
type="file"
|
|
816
|
-
@change="handleFileUpload"
|
|
817
|
-
accept="image/*"
|
|
818
|
-
>
|
|
825
|
+
<input type="file" @change="handleFileUpload" accept="image/*" />
|
|
819
826
|
|
|
820
827
|
<div v-if="loading">Generating theme...</div>
|
|
821
828
|
|
|
@@ -837,7 +844,7 @@ export default {
|
|
|
837
844
|
data() {
|
|
838
845
|
return {
|
|
839
846
|
theme: null,
|
|
840
|
-
loading: false
|
|
847
|
+
loading: false,
|
|
841
848
|
};
|
|
842
849
|
},
|
|
843
850
|
|
|
@@ -853,7 +860,7 @@ export default {
|
|
|
853
860
|
const theme = await this.generateTheme(pixels);
|
|
854
861
|
this.theme = theme;
|
|
855
862
|
} catch (error) {
|
|
856
|
-
console.error(
|
|
863
|
+
console.error("Failed to generate theme:", error);
|
|
857
864
|
} finally {
|
|
858
865
|
this.loading = false;
|
|
859
866
|
}
|
|
@@ -866,10 +873,10 @@ export default {
|
|
|
866
873
|
async generateTheme(pixels) {
|
|
867
874
|
return await generateThemeFromImage({
|
|
868
875
|
imageData: pixels,
|
|
869
|
-
variant:
|
|
876
|
+
variant: "tonalSpot",
|
|
870
877
|
});
|
|
871
|
-
}
|
|
872
|
-
}
|
|
878
|
+
},
|
|
879
|
+
},
|
|
873
880
|
};
|
|
874
881
|
</script>
|
|
875
882
|
```
|
|
@@ -879,4 +886,4 @@ export default {
|
|
|
879
886
|
- Learn about [Creating Themes](./creating-themes.md) for comprehensive design systems
|
|
880
887
|
- Explore [CSS Refactoring](./css-refactoring.md) to apply extracted colors
|
|
881
888
|
- Read [Image Analysis Concepts](../concepts/image-analysis.md) for theory
|
|
882
|
-
- Check [Image Extraction Tools](../tools/image-extraction.md) for API reference
|
|
889
|
+
- Check [Image Extraction Tools](../tools/image-extraction.md) for API reference
|