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.
@@ -0,0 +1,390 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/adapters/sharp.ts
31
+ var sharp_exports = {};
32
+ __export(sharp_exports, {
33
+ colorlip: () => colorlip,
34
+ colorlipFromBuffer: () => colorlipFromBuffer,
35
+ colorlipFromFile: () => colorlipFromFile
36
+ });
37
+ module.exports = __toCommonJS(sharp_exports);
38
+ var import_sharp = __toESM(require("sharp"), 1);
39
+
40
+ // src/constants.ts
41
+ var DEFAULT_OPTIONS = {
42
+ numColors: 3,
43
+ saturationThreshold: 0.15,
44
+ brightnessMin: 20,
45
+ brightnessMax: 235,
46
+ quantizationStep: 12
47
+ };
48
+ var FALLBACK_QUANTIZATION_STEP = 16;
49
+ var ALPHA_THRESHOLD = 16;
50
+ var EDGE_STRENGTH_DIVISOR = 100;
51
+ var BORDER_EDGE_WEIGHT = 1.5;
52
+ var MAX_RESIZE = 150;
53
+
54
+ // src/core.ts
55
+ function rgbToLab(r, g, b) {
56
+ let rl = r / 255;
57
+ let gl = g / 255;
58
+ let bl = b / 255;
59
+ rl = rl > 0.04045 ? ((rl + 0.055) / 1.055) ** 2.4 : rl / 12.92;
60
+ gl = gl > 0.04045 ? ((gl + 0.055) / 1.055) ** 2.4 : gl / 12.92;
61
+ bl = bl > 0.04045 ? ((bl + 0.055) / 1.055) ** 2.4 : bl / 12.92;
62
+ let x = (rl * 0.4124564 + gl * 0.3575761 + bl * 0.1804375) / 0.95047;
63
+ let y = rl * 0.2126729 + gl * 0.7151522 + bl * 0.072175;
64
+ let z = (rl * 0.0193339 + gl * 0.119192 + bl * 0.9503041) / 1.08883;
65
+ const epsilon = 8856e-6;
66
+ const kappa = 903.3;
67
+ x = x > epsilon ? Math.cbrt(x) : (kappa * x + 16) / 116;
68
+ y = y > epsilon ? Math.cbrt(y) : (kappa * y + 16) / 116;
69
+ z = z > epsilon ? Math.cbrt(z) : (kappa * z + 16) / 116;
70
+ return {
71
+ L: 116 * y - 16,
72
+ a: 500 * (x - y),
73
+ b: 200 * (y - z)
74
+ };
75
+ }
76
+ function deltaE76(lab1, lab2) {
77
+ return Math.sqrt((lab1.L - lab2.L) ** 2 + (lab1.a - lab2.a) ** 2 + (lab1.b - lab2.b) ** 2);
78
+ }
79
+ function analyzeImageStats(data, width, height, channels) {
80
+ const pixelCount = width * height;
81
+ if (pixelCount === 0) return { medianSaturation: 0, edgeCentrality: 0.5 };
82
+ const sampleCount = Math.min(Math.max(Math.floor(pixelCount * 0.1), 100), 2e3);
83
+ const stride = Math.max(1, Math.floor(pixelCount / sampleCount));
84
+ const saturations = [];
85
+ const centerX = width / 2;
86
+ const centerY = height / 2;
87
+ const maxDist = Math.sqrt(centerX ** 2 + centerY ** 2);
88
+ let edgeWeightedCenterDist = 0;
89
+ let totalEdgeStrength = 0;
90
+ for (let idx = 0; idx < pixelCount; idx += stride) {
91
+ const x = idx % width;
92
+ const y = Math.floor(idx / width);
93
+ const i = idx * channels;
94
+ const r = data[i] ?? 0;
95
+ const g = data[i + 1] ?? 0;
96
+ const b = data[i + 2] ?? 0;
97
+ const max = Math.max(r, g, b);
98
+ const min = Math.min(r, g, b);
99
+ saturations.push(max === 0 ? 0 : (max - min) / max);
100
+ if (x > 0 && x < width - 1 && y > 0 && y < height - 1) {
101
+ const getGray = (px, py) => {
102
+ const j = (py * width + px) * channels;
103
+ return ((data[j] ?? 0) + (data[j + 1] ?? 0) + (data[j + 2] ?? 0)) / 3;
104
+ };
105
+ const center = getGray(x, y);
106
+ 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));
107
+ if (strength > 10) {
108
+ const distFromCenter = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
109
+ const normalizedDist = maxDist > 0 ? distFromCenter / maxDist : 0;
110
+ edgeWeightedCenterDist += normalizedDist * strength;
111
+ totalEdgeStrength += strength;
112
+ }
113
+ }
114
+ }
115
+ saturations.sort((a, b) => a - b);
116
+ const medianSaturation = saturations[Math.floor(saturations.length / 2)] ?? 0;
117
+ const avgEdgeDist = totalEdgeStrength > 0 ? edgeWeightedCenterDist / totalEdgeStrength : 0.5;
118
+ const edgeCentrality = 1 - avgEdgeDist;
119
+ return { medianSaturation, edgeCentrality };
120
+ }
121
+ function colorlip(data, width, height, channels, options) {
122
+ const opts = resolveOptions(options);
123
+ const result = extractAdvanced(data, width, height, channels, opts);
124
+ if (result.length > 0) return result;
125
+ return extractFallbackPalette(data, width, height, channels, opts);
126
+ }
127
+ function extractFallbackPalette(data, width, height, channels, options) {
128
+ const opts = resolveOptions(options);
129
+ const pixelCount = width * height;
130
+ if (pixelCount === 0) return [];
131
+ const colorCounts = /* @__PURE__ */ new Map();
132
+ let validPixels = 0;
133
+ for (let y = 0; y < height; y++) {
134
+ for (let x = 0; x < width; x++) {
135
+ const index = (y * width + x) * channels;
136
+ const alpha = channels >= 4 ? data[index + 3] ?? 255 : 255;
137
+ if (alpha < ALPHA_THRESHOLD) continue;
138
+ const r = data[index] ?? 0;
139
+ const g = data[index + 1] ?? 0;
140
+ const b = data[index + 2] ?? 0;
141
+ const qr = Math.round(r / FALLBACK_QUANTIZATION_STEP) * FALLBACK_QUANTIZATION_STEP;
142
+ const qg = Math.round(g / FALLBACK_QUANTIZATION_STEP) * FALLBACK_QUANTIZATION_STEP;
143
+ const qb = Math.round(b / FALLBACK_QUANTIZATION_STEP) * FALLBACK_QUANTIZATION_STEP;
144
+ const key = qr << 16 | qg << 8 | qb;
145
+ colorCounts.set(key, (colorCounts.get(key) ?? 0) + 1);
146
+ validPixels++;
147
+ }
148
+ }
149
+ if (validPixels === 0) return [];
150
+ if (colorCounts.size === 0) {
151
+ const avg = calculateAverageColor(data, width, height, channels);
152
+ return avg ? [avg] : [];
153
+ }
154
+ const sorted = Array.from(colorCounts.entries()).sort((a, b) => b[1] - a[1]);
155
+ return sorted.slice(0, opts.numColors).map(([key, count]) => {
156
+ const r = key >> 16 & 255;
157
+ const g = key >> 8 & 255;
158
+ const b = key & 255;
159
+ return createDominantColor(r, g, b, count / validPixels);
160
+ });
161
+ }
162
+ function rgbToHex(r, g, b) {
163
+ return `#${[r, g, b].map((x) => {
164
+ const hex = x.toString(16);
165
+ return hex.length === 1 ? `0${hex}` : hex;
166
+ }).join("").toUpperCase()}`;
167
+ }
168
+ function rgbToHsl(r, g, b) {
169
+ const rn = r / 255;
170
+ const gn = g / 255;
171
+ const bn = b / 255;
172
+ const max = Math.max(rn, gn, bn);
173
+ const min = Math.min(rn, gn, bn);
174
+ let h = 0;
175
+ let s = 0;
176
+ const l = (max + min) / 2;
177
+ if (max !== min) {
178
+ const d = max - min;
179
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
180
+ switch (max) {
181
+ case rn:
182
+ h = (gn - bn) / d + (gn < bn ? 6 : 0);
183
+ break;
184
+ case gn:
185
+ h = (bn - rn) / d + 2;
186
+ break;
187
+ case bn:
188
+ h = (rn - gn) / d + 4;
189
+ break;
190
+ }
191
+ h /= 6;
192
+ }
193
+ return {
194
+ h: Math.round(h * 360),
195
+ s: Math.round(s * 100),
196
+ l: Math.round(l * 100)
197
+ };
198
+ }
199
+ function getHueCategory(hue) {
200
+ let h = hue % 360;
201
+ if (h < 0) h += 360;
202
+ if (h >= 345 || h < 15) return "red";
203
+ if (h < 45) return "orange";
204
+ if (h < 75) return "yellow";
205
+ if (h < 135) return "green";
206
+ if (h < 195) return "cyan";
207
+ if (h < 255) return "blue";
208
+ if (h < 345) return "violet";
209
+ return "gray";
210
+ }
211
+ function createDominantColor(r, g, b, percentage) {
212
+ const hsl = rgbToHsl(r, g, b);
213
+ const hueCategory = hsl.s <= 5 ? "gray" : getHueCategory(hsl.h);
214
+ return {
215
+ r,
216
+ g,
217
+ b,
218
+ hex: rgbToHex(r, g, b),
219
+ percentage,
220
+ hue: hsl.h,
221
+ saturation: hsl.s,
222
+ lightness: hsl.l,
223
+ hueCategory
224
+ };
225
+ }
226
+ function resolveOptions(options) {
227
+ return { ...DEFAULT_OPTIONS, ...options };
228
+ }
229
+ function calculateSaturation(r, g, b) {
230
+ const max = Math.max(r, g, b);
231
+ const min = Math.min(r, g, b);
232
+ if (max === 0) return 0;
233
+ return (max - min) / max;
234
+ }
235
+ function calculateEdgeWeight(data, x, y, width, height, channels) {
236
+ if (x === 0 || y === 0 || x === width - 1 || y === height - 1) {
237
+ return BORDER_EDGE_WEIGHT;
238
+ }
239
+ const getGray = (px, py) => {
240
+ const i = (py * width + px) * channels;
241
+ return ((data[i] ?? 0) + (data[i + 1] ?? 0) + (data[i + 2] ?? 0)) / 3;
242
+ };
243
+ const center = getGray(x, y);
244
+ 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));
245
+ return 1 + Math.min(edgeStrength / EDGE_STRENGTH_DIVISOR, 1);
246
+ }
247
+ function calculateAverageColor(data, width, height, channels) {
248
+ let sumR = 0;
249
+ let sumG = 0;
250
+ let sumB = 0;
251
+ let counted = 0;
252
+ for (let y = 0; y < height; y++) {
253
+ for (let x = 0; x < width; x++) {
254
+ const index = (y * width + x) * channels;
255
+ const alpha = channels >= 4 ? data[index + 3] ?? 255 : 255;
256
+ if (alpha < ALPHA_THRESHOLD) continue;
257
+ sumR += data[index] ?? 0;
258
+ sumG += data[index + 1] ?? 0;
259
+ sumB += data[index + 2] ?? 0;
260
+ counted++;
261
+ }
262
+ }
263
+ if (counted === 0) return null;
264
+ return createDominantColor(
265
+ Math.round(sumR / counted),
266
+ Math.round(sumG / counted),
267
+ Math.round(sumB / counted),
268
+ 1
269
+ );
270
+ }
271
+ var DEFAULT_MERGE_DELTA_E = 15;
272
+ function extractAdvanced(data, width, height, channels, opts) {
273
+ const pixelCount = width * height;
274
+ if (pixelCount === 0) return [];
275
+ const stats = analyzeImageStats(data, width, height, channels);
276
+ const adaptedSatThreshold = stats.medianSaturation < 0.1 ? Math.min(opts.saturationThreshold, 0.05) : opts.saturationThreshold;
277
+ const centerWeightScale = 0.3 + 0.7 * stats.edgeCentrality;
278
+ const centerX = Math.floor(width / 2);
279
+ const centerY = Math.floor(height / 2);
280
+ const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
281
+ const colorMap = /* @__PURE__ */ new Map();
282
+ for (let y = 0; y < height; y++) {
283
+ for (let x = 0; x < width; x++) {
284
+ const i = (y * width + x) * channels;
285
+ const r = data[i] ?? 0;
286
+ const g = data[i + 1] ?? 0;
287
+ const b = data[i + 2] ?? 0;
288
+ const saturation = calculateSaturation(r, g, b);
289
+ if (saturation < adaptedSatThreshold) continue;
290
+ const brightness = (r + g + b) / 3;
291
+ if (brightness < opts.brightnessMin || brightness > opts.brightnessMax) continue;
292
+ const distanceFromCenter = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
293
+ const rawCenterWeight = maxDistance > 0 ? 1 - distanceFromCenter / maxDistance : 0;
294
+ const centerWeight = 1 + rawCenterWeight * centerWeightScale;
295
+ const edgeWeight = calculateEdgeWeight(data, x, y, width, height, channels);
296
+ const step = opts.quantizationStep;
297
+ const qr = Math.round(r / step) * step;
298
+ const qg = Math.round(g / step) * step;
299
+ const qb = Math.round(b / step) * step;
300
+ const key = qr << 16 | qg << 8 | qb;
301
+ const totalWeight = centerWeight * edgeWeight * (1 + saturation);
302
+ const existing = colorMap.get(key);
303
+ if (existing) {
304
+ existing.weight += totalWeight;
305
+ existing.sumX += x;
306
+ existing.sumY += y;
307
+ existing.sumX2 += x * x;
308
+ existing.sumY2 += y * y;
309
+ existing.count++;
310
+ } else {
311
+ colorMap.set(key, {
312
+ weight: totalWeight,
313
+ saturation,
314
+ sumX: x,
315
+ sumY: y,
316
+ sumX2: x * x,
317
+ sumY2: y * y,
318
+ count: 1
319
+ });
320
+ }
321
+ }
322
+ }
323
+ const colorEntries = Array.from(colorMap.entries()).map(([key, entry]) => {
324
+ const r = key >> 16 & 255;
325
+ const g = key >> 8 & 255;
326
+ const b = key & 255;
327
+ let variance = 0;
328
+ if (entry.count >= 2) {
329
+ const n = entry.count;
330
+ variance = entry.sumX2 / n - (entry.sumX / n) ** 2 + (entry.sumY2 / n - (entry.sumY / n) ** 2);
331
+ }
332
+ const normalizedVariance = Math.min(variance / (width * height), 1);
333
+ const score = entry.weight * (1 + entry.saturation) * (1 + normalizedVariance * 0.5);
334
+ const lab = rgbToLab(r, g, b);
335
+ return { r, g, b, score, weight: entry.weight, key, lab };
336
+ });
337
+ const sortedColors = colorEntries.sort((a, b) => b.score - a.score);
338
+ const dominantColors = [];
339
+ const usedEntries = [];
340
+ for (const color of sortedColors) {
341
+ if (dominantColors.length >= opts.numColors) break;
342
+ if (usedEntries.some((u) => u.key === color.key)) continue;
343
+ let merged = false;
344
+ for (const used of usedEntries) {
345
+ if (deltaE76(color.lab, used.lab) < DEFAULT_MERGE_DELTA_E) {
346
+ merged = true;
347
+ break;
348
+ }
349
+ }
350
+ if (!merged) {
351
+ usedEntries.push({ key: color.key, lab: color.lab });
352
+ dominantColors.push(
353
+ createDominantColor(color.r, color.g, color.b, color.weight / pixelCount)
354
+ );
355
+ }
356
+ }
357
+ return dominantColors;
358
+ }
359
+
360
+ // src/adapters/sharp.ts
361
+ async function colorlipFromFile(filePath, options) {
362
+ const image = (0, import_sharp.default)(filePath);
363
+ return extractFromSharpInstance(image, options);
364
+ }
365
+ async function colorlipFromBuffer(buffer, options) {
366
+ const image = (0, import_sharp.default)(buffer);
367
+ return extractFromSharpInstance(image, options);
368
+ }
369
+ async function extractFromSharpInstance(image, options) {
370
+ const metadata = await image.metadata();
371
+ if (!metadata.width || !metadata.height) return [];
372
+ const scale = Math.min(MAX_RESIZE / metadata.width, MAX_RESIZE / metadata.height, 1);
373
+ const newWidth = Math.max(1, Math.round(metadata.width * scale));
374
+ const newHeight = Math.max(1, Math.round(metadata.height * scale));
375
+ const { data, info } = await image.resize(newWidth, newHeight, { fit: "inside" }).raw().toBuffer({ resolveWithObject: true });
376
+ return colorlip(
377
+ new Uint8Array(data.buffer, data.byteOffset, data.byteLength),
378
+ info.width,
379
+ info.height,
380
+ info.channels,
381
+ options
382
+ );
383
+ }
384
+ // Annotate the CommonJS export names for ESM import in node:
385
+ 0 && (module.exports = {
386
+ colorlip,
387
+ colorlipFromBuffer,
388
+ colorlipFromFile
389
+ });
390
+ //# sourceMappingURL=sharp.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/sharp.ts","../../src/constants.ts","../../src/core.ts"],"sourcesContent":["import sharp from \"sharp\";\nimport { MAX_RESIZE } from \"../constants\";\nimport { colorlip } from \"../core\";\nimport type { DominantColor, ExtractOptions } from \"../types\";\n\n/**\n * 画像ファイルから代表色を抽出する(Node.js / sharp 使用)。\n */\nexport { colorlip } from \"../core\";\n\nexport async function colorlipFromFile(\n filePath: string,\n options?: ExtractOptions,\n): Promise<DominantColor[]> {\n const image = sharp(filePath);\n return extractFromSharpInstance(image, options);\n}\n\n/**\n * 画像バッファから代表色を抽出する(Node.js / sharp 使用)。\n */\nexport async function colorlipFromBuffer(\n buffer: Buffer | Uint8Array,\n options?: ExtractOptions,\n): Promise<DominantColor[]> {\n const image = sharp(buffer);\n return extractFromSharpInstance(image, options);\n}\n\nasync function extractFromSharpInstance(\n image: sharp.Sharp,\n options?: ExtractOptions,\n): Promise<DominantColor[]> {\n const metadata = await image.metadata();\n if (!metadata.width || !metadata.height) return [];\n\n const scale = Math.min(MAX_RESIZE / metadata.width, MAX_RESIZE / metadata.height, 1);\n const newWidth = Math.max(1, Math.round(metadata.width * scale));\n const newHeight = Math.max(1, Math.round(metadata.height * scale));\n\n const { data, info } = await image\n .resize(newWidth, newHeight, { fit: \"inside\" })\n .raw()\n .toBuffer({ resolveWithObject: true });\n\n return colorlip(\n new Uint8Array(data.buffer, data.byteOffset, data.byteLength),\n info.width,\n info.height,\n info.channels,\n options,\n );\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":["sharp"]}
@@ -0,0 +1,10 @@
1
+ import { E as ExtractOptions, D as DominantColor } from './core-OmHuknYv.cjs';
2
+ export { c as colorlip } from './core-OmHuknYv.cjs';
3
+
4
+ declare function colorlipFromFile(filePath: string, options?: ExtractOptions): Promise<DominantColor[]>;
5
+ /**
6
+ * 画像バッファから代表色を抽出する(Node.js / sharp 使用)。
7
+ */
8
+ declare function colorlipFromBuffer(buffer: Buffer | Uint8Array, options?: ExtractOptions): Promise<DominantColor[]>;
9
+
10
+ export { colorlipFromBuffer, colorlipFromFile };
@@ -0,0 +1,10 @@
1
+ import { E as ExtractOptions, D as DominantColor } from './core-OmHuknYv.js';
2
+ export { c as colorlip } from './core-OmHuknYv.js';
3
+
4
+ declare function colorlipFromFile(filePath: string, options?: ExtractOptions): Promise<DominantColor[]>;
5
+ /**
6
+ * 画像バッファから代表色を抽出する(Node.js / sharp 使用)。
7
+ */
8
+ declare function colorlipFromBuffer(buffer: Buffer | Uint8Array, options?: ExtractOptions): Promise<DominantColor[]>;
9
+
10
+ export { colorlipFromBuffer, colorlipFromFile };
@@ -0,0 +1,36 @@
1
+ import {
2
+ MAX_RESIZE,
3
+ colorlip
4
+ } from "./chunk-5PQQ7VW4.js";
5
+
6
+ // src/adapters/sharp.ts
7
+ import sharp from "sharp";
8
+ async function colorlipFromFile(filePath, options) {
9
+ const image = sharp(filePath);
10
+ return extractFromSharpInstance(image, options);
11
+ }
12
+ async function colorlipFromBuffer(buffer, options) {
13
+ const image = sharp(buffer);
14
+ return extractFromSharpInstance(image, options);
15
+ }
16
+ async function extractFromSharpInstance(image, options) {
17
+ const metadata = await image.metadata();
18
+ if (!metadata.width || !metadata.height) return [];
19
+ const scale = Math.min(MAX_RESIZE / metadata.width, MAX_RESIZE / metadata.height, 1);
20
+ const newWidth = Math.max(1, Math.round(metadata.width * scale));
21
+ const newHeight = Math.max(1, Math.round(metadata.height * scale));
22
+ const { data, info } = await image.resize(newWidth, newHeight, { fit: "inside" }).raw().toBuffer({ resolveWithObject: true });
23
+ return colorlip(
24
+ new Uint8Array(data.buffer, data.byteOffset, data.byteLength),
25
+ info.width,
26
+ info.height,
27
+ info.channels,
28
+ options
29
+ );
30
+ }
31
+ export {
32
+ colorlip,
33
+ colorlipFromBuffer,
34
+ colorlipFromFile
35
+ };
36
+ //# sourceMappingURL=sharp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/sharp.ts"],"sourcesContent":["import sharp from \"sharp\";\nimport { MAX_RESIZE } from \"../constants\";\nimport { colorlip } from \"../core\";\nimport type { DominantColor, ExtractOptions } from \"../types\";\n\n/**\n * 画像ファイルから代表色を抽出する(Node.js / sharp 使用)。\n */\nexport { colorlip } from \"../core\";\n\nexport async function colorlipFromFile(\n filePath: string,\n options?: ExtractOptions,\n): Promise<DominantColor[]> {\n const image = sharp(filePath);\n return extractFromSharpInstance(image, options);\n}\n\n/**\n * 画像バッファから代表色を抽出する(Node.js / sharp 使用)。\n */\nexport async function colorlipFromBuffer(\n buffer: Buffer | Uint8Array,\n options?: ExtractOptions,\n): Promise<DominantColor[]> {\n const image = sharp(buffer);\n return extractFromSharpInstance(image, options);\n}\n\nasync function extractFromSharpInstance(\n image: sharp.Sharp,\n options?: ExtractOptions,\n): Promise<DominantColor[]> {\n const metadata = await image.metadata();\n if (!metadata.width || !metadata.height) return [];\n\n const scale = Math.min(MAX_RESIZE / metadata.width, MAX_RESIZE / metadata.height, 1);\n const newWidth = Math.max(1, Math.round(metadata.width * scale));\n const newHeight = Math.max(1, Math.round(metadata.height * scale));\n\n const { data, info } = await image\n .resize(newWidth, newHeight, { fit: \"inside\" })\n .raw()\n .toBuffer({ resolveWithObject: true });\n\n return colorlip(\n new Uint8Array(data.buffer, data.byteOffset, data.byteLength),\n info.width,\n info.height,\n info.channels,\n options,\n );\n}\n"],"mappings":";;;;;;AAAA,OAAO,WAAW;AAUlB,eAAsB,iBACpB,UACA,SAC0B;AAC1B,QAAM,QAAQ,MAAM,QAAQ;AAC5B,SAAO,yBAAyB,OAAO,OAAO;AAChD;AAKA,eAAsB,mBACpB,QACA,SAC0B;AAC1B,QAAM,QAAQ,MAAM,MAAM;AAC1B,SAAO,yBAAyB,OAAO,OAAO;AAChD;AAEA,eAAe,yBACb,OACA,SAC0B;AAC1B,QAAM,WAAW,MAAM,MAAM,SAAS;AACtC,MAAI,CAAC,SAAS,SAAS,CAAC,SAAS,OAAQ,QAAO,CAAC;AAEjD,QAAM,QAAQ,KAAK,IAAI,aAAa,SAAS,OAAO,aAAa,SAAS,QAAQ,CAAC;AACnF,QAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,QAAQ,KAAK,CAAC;AAC/D,QAAM,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,SAAS,KAAK,CAAC;AAEjE,QAAM,EAAE,MAAM,KAAK,IAAI,MAAM,MAC1B,OAAO,UAAU,WAAW,EAAE,KAAK,SAAS,CAAC,EAC7C,IAAI,EACJ,SAAS,EAAE,mBAAmB,KAAK,CAAC;AAEvC,SAAO;AAAA,IACL,IAAI,WAAW,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU;AAAA,IAC5D,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL;AAAA,EACF;AACF;","names":[]}