@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.
Files changed (197) hide show
  1. package/.claude/settings.local.json +39 -0
  2. package/.env +2 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +73 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +71 -0
  5. package/.github/pull_request_template.md +97 -0
  6. package/.github/workflows/ci.yml +127 -0
  7. package/.github/workflows/deploy-docs.yml +56 -0
  8. package/.github/workflows/release.yml +99 -0
  9. package/.mcp.json +12 -0
  10. package/.prettierignore +1 -0
  11. package/CLAUDE.md +201 -0
  12. package/DOCUMENTATION.md +274 -0
  13. package/GEMINI.md +54 -0
  14. package/LICENSE +21 -0
  15. package/README.md +401 -0
  16. package/demo/content_based_color.png +0 -0
  17. package/demo/music-player.html +621 -0
  18. package/demo/podcast-player.html +903 -0
  19. package/dist/bin/coolors-mcp.d.ts +1 -0
  20. package/dist/bin/coolors-mcp.js +154 -0
  21. package/dist/bin/coolors-mcp.js.map +1 -0
  22. package/dist/bin/server.d.ts +1 -0
  23. package/dist/bin/server.js +3292 -0
  24. package/dist/bin/server.js.map +1 -0
  25. package/dist/chunk-IQ7NN26V.js +114 -0
  26. package/dist/chunk-IQ7NN26V.js.map +1 -0
  27. package/dist/chunk-P3ARRKLS.js +1214 -0
  28. package/dist/chunk-P3ARRKLS.js.map +1 -0
  29. package/dist/color/index.d.ts +716 -0
  30. package/dist/color/index.js +153 -0
  31. package/dist/color/index.js.map +1 -0
  32. package/dist/coolors-mcp.d.ts +136 -0
  33. package/dist/coolors-mcp.js +7 -0
  34. package/dist/coolors-mcp.js.map +1 -0
  35. package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +93 -0
  36. package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js.map +7 -0
  37. package/docs/.vitepress/cache/deps/_metadata.json +127 -0
  38. package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -0
  39. package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +7 -0
  40. package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +12683 -0
  41. package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +7 -0
  42. package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +9719 -0
  43. package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +7 -0
  44. package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +4710 -0
  45. package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +7 -0
  46. package/docs/.vitepress/cache/deps/cytoscape.js +30278 -0
  47. package/docs/.vitepress/cache/deps/cytoscape.js.map +7 -0
  48. package/docs/.vitepress/cache/deps/dayjs.js +285 -0
  49. package/docs/.vitepress/cache/deps/dayjs.js.map +7 -0
  50. package/docs/.vitepress/cache/deps/debug.js +468 -0
  51. package/docs/.vitepress/cache/deps/debug.js.map +7 -0
  52. package/docs/.vitepress/cache/deps/package.json +3 -0
  53. package/docs/.vitepress/cache/deps/prismjs.js +1466 -0
  54. package/docs/.vitepress/cache/deps/prismjs.js.map +7 -0
  55. package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +228 -0
  56. package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +7 -0
  57. package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +142 -0
  58. package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js.map +7 -0
  59. package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +27 -0
  60. package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js.map +7 -0
  61. package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +65 -0
  62. package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +7 -0
  63. package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +53 -0
  64. package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +7 -0
  65. package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +73 -0
  66. package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +7 -0
  67. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4507 -0
  68. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  69. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +584 -0
  70. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  71. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1146 -0
  72. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
  73. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1667 -0
  74. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
  75. package/docs/.vitepress/cache/deps/vitepress___minisearch.js +1814 -0
  76. package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
  77. package/docs/.vitepress/cache/deps/vue.js +344 -0
  78. package/docs/.vitepress/cache/deps/vue.js.map +7 -0
  79. package/docs/.vitepress/components/ClientGrid.vue +125 -0
  80. package/docs/.vitepress/components/CodeBlock.vue +231 -0
  81. package/docs/.vitepress/components/ConfigModal.vue +477 -0
  82. package/docs/.vitepress/components/DiagramModal.vue +528 -0
  83. package/docs/.vitepress/components/TroubleshootingModal.vue +472 -0
  84. package/docs/.vitepress/config.js +162 -0
  85. package/docs/.vitepress/theme/FundingLayout.vue +251 -0
  86. package/docs/.vitepress/theme/Layout.vue +134 -0
  87. package/docs/.vitepress/theme/components/AdBanner.vue +317 -0
  88. package/docs/.vitepress/theme/components/AdPlaceholder.vue +78 -0
  89. package/docs/.vitepress/theme/components/FundingEffects.vue +322 -0
  90. package/docs/.vitepress/theme/components/FundingHero.vue +345 -0
  91. package/docs/.vitepress/theme/components/SupportSection.vue +511 -0
  92. package/docs/.vitepress/theme/custom-app.css +339 -0
  93. package/docs/.vitepress/theme/custom.css +699 -0
  94. package/docs/.vitepress/theme/index.js +25 -0
  95. package/docs/README.md +198 -0
  96. package/docs/concepts/accessibility.md +473 -0
  97. package/docs/concepts/color-spaces.md +222 -0
  98. package/docs/concepts/distance-metrics.md +384 -0
  99. package/docs/concepts/hct.md +261 -0
  100. package/docs/concepts/image-analysis.md +396 -0
  101. package/docs/concepts/material-design.md +306 -0
  102. package/docs/concepts/theme-matching.md +399 -0
  103. package/docs/examples/basic-colors.md +490 -0
  104. package/docs/examples/creating-themes.md +898 -0
  105. package/docs/examples/css-refactoring.md +824 -0
  106. package/docs/examples/image-extraction.md +882 -0
  107. package/docs/getting-started.md +366 -0
  108. package/docs/index.md +190 -0
  109. package/docs/installation.md +157 -0
  110. package/docs/tools/README.md +234 -0
  111. package/docs/tools/accessibility.md +614 -0
  112. package/docs/tools/color-operations.md +374 -0
  113. package/docs/tools/image-extraction.md +624 -0
  114. package/docs/tools/material-design.md +347 -0
  115. package/docs/tools/theme-matching.md +552 -0
  116. package/eslint.config.ts +14 -0
  117. package/examples/theme-matching.md +113 -0
  118. package/jsr.json +7 -0
  119. package/mcp-config.json +8 -0
  120. package/note.md +35 -0
  121. package/package.json +122 -0
  122. package/research_results.md +53 -0
  123. package/src/bin/coolors-mcp.ts +194 -0
  124. package/src/bin/server.ts +61 -0
  125. package/src/color/__tests__/conversions-argb.test.ts +198 -0
  126. package/src/color/__tests__/extract-colors.test.ts +360 -0
  127. package/src/color/__tests__/image-utils.test.ts +242 -0
  128. package/src/color/__tests__/reference-colors.test.ts +278 -0
  129. package/src/color/__tests__/round-trip.test.ts +197 -0
  130. package/src/color/conversions.test.ts +402 -0
  131. package/src/color/conversions.ts +393 -0
  132. package/src/color/dislike/__tests__/dislike-analyzer.test.ts +248 -0
  133. package/src/color/dislike/dislike-analyzer.ts +114 -0
  134. package/src/color/extract-colors.ts +228 -0
  135. package/src/color/hct/__tests__/hct-class.test.ts +232 -0
  136. package/src/color/hct/harmonization.ts +204 -0
  137. package/src/color/hct/hct-class.ts +109 -0
  138. package/src/color/hct/hct-solver.ts +168 -0
  139. package/src/color/hct/index.ts +39 -0
  140. package/src/color/hct/tonal-palette.ts +211 -0
  141. package/src/color/hct/types.ts +88 -0
  142. package/src/color/image-utils.ts +79 -0
  143. package/src/color/index.ts +87 -0
  144. package/src/color/material-theme.ts +157 -0
  145. package/src/color/metrics.test.ts +276 -0
  146. package/src/color/metrics.ts +281 -0
  147. package/src/color/quantize/__tests__/quantizer_celebi.test.ts +195 -0
  148. package/src/color/quantize/lab_point_provider.ts +55 -0
  149. package/src/color/quantize/point_provider.ts +27 -0
  150. package/src/color/quantize/quantizer_celebi.ts +51 -0
  151. package/src/color/quantize/quantizer_celebi_test.ts +71 -0
  152. package/src/color/quantize/quantizer_map.ts +47 -0
  153. package/src/color/quantize/quantizer_wsmeans.ts +232 -0
  154. package/src/color/quantize/quantizer_wu.ts +472 -0
  155. package/src/color/score/__tests__/score.test.ts +224 -0
  156. package/src/color/score/score.ts +175 -0
  157. package/src/color/types.ts +151 -0
  158. package/src/color/utils/color_utils.ts +292 -0
  159. package/src/color/utils/math_utils.ts +145 -0
  160. package/src/color/utils.test.ts +403 -0
  161. package/src/color/utils.ts +315 -0
  162. package/src/constants.ts +5 -0
  163. package/src/coolors-mcp.ts +37 -0
  164. package/src/examples/addition.ts +333 -0
  165. package/src/examples/color-demo.ts +125 -0
  166. package/src/examples/custom-logger.ts +201 -0
  167. package/src/examples/oauth-server.ts +113 -0
  168. package/src/examples/session-context.ts +269 -0
  169. package/src/session.ts +116 -0
  170. package/src/theme/__tests__/matcher.test.ts +180 -0
  171. package/src/theme/__tests__/parser.test.ts +148 -0
  172. package/src/theme/__tests__/refactor.test.ts +224 -0
  173. package/src/theme/index.ts +34 -0
  174. package/src/theme/matcher.ts +395 -0
  175. package/src/theme/parser.ts +392 -0
  176. package/src/theme/refactor.ts +360 -0
  177. package/src/theme/types.ts +152 -0
  178. package/src/tools/__tests__/gradient-generator.test.ts +206 -0
  179. package/src/tools/__tests__/palette-with-locks.test.ts +109 -0
  180. package/src/tools/color-conversion.tool.ts +54 -0
  181. package/src/tools/color-distance.tool.ts +41 -0
  182. package/src/tools/colors.ts +31 -0
  183. package/src/tools/contrast-checker.tool.ts +37 -0
  184. package/src/tools/dislike-analyzer.tool.ts +247 -0
  185. package/src/tools/gradient-generator.tool.ts +250 -0
  186. package/src/tools/image-extraction.tools.ts +289 -0
  187. package/src/tools/index.ts +39 -0
  188. package/src/tools/material-theme.tools.ts +250 -0
  189. package/src/tools/palette-generator.tool.ts +135 -0
  190. package/src/tools/palette-with-locks.tool.ts +221 -0
  191. package/src/tools/registry.ts +142 -0
  192. package/src/tools/simple-tools.ts +37 -0
  193. package/src/tools/theme-matching.tools.ts +334 -0
  194. package/src/types.ts +182 -0
  195. package/src/utils.ts +22 -0
  196. package/tsconfig.json +8 -0
  197. 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