@dilemmagx/palette 0.2.0 → 0.2.2

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/dist/cli.js CHANGED
@@ -10067,6 +10067,15 @@ function registerBuiltInAlgorithms() {
10067
10067
  const sourceArgb = argbFromHex(input.baseColor);
10068
10068
  return buildMonetPaletteFromSourceArgb(sourceArgb, resolveMonetCount(input.count, 6));
10069
10069
  });
10070
+ registerAlgorithm("dominant", async (input) => {
10071
+ if (input.buffer) {
10072
+ const colors = await extractDominantPalette(input.buffer, resolveCount(input.count, 8));
10073
+ if (colors.length > 0) {
10074
+ return colors;
10075
+ }
10076
+ }
10077
+ return buildHslPaletteColors(input, "monochrome");
10078
+ });
10070
10079
  }
10071
10080
  registerBuiltInAlgorithms();
10072
10081
  async function buildPaletteResult(input, algorithm) {
@@ -10199,6 +10208,74 @@ async function extractMonetPalette(buffer, count) {
10199
10208
  const sourceArgb = ranked[0] ?? sampled[0];
10200
10209
  return buildMonetPaletteFromSourceArgb(sourceArgb, count);
10201
10210
  }
10211
+ async function extractDominantPalette(buffer, count) {
10212
+ const { data, info } = await (0, import_sharp.default)(buffer).resize({ width: 96, height: 96, fit: "inside" }).removeAlpha().raw().toBuffer({ resolveWithObject: true });
10213
+ const step = info.channels;
10214
+ const pixels = [];
10215
+ for (let i = 0; i < data.length; i += step) {
10216
+ const r = data[i] ?? 0;
10217
+ const g = data[i + 1] ?? 0;
10218
+ const b = data[i + 2] ?? 0;
10219
+ pixels.push(argbFromRgb(r, g, b));
10220
+ }
10221
+ if (pixels.length === 0) return [];
10222
+ const sampled = downsamplePixels(pixels, 2400);
10223
+ const quantized = QuantizerCelebi.quantize(sampled, 128);
10224
+ const entries = Array.from(quantized.entries()).map(([argb, population]) => ({
10225
+ rgb: argbToRgb(argb),
10226
+ count: population
10227
+ }));
10228
+ if (entries.length === 0) return [];
10229
+ entries.sort((a, b) => {
10230
+ const countDelta = b.count - a.count;
10231
+ if (countDelta !== 0) return countDelta;
10232
+ return rgbKey(a.rgb) - rgbKey(b.rgb);
10233
+ });
10234
+ const totalCount = entries.reduce((sum, entry) => sum + entry.count, 0);
10235
+ const coverageTarget = 0.95;
10236
+ const candidateEntries = [];
10237
+ let coverage = 0;
10238
+ for (const entry of entries) {
10239
+ if (candidateEntries.length >= count) {
10240
+ candidateEntries.push(entry);
10241
+ continue;
10242
+ }
10243
+ candidateEntries.push(entry);
10244
+ coverage += entry.count / Math.max(1, totalCount);
10245
+ if (coverage >= coverageTarget) {
10246
+ break;
10247
+ }
10248
+ }
10249
+ if (candidateEntries.length < count) {
10250
+ candidateEntries.splice(0, candidateEntries.length, ...entries);
10251
+ }
10252
+ if (entries.length <= count) {
10253
+ return entries.map((entry) => rgbToHex(entry.rgb));
10254
+ }
10255
+ const maxCount = Math.max(1, entries[0]?.count ?? 1);
10256
+ const seeds = [candidateEntries[0].rgb];
10257
+ while (seeds.length < count) {
10258
+ let best = null;
10259
+ let bestScore = -1;
10260
+ for (const entry of candidateEntries) {
10261
+ if (seeds.some((seed) => rgbDistanceSq(seed, entry.rgb) === 0)) continue;
10262
+ let minDist = Number.POSITIVE_INFINITY;
10263
+ for (const seed of seeds) {
10264
+ minDist = Math.min(minDist, rgbDistanceSq(seed, entry.rgb));
10265
+ }
10266
+ const weight = entry.count / maxCount;
10267
+ const score = minDist * (0.2 + Math.sqrt(weight));
10268
+ if (score > bestScore + 1e-9 || Math.abs(score - bestScore) <= 1e-9 && best !== null && rgbKey(entry.rgb) < rgbKey(best.rgb)) {
10269
+ bestScore = score;
10270
+ best = entry;
10271
+ }
10272
+ }
10273
+ if (!best) break;
10274
+ seeds.push(best.rgb);
10275
+ }
10276
+ const refined = refineCentroids(seeds, entries);
10277
+ return refined.slice(0, count).map((color) => rgbToHex(color));
10278
+ }
10202
10279
  function resolveCount(count, fallback) {
10203
10280
  if (!count) return fallback;
10204
10281
  return Math.max(2, Math.min(12, Math.floor(count)));
@@ -10299,6 +10376,50 @@ async function fetchBuffer(url) {
10299
10376
  function clamp(value, min, max) {
10300
10377
  return Math.min(max, Math.max(min, value));
10301
10378
  }
10379
+ function rgbDistanceSq(a, b) {
10380
+ const dr = a.r - b.r;
10381
+ const dg = a.g - b.g;
10382
+ const db = a.b - b.b;
10383
+ return dr * dr + dg * dg + db * db;
10384
+ }
10385
+ function rgbKey(color) {
10386
+ return (color.r << 16) + (color.g << 8) + color.b;
10387
+ }
10388
+ function argbToRgb(argb) {
10389
+ return {
10390
+ r: argb >>> 16 & 255,
10391
+ g: argb >>> 8 & 255,
10392
+ b: argb & 255
10393
+ };
10394
+ }
10395
+ function refineCentroids(seeds, entries) {
10396
+ const accum = seeds.map(() => ({ r: 0, g: 0, b: 0, count: 0 }));
10397
+ for (const entry of entries) {
10398
+ let bestIndex = 0;
10399
+ let bestDist = Number.POSITIVE_INFINITY;
10400
+ for (let i = 0; i < seeds.length; i += 1) {
10401
+ const dist = rgbDistanceSq(seeds[i], entry.rgb);
10402
+ if (dist < bestDist) {
10403
+ bestDist = dist;
10404
+ bestIndex = i;
10405
+ }
10406
+ }
10407
+ const target = accum[bestIndex];
10408
+ target.r += entry.rgb.r * entry.count;
10409
+ target.g += entry.rgb.g * entry.count;
10410
+ target.b += entry.rgb.b * entry.count;
10411
+ target.count += entry.count;
10412
+ }
10413
+ return seeds.map((seed, index) => {
10414
+ const bucket = accum[index];
10415
+ if (!bucket || bucket.count === 0) return seed;
10416
+ return {
10417
+ r: clamp(bucket.r / bucket.count, 0, 255),
10418
+ g: clamp(bucket.g / bucket.count, 0, 255),
10419
+ b: clamp(bucket.b / bucket.count, 0, 255)
10420
+ };
10421
+ });
10422
+ }
10302
10423
  function normalizeHue(hue) {
10303
10424
  const normalized = hue % 360;
10304
10425
  return normalized < 0 ? normalized + 360 : normalized;
package/dist/core.d.ts CHANGED
@@ -5,7 +5,7 @@ export type InputType = 'hex' | 'datauri' | 'url' | 'path';
5
5
  /**
6
6
  * Built-in palette algorithms.
7
7
  */
8
- export type BuiltInPaletteAlgorithm = 'analogous' | 'complementary' | 'triadic' | 'tetradic' | 'split-complementary' | 'monochrome' | 'monet';
8
+ export type BuiltInPaletteAlgorithm = 'analogous' | 'complementary' | 'triadic' | 'tetradic' | 'split-complementary' | 'monochrome' | 'monet' | 'dominant';
9
9
  /**
10
10
  * Palette algorithm name, including custom algorithms.
11
11
  */