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 +21 -0
- package/README.md +129 -0
- package/dist/adapters/canvas.cjs +387 -0
- package/dist/adapters/canvas.cjs.map +1 -0
- package/dist/adapters/canvas.d.cts +13 -0
- package/dist/adapters/canvas.d.ts +13 -0
- package/dist/adapters/canvas.js +43 -0
- package/dist/adapters/canvas.js.map +1 -0
- package/dist/adapters/chunk-5PQQ7VW4.js +325 -0
- package/dist/adapters/chunk-5PQQ7VW4.js.map +1 -0
- package/dist/adapters/core-OmHuknYv.d.cts +42 -0
- package/dist/adapters/core-OmHuknYv.d.ts +42 -0
- package/dist/adapters/sharp.cjs +390 -0
- package/dist/adapters/sharp.cjs.map +1 -0
- package/dist/adapters/sharp.d.cts +10 -0
- package/dist/adapters/sharp.d.ts +10 -0
- package/dist/adapters/sharp.js +36 -0
- package/dist/adapters/sharp.js.map +1 -0
- package/dist/index.cjs +380 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +84 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +346 -0
- package/dist/index.js.map +1 -0
- package/package.json +73 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
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/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
|
|
24
|
+
aggregateColors: () => aggregateColors,
|
|
25
|
+
colorlip: () => colorlip,
|
|
26
|
+
createDominantColor: () => createDominantColor,
|
|
27
|
+
extractFallbackPalette: () => extractFallbackPalette,
|
|
28
|
+
getHueCategory: () => getHueCategory,
|
|
29
|
+
rgbToHex: () => rgbToHex,
|
|
30
|
+
rgbToHsl: () => rgbToHsl
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/constants.ts
|
|
35
|
+
var DEFAULT_OPTIONS = {
|
|
36
|
+
numColors: 3,
|
|
37
|
+
saturationThreshold: 0.15,
|
|
38
|
+
brightnessMin: 20,
|
|
39
|
+
brightnessMax: 235,
|
|
40
|
+
quantizationStep: 12
|
|
41
|
+
};
|
|
42
|
+
var FALLBACK_QUANTIZATION_STEP = 16;
|
|
43
|
+
var ALPHA_THRESHOLD = 16;
|
|
44
|
+
var EDGE_STRENGTH_DIVISOR = 100;
|
|
45
|
+
var BORDER_EDGE_WEIGHT = 1.5;
|
|
46
|
+
|
|
47
|
+
// src/core.ts
|
|
48
|
+
function rgbToLab(r, g, b) {
|
|
49
|
+
let rl = r / 255;
|
|
50
|
+
let gl = g / 255;
|
|
51
|
+
let bl = b / 255;
|
|
52
|
+
rl = rl > 0.04045 ? ((rl + 0.055) / 1.055) ** 2.4 : rl / 12.92;
|
|
53
|
+
gl = gl > 0.04045 ? ((gl + 0.055) / 1.055) ** 2.4 : gl / 12.92;
|
|
54
|
+
bl = bl > 0.04045 ? ((bl + 0.055) / 1.055) ** 2.4 : bl / 12.92;
|
|
55
|
+
let x = (rl * 0.4124564 + gl * 0.3575761 + bl * 0.1804375) / 0.95047;
|
|
56
|
+
let y = rl * 0.2126729 + gl * 0.7151522 + bl * 0.072175;
|
|
57
|
+
let z = (rl * 0.0193339 + gl * 0.119192 + bl * 0.9503041) / 1.08883;
|
|
58
|
+
const epsilon = 8856e-6;
|
|
59
|
+
const kappa = 903.3;
|
|
60
|
+
x = x > epsilon ? Math.cbrt(x) : (kappa * x + 16) / 116;
|
|
61
|
+
y = y > epsilon ? Math.cbrt(y) : (kappa * y + 16) / 116;
|
|
62
|
+
z = z > epsilon ? Math.cbrt(z) : (kappa * z + 16) / 116;
|
|
63
|
+
return {
|
|
64
|
+
L: 116 * y - 16,
|
|
65
|
+
a: 500 * (x - y),
|
|
66
|
+
b: 200 * (y - z)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function deltaE76(lab1, lab2) {
|
|
70
|
+
return Math.sqrt((lab1.L - lab2.L) ** 2 + (lab1.a - lab2.a) ** 2 + (lab1.b - lab2.b) ** 2);
|
|
71
|
+
}
|
|
72
|
+
function analyzeImageStats(data, width, height, channels) {
|
|
73
|
+
const pixelCount = width * height;
|
|
74
|
+
if (pixelCount === 0) return { medianSaturation: 0, edgeCentrality: 0.5 };
|
|
75
|
+
const sampleCount = Math.min(Math.max(Math.floor(pixelCount * 0.1), 100), 2e3);
|
|
76
|
+
const stride = Math.max(1, Math.floor(pixelCount / sampleCount));
|
|
77
|
+
const saturations = [];
|
|
78
|
+
const centerX = width / 2;
|
|
79
|
+
const centerY = height / 2;
|
|
80
|
+
const maxDist = Math.sqrt(centerX ** 2 + centerY ** 2);
|
|
81
|
+
let edgeWeightedCenterDist = 0;
|
|
82
|
+
let totalEdgeStrength = 0;
|
|
83
|
+
for (let idx = 0; idx < pixelCount; idx += stride) {
|
|
84
|
+
const x = idx % width;
|
|
85
|
+
const y = Math.floor(idx / width);
|
|
86
|
+
const i = idx * channels;
|
|
87
|
+
const r = data[i] ?? 0;
|
|
88
|
+
const g = data[i + 1] ?? 0;
|
|
89
|
+
const b = data[i + 2] ?? 0;
|
|
90
|
+
const max = Math.max(r, g, b);
|
|
91
|
+
const min = Math.min(r, g, b);
|
|
92
|
+
saturations.push(max === 0 ? 0 : (max - min) / max);
|
|
93
|
+
if (x > 0 && x < width - 1 && y > 0 && y < height - 1) {
|
|
94
|
+
const getGray = (px, py) => {
|
|
95
|
+
const j = (py * width + px) * channels;
|
|
96
|
+
return ((data[j] ?? 0) + (data[j + 1] ?? 0) + (data[j + 2] ?? 0)) / 3;
|
|
97
|
+
};
|
|
98
|
+
const center = getGray(x, y);
|
|
99
|
+
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));
|
|
100
|
+
if (strength > 10) {
|
|
101
|
+
const distFromCenter = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
|
|
102
|
+
const normalizedDist = maxDist > 0 ? distFromCenter / maxDist : 0;
|
|
103
|
+
edgeWeightedCenterDist += normalizedDist * strength;
|
|
104
|
+
totalEdgeStrength += strength;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
saturations.sort((a, b) => a - b);
|
|
109
|
+
const medianSaturation = saturations[Math.floor(saturations.length / 2)] ?? 0;
|
|
110
|
+
const avgEdgeDist = totalEdgeStrength > 0 ? edgeWeightedCenterDist / totalEdgeStrength : 0.5;
|
|
111
|
+
const edgeCentrality = 1 - avgEdgeDist;
|
|
112
|
+
return { medianSaturation, edgeCentrality };
|
|
113
|
+
}
|
|
114
|
+
function colorlip(data, width, height, channels, options) {
|
|
115
|
+
const opts = resolveOptions(options);
|
|
116
|
+
const result = extractAdvanced(data, width, height, channels, opts);
|
|
117
|
+
if (result.length > 0) return result;
|
|
118
|
+
return extractFallbackPalette(data, width, height, channels, opts);
|
|
119
|
+
}
|
|
120
|
+
function extractFallbackPalette(data, width, height, channels, options) {
|
|
121
|
+
const opts = resolveOptions(options);
|
|
122
|
+
const pixelCount = width * height;
|
|
123
|
+
if (pixelCount === 0) return [];
|
|
124
|
+
const colorCounts = /* @__PURE__ */ new Map();
|
|
125
|
+
let validPixels = 0;
|
|
126
|
+
for (let y = 0; y < height; y++) {
|
|
127
|
+
for (let x = 0; x < width; x++) {
|
|
128
|
+
const index = (y * width + x) * channels;
|
|
129
|
+
const alpha = channels >= 4 ? data[index + 3] ?? 255 : 255;
|
|
130
|
+
if (alpha < ALPHA_THRESHOLD) continue;
|
|
131
|
+
const r = data[index] ?? 0;
|
|
132
|
+
const g = data[index + 1] ?? 0;
|
|
133
|
+
const b = data[index + 2] ?? 0;
|
|
134
|
+
const qr = Math.round(r / FALLBACK_QUANTIZATION_STEP) * FALLBACK_QUANTIZATION_STEP;
|
|
135
|
+
const qg = Math.round(g / FALLBACK_QUANTIZATION_STEP) * FALLBACK_QUANTIZATION_STEP;
|
|
136
|
+
const qb = Math.round(b / FALLBACK_QUANTIZATION_STEP) * FALLBACK_QUANTIZATION_STEP;
|
|
137
|
+
const key = qr << 16 | qg << 8 | qb;
|
|
138
|
+
colorCounts.set(key, (colorCounts.get(key) ?? 0) + 1);
|
|
139
|
+
validPixels++;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (validPixels === 0) return [];
|
|
143
|
+
if (colorCounts.size === 0) {
|
|
144
|
+
const avg = calculateAverageColor(data, width, height, channels);
|
|
145
|
+
return avg ? [avg] : [];
|
|
146
|
+
}
|
|
147
|
+
const sorted = Array.from(colorCounts.entries()).sort((a, b) => b[1] - a[1]);
|
|
148
|
+
return sorted.slice(0, opts.numColors).map(([key, count]) => {
|
|
149
|
+
const r = key >> 16 & 255;
|
|
150
|
+
const g = key >> 8 & 255;
|
|
151
|
+
const b = key & 255;
|
|
152
|
+
return createDominantColor(r, g, b, count / validPixels);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function rgbToHex(r, g, b) {
|
|
156
|
+
return `#${[r, g, b].map((x) => {
|
|
157
|
+
const hex = x.toString(16);
|
|
158
|
+
return hex.length === 1 ? `0${hex}` : hex;
|
|
159
|
+
}).join("").toUpperCase()}`;
|
|
160
|
+
}
|
|
161
|
+
function rgbToHsl(r, g, b) {
|
|
162
|
+
const rn = r / 255;
|
|
163
|
+
const gn = g / 255;
|
|
164
|
+
const bn = b / 255;
|
|
165
|
+
const max = Math.max(rn, gn, bn);
|
|
166
|
+
const min = Math.min(rn, gn, bn);
|
|
167
|
+
let h = 0;
|
|
168
|
+
let s = 0;
|
|
169
|
+
const l = (max + min) / 2;
|
|
170
|
+
if (max !== min) {
|
|
171
|
+
const d = max - min;
|
|
172
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
173
|
+
switch (max) {
|
|
174
|
+
case rn:
|
|
175
|
+
h = (gn - bn) / d + (gn < bn ? 6 : 0);
|
|
176
|
+
break;
|
|
177
|
+
case gn:
|
|
178
|
+
h = (bn - rn) / d + 2;
|
|
179
|
+
break;
|
|
180
|
+
case bn:
|
|
181
|
+
h = (rn - gn) / d + 4;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
h /= 6;
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
h: Math.round(h * 360),
|
|
188
|
+
s: Math.round(s * 100),
|
|
189
|
+
l: Math.round(l * 100)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function getHueCategory(hue) {
|
|
193
|
+
let h = hue % 360;
|
|
194
|
+
if (h < 0) h += 360;
|
|
195
|
+
if (h >= 345 || h < 15) return "red";
|
|
196
|
+
if (h < 45) return "orange";
|
|
197
|
+
if (h < 75) return "yellow";
|
|
198
|
+
if (h < 135) return "green";
|
|
199
|
+
if (h < 195) return "cyan";
|
|
200
|
+
if (h < 255) return "blue";
|
|
201
|
+
if (h < 345) return "violet";
|
|
202
|
+
return "gray";
|
|
203
|
+
}
|
|
204
|
+
function createDominantColor(r, g, b, percentage) {
|
|
205
|
+
const hsl = rgbToHsl(r, g, b);
|
|
206
|
+
const hueCategory = hsl.s <= 5 ? "gray" : getHueCategory(hsl.h);
|
|
207
|
+
return {
|
|
208
|
+
r,
|
|
209
|
+
g,
|
|
210
|
+
b,
|
|
211
|
+
hex: rgbToHex(r, g, b),
|
|
212
|
+
percentage,
|
|
213
|
+
hue: hsl.h,
|
|
214
|
+
saturation: hsl.s,
|
|
215
|
+
lightness: hsl.l,
|
|
216
|
+
hueCategory
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function aggregateColors(colorSets, numColors = 3) {
|
|
220
|
+
const colorMap = /* @__PURE__ */ new Map();
|
|
221
|
+
for (const colors of colorSets) {
|
|
222
|
+
for (const color of colors) {
|
|
223
|
+
const key = color.hex;
|
|
224
|
+
const existing = colorMap.get(key);
|
|
225
|
+
if (existing) {
|
|
226
|
+
existing.weight += color.percentage;
|
|
227
|
+
} else {
|
|
228
|
+
colorMap.set(key, { color: { ...color }, weight: color.percentage });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return Array.from(colorMap.values()).sort((a, b) => b.weight - a.weight).slice(0, numColors).map(
|
|
233
|
+
(item) => createDominantColor(item.color.r, item.color.g, item.color.b, item.weight / colorSets.length)
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
function resolveOptions(options) {
|
|
237
|
+
return { ...DEFAULT_OPTIONS, ...options };
|
|
238
|
+
}
|
|
239
|
+
function calculateSaturation(r, g, b) {
|
|
240
|
+
const max = Math.max(r, g, b);
|
|
241
|
+
const min = Math.min(r, g, b);
|
|
242
|
+
if (max === 0) return 0;
|
|
243
|
+
return (max - min) / max;
|
|
244
|
+
}
|
|
245
|
+
function calculateEdgeWeight(data, x, y, width, height, channels) {
|
|
246
|
+
if (x === 0 || y === 0 || x === width - 1 || y === height - 1) {
|
|
247
|
+
return BORDER_EDGE_WEIGHT;
|
|
248
|
+
}
|
|
249
|
+
const getGray = (px, py) => {
|
|
250
|
+
const i = (py * width + px) * channels;
|
|
251
|
+
return ((data[i] ?? 0) + (data[i + 1] ?? 0) + (data[i + 2] ?? 0)) / 3;
|
|
252
|
+
};
|
|
253
|
+
const center = getGray(x, y);
|
|
254
|
+
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));
|
|
255
|
+
return 1 + Math.min(edgeStrength / EDGE_STRENGTH_DIVISOR, 1);
|
|
256
|
+
}
|
|
257
|
+
function calculateAverageColor(data, width, height, channels) {
|
|
258
|
+
let sumR = 0;
|
|
259
|
+
let sumG = 0;
|
|
260
|
+
let sumB = 0;
|
|
261
|
+
let counted = 0;
|
|
262
|
+
for (let y = 0; y < height; y++) {
|
|
263
|
+
for (let x = 0; x < width; x++) {
|
|
264
|
+
const index = (y * width + x) * channels;
|
|
265
|
+
const alpha = channels >= 4 ? data[index + 3] ?? 255 : 255;
|
|
266
|
+
if (alpha < ALPHA_THRESHOLD) continue;
|
|
267
|
+
sumR += data[index] ?? 0;
|
|
268
|
+
sumG += data[index + 1] ?? 0;
|
|
269
|
+
sumB += data[index + 2] ?? 0;
|
|
270
|
+
counted++;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (counted === 0) return null;
|
|
274
|
+
return createDominantColor(
|
|
275
|
+
Math.round(sumR / counted),
|
|
276
|
+
Math.round(sumG / counted),
|
|
277
|
+
Math.round(sumB / counted),
|
|
278
|
+
1
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
var DEFAULT_MERGE_DELTA_E = 15;
|
|
282
|
+
function extractAdvanced(data, width, height, channels, opts) {
|
|
283
|
+
const pixelCount = width * height;
|
|
284
|
+
if (pixelCount === 0) return [];
|
|
285
|
+
const stats = analyzeImageStats(data, width, height, channels);
|
|
286
|
+
const adaptedSatThreshold = stats.medianSaturation < 0.1 ? Math.min(opts.saturationThreshold, 0.05) : opts.saturationThreshold;
|
|
287
|
+
const centerWeightScale = 0.3 + 0.7 * stats.edgeCentrality;
|
|
288
|
+
const centerX = Math.floor(width / 2);
|
|
289
|
+
const centerY = Math.floor(height / 2);
|
|
290
|
+
const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
|
|
291
|
+
const colorMap = /* @__PURE__ */ new Map();
|
|
292
|
+
for (let y = 0; y < height; y++) {
|
|
293
|
+
for (let x = 0; x < width; x++) {
|
|
294
|
+
const i = (y * width + x) * channels;
|
|
295
|
+
const r = data[i] ?? 0;
|
|
296
|
+
const g = data[i + 1] ?? 0;
|
|
297
|
+
const b = data[i + 2] ?? 0;
|
|
298
|
+
const saturation = calculateSaturation(r, g, b);
|
|
299
|
+
if (saturation < adaptedSatThreshold) continue;
|
|
300
|
+
const brightness = (r + g + b) / 3;
|
|
301
|
+
if (brightness < opts.brightnessMin || brightness > opts.brightnessMax) continue;
|
|
302
|
+
const distanceFromCenter = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
|
|
303
|
+
const rawCenterWeight = maxDistance > 0 ? 1 - distanceFromCenter / maxDistance : 0;
|
|
304
|
+
const centerWeight = 1 + rawCenterWeight * centerWeightScale;
|
|
305
|
+
const edgeWeight = calculateEdgeWeight(data, x, y, width, height, channels);
|
|
306
|
+
const step = opts.quantizationStep;
|
|
307
|
+
const qr = Math.round(r / step) * step;
|
|
308
|
+
const qg = Math.round(g / step) * step;
|
|
309
|
+
const qb = Math.round(b / step) * step;
|
|
310
|
+
const key = qr << 16 | qg << 8 | qb;
|
|
311
|
+
const totalWeight = centerWeight * edgeWeight * (1 + saturation);
|
|
312
|
+
const existing = colorMap.get(key);
|
|
313
|
+
if (existing) {
|
|
314
|
+
existing.weight += totalWeight;
|
|
315
|
+
existing.sumX += x;
|
|
316
|
+
existing.sumY += y;
|
|
317
|
+
existing.sumX2 += x * x;
|
|
318
|
+
existing.sumY2 += y * y;
|
|
319
|
+
existing.count++;
|
|
320
|
+
} else {
|
|
321
|
+
colorMap.set(key, {
|
|
322
|
+
weight: totalWeight,
|
|
323
|
+
saturation,
|
|
324
|
+
sumX: x,
|
|
325
|
+
sumY: y,
|
|
326
|
+
sumX2: x * x,
|
|
327
|
+
sumY2: y * y,
|
|
328
|
+
count: 1
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const colorEntries = Array.from(colorMap.entries()).map(([key, entry]) => {
|
|
334
|
+
const r = key >> 16 & 255;
|
|
335
|
+
const g = key >> 8 & 255;
|
|
336
|
+
const b = key & 255;
|
|
337
|
+
let variance = 0;
|
|
338
|
+
if (entry.count >= 2) {
|
|
339
|
+
const n = entry.count;
|
|
340
|
+
variance = entry.sumX2 / n - (entry.sumX / n) ** 2 + (entry.sumY2 / n - (entry.sumY / n) ** 2);
|
|
341
|
+
}
|
|
342
|
+
const normalizedVariance = Math.min(variance / (width * height), 1);
|
|
343
|
+
const score = entry.weight * (1 + entry.saturation) * (1 + normalizedVariance * 0.5);
|
|
344
|
+
const lab = rgbToLab(r, g, b);
|
|
345
|
+
return { r, g, b, score, weight: entry.weight, key, lab };
|
|
346
|
+
});
|
|
347
|
+
const sortedColors = colorEntries.sort((a, b) => b.score - a.score);
|
|
348
|
+
const dominantColors = [];
|
|
349
|
+
const usedEntries = [];
|
|
350
|
+
for (const color of sortedColors) {
|
|
351
|
+
if (dominantColors.length >= opts.numColors) break;
|
|
352
|
+
if (usedEntries.some((u) => u.key === color.key)) continue;
|
|
353
|
+
let merged = false;
|
|
354
|
+
for (const used of usedEntries) {
|
|
355
|
+
if (deltaE76(color.lab, used.lab) < DEFAULT_MERGE_DELTA_E) {
|
|
356
|
+
merged = true;
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (!merged) {
|
|
361
|
+
usedEntries.push({ key: color.key, lab: color.lab });
|
|
362
|
+
dominantColors.push(
|
|
363
|
+
createDominantColor(color.r, color.g, color.b, color.weight / pixelCount)
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return dominantColors;
|
|
368
|
+
}
|
|
369
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
370
|
+
0 && (module.exports = {
|
|
371
|
+
DEFAULT_OPTIONS,
|
|
372
|
+
aggregateColors,
|
|
373
|
+
colorlip,
|
|
374
|
+
createDominantColor,
|
|
375
|
+
extractFallbackPalette,
|
|
376
|
+
getHueCategory,
|
|
377
|
+
rgbToHex,
|
|
378
|
+
rgbToHsl
|
|
379
|
+
});
|
|
380
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/core.ts"],"sourcesContent":["// Types\nexport type {\n DominantColor,\n ExtractOptions,\n HSL,\n HueCategory,\n ImageInfo,\n PixelData,\n} from \"./types\";\n\n// Constants\nexport { DEFAULT_OPTIONS } from \"./constants\";\n\n// Core functions\nexport {\n aggregateColors,\n createDominantColor,\n extractFallbackPalette,\n colorlip,\n getHueCategory,\n rgbToHex,\n rgbToHsl,\n} from \"./core\";\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":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** 色相カテゴリ(7色 + gray) */
|
|
2
|
+
type HueCategory = "red" | "orange" | "yellow" | "green" | "cyan" | "blue" | "violet" | "gray";
|
|
3
|
+
/** HSL 色空間 */
|
|
4
|
+
interface HSL {
|
|
5
|
+
/** 色相 (0–360) */
|
|
6
|
+
h: number;
|
|
7
|
+
/** 彩度 (0–100) */
|
|
8
|
+
s: number;
|
|
9
|
+
/** 明度 (0–100) */
|
|
10
|
+
l: number;
|
|
11
|
+
}
|
|
12
|
+
/** 画像のピクセルバッファ情報 */
|
|
13
|
+
interface ImageInfo {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
channels: number;
|
|
17
|
+
}
|
|
18
|
+
/** 抽出された代表色 */
|
|
19
|
+
interface DominantColor {
|
|
20
|
+
r: number;
|
|
21
|
+
g: number;
|
|
22
|
+
b: number;
|
|
23
|
+
hex: string;
|
|
24
|
+
percentage: number;
|
|
25
|
+
/** 色相 (0–360) */
|
|
26
|
+
hue: number;
|
|
27
|
+
/** 彩度 (0–100) */
|
|
28
|
+
saturation: number;
|
|
29
|
+
/** 明度 (0–100) */
|
|
30
|
+
lightness: number;
|
|
31
|
+
/** 色相カテゴリ */
|
|
32
|
+
hueCategory: HueCategory;
|
|
33
|
+
}
|
|
34
|
+
/** 抽出オプション */
|
|
35
|
+
interface ExtractOptions {
|
|
36
|
+
/** 抽出する色の数(デフォルト: 3) */
|
|
37
|
+
numColors?: number;
|
|
38
|
+
/** 彩度フィルタの閾値(デフォルト: 0.15) */
|
|
39
|
+
saturationThreshold?: number;
|
|
40
|
+
/** 明度の下限(デフォルト: 20) */
|
|
41
|
+
brightnessMin?: number;
|
|
42
|
+
/** 明度の上限(デフォルト: 235) */
|
|
43
|
+
brightnessMax?: number;
|
|
44
|
+
/** 量子化ステップ(デフォルト: 12) */
|
|
45
|
+
quantizationStep?: number;
|
|
46
|
+
}
|
|
47
|
+
/** ピクセルデータの型(Node.js Buffer / ブラウザ Uint8ClampedArray 双方対応) */
|
|
48
|
+
type PixelData = Uint8Array | Uint8ClampedArray;
|
|
49
|
+
|
|
50
|
+
declare const DEFAULT_OPTIONS: Required<ExtractOptions>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* ピクセルデータから代表色を抽出する(メインエントリ)。
|
|
54
|
+
*
|
|
55
|
+
* 内部で高度なアルゴリズム → フォールバック → 平均色 の順にフォールバックする。
|
|
56
|
+
*/
|
|
57
|
+
declare function colorlip(data: PixelData, width: number, height: number, channels: number, options?: ExtractOptions): DominantColor[];
|
|
58
|
+
/**
|
|
59
|
+
* フォールバック用パレット抽出。
|
|
60
|
+
* グレースケール画像など、メインアルゴリズムで色が取れない場合に使用。
|
|
61
|
+
*/
|
|
62
|
+
declare function extractFallbackPalette(data: PixelData, width: number, height: number, channels: number, options?: ExtractOptions): DominantColor[];
|
|
63
|
+
/**
|
|
64
|
+
* RGB → 16進数カラーコード(例: `#FF00AA`)。
|
|
65
|
+
*/
|
|
66
|
+
declare function rgbToHex(r: number, g: number, b: number): string;
|
|
67
|
+
/**
|
|
68
|
+
* RGB → HSL 変換。
|
|
69
|
+
*/
|
|
70
|
+
declare function rgbToHsl(r: number, g: number, b: number): HSL;
|
|
71
|
+
/**
|
|
72
|
+
* 色相値から色相カテゴリを判定。
|
|
73
|
+
*/
|
|
74
|
+
declare function getHueCategory(hue: number): HueCategory;
|
|
75
|
+
/**
|
|
76
|
+
* RGB + percentage から DominantColor オブジェクトを生成。
|
|
77
|
+
*/
|
|
78
|
+
declare function createDominantColor(r: number, g: number, b: number, percentage: number): DominantColor;
|
|
79
|
+
/**
|
|
80
|
+
* 複数画像の色セットを集約し、上位 numColors 色を返す。
|
|
81
|
+
*/
|
|
82
|
+
declare function aggregateColors(colorSets: DominantColor[][], numColors?: number): DominantColor[];
|
|
83
|
+
|
|
84
|
+
export { DEFAULT_OPTIONS, type DominantColor, type ExtractOptions, type HSL, type HueCategory, type ImageInfo, type PixelData, aggregateColors, colorlip, createDominantColor, extractFallbackPalette, getHueCategory, rgbToHex, rgbToHsl };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** 色相カテゴリ(7色 + gray) */
|
|
2
|
+
type HueCategory = "red" | "orange" | "yellow" | "green" | "cyan" | "blue" | "violet" | "gray";
|
|
3
|
+
/** HSL 色空間 */
|
|
4
|
+
interface HSL {
|
|
5
|
+
/** 色相 (0–360) */
|
|
6
|
+
h: number;
|
|
7
|
+
/** 彩度 (0–100) */
|
|
8
|
+
s: number;
|
|
9
|
+
/** 明度 (0–100) */
|
|
10
|
+
l: number;
|
|
11
|
+
}
|
|
12
|
+
/** 画像のピクセルバッファ情報 */
|
|
13
|
+
interface ImageInfo {
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
channels: number;
|
|
17
|
+
}
|
|
18
|
+
/** 抽出された代表色 */
|
|
19
|
+
interface DominantColor {
|
|
20
|
+
r: number;
|
|
21
|
+
g: number;
|
|
22
|
+
b: number;
|
|
23
|
+
hex: string;
|
|
24
|
+
percentage: number;
|
|
25
|
+
/** 色相 (0–360) */
|
|
26
|
+
hue: number;
|
|
27
|
+
/** 彩度 (0–100) */
|
|
28
|
+
saturation: number;
|
|
29
|
+
/** 明度 (0–100) */
|
|
30
|
+
lightness: number;
|
|
31
|
+
/** 色相カテゴリ */
|
|
32
|
+
hueCategory: HueCategory;
|
|
33
|
+
}
|
|
34
|
+
/** 抽出オプション */
|
|
35
|
+
interface ExtractOptions {
|
|
36
|
+
/** 抽出する色の数(デフォルト: 3) */
|
|
37
|
+
numColors?: number;
|
|
38
|
+
/** 彩度フィルタの閾値(デフォルト: 0.15) */
|
|
39
|
+
saturationThreshold?: number;
|
|
40
|
+
/** 明度の下限(デフォルト: 20) */
|
|
41
|
+
brightnessMin?: number;
|
|
42
|
+
/** 明度の上限(デフォルト: 235) */
|
|
43
|
+
brightnessMax?: number;
|
|
44
|
+
/** 量子化ステップ(デフォルト: 12) */
|
|
45
|
+
quantizationStep?: number;
|
|
46
|
+
}
|
|
47
|
+
/** ピクセルデータの型(Node.js Buffer / ブラウザ Uint8ClampedArray 双方対応) */
|
|
48
|
+
type PixelData = Uint8Array | Uint8ClampedArray;
|
|
49
|
+
|
|
50
|
+
declare const DEFAULT_OPTIONS: Required<ExtractOptions>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* ピクセルデータから代表色を抽出する(メインエントリ)。
|
|
54
|
+
*
|
|
55
|
+
* 内部で高度なアルゴリズム → フォールバック → 平均色 の順にフォールバックする。
|
|
56
|
+
*/
|
|
57
|
+
declare function colorlip(data: PixelData, width: number, height: number, channels: number, options?: ExtractOptions): DominantColor[];
|
|
58
|
+
/**
|
|
59
|
+
* フォールバック用パレット抽出。
|
|
60
|
+
* グレースケール画像など、メインアルゴリズムで色が取れない場合に使用。
|
|
61
|
+
*/
|
|
62
|
+
declare function extractFallbackPalette(data: PixelData, width: number, height: number, channels: number, options?: ExtractOptions): DominantColor[];
|
|
63
|
+
/**
|
|
64
|
+
* RGB → 16進数カラーコード(例: `#FF00AA`)。
|
|
65
|
+
*/
|
|
66
|
+
declare function rgbToHex(r: number, g: number, b: number): string;
|
|
67
|
+
/**
|
|
68
|
+
* RGB → HSL 変換。
|
|
69
|
+
*/
|
|
70
|
+
declare function rgbToHsl(r: number, g: number, b: number): HSL;
|
|
71
|
+
/**
|
|
72
|
+
* 色相値から色相カテゴリを判定。
|
|
73
|
+
*/
|
|
74
|
+
declare function getHueCategory(hue: number): HueCategory;
|
|
75
|
+
/**
|
|
76
|
+
* RGB + percentage から DominantColor オブジェクトを生成。
|
|
77
|
+
*/
|
|
78
|
+
declare function createDominantColor(r: number, g: number, b: number, percentage: number): DominantColor;
|
|
79
|
+
/**
|
|
80
|
+
* 複数画像の色セットを集約し、上位 numColors 色を返す。
|
|
81
|
+
*/
|
|
82
|
+
declare function aggregateColors(colorSets: DominantColor[][], numColors?: number): DominantColor[];
|
|
83
|
+
|
|
84
|
+
export { DEFAULT_OPTIONS, type DominantColor, type ExtractOptions, type HSL, type HueCategory, type ImageInfo, type PixelData, aggregateColors, colorlip, createDominantColor, extractFallbackPalette, getHueCategory, rgbToHex, rgbToHsl };
|