@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,882 @@
|
|
|
1
|
+
# Image Extraction Examples
|
|
2
|
+
|
|
3
|
+
Learn how to extract colors from images and generate dynamic themes using Coolors MCP's advanced quantization and scoring algorithms.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Extract Colors from Image
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// Extract dominant colors
|
|
11
|
+
"Extract the main colors from this image"
|
|
12
|
+
|
|
13
|
+
// Process:
|
|
14
|
+
// 1. Quantizes image using Celebi algorithm (Wu + WSMeans)
|
|
15
|
+
// 2. Scores colors for UI suitability (target chroma ~48)
|
|
16
|
+
// 3. Filters out universally disliked colors
|
|
17
|
+
// 4. Returns ranked color palette
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Generate Theme from Image
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
// Create Material Design theme from image
|
|
24
|
+
"Generate a theme from this album artwork"
|
|
25
|
+
|
|
26
|
+
// Result:
|
|
27
|
+
// - Extracts dominant colors
|
|
28
|
+
// - Selects best color for theme generation
|
|
29
|
+
// - Creates complete light/dark schemes
|
|
30
|
+
// - Provides CSS variables and palettes
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Working with Image Data
|
|
34
|
+
|
|
35
|
+
### Browser Canvas Example
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
// Get image data from canvas
|
|
39
|
+
const canvas = document.getElementById('imageCanvas');
|
|
40
|
+
const ctx = canvas.getContext('2d');
|
|
41
|
+
const img = new Image();
|
|
42
|
+
|
|
43
|
+
img.onload = function() {
|
|
44
|
+
// Draw image to canvas
|
|
45
|
+
ctx.drawImage(img, 0, 0);
|
|
46
|
+
|
|
47
|
+
// Get pixel data
|
|
48
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
49
|
+
const pixels = Array.from(imageData.data);
|
|
50
|
+
|
|
51
|
+
// Extract colors
|
|
52
|
+
const colors = await extractImageColors({
|
|
53
|
+
imageData: pixels,
|
|
54
|
+
maxColors: 5
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
console.log('Dominant colors:', colors);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
img.src = 'path/to/image.jpg';
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Node.js with Sharp
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
const sharp = require('sharp');
|
|
67
|
+
|
|
68
|
+
async function extractColorsFromFile(imagePath) {
|
|
69
|
+
// Load and prepare image
|
|
70
|
+
const { data, info } = await sharp(imagePath)
|
|
71
|
+
.resize(300, 300, { fit: 'inside' }) // Resize for performance
|
|
72
|
+
.raw()
|
|
73
|
+
.toBuffer({ resolveWithObject: true });
|
|
74
|
+
|
|
75
|
+
// Convert to RGBA array
|
|
76
|
+
const pixels = Array.from(data);
|
|
77
|
+
|
|
78
|
+
// Extract colors
|
|
79
|
+
const result = await extractImageColors({
|
|
80
|
+
imageData: pixels,
|
|
81
|
+
maxColors: 8,
|
|
82
|
+
minPopulation: 0.01 // Include colors with 1%+ coverage
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Usage
|
|
89
|
+
const colors = await extractColorsFromFile('logo.png');
|
|
90
|
+
console.log(`Found ${colors.colors.length} dominant colors`);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### File Upload Example
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<!-- HTML -->
|
|
97
|
+
<input type="file" id="imageUpload" accept="image/*">
|
|
98
|
+
<div id="colorPalette"></div>
|
|
99
|
+
|
|
100
|
+
<script>
|
|
101
|
+
document.getElementById('imageUpload').addEventListener('change', async (e) => {
|
|
102
|
+
const file = e.target.files[0];
|
|
103
|
+
if (!file) return;
|
|
104
|
+
|
|
105
|
+
// Read file as image
|
|
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
|
+
});
|
|
132
|
+
|
|
133
|
+
// Display palette
|
|
134
|
+
displayPalette(colors.colors);
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
reader.readAsDataURL(file);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
function displayPalette(colors) {
|
|
142
|
+
const palette = document.getElementById('colorPalette');
|
|
143
|
+
palette.innerHTML = colors.map(color => `
|
|
144
|
+
<div style="
|
|
145
|
+
background: ${color.hex};
|
|
146
|
+
width: 100px;
|
|
147
|
+
height: 100px;
|
|
148
|
+
display: inline-block;
|
|
149
|
+
margin: 5px;
|
|
150
|
+
border-radius: 8px;
|
|
151
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
152
|
+
">
|
|
153
|
+
<span style="
|
|
154
|
+
background: white;
|
|
155
|
+
padding: 2px 4px;
|
|
156
|
+
font-size: 12px;
|
|
157
|
+
">${color.hex}</span>
|
|
158
|
+
</div>
|
|
159
|
+
`).join('');
|
|
160
|
+
}
|
|
161
|
+
</script>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Real-World Examples
|
|
165
|
+
|
|
166
|
+
### Album Art Dynamic Theming
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
// Music player that adapts to album artwork
|
|
170
|
+
class AlbumThemeGenerator {
|
|
171
|
+
async generateFromArtwork(artworkUrl) {
|
|
172
|
+
// Load artwork
|
|
173
|
+
const pixels = await this.loadImage(artworkUrl);
|
|
174
|
+
|
|
175
|
+
// Extract colors with UI optimization
|
|
176
|
+
const extraction = await extractImageColors({
|
|
177
|
+
imageData: pixels,
|
|
178
|
+
maxColors: 5,
|
|
179
|
+
targetChroma: 48 // Optimal for UI
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Generate Material theme
|
|
183
|
+
const theme = await generateThemeFromImage({
|
|
184
|
+
imageData: pixels,
|
|
185
|
+
variant: 'expressive', // Creative for music
|
|
186
|
+
contrastLevel: 0
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Create player theme
|
|
190
|
+
return {
|
|
191
|
+
// Background gradient from extracted colors
|
|
192
|
+
backgroundGradient: `linear-gradient(135deg,
|
|
193
|
+
${extraction.colors[0].hex} 0%,
|
|
194
|
+
${extraction.colors[1].hex} 100%)`,
|
|
195
|
+
|
|
196
|
+
// UI elements from Material theme
|
|
197
|
+
playerBackground: theme.schemes.dark.surface,
|
|
198
|
+
controlsColor: theme.schemes.dark.primary,
|
|
199
|
+
progressBar: theme.schemes.dark.primary,
|
|
200
|
+
textPrimary: theme.schemes.dark.onSurface,
|
|
201
|
+
textSecondary: theme.schemes.dark.onSurfaceVariant,
|
|
202
|
+
|
|
203
|
+
// Visualizer colors
|
|
204
|
+
visualizerColors: extraction.colors.map(c => c.hex)
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Usage
|
|
210
|
+
const player = new AlbumThemeGenerator();
|
|
211
|
+
const theme = await player.generateFromArtwork('album.jpg');
|
|
212
|
+
|
|
213
|
+
// Apply theme
|
|
214
|
+
document.querySelector('.player').style.background = theme.backgroundGradient;
|
|
215
|
+
document.querySelector('.controls').style.color = theme.controlsColor;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Logo Brand Color Extraction
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
// Extract brand colors from company logo
|
|
222
|
+
async function extractBrandColors(logoPath) {
|
|
223
|
+
const pixels = await loadLogo(logoPath);
|
|
224
|
+
|
|
225
|
+
// Extract with focus on distinct colors
|
|
226
|
+
const result = await extractImageColors({
|
|
227
|
+
imageData: pixels,
|
|
228
|
+
maxColors: 3, // Most logos have 1-3 colors
|
|
229
|
+
minPopulation: 0.05 // 5% minimum for significance
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Check for disliked colors and fix
|
|
233
|
+
const fixedColors = await fixDislikedColorsBatch({
|
|
234
|
+
colors: result.colors.map(c => c.hex)
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Build brand palette
|
|
238
|
+
const brandPalette = {
|
|
239
|
+
primary: fixedColors.fixedColors[0],
|
|
240
|
+
secondary: fixedColors.fixedColors[1] || null,
|
|
241
|
+
accent: fixedColors.fixedColors[2] || null
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// Generate full theme from primary
|
|
245
|
+
const fullTheme = await generateMaterialTheme({
|
|
246
|
+
sourceColor: brandPalette.primary,
|
|
247
|
+
customColors: brandPalette.secondary ? [
|
|
248
|
+
{ name: 'brand', color: brandPalette.secondary }
|
|
249
|
+
] : []
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
extractedColors: result.colors,
|
|
254
|
+
brandPalette,
|
|
255
|
+
fullTheme,
|
|
256
|
+
cssVariables: generateBrandCSS(brandPalette)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function generateBrandCSS(palette) {
|
|
261
|
+
return `
|
|
262
|
+
:root {
|
|
263
|
+
--brand-primary: ${palette.primary};
|
|
264
|
+
--brand-secondary: ${palette.secondary || palette.primary};
|
|
265
|
+
--brand-accent: ${palette.accent || palette.primary};
|
|
266
|
+
|
|
267
|
+
/* Generate shades */
|
|
268
|
+
${generateShades('brand-primary', palette.primary)}
|
|
269
|
+
}
|
|
270
|
+
`;
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Product Photo Color Analysis
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
// E-commerce product color detection
|
|
278
|
+
class ProductColorAnalyzer {
|
|
279
|
+
async analyzeProduct(imageUrl) {
|
|
280
|
+
const pixels = await this.loadProductImage(imageUrl);
|
|
281
|
+
|
|
282
|
+
// Extract more colors for products
|
|
283
|
+
const extracted = await extractImageColors({
|
|
284
|
+
imageData: pixels,
|
|
285
|
+
maxColors: 10,
|
|
286
|
+
minPopulation: 0.02 // 2% threshold
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Categorize colors
|
|
290
|
+
const categorized = this.categorizeColors(extracted.colors);
|
|
291
|
+
|
|
292
|
+
// Generate variants
|
|
293
|
+
const variants = await this.generateProductVariants(categorized);
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
mainColor: categorized.primary,
|
|
297
|
+
accentColors: categorized.accents,
|
|
298
|
+
availableVariants: variants,
|
|
299
|
+
colorDescription: this.generateDescription(categorized),
|
|
300
|
+
searchTags: this.generateSearchTags(categorized)
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
categorizeColors(colors) {
|
|
305
|
+
// Sort by population
|
|
306
|
+
const sorted = colors.sort((a, b) => b.population - a.population);
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
primary: sorted[0], // Most dominant
|
|
310
|
+
accents: sorted.slice(1, 4), // Next 3 colors
|
|
311
|
+
details: sorted.slice(4) // Remaining colors
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
generateProductVariants(categorized) {
|
|
316
|
+
// Find similar products in different colors
|
|
317
|
+
const hue = categorized.primary.hct.hue;
|
|
318
|
+
|
|
319
|
+
return [
|
|
320
|
+
{ name: 'Original', color: categorized.primary.hex },
|
|
321
|
+
{ name: 'Darker', color: adjustTone(categorized.primary, -20) },
|
|
322
|
+
{ name: 'Lighter', color: adjustTone(categorized.primary, +20) },
|
|
323
|
+
{ name: 'Vibrant', color: adjustChroma(categorized.primary, +30) }
|
|
324
|
+
];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
generateDescription(categorized) {
|
|
328
|
+
const primary = categorized.primary;
|
|
329
|
+
const hue = primary.hct.hue;
|
|
330
|
+
const chroma = primary.hct.chroma;
|
|
331
|
+
const tone = primary.hct.tone;
|
|
332
|
+
|
|
333
|
+
// Generate human-readable description
|
|
334
|
+
let description = '';
|
|
335
|
+
|
|
336
|
+
// Hue description
|
|
337
|
+
if (hue >= 0 && hue < 20) description += 'Red';
|
|
338
|
+
else if (hue >= 20 && hue < 40) description += 'Orange';
|
|
339
|
+
else if (hue >= 40 && hue < 60) description += 'Yellow';
|
|
340
|
+
// ... etc
|
|
341
|
+
|
|
342
|
+
// Saturation
|
|
343
|
+
if (chroma < 20) description += ', muted';
|
|
344
|
+
else if (chroma > 60) description += ', vibrant';
|
|
345
|
+
|
|
346
|
+
// Lightness
|
|
347
|
+
if (tone < 30) description += ', dark';
|
|
348
|
+
else if (tone > 70) description += ', light';
|
|
349
|
+
|
|
350
|
+
return description;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Website Screenshot Theming
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
// Extract theme from website screenshot
|
|
359
|
+
async function extractWebsiteTheme(screenshotUrl) {
|
|
360
|
+
const pixels = await loadScreenshot(screenshotUrl);
|
|
361
|
+
|
|
362
|
+
// Extract with UI-appropriate settings
|
|
363
|
+
const colors = await extractImageColors({
|
|
364
|
+
imageData: pixels,
|
|
365
|
+
maxColors: 8,
|
|
366
|
+
targetChroma: 40 // Lower chroma for web UI
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Identify UI elements
|
|
370
|
+
const identified = identifyUIColors(colors.colors);
|
|
371
|
+
|
|
372
|
+
// Generate compatible theme
|
|
373
|
+
const theme = {
|
|
374
|
+
// Primary brand color (most vibrant)
|
|
375
|
+
primary: identified.brand || colors.colors[0].hex,
|
|
376
|
+
|
|
377
|
+
// Background (lightest color)
|
|
378
|
+
background: identified.background || '#ffffff',
|
|
379
|
+
|
|
380
|
+
// Surface (cards, modals)
|
|
381
|
+
surface: identified.surface || '#ffffff',
|
|
382
|
+
|
|
383
|
+
// Text colors
|
|
384
|
+
textPrimary: identified.textPrimary || '#1f2937',
|
|
385
|
+
textSecondary: identified.textSecondary || '#6b7280',
|
|
386
|
+
|
|
387
|
+
// Borders and dividers
|
|
388
|
+
border: identified.border || '#e5e7eb',
|
|
389
|
+
|
|
390
|
+
// Interactive elements
|
|
391
|
+
link: identified.link || colors.colors[0].hex,
|
|
392
|
+
button: identified.button || colors.colors[0].hex
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
extractedColors: colors,
|
|
397
|
+
identifiedTheme: theme,
|
|
398
|
+
css: generateThemeCSS(theme)
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function identifyUIColors(colors) {
|
|
403
|
+
const identified = {};
|
|
404
|
+
|
|
405
|
+
colors.forEach(color => {
|
|
406
|
+
const { hue, chroma, tone } = color.hct;
|
|
407
|
+
|
|
408
|
+
// Very light colors are likely backgrounds
|
|
409
|
+
if (tone > 95) {
|
|
410
|
+
identified.background = color.hex;
|
|
411
|
+
}
|
|
412
|
+
// Light grays are surfaces
|
|
413
|
+
else if (tone > 90 && chroma < 10) {
|
|
414
|
+
identified.surface = color.hex;
|
|
415
|
+
}
|
|
416
|
+
// Dark colors are text
|
|
417
|
+
else if (tone < 30) {
|
|
418
|
+
identified.textPrimary = color.hex;
|
|
419
|
+
}
|
|
420
|
+
// Medium grays are secondary text or borders
|
|
421
|
+
else if (tone > 40 && tone < 70 && chroma < 10) {
|
|
422
|
+
if (!identified.textSecondary) {
|
|
423
|
+
identified.textSecondary = color.hex;
|
|
424
|
+
} else {
|
|
425
|
+
identified.border = color.hex;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// Vibrant colors are brand/interactive
|
|
429
|
+
else if (chroma > 40) {
|
|
430
|
+
identified.brand = color.hex;
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
return identified;
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Advanced Extraction Techniques
|
|
439
|
+
|
|
440
|
+
### Focusing on Specific Regions
|
|
441
|
+
|
|
442
|
+
```javascript
|
|
443
|
+
// Extract colors from specific image region
|
|
444
|
+
function extractFromRegion(imageData, region) {
|
|
445
|
+
const { x, y, width, height } = region;
|
|
446
|
+
const fullWidth = Math.sqrt(imageData.length / 4);
|
|
447
|
+
|
|
448
|
+
// Extract pixels from region
|
|
449
|
+
const regionPixels = [];
|
|
450
|
+
for (let row = y; row < y + height; row++) {
|
|
451
|
+
for (let col = x; col < x + width; col++) {
|
|
452
|
+
const idx = (row * fullWidth + col) * 4;
|
|
453
|
+
regionPixels.push(
|
|
454
|
+
imageData[idx], // R
|
|
455
|
+
imageData[idx + 1], // G
|
|
456
|
+
imageData[idx + 2], // B
|
|
457
|
+
imageData[idx + 3] // A
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Extract colors from region
|
|
463
|
+
return extractImageColors({
|
|
464
|
+
imageData: regionPixels,
|
|
465
|
+
maxColors: 5
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Usage: Extract logo colors from header
|
|
470
|
+
const headerRegion = { x: 0, y: 0, width: 200, height: 100 };
|
|
471
|
+
const logoColors = await extractFromRegion(pixels, headerRegion);
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Excluding Background Colors
|
|
475
|
+
|
|
476
|
+
```javascript
|
|
477
|
+
// Remove background color from extraction
|
|
478
|
+
async function extractForegroundColors(imageData, backgroundColor) {
|
|
479
|
+
// Extract all colors
|
|
480
|
+
const allColors = await extractImageColors({
|
|
481
|
+
imageData,
|
|
482
|
+
maxColors: 10
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Filter out colors similar to background
|
|
486
|
+
const foregroundColors = allColors.colors.filter(color => {
|
|
487
|
+
const distance = colorDistance(color.hex, backgroundColor);
|
|
488
|
+
return distance > 10; // Significant difference
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
return foregroundColors;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Usage: Extract product colors without white background
|
|
495
|
+
const productColors = await extractForegroundColors(pixels, '#ffffff');
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Weighted Extraction
|
|
499
|
+
|
|
500
|
+
```javascript
|
|
501
|
+
// Weight center pixels more heavily
|
|
502
|
+
function createWeightedPixelArray(imageData, width, height) {
|
|
503
|
+
const weighted = [];
|
|
504
|
+
const centerX = width / 2;
|
|
505
|
+
const centerY = height / 2;
|
|
506
|
+
const maxDist = Math.sqrt(centerX * centerX + centerY * centerY);
|
|
507
|
+
|
|
508
|
+
for (let y = 0; y < height; y++) {
|
|
509
|
+
for (let x = 0; x < width; x++) {
|
|
510
|
+
const idx = (y * width + x) * 4;
|
|
511
|
+
|
|
512
|
+
// Calculate weight based on distance from center
|
|
513
|
+
const dist = Math.sqrt(
|
|
514
|
+
Math.pow(x - centerX, 2) +
|
|
515
|
+
Math.pow(y - centerY, 2)
|
|
516
|
+
);
|
|
517
|
+
const weight = 1 - (dist / maxDist) * 0.5; // 50-100% weight
|
|
518
|
+
|
|
519
|
+
// Add pixel multiple times based on weight
|
|
520
|
+
const copies = Math.ceil(weight * 3);
|
|
521
|
+
for (let i = 0; i < copies; i++) {
|
|
522
|
+
weighted.push(
|
|
523
|
+
imageData[idx],
|
|
524
|
+
imageData[idx + 1],
|
|
525
|
+
imageData[idx + 2],
|
|
526
|
+
imageData[idx + 3]
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return weighted;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Usage: Focus on center of image
|
|
536
|
+
const weighted = createWeightedPixelArray(pixels, 300, 300);
|
|
537
|
+
const centralColors = await extractImageColors({
|
|
538
|
+
imageData: weighted,
|
|
539
|
+
maxColors: 5
|
|
540
|
+
});
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
## Dislike Zone Handling
|
|
544
|
+
|
|
545
|
+
### Automatic Correction
|
|
546
|
+
|
|
547
|
+
```javascript
|
|
548
|
+
// Fix universally disliked colors
|
|
549
|
+
async function extractLikableColors(imageData) {
|
|
550
|
+
// Extract colors
|
|
551
|
+
const extracted = await extractImageColors({
|
|
552
|
+
imageData,
|
|
553
|
+
maxColors: 8
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// Check and fix disliked colors
|
|
557
|
+
const colorHexes = extracted.colors.map(c => c.hex);
|
|
558
|
+
const fixed = await fixDislikedColorsBatch({
|
|
559
|
+
colors: colorHexes,
|
|
560
|
+
strategy: 'both' // Shift hue and adjust tone
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// Map back to full color objects
|
|
564
|
+
const likableColors = extracted.colors.map((color, i) => {
|
|
565
|
+
if (fixed.results[i].isDisliked) {
|
|
566
|
+
return {
|
|
567
|
+
...color,
|
|
568
|
+
hex: fixed.results[i].fixed,
|
|
569
|
+
wasFixed: true,
|
|
570
|
+
originalHex: color.hex
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
return color;
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
return {
|
|
577
|
+
colors: likableColors,
|
|
578
|
+
fixedCount: fixed.summary.fixed,
|
|
579
|
+
originalColors: extracted.colors
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Bile Zone Detection
|
|
585
|
+
|
|
586
|
+
```javascript
|
|
587
|
+
// Identify problematic colors in palette
|
|
588
|
+
function detectBileZoneColors(colors) {
|
|
589
|
+
const problematic = [];
|
|
590
|
+
|
|
591
|
+
colors.forEach(color => {
|
|
592
|
+
const { hue, chroma, tone } = color.hct;
|
|
593
|
+
|
|
594
|
+
// Bile zone: dark yellow-greens
|
|
595
|
+
if (hue >= 50 && hue <= 120 && // Yellow-green range
|
|
596
|
+
chroma >= 20 && chroma <= 50 && // Moderate saturation
|
|
597
|
+
tone >= 20 && tone <= 50) { // Dark to medium
|
|
598
|
+
problematic.push({
|
|
599
|
+
color: color.hex,
|
|
600
|
+
reason: 'Falls in universally disliked "bile zone"',
|
|
601
|
+
suggestion: adjustToLikable(color)
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
return problematic;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function adjustToLikable(color) {
|
|
610
|
+
// Shift hue away from problematic range
|
|
611
|
+
let newHue = color.hct.hue;
|
|
612
|
+
if (newHue >= 50 && newHue <= 120) {
|
|
613
|
+
newHue = newHue < 85 ? 40 : 130; // Shift to yellow or green
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Increase tone for lighter appearance
|
|
617
|
+
const newTone = Math.min(color.hct.tone + 20, 80);
|
|
618
|
+
|
|
619
|
+
return hctToHex({ hue: newHue, chroma: color.hct.chroma, tone: newTone });
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
## Performance Optimization
|
|
624
|
+
|
|
625
|
+
### Image Preprocessing
|
|
626
|
+
|
|
627
|
+
```javascript
|
|
628
|
+
// Optimize image before extraction
|
|
629
|
+
async function preprocessImage(imageUrl) {
|
|
630
|
+
const img = await loadImage(imageUrl);
|
|
631
|
+
|
|
632
|
+
// Resize for performance
|
|
633
|
+
const maxDimension = 400;
|
|
634
|
+
const scale = Math.min(
|
|
635
|
+
maxDimension / img.width,
|
|
636
|
+
maxDimension / img.height,
|
|
637
|
+
1
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
// Apply slight blur to reduce noise
|
|
641
|
+
const canvas = document.createElement('canvas');
|
|
642
|
+
const ctx = canvas.getContext('2d');
|
|
643
|
+
canvas.width = img.width * scale;
|
|
644
|
+
canvas.height = img.height * scale;
|
|
645
|
+
|
|
646
|
+
// Enable image smoothing
|
|
647
|
+
ctx.imageSmoothingEnabled = true;
|
|
648
|
+
ctx.imageSmoothingQuality = 'high';
|
|
649
|
+
|
|
650
|
+
// Draw scaled image
|
|
651
|
+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
652
|
+
|
|
653
|
+
// Optional: Apply slight blur
|
|
654
|
+
ctx.filter = 'blur(0.5px)';
|
|
655
|
+
ctx.drawImage(canvas, 0, 0);
|
|
656
|
+
|
|
657
|
+
return ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Caching Extracted Colors
|
|
662
|
+
|
|
663
|
+
```javascript
|
|
664
|
+
// Cache extraction results
|
|
665
|
+
class ColorExtractionCache {
|
|
666
|
+
constructor() {
|
|
667
|
+
this.cache = new Map();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
async extractColors(imageUrl, options = {}) {
|
|
671
|
+
// Generate cache key
|
|
672
|
+
const key = this.generateKey(imageUrl, options);
|
|
673
|
+
|
|
674
|
+
// Check cache
|
|
675
|
+
if (this.cache.has(key)) {
|
|
676
|
+
return this.cache.get(key);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Extract colors
|
|
680
|
+
const pixels = await loadImage(imageUrl);
|
|
681
|
+
const result = await extractImageColors({
|
|
682
|
+
imageData: pixels,
|
|
683
|
+
...options
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
// Cache result
|
|
687
|
+
this.cache.set(key, result);
|
|
688
|
+
|
|
689
|
+
// Limit cache size
|
|
690
|
+
if (this.cache.size > 100) {
|
|
691
|
+
const firstKey = this.cache.keys().next().value;
|
|
692
|
+
this.cache.delete(firstKey);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return result;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
generateKey(url, options) {
|
|
699
|
+
return `${url}_${JSON.stringify(options)}`;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
### Batch Processing
|
|
705
|
+
|
|
706
|
+
```javascript
|
|
707
|
+
// Process multiple images efficiently
|
|
708
|
+
async function batchExtractColors(imageUrls, options = {}) {
|
|
709
|
+
const batchSize = 5; // Process 5 at a time
|
|
710
|
+
const results = [];
|
|
711
|
+
|
|
712
|
+
for (let i = 0; i < imageUrls.length; i += batchSize) {
|
|
713
|
+
const batch = imageUrls.slice(i, i + batchSize);
|
|
714
|
+
|
|
715
|
+
// Process batch in parallel
|
|
716
|
+
const batchResults = await Promise.all(
|
|
717
|
+
batch.map(async url => {
|
|
718
|
+
try {
|
|
719
|
+
const pixels = await loadImage(url);
|
|
720
|
+
return await extractImageColors({
|
|
721
|
+
imageData: pixels,
|
|
722
|
+
...options
|
|
723
|
+
});
|
|
724
|
+
} catch (error) {
|
|
725
|
+
console.error(`Failed to process ${url}:`, error);
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
})
|
|
729
|
+
);
|
|
730
|
+
|
|
731
|
+
results.push(...batchResults);
|
|
732
|
+
|
|
733
|
+
// Progress callback
|
|
734
|
+
if (options.onProgress) {
|
|
735
|
+
options.onProgress({
|
|
736
|
+
processed: Math.min(i + batchSize, imageUrls.length),
|
|
737
|
+
total: imageUrls.length
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return results.filter(r => r !== null);
|
|
743
|
+
}
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
## Integration Examples
|
|
747
|
+
|
|
748
|
+
### React Component
|
|
749
|
+
|
|
750
|
+
```jsx
|
|
751
|
+
// React hook for image color extraction
|
|
752
|
+
function useImageColors(imageUrl) {
|
|
753
|
+
const [colors, setColors] = useState(null);
|
|
754
|
+
const [loading, setLoading] = useState(false);
|
|
755
|
+
const [error, setError] = useState(null);
|
|
756
|
+
|
|
757
|
+
useEffect(() => {
|
|
758
|
+
if (!imageUrl) return;
|
|
759
|
+
|
|
760
|
+
const extractColors = async () => {
|
|
761
|
+
setLoading(true);
|
|
762
|
+
setError(null);
|
|
763
|
+
|
|
764
|
+
try {
|
|
765
|
+
const pixels = await loadImageFromUrl(imageUrl);
|
|
766
|
+
const result = await extractImageColors({
|
|
767
|
+
imageData: pixels,
|
|
768
|
+
maxColors: 5
|
|
769
|
+
});
|
|
770
|
+
setColors(result.colors);
|
|
771
|
+
} catch (err) {
|
|
772
|
+
setError(err.message);
|
|
773
|
+
} finally {
|
|
774
|
+
setLoading(false);
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
extractColors();
|
|
779
|
+
}, [imageUrl]);
|
|
780
|
+
|
|
781
|
+
return { colors, loading, error };
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Usage in component
|
|
785
|
+
function ImagePalette({ imageUrl }) {
|
|
786
|
+
const { colors, loading, error } = useImageColors(imageUrl);
|
|
787
|
+
|
|
788
|
+
if (loading) return <div>Extracting colors...</div>;
|
|
789
|
+
if (error) return <div>Error: {error}</div>;
|
|
790
|
+
if (!colors) return null;
|
|
791
|
+
|
|
792
|
+
return (
|
|
793
|
+
<div className="palette">
|
|
794
|
+
{colors.map((color, i) => (
|
|
795
|
+
<div
|
|
796
|
+
key={i}
|
|
797
|
+
className="color-swatch"
|
|
798
|
+
style={{ backgroundColor: color.hex }}
|
|
799
|
+
>
|
|
800
|
+
<span>{color.hex}</span>
|
|
801
|
+
<span>{Math.round(color.population * 100)}%</span>
|
|
802
|
+
</div>
|
|
803
|
+
))}
|
|
804
|
+
</div>
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### Vue Component
|
|
810
|
+
|
|
811
|
+
```vue
|
|
812
|
+
<template>
|
|
813
|
+
<div class="image-theme-generator">
|
|
814
|
+
<input
|
|
815
|
+
type="file"
|
|
816
|
+
@change="handleFileUpload"
|
|
817
|
+
accept="image/*"
|
|
818
|
+
>
|
|
819
|
+
|
|
820
|
+
<div v-if="loading">Generating theme...</div>
|
|
821
|
+
|
|
822
|
+
<div v-if="theme" class="theme-preview">
|
|
823
|
+
<div
|
|
824
|
+
v-for="(color, name) in theme.schemes.light"
|
|
825
|
+
:key="name"
|
|
826
|
+
:style="{ backgroundColor: color }"
|
|
827
|
+
class="color-block"
|
|
828
|
+
>
|
|
829
|
+
{{ name }}: {{ color }}
|
|
830
|
+
</div>
|
|
831
|
+
</div>
|
|
832
|
+
</div>
|
|
833
|
+
</template>
|
|
834
|
+
|
|
835
|
+
<script>
|
|
836
|
+
export default {
|
|
837
|
+
data() {
|
|
838
|
+
return {
|
|
839
|
+
theme: null,
|
|
840
|
+
loading: false
|
|
841
|
+
};
|
|
842
|
+
},
|
|
843
|
+
|
|
844
|
+
methods: {
|
|
845
|
+
async handleFileUpload(event) {
|
|
846
|
+
const file = event.target.files[0];
|
|
847
|
+
if (!file) return;
|
|
848
|
+
|
|
849
|
+
this.loading = true;
|
|
850
|
+
|
|
851
|
+
try {
|
|
852
|
+
const pixels = await this.getImagePixels(file);
|
|
853
|
+
const theme = await this.generateTheme(pixels);
|
|
854
|
+
this.theme = theme;
|
|
855
|
+
} catch (error) {
|
|
856
|
+
console.error('Failed to generate theme:', error);
|
|
857
|
+
} finally {
|
|
858
|
+
this.loading = false;
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
|
|
862
|
+
async getImagePixels(file) {
|
|
863
|
+
// Implementation here
|
|
864
|
+
},
|
|
865
|
+
|
|
866
|
+
async generateTheme(pixels) {
|
|
867
|
+
return await generateThemeFromImage({
|
|
868
|
+
imageData: pixels,
|
|
869
|
+
variant: 'tonalSpot'
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
</script>
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
## Next Steps
|
|
878
|
+
|
|
879
|
+
- Learn about [Creating Themes](./creating-themes.md) for comprehensive design systems
|
|
880
|
+
- Explore [CSS Refactoring](./css-refactoring.md) to apply extracted colors
|
|
881
|
+
- Read [Image Analysis Concepts](../concepts/image-analysis.md) for theory
|
|
882
|
+
- Check [Image Extraction Tools](../tools/image-extraction.md) for API reference
|