colorlip 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nazunya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # colorlip
2
+
3
+ Fast, general-purpose dominant color extraction library for Node.js and Browser. Especially strong with illustrations and artwork. Zero-dependency core.
4
+
5
+ ## Features
6
+
7
+ - Adaptive color extraction using CIELAB Delta E perceptual distance
8
+ - Rich output: hex, HSL, hue category (`red`, `blue`, `green`, ...) per color
9
+ - Platform-agnostic core (`colorlip`) works anywhere
10
+ - Built-in adapters for **Node.js (sharp)** and **Browser (Canvas API)**
11
+ - TypeScript-first with full type definitions
12
+ - Zero runtime dependencies in core
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install colorlip
18
+ ```
19
+
20
+ For Node.js usage with sharp adapter:
21
+
22
+ ```bash
23
+ npm install colorlip sharp
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ### Node.js (sharp)
29
+
30
+ ```ts
31
+ import { colorlipFromFile } from "colorlip/sharp";
32
+
33
+ const colors = await colorlipFromFile("photo.jpg");
34
+
35
+ console.log(colors);
36
+ // [
37
+ // { r: 42, g: 98, b: 168, hex: '#2A62A8', percentage: 0.34,
38
+ // hue: 213, saturation: 75, lightness: 41, hueCategory: 'blue' },
39
+ // ...
40
+ // ]
41
+ ```
42
+
43
+ ### Browser (Canvas API)
44
+
45
+ ```ts
46
+ import { colorlipFromImage } from "colorlip/canvas";
47
+
48
+ const colors = await colorlipFromImage(imgElement);
49
+ ```
50
+
51
+ ### Raw pixels (any environment)
52
+
53
+ ```ts
54
+ import { colorlip } from "colorlip";
55
+
56
+ const colors = colorlip(pixelData, width, height, channels);
57
+ ```
58
+
59
+ ## API
60
+
61
+ ### `colorlipFromFile(filePath, options?)` — Node.js / sharp
62
+
63
+ Reads an image file and returns dominant colors.
64
+
65
+ ### `colorlipFromBuffer(buffer, options?)` — Node.js / sharp
66
+
67
+ Extracts from an in-memory image buffer.
68
+
69
+ ### `colorlipFromImage(source, options?)` — Browser / Canvas
70
+
71
+ Accepts `HTMLImageElement`, `ImageBitmap`, `Blob`, or image URL string.
72
+
73
+ ### `colorlipFromImageData(imageData, options?)` — Browser / Canvas
74
+
75
+ Extracts from a Canvas `ImageData` object directly.
76
+
77
+ ### `colorlip(data, width, height, channels, options?)` — Core
78
+
79
+ Low-level function that works with raw pixel data (`Uint8Array` / `Uint8ClampedArray`). Platform-agnostic.
80
+
81
+ ### Options
82
+
83
+ ```ts
84
+ interface ExtractOptions {
85
+ numColors?: number; // Number of colors to extract (default: 3)
86
+ saturationThreshold?: number; // Saturation filter threshold (default: 0.15)
87
+ brightnessMin?: number; // Min brightness filter (default: 20)
88
+ brightnessMax?: number; // Max brightness filter (default: 235)
89
+ quantizationStep?: number; // Quantization step size (default: 12)
90
+ }
91
+ ```
92
+
93
+ ### Output
94
+
95
+ Each color in the result array is a `DominantColor`:
96
+
97
+ ```ts
98
+ interface DominantColor {
99
+ r: number; // 0-255
100
+ g: number; // 0-255
101
+ b: number; // 0-255
102
+ hex: string; // e.g. "#2A62A8"
103
+ percentage: number; // Relative weight (0-1)
104
+ hue: number; // 0-360
105
+ saturation: number; // 0-100
106
+ lightness: number; // 0-100
107
+ hueCategory: HueCategory; // "red" | "orange" | "yellow" | "green" | "cyan" | "blue" | "violet" | "gray"
108
+ }
109
+ ```
110
+
111
+ ### Utility functions
112
+
113
+ ```ts
114
+ import { rgbToHex, rgbToHsl, getHueCategory, createDominantColor, aggregateColors } from "colorlip";
115
+ ```
116
+
117
+ ## How It Works
118
+
119
+ 1. **Resize** — Image is downscaled to 150x150 max via adapter (sharp / canvas)
120
+ 2. **Analyze** — Sampling pass estimates image characteristics (median saturation, edge centrality)
121
+ 3. **Extract** — Single-pass pixel scan with adaptive saturation threshold, center weighting, and edge weighting
122
+ 4. **Quantize** — Colors are bucketed by quantization step into a Map
123
+ 5. **Score** — Each color bucket is scored by weight, saturation, and spatial variance
124
+ 6. **Merge** — Similar colors are merged using CIE76 Delta E (threshold: 15)
125
+ 7. **Fallback** — If no colors pass the filter, a simpler histogram-based extraction is used
126
+
127
+ ## License
128
+
129
+ [MIT](./LICENSE)
@@ -0,0 +1,387 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/adapters/canvas.ts
21
+ var canvas_exports = {};
22
+ __export(canvas_exports, {
23
+ colorlip: () => colorlip,
24
+ colorlipFromImage: () => colorlipFromImage,
25
+ colorlipFromImageData: () => colorlipFromImageData
26
+ });
27
+ module.exports = __toCommonJS(canvas_exports);
28
+
29
+ // src/constants.ts
30
+ var DEFAULT_OPTIONS = {
31
+ numColors: 3,
32
+ saturationThreshold: 0.15,
33
+ brightnessMin: 20,
34
+ brightnessMax: 235,
35
+ quantizationStep: 12
36
+ };
37
+ var FALLBACK_QUANTIZATION_STEP = 16;
38
+ var ALPHA_THRESHOLD = 16;
39
+ var EDGE_STRENGTH_DIVISOR = 100;
40
+ var BORDER_EDGE_WEIGHT = 1.5;
41
+
42
+ // src/core.ts
43
+ function rgbToLab(r, g, b) {
44
+ let rl = r / 255;
45
+ let gl = g / 255;
46
+ let bl = b / 255;
47
+ rl = rl > 0.04045 ? ((rl + 0.055) / 1.055) ** 2.4 : rl / 12.92;
48
+ gl = gl > 0.04045 ? ((gl + 0.055) / 1.055) ** 2.4 : gl / 12.92;
49
+ bl = bl > 0.04045 ? ((bl + 0.055) / 1.055) ** 2.4 : bl / 12.92;
50
+ let x = (rl * 0.4124564 + gl * 0.3575761 + bl * 0.1804375) / 0.95047;
51
+ let y = rl * 0.2126729 + gl * 0.7151522 + bl * 0.072175;
52
+ let z = (rl * 0.0193339 + gl * 0.119192 + bl * 0.9503041) / 1.08883;
53
+ const epsilon = 8856e-6;
54
+ const kappa = 903.3;
55
+ x = x > epsilon ? Math.cbrt(x) : (kappa * x + 16) / 116;
56
+ y = y > epsilon ? Math.cbrt(y) : (kappa * y + 16) / 116;
57
+ z = z > epsilon ? Math.cbrt(z) : (kappa * z + 16) / 116;
58
+ return {
59
+ L: 116 * y - 16,
60
+ a: 500 * (x - y),
61
+ b: 200 * (y - z)
62
+ };
63
+ }
64
+ function deltaE76(lab1, lab2) {
65
+ return Math.sqrt((lab1.L - lab2.L) ** 2 + (lab1.a - lab2.a) ** 2 + (lab1.b - lab2.b) ** 2);
66
+ }
67
+ function analyzeImageStats(data, width, height, channels) {
68
+ const pixelCount = width * height;
69
+ if (pixelCount === 0) return { medianSaturation: 0, edgeCentrality: 0.5 };
70
+ const sampleCount = Math.min(Math.max(Math.floor(pixelCount * 0.1), 100), 2e3);
71
+ const stride = Math.max(1, Math.floor(pixelCount / sampleCount));
72
+ const saturations = [];
73
+ const centerX = width / 2;
74
+ const centerY = height / 2;
75
+ const maxDist = Math.sqrt(centerX ** 2 + centerY ** 2);
76
+ let edgeWeightedCenterDist = 0;
77
+ let totalEdgeStrength = 0;
78
+ for (let idx = 0; idx < pixelCount; idx += stride) {
79
+ const x = idx % width;
80
+ const y = Math.floor(idx / width);
81
+ const i = idx * channels;
82
+ const r = data[i] ?? 0;
83
+ const g = data[i + 1] ?? 0;
84
+ const b = data[i + 2] ?? 0;
85
+ const max = Math.max(r, g, b);
86
+ const min = Math.min(r, g, b);
87
+ saturations.push(max === 0 ? 0 : (max - min) / max);
88
+ if (x > 0 && x < width - 1 && y > 0 && y < height - 1) {
89
+ const getGray = (px, py) => {
90
+ const j = (py * width + px) * channels;
91
+ return ((data[j] ?? 0) + (data[j + 1] ?? 0) + (data[j + 2] ?? 0)) / 3;
92
+ };
93
+ const center = getGray(x, y);
94
+ const strength = Math.abs(center - getGray(x - 1, y)) + Math.abs(center - getGray(x + 1, y)) + Math.abs(center - getGray(x, y - 1)) + Math.abs(center - getGray(x, y + 1));
95
+ if (strength > 10) {
96
+ const distFromCenter = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
97
+ const normalizedDist = maxDist > 0 ? distFromCenter / maxDist : 0;
98
+ edgeWeightedCenterDist += normalizedDist * strength;
99
+ totalEdgeStrength += strength;
100
+ }
101
+ }
102
+ }
103
+ saturations.sort((a, b) => a - b);
104
+ const medianSaturation = saturations[Math.floor(saturations.length / 2)] ?? 0;
105
+ const avgEdgeDist = totalEdgeStrength > 0 ? edgeWeightedCenterDist / totalEdgeStrength : 0.5;
106
+ const edgeCentrality = 1 - avgEdgeDist;
107
+ return { medianSaturation, edgeCentrality };
108
+ }
109
+ function colorlip(data, width, height, channels, options) {
110
+ const opts = resolveOptions(options);
111
+ const result = extractAdvanced(data, width, height, channels, opts);
112
+ if (result.length > 0) return result;
113
+ return extractFallbackPalette(data, width, height, channels, opts);
114
+ }
115
+ function extractFallbackPalette(data, width, height, channels, options) {
116
+ const opts = resolveOptions(options);
117
+ const pixelCount = width * height;
118
+ if (pixelCount === 0) return [];
119
+ const colorCounts = /* @__PURE__ */ new Map();
120
+ let validPixels = 0;
121
+ for (let y = 0; y < height; y++) {
122
+ for (let x = 0; x < width; x++) {
123
+ const index = (y * width + x) * channels;
124
+ const alpha = channels >= 4 ? data[index + 3] ?? 255 : 255;
125
+ if (alpha < ALPHA_THRESHOLD) continue;
126
+ const r = data[index] ?? 0;
127
+ const g = data[index + 1] ?? 0;
128
+ const b = data[index + 2] ?? 0;
129
+ const qr = Math.round(r / FALLBACK_QUANTIZATION_STEP) * FALLBACK_QUANTIZATION_STEP;
130
+ const qg = Math.round(g / FALLBACK_QUANTIZATION_STEP) * FALLBACK_QUANTIZATION_STEP;
131
+ const qb = Math.round(b / FALLBACK_QUANTIZATION_STEP) * FALLBACK_QUANTIZATION_STEP;
132
+ const key = qr << 16 | qg << 8 | qb;
133
+ colorCounts.set(key, (colorCounts.get(key) ?? 0) + 1);
134
+ validPixels++;
135
+ }
136
+ }
137
+ if (validPixels === 0) return [];
138
+ if (colorCounts.size === 0) {
139
+ const avg = calculateAverageColor(data, width, height, channels);
140
+ return avg ? [avg] : [];
141
+ }
142
+ const sorted = Array.from(colorCounts.entries()).sort((a, b) => b[1] - a[1]);
143
+ return sorted.slice(0, opts.numColors).map(([key, count]) => {
144
+ const r = key >> 16 & 255;
145
+ const g = key >> 8 & 255;
146
+ const b = key & 255;
147
+ return createDominantColor(r, g, b, count / validPixels);
148
+ });
149
+ }
150
+ function rgbToHex(r, g, b) {
151
+ return `#${[r, g, b].map((x) => {
152
+ const hex = x.toString(16);
153
+ return hex.length === 1 ? `0${hex}` : hex;
154
+ }).join("").toUpperCase()}`;
155
+ }
156
+ function rgbToHsl(r, g, b) {
157
+ const rn = r / 255;
158
+ const gn = g / 255;
159
+ const bn = b / 255;
160
+ const max = Math.max(rn, gn, bn);
161
+ const min = Math.min(rn, gn, bn);
162
+ let h = 0;
163
+ let s = 0;
164
+ const l = (max + min) / 2;
165
+ if (max !== min) {
166
+ const d = max - min;
167
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
168
+ switch (max) {
169
+ case rn:
170
+ h = (gn - bn) / d + (gn < bn ? 6 : 0);
171
+ break;
172
+ case gn:
173
+ h = (bn - rn) / d + 2;
174
+ break;
175
+ case bn:
176
+ h = (rn - gn) / d + 4;
177
+ break;
178
+ }
179
+ h /= 6;
180
+ }
181
+ return {
182
+ h: Math.round(h * 360),
183
+ s: Math.round(s * 100),
184
+ l: Math.round(l * 100)
185
+ };
186
+ }
187
+ function getHueCategory(hue) {
188
+ let h = hue % 360;
189
+ if (h < 0) h += 360;
190
+ if (h >= 345 || h < 15) return "red";
191
+ if (h < 45) return "orange";
192
+ if (h < 75) return "yellow";
193
+ if (h < 135) return "green";
194
+ if (h < 195) return "cyan";
195
+ if (h < 255) return "blue";
196
+ if (h < 345) return "violet";
197
+ return "gray";
198
+ }
199
+ function createDominantColor(r, g, b, percentage) {
200
+ const hsl = rgbToHsl(r, g, b);
201
+ const hueCategory = hsl.s <= 5 ? "gray" : getHueCategory(hsl.h);
202
+ return {
203
+ r,
204
+ g,
205
+ b,
206
+ hex: rgbToHex(r, g, b),
207
+ percentage,
208
+ hue: hsl.h,
209
+ saturation: hsl.s,
210
+ lightness: hsl.l,
211
+ hueCategory
212
+ };
213
+ }
214
+ function resolveOptions(options) {
215
+ return { ...DEFAULT_OPTIONS, ...options };
216
+ }
217
+ function calculateSaturation(r, g, b) {
218
+ const max = Math.max(r, g, b);
219
+ const min = Math.min(r, g, b);
220
+ if (max === 0) return 0;
221
+ return (max - min) / max;
222
+ }
223
+ function calculateEdgeWeight(data, x, y, width, height, channels) {
224
+ if (x === 0 || y === 0 || x === width - 1 || y === height - 1) {
225
+ return BORDER_EDGE_WEIGHT;
226
+ }
227
+ const getGray = (px, py) => {
228
+ const i = (py * width + px) * channels;
229
+ return ((data[i] ?? 0) + (data[i + 1] ?? 0) + (data[i + 2] ?? 0)) / 3;
230
+ };
231
+ const center = getGray(x, y);
232
+ const edgeStrength = Math.abs(center - getGray(x - 1, y)) + Math.abs(center - getGray(x + 1, y)) + Math.abs(center - getGray(x, y - 1)) + Math.abs(center - getGray(x, y + 1));
233
+ return 1 + Math.min(edgeStrength / EDGE_STRENGTH_DIVISOR, 1);
234
+ }
235
+ function calculateAverageColor(data, width, height, channels) {
236
+ let sumR = 0;
237
+ let sumG = 0;
238
+ let sumB = 0;
239
+ let counted = 0;
240
+ for (let y = 0; y < height; y++) {
241
+ for (let x = 0; x < width; x++) {
242
+ const index = (y * width + x) * channels;
243
+ const alpha = channels >= 4 ? data[index + 3] ?? 255 : 255;
244
+ if (alpha < ALPHA_THRESHOLD) continue;
245
+ sumR += data[index] ?? 0;
246
+ sumG += data[index + 1] ?? 0;
247
+ sumB += data[index + 2] ?? 0;
248
+ counted++;
249
+ }
250
+ }
251
+ if (counted === 0) return null;
252
+ return createDominantColor(
253
+ Math.round(sumR / counted),
254
+ Math.round(sumG / counted),
255
+ Math.round(sumB / counted),
256
+ 1
257
+ );
258
+ }
259
+ var DEFAULT_MERGE_DELTA_E = 15;
260
+ function extractAdvanced(data, width, height, channels, opts) {
261
+ const pixelCount = width * height;
262
+ if (pixelCount === 0) return [];
263
+ const stats = analyzeImageStats(data, width, height, channels);
264
+ const adaptedSatThreshold = stats.medianSaturation < 0.1 ? Math.min(opts.saturationThreshold, 0.05) : opts.saturationThreshold;
265
+ const centerWeightScale = 0.3 + 0.7 * stats.edgeCentrality;
266
+ const centerX = Math.floor(width / 2);
267
+ const centerY = Math.floor(height / 2);
268
+ const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
269
+ const colorMap = /* @__PURE__ */ new Map();
270
+ for (let y = 0; y < height; y++) {
271
+ for (let x = 0; x < width; x++) {
272
+ const i = (y * width + x) * channels;
273
+ const r = data[i] ?? 0;
274
+ const g = data[i + 1] ?? 0;
275
+ const b = data[i + 2] ?? 0;
276
+ const saturation = calculateSaturation(r, g, b);
277
+ if (saturation < adaptedSatThreshold) continue;
278
+ const brightness = (r + g + b) / 3;
279
+ if (brightness < opts.brightnessMin || brightness > opts.brightnessMax) continue;
280
+ const distanceFromCenter = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
281
+ const rawCenterWeight = maxDistance > 0 ? 1 - distanceFromCenter / maxDistance : 0;
282
+ const centerWeight = 1 + rawCenterWeight * centerWeightScale;
283
+ const edgeWeight = calculateEdgeWeight(data, x, y, width, height, channels);
284
+ const step = opts.quantizationStep;
285
+ const qr = Math.round(r / step) * step;
286
+ const qg = Math.round(g / step) * step;
287
+ const qb = Math.round(b / step) * step;
288
+ const key = qr << 16 | qg << 8 | qb;
289
+ const totalWeight = centerWeight * edgeWeight * (1 + saturation);
290
+ const existing = colorMap.get(key);
291
+ if (existing) {
292
+ existing.weight += totalWeight;
293
+ existing.sumX += x;
294
+ existing.sumY += y;
295
+ existing.sumX2 += x * x;
296
+ existing.sumY2 += y * y;
297
+ existing.count++;
298
+ } else {
299
+ colorMap.set(key, {
300
+ weight: totalWeight,
301
+ saturation,
302
+ sumX: x,
303
+ sumY: y,
304
+ sumX2: x * x,
305
+ sumY2: y * y,
306
+ count: 1
307
+ });
308
+ }
309
+ }
310
+ }
311
+ const colorEntries = Array.from(colorMap.entries()).map(([key, entry]) => {
312
+ const r = key >> 16 & 255;
313
+ const g = key >> 8 & 255;
314
+ const b = key & 255;
315
+ let variance = 0;
316
+ if (entry.count >= 2) {
317
+ const n = entry.count;
318
+ variance = entry.sumX2 / n - (entry.sumX / n) ** 2 + (entry.sumY2 / n - (entry.sumY / n) ** 2);
319
+ }
320
+ const normalizedVariance = Math.min(variance / (width * height), 1);
321
+ const score = entry.weight * (1 + entry.saturation) * (1 + normalizedVariance * 0.5);
322
+ const lab = rgbToLab(r, g, b);
323
+ return { r, g, b, score, weight: entry.weight, key, lab };
324
+ });
325
+ const sortedColors = colorEntries.sort((a, b) => b.score - a.score);
326
+ const dominantColors = [];
327
+ const usedEntries = [];
328
+ for (const color of sortedColors) {
329
+ if (dominantColors.length >= opts.numColors) break;
330
+ if (usedEntries.some((u) => u.key === color.key)) continue;
331
+ let merged = false;
332
+ for (const used of usedEntries) {
333
+ if (deltaE76(color.lab, used.lab) < DEFAULT_MERGE_DELTA_E) {
334
+ merged = true;
335
+ break;
336
+ }
337
+ }
338
+ if (!merged) {
339
+ usedEntries.push({ key: color.key, lab: color.lab });
340
+ dominantColors.push(
341
+ createDominantColor(color.r, color.g, color.b, color.weight / pixelCount)
342
+ );
343
+ }
344
+ }
345
+ return dominantColors;
346
+ }
347
+
348
+ // src/adapters/canvas.ts
349
+ var MAX_CANVAS_SIZE = 150;
350
+ async function colorlipFromImage(source, options) {
351
+ const bitmap = await toBitmap(source);
352
+ try {
353
+ const scale = Math.min(MAX_CANVAS_SIZE / bitmap.width, MAX_CANVAS_SIZE / bitmap.height, 1);
354
+ const w = Math.max(1, Math.round(bitmap.width * scale));
355
+ const h = Math.max(1, Math.round(bitmap.height * scale));
356
+ const canvas = new OffscreenCanvas(w, h);
357
+ const ctx = canvas.getContext("2d");
358
+ if (!ctx) throw new Error("Failed to get 2d context from OffscreenCanvas");
359
+ ctx.drawImage(bitmap, 0, 0, w, h);
360
+ const imageData = ctx.getImageData(0, 0, w, h);
361
+ return colorlipFromImageData(imageData, options);
362
+ } finally {
363
+ if ("close" in bitmap && typeof bitmap.close === "function") {
364
+ bitmap.close();
365
+ }
366
+ }
367
+ }
368
+ function colorlipFromImageData(imageData, options) {
369
+ return colorlip(imageData.data, imageData.width, imageData.height, 4, options);
370
+ }
371
+ async function toBitmap(source) {
372
+ if (source instanceof ImageBitmap) return source;
373
+ if (source instanceof Blob) return createImageBitmap(source);
374
+ if (typeof source === "string") {
375
+ const res = await fetch(source);
376
+ const blob = await res.blob();
377
+ return createImageBitmap(blob);
378
+ }
379
+ return createImageBitmap(source);
380
+ }
381
+ // Annotate the CommonJS export names for ESM import in node:
382
+ 0 && (module.exports = {
383
+ colorlip,
384
+ colorlipFromImage,
385
+ colorlipFromImageData
386
+ });
387
+ //# sourceMappingURL=canvas.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/canvas.ts","../../src/constants.ts","../../src/core.ts"],"sourcesContent":["import { colorlip } from \"../core\";\nimport type { DominantColor, ExtractOptions } from \"../types\";\n\n/** Canvas API 経由で受け付ける画像ソース */\nexport type ImageSource = HTMLImageElement | ImageBitmap | Blob | string;\n\nconst MAX_CANVAS_SIZE = 150;\n\n/**\n * 画像ソースから代表色を抽出する(ブラウザ / Canvas API 使用)。\n *\n * @param source HTMLImageElement, ImageBitmap, Blob, または画像 URL\n */\nexport { colorlip } from \"../core\";\n\nexport async function colorlipFromImage(\n source: ImageSource,\n options?: ExtractOptions,\n): Promise<DominantColor[]> {\n const bitmap = await toBitmap(source);\n try {\n const scale = Math.min(MAX_CANVAS_SIZE / bitmap.width, MAX_CANVAS_SIZE / bitmap.height, 1);\n const w = Math.max(1, Math.round(bitmap.width * scale));\n const h = Math.max(1, Math.round(bitmap.height * scale));\n\n const canvas = new OffscreenCanvas(w, h);\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Failed to get 2d context from OffscreenCanvas\");\n\n ctx.drawImage(bitmap, 0, 0, w, h);\n const imageData = ctx.getImageData(0, 0, w, h);\n\n return colorlipFromImageData(imageData, options);\n } finally {\n if (\"close\" in bitmap && typeof bitmap.close === \"function\") {\n bitmap.close();\n }\n }\n}\n\n/**\n * ImageData から代表色を抽出する(Canvas API 使用)。\n */\nexport function colorlipFromImageData(\n imageData: ImageData,\n options?: ExtractOptions,\n): DominantColor[] {\n return colorlip(imageData.data, imageData.width, imageData.height, 4, options);\n}\n\nasync function toBitmap(source: ImageSource): Promise<ImageBitmap> {\n if (source instanceof ImageBitmap) return source;\n if (source instanceof Blob) return createImageBitmap(source);\n if (typeof source === \"string\") {\n const res = await fetch(source);\n const blob = await res.blob();\n return createImageBitmap(blob);\n }\n // HTMLImageElement\n return createImageBitmap(source);\n}\n","import type { ExtractOptions } from \"./types\";\n\nexport const DEFAULT_OPTIONS: Required<ExtractOptions> = {\n numColors: 3,\n saturationThreshold: 0.15,\n brightnessMin: 20,\n brightnessMax: 235,\n quantizationStep: 12,\n} as const;\n\n/** フォールバック時の量子化ステップ */\nexport const FALLBACK_QUANTIZATION_STEP = 16;\n\n/** アルファ値の最小閾値(これ未満は透明扱い) */\nexport const ALPHA_THRESHOLD = 16;\n\n/** エッジ重みの正規化係数 */\nexport const EDGE_STRENGTH_DIVISOR = 100;\n\n/** 境界ピクセルのエッジ重み */\nexport const BORDER_EDGE_WEIGHT = 1.5;\n\n/** sharp アダプター用リサイズ上限 */\nexport const MAX_RESIZE = 150;\n","import {\n ALPHA_THRESHOLD,\n BORDER_EDGE_WEIGHT,\n DEFAULT_OPTIONS,\n EDGE_STRENGTH_DIVISOR,\n FALLBACK_QUANTIZATION_STEP,\n} from \"./constants\";\nimport type { DominantColor, ExtractOptions, HSL, HueCategory, PixelData } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// CIELAB color space\n// ---------------------------------------------------------------------------\n\ninterface Lab {\n L: number;\n a: number;\n b: number;\n}\n\n/** RGB (0-255) → CIELAB。D65 白色点使用。 */\nfunction rgbToLab(r: number, g: number, b: number): Lab {\n // sRGB → linear RGB\n let rl = r / 255;\n let gl = g / 255;\n let bl = b / 255;\n\n rl = rl > 0.04045 ? ((rl + 0.055) / 1.055) ** 2.4 : rl / 12.92;\n gl = gl > 0.04045 ? ((gl + 0.055) / 1.055) ** 2.4 : gl / 12.92;\n bl = bl > 0.04045 ? ((bl + 0.055) / 1.055) ** 2.4 : bl / 12.92;\n\n // linear RGB → XYZ (D65)\n let x = (rl * 0.4124564 + gl * 0.3575761 + bl * 0.1804375) / 0.95047;\n let y = rl * 0.2126729 + gl * 0.7151522 + bl * 0.072175;\n let z = (rl * 0.0193339 + gl * 0.119192 + bl * 0.9503041) / 1.08883;\n\n // XYZ → Lab\n const epsilon = 0.008856;\n const kappa = 903.3;\n\n x = x > epsilon ? Math.cbrt(x) : (kappa * x + 16) / 116;\n y = y > epsilon ? Math.cbrt(y) : (kappa * y + 16) / 116;\n z = z > epsilon ? Math.cbrt(z) : (kappa * z + 16) / 116;\n\n return {\n L: 116 * y - 16,\n a: 500 * (x - y),\n b: 200 * (y - z),\n };\n}\n\n/** CIE76 Delta E(Lab 空間でのユークリッド距離) */\nfunction deltaE76(lab1: Lab, lab2: Lab): number {\n return Math.sqrt((lab1.L - lab2.L) ** 2 + (lab1.a - lab2.a) ** 2 + (lab1.b - lab2.b) ** 2);\n}\n\n// ---------------------------------------------------------------------------\n// Image analysis (sampling pass)\n// ---------------------------------------------------------------------------\n\ninterface ImageStats {\n /** 彩度の中央値 (0-1) */\n medianSaturation: number;\n /** エッジの中央集中度 (0-1, 1=完全に中央集中) */\n edgeCentrality: number;\n}\n\n/** ストライドサンプリングで画像統計を計算 */\nfunction analyzeImageStats(\n data: PixelData,\n width: number,\n height: number,\n channels: number,\n): ImageStats {\n const pixelCount = width * height;\n if (pixelCount === 0) return { medianSaturation: 0, edgeCentrality: 0.5 };\n\n // ~10% サンプリング(最低100、最大2000ピクセル)\n const sampleCount = Math.min(Math.max(Math.floor(pixelCount * 0.1), 100), 2000);\n const stride = Math.max(1, Math.floor(pixelCount / sampleCount));\n\n const saturations: number[] = [];\n\n const centerX = width / 2;\n const centerY = height / 2;\n const maxDist = Math.sqrt(centerX ** 2 + centerY ** 2);\n\n let edgeWeightedCenterDist = 0;\n let totalEdgeStrength = 0;\n\n for (let idx = 0; idx < pixelCount; idx += stride) {\n const x = idx % width;\n const y = Math.floor(idx / width);\n const i = idx * channels;\n\n const r = data[i] ?? 0;\n const g = data[i + 1] ?? 0;\n const b = data[i + 2] ?? 0;\n\n // 彩度を収集\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n saturations.push(max === 0 ? 0 : (max - min) / max);\n\n // エッジ強度を計算(境界でなければ)\n if (x > 0 && x < width - 1 && y > 0 && y < height - 1) {\n const getGray = (px: number, py: number): number => {\n const j = (py * width + px) * channels;\n return ((data[j] ?? 0) + (data[j + 1] ?? 0) + (data[j + 2] ?? 0)) / 3;\n };\n const center = getGray(x, y);\n const strength =\n Math.abs(center - getGray(x - 1, y)) +\n Math.abs(center - getGray(x + 1, y)) +\n Math.abs(center - getGray(x, y - 1)) +\n Math.abs(center - getGray(x, y + 1));\n\n if (strength > 10) {\n const distFromCenter = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);\n const normalizedDist = maxDist > 0 ? distFromCenter / maxDist : 0;\n edgeWeightedCenterDist += normalizedDist * strength;\n totalEdgeStrength += strength;\n }\n }\n }\n\n // 彩度の中央値\n saturations.sort((a, b) => a - b);\n const medianSaturation = saturations[Math.floor(saturations.length / 2)] ?? 0;\n\n // エッジ集中度: 平均重み付き距離が小さいほど中央集中\n // 0 = 全エッジが中央、1 = 全エッジが辺縁\n const avgEdgeDist = totalEdgeStrength > 0 ? edgeWeightedCenterDist / totalEdgeStrength : 0.5;\n const edgeCentrality = 1 - avgEdgeDist;\n\n return { medianSaturation, edgeCentrality };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * ピクセルデータから代表色を抽出する(メインエントリ)。\n *\n * 内部で高度なアルゴリズム → フォールバック → 平均色 の順にフォールバックする。\n */\nexport function colorlip(\n data: PixelData,\n width: number,\n height: number,\n channels: number,\n options?: ExtractOptions,\n): DominantColor[] {\n const opts = resolveOptions(options);\n\n const result = extractAdvanced(data, width, height, channels, opts);\n if (result.length > 0) return result;\n\n return extractFallbackPalette(data, width, height, channels, opts);\n}\n\n/**\n * フォールバック用パレット抽出。\n * グレースケール画像など、メインアルゴリズムで色が取れない場合に使用。\n */\nexport function extractFallbackPalette(\n data: PixelData,\n width: number,\n height: number,\n channels: number,\n options?: ExtractOptions,\n): DominantColor[] {\n const opts = resolveOptions(options);\n const pixelCount = width * height;\n if (pixelCount === 0) return [];\n\n const colorCounts = new Map<number, number>();\n let validPixels = 0;\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const index = (y * width + x) * channels;\n const alpha = channels >= 4 ? (data[index + 3] ?? 255) : 255;\n if (alpha < ALPHA_THRESHOLD) continue;\n\n const r = data[index] ?? 0;\n const g = data[index + 1] ?? 0;\n const b = data[index + 2] ?? 0;\n\n const qr = Math.round(r / FALLBACK_QUANTIZATION_STEP) * FALLBACK_QUANTIZATION_STEP;\n const qg = Math.round(g / FALLBACK_QUANTIZATION_STEP) * FALLBACK_QUANTIZATION_STEP;\n const qb = Math.round(b / FALLBACK_QUANTIZATION_STEP) * FALLBACK_QUANTIZATION_STEP;\n const key = (qr << 16) | (qg << 8) | qb;\n\n colorCounts.set(key, (colorCounts.get(key) ?? 0) + 1);\n validPixels++;\n }\n }\n\n if (validPixels === 0) return [];\n\n if (colorCounts.size === 0) {\n const avg = calculateAverageColor(data, width, height, channels);\n return avg ? [avg] : [];\n }\n\n const sorted = Array.from(colorCounts.entries()).sort((a, b) => b[1] - a[1]);\n return sorted.slice(0, opts.numColors).map(([key, count]) => {\n const r = (key >> 16) & 0xff;\n const g = (key >> 8) & 0xff;\n const b = key & 0xff;\n return createDominantColor(r, g, b, count / validPixels);\n });\n}\n\n/**\n * RGB → 16進数カラーコード(例: `#FF00AA`)。\n */\nexport function rgbToHex(r: number, g: number, b: number): string {\n return `#${[r, g, b]\n .map((x) => {\n const hex = x.toString(16);\n return hex.length === 1 ? `0${hex}` : hex;\n })\n .join(\"\")\n .toUpperCase()}`;\n}\n\n/**\n * RGB → HSL 変換。\n */\nexport function rgbToHsl(r: number, g: number, b: number): HSL {\n const rn = r / 255;\n const gn = g / 255;\n const bn = b / 255;\n\n const max = Math.max(rn, gn, bn);\n const min = Math.min(rn, gn, bn);\n let h = 0;\n let s = 0;\n const l = (max + min) / 2;\n\n if (max !== min) {\n const d = max - min;\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n\n switch (max) {\n case rn:\n h = (gn - bn) / d + (gn < bn ? 6 : 0);\n break;\n case gn:\n h = (bn - rn) / d + 2;\n break;\n case bn:\n h = (rn - gn) / d + 4;\n break;\n }\n h /= 6;\n }\n\n return {\n h: Math.round(h * 360),\n s: Math.round(s * 100),\n l: Math.round(l * 100),\n };\n}\n\n/**\n * 色相値から色相カテゴリを判定。\n */\nexport function getHueCategory(hue: number): HueCategory {\n let h = hue % 360;\n if (h < 0) h += 360;\n\n if (h >= 345 || h < 15) return \"red\";\n if (h < 45) return \"orange\";\n if (h < 75) return \"yellow\";\n if (h < 135) return \"green\";\n if (h < 195) return \"cyan\";\n if (h < 255) return \"blue\";\n if (h < 345) return \"violet\";\n\n return \"gray\";\n}\n\n/**\n * RGB + percentage から DominantColor オブジェクトを生成。\n */\nexport function createDominantColor(\n r: number,\n g: number,\n b: number,\n percentage: number,\n): DominantColor {\n const hsl = rgbToHsl(r, g, b);\n const hueCategory: HueCategory = hsl.s <= 5 ? \"gray\" : getHueCategory(hsl.h);\n return {\n r,\n g,\n b,\n hex: rgbToHex(r, g, b),\n percentage,\n hue: hsl.h,\n saturation: hsl.s,\n lightness: hsl.l,\n hueCategory,\n };\n}\n\n/**\n * 複数画像の色セットを集約し、上位 numColors 色を返す。\n */\nexport function aggregateColors(colorSets: DominantColor[][], numColors = 3): DominantColor[] {\n const colorMap = new Map<string, { color: DominantColor; weight: number }>();\n\n for (const colors of colorSets) {\n for (const color of colors) {\n const key = color.hex;\n const existing = colorMap.get(key);\n\n if (existing) {\n existing.weight += color.percentage;\n } else {\n colorMap.set(key, { color: { ...color }, weight: color.percentage });\n }\n }\n }\n\n return Array.from(colorMap.values())\n .sort((a, b) => b.weight - a.weight)\n .slice(0, numColors)\n .map((item) =>\n createDominantColor(item.color.r, item.color.g, item.color.b, item.weight / colorSets.length),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction resolveOptions(options?: ExtractOptions): Required<ExtractOptions> {\n return { ...DEFAULT_OPTIONS, ...options };\n}\n\n/** HSV 彩度を計算 */\nfunction calculateSaturation(r: number, g: number, b: number): number {\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n if (max === 0) return 0;\n return (max - min) / max;\n}\n\n/** エッジ重みを計算(簡易ソーベルフィルタ) */\nfunction calculateEdgeWeight(\n data: PixelData,\n x: number,\n y: number,\n width: number,\n height: number,\n channels: number,\n): number {\n if (x === 0 || y === 0 || x === width - 1 || y === height - 1) {\n return BORDER_EDGE_WEIGHT;\n }\n\n const getGray = (px: number, py: number): number => {\n const i = (py * width + px) * channels;\n return ((data[i] ?? 0) + (data[i + 1] ?? 0) + (data[i + 2] ?? 0)) / 3;\n };\n\n const center = getGray(x, y);\n const edgeStrength =\n Math.abs(center - getGray(x - 1, y)) +\n Math.abs(center - getGray(x + 1, y)) +\n Math.abs(center - getGray(x, y - 1)) +\n Math.abs(center - getGray(x, y + 1));\n\n return 1 + Math.min(edgeStrength / EDGE_STRENGTH_DIVISOR, 1);\n}\n\n/** 画像全体の平均色を計算 */\nfunction calculateAverageColor(\n data: PixelData,\n width: number,\n height: number,\n channels: number,\n): DominantColor | null {\n let sumR = 0;\n let sumG = 0;\n let sumB = 0;\n let counted = 0;\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const index = (y * width + x) * channels;\n const alpha = channels >= 4 ? (data[index + 3] ?? 255) : 255;\n if (alpha < ALPHA_THRESHOLD) continue;\n\n sumR += data[index] ?? 0;\n sumG += data[index + 1] ?? 0;\n sumB += data[index + 2] ?? 0;\n counted++;\n }\n }\n\n if (counted === 0) return null;\n\n return createDominantColor(\n Math.round(sumR / counted),\n Math.round(sumG / counted),\n Math.round(sumB / counted),\n 1,\n );\n}\n\n// ---------------------------------------------------------------------------\n// Main algorithm (CIELAB Delta E + adaptive thresholds)\n// ---------------------------------------------------------------------------\n\n/** Delta E のデフォルトマージ閾値 */\nconst DEFAULT_MERGE_DELTA_E = 15;\n\n/** 高度な色抽出アルゴリズム(メインロジック) */\nfunction extractAdvanced(\n data: PixelData,\n width: number,\n height: number,\n channels: number,\n opts: Required<ExtractOptions>,\n): DominantColor[] {\n const pixelCount = width * height;\n if (pixelCount === 0) return [];\n\n // サンプリングパスで画像特性を分析\n const stats = analyzeImageStats(data, width, height, channels);\n\n // 適応的彩度閾値\n const adaptedSatThreshold =\n stats.medianSaturation < 0.1\n ? Math.min(opts.saturationThreshold, 0.05)\n : opts.saturationThreshold;\n\n // 適応的中央重み係数 (0.3 〜 1.0)\n // edgeCentrality が高い → イラスト型 → 中央重み強め\n // edgeCentrality が低い → 写真型 → 中央重み弱め\n const centerWeightScale = 0.3 + 0.7 * stats.edgeCentrality;\n\n const centerX = Math.floor(width / 2);\n const centerY = Math.floor(height / 2);\n const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);\n\n const colorMap = new Map<\n number,\n {\n weight: number;\n saturation: number;\n sumX: number;\n sumY: number;\n sumX2: number;\n sumY2: number;\n count: number;\n }\n >();\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const i = (y * width + x) * channels;\n const r = data[i] ?? 0;\n const g = data[i + 1] ?? 0;\n const b = data[i + 2] ?? 0;\n\n // 適応的彩度フィルタ\n const saturation = calculateSaturation(r, g, b);\n if (saturation < adaptedSatThreshold) continue;\n\n const brightness = (r + g + b) / 3;\n if (brightness < opts.brightnessMin || brightness > opts.brightnessMax) continue;\n\n // 適応的中央重み\n const distanceFromCenter = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);\n const rawCenterWeight = maxDistance > 0 ? 1 - distanceFromCenter / maxDistance : 0;\n const centerWeight = 1 + rawCenterWeight * centerWeightScale;\n\n const edgeWeight = calculateEdgeWeight(data, x, y, width, height, channels);\n\n const step = opts.quantizationStep;\n const qr = Math.round(r / step) * step;\n const qg = Math.round(g / step) * step;\n const qb = Math.round(b / step) * step;\n const key = (qr << 16) | (qg << 8) | qb;\n\n const totalWeight = centerWeight * edgeWeight * (1 + saturation);\n\n const existing = colorMap.get(key);\n if (existing) {\n existing.weight += totalWeight;\n existing.sumX += x;\n existing.sumY += y;\n existing.sumX2 += x * x;\n existing.sumY2 += y * y;\n existing.count++;\n } else {\n colorMap.set(key, {\n weight: totalWeight,\n saturation,\n sumX: x,\n sumY: y,\n sumX2: x * x,\n sumY2: y * y,\n count: 1,\n });\n }\n }\n }\n\n // スコアリング\n const colorEntries = Array.from(colorMap.entries()).map(([key, entry]) => {\n const r = (key >> 16) & 0xff;\n const g = (key >> 8) & 0xff;\n const b = key & 0xff;\n\n // オンライン分散: Var(X) + Var(Y) = (sumX2/n - (sumX/n)^2) + (sumY2/n - (sumY/n)^2)\n let variance = 0;\n if (entry.count >= 2) {\n const n = entry.count;\n variance =\n entry.sumX2 / n - (entry.sumX / n) ** 2 + (entry.sumY2 / n - (entry.sumY / n) ** 2);\n }\n const normalizedVariance = Math.min(variance / (width * height), 1);\n const score = entry.weight * (1 + entry.saturation) * (1 + normalizedVariance * 0.5);\n\n // Lab を事前計算してマージで使う\n const lab = rgbToLab(r, g, b);\n\n return { r, g, b, score, weight: entry.weight, key, lab };\n });\n\n // スコア順ソート → Lab ベースの類似色マージ\n const sortedColors = colorEntries.sort((a, b) => b.score - a.score);\n const dominantColors: DominantColor[] = [];\n const usedEntries: Array<{ key: number; lab: Lab }> = [];\n\n for (const color of sortedColors) {\n if (dominantColors.length >= opts.numColors) break;\n if (usedEntries.some((u) => u.key === color.key)) continue;\n\n // Delta E によるマージ判定\n let merged = false;\n for (const used of usedEntries) {\n if (deltaE76(color.lab, used.lab) < DEFAULT_MERGE_DELTA_E) {\n merged = true;\n break;\n }\n }\n\n if (!merged) {\n usedEntries.push({ key: color.key, lab: color.lab });\n dominantColors.push(\n createDominantColor(color.r, color.g, color.b, color.weight / pixelCount),\n );\n }\n }\n\n return dominantColors;\n}\n\n// ---------------------------------------------------------------------------\n// Test exports (internal functions exposed for testing)\n// ---------------------------------------------------------------------------\n\nexport const _internals = {\n rgbToLab,\n deltaE76,\n analyzeImageStats,\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,13 @@
1
+ import { E as ExtractOptions, D as DominantColor } from './core-OmHuknYv.cjs';
2
+ export { c as colorlip } from './core-OmHuknYv.cjs';
3
+
4
+ /** Canvas API 経由で受け付ける画像ソース */
5
+ type ImageSource = HTMLImageElement | ImageBitmap | Blob | string;
6
+
7
+ declare function colorlipFromImage(source: ImageSource, options?: ExtractOptions): Promise<DominantColor[]>;
8
+ /**
9
+ * ImageData から代表色を抽出する(Canvas API 使用)。
10
+ */
11
+ declare function colorlipFromImageData(imageData: ImageData, options?: ExtractOptions): DominantColor[];
12
+
13
+ export { type ImageSource, colorlipFromImage, colorlipFromImageData };
@@ -0,0 +1,13 @@
1
+ import { E as ExtractOptions, D as DominantColor } from './core-OmHuknYv.js';
2
+ export { c as colorlip } from './core-OmHuknYv.js';
3
+
4
+ /** Canvas API 経由で受け付ける画像ソース */
5
+ type ImageSource = HTMLImageElement | ImageBitmap | Blob | string;
6
+
7
+ declare function colorlipFromImage(source: ImageSource, options?: ExtractOptions): Promise<DominantColor[]>;
8
+ /**
9
+ * ImageData から代表色を抽出する(Canvas API 使用)。
10
+ */
11
+ declare function colorlipFromImageData(imageData: ImageData, options?: ExtractOptions): DominantColor[];
12
+
13
+ export { type ImageSource, colorlipFromImage, colorlipFromImageData };
@@ -0,0 +1,43 @@
1
+ import {
2
+ colorlip
3
+ } from "./chunk-5PQQ7VW4.js";
4
+
5
+ // src/adapters/canvas.ts
6
+ var MAX_CANVAS_SIZE = 150;
7
+ async function colorlipFromImage(source, options) {
8
+ const bitmap = await toBitmap(source);
9
+ try {
10
+ const scale = Math.min(MAX_CANVAS_SIZE / bitmap.width, MAX_CANVAS_SIZE / bitmap.height, 1);
11
+ const w = Math.max(1, Math.round(bitmap.width * scale));
12
+ const h = Math.max(1, Math.round(bitmap.height * scale));
13
+ const canvas = new OffscreenCanvas(w, h);
14
+ const ctx = canvas.getContext("2d");
15
+ if (!ctx) throw new Error("Failed to get 2d context from OffscreenCanvas");
16
+ ctx.drawImage(bitmap, 0, 0, w, h);
17
+ const imageData = ctx.getImageData(0, 0, w, h);
18
+ return colorlipFromImageData(imageData, options);
19
+ } finally {
20
+ if ("close" in bitmap && typeof bitmap.close === "function") {
21
+ bitmap.close();
22
+ }
23
+ }
24
+ }
25
+ function colorlipFromImageData(imageData, options) {
26
+ return colorlip(imageData.data, imageData.width, imageData.height, 4, options);
27
+ }
28
+ async function toBitmap(source) {
29
+ if (source instanceof ImageBitmap) return source;
30
+ if (source instanceof Blob) return createImageBitmap(source);
31
+ if (typeof source === "string") {
32
+ const res = await fetch(source);
33
+ const blob = await res.blob();
34
+ return createImageBitmap(blob);
35
+ }
36
+ return createImageBitmap(source);
37
+ }
38
+ export {
39
+ colorlip,
40
+ colorlipFromImage,
41
+ colorlipFromImageData
42
+ };
43
+ //# sourceMappingURL=canvas.js.map