chromonym 2.0.0 → 3.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/NOTICE.md +32 -0
- package/README.md +140 -74
- package/dist/conversions/hsl.d.ts.map +1 -1
- package/dist/conversions/hsl.js.map +1 -1
- package/dist/conversions/hsv.d.ts.map +1 -1
- package/dist/conversions/hsv.js.map +1 -1
- package/dist/identify.d.ts +36 -11
- package/dist/identify.d.ts.map +1 -1
- package/dist/identify.js +34 -19
- package/dist/identify.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/indexing.d.ts +20 -14
- package/dist/indexing.d.ts.map +1 -1
- package/dist/indexing.js +64 -13
- package/dist/indexing.js.map +1 -1
- package/dist/math/editDistance.d.ts +17 -0
- package/dist/math/editDistance.d.ts.map +1 -0
- package/dist/math/editDistance.js +48 -0
- package/dist/math/editDistance.js.map +1 -0
- package/dist/palettes/crayola.d.ts +200 -0
- package/dist/palettes/crayola.d.ts.map +1 -0
- package/dist/palettes/crayola.js +121 -0
- package/dist/palettes/crayola.js.map +1 -0
- package/dist/resolve.d.ts +39 -6
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +39 -12
- package/dist/resolve.js.map +1 -1
- package/package.json +14 -5
- package/dist/conversions/index.d.ts +0 -6
- package/dist/conversions/index.d.ts.map +0 -1
- package/dist/conversions/index.js +0 -6
- package/dist/conversions/index.js.map +0 -1
- package/dist/palettes/index.d.ts +0 -4
- package/dist/palettes/index.d.ts.map +0 -1
- package/dist/palettes/index.js +0 -4
- package/dist/palettes/index.js.map +0 -1
package/dist/identify.js
CHANGED
|
@@ -1,29 +1,44 @@
|
|
|
1
|
+
import { hexToRgba } from './conversions/hex.js';
|
|
1
2
|
import { toRgba } from './convert.js';
|
|
2
3
|
import { detectFormat } from './detectFormat.js';
|
|
3
|
-
import { nearest } from './indexing.js';
|
|
4
|
+
import { getNameIndex, nearest, nearestAll } from './indexing.js';
|
|
4
5
|
import { web } from './palettes/web.js';
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* The distance metric defaults to the palette's own `defaultMetric`
|
|
9
|
-
* (web/x11: `deltaE76`, pantone: `deltaE2000`). Override with the `metric`
|
|
10
|
-
* option — e.g. `{ metric: 'euclidean-srgb' }` for maximum speed, or
|
|
11
|
-
* `{ metric: 'deltaE2000' }` for strictest perceptual accuracy.
|
|
12
|
-
*
|
|
13
|
-
* Returns `null` if the input isn't a recognized color shape.
|
|
14
|
-
*
|
|
15
|
-
* Defaults: palette = `web`.
|
|
16
|
-
*
|
|
17
|
-
* BYO palettes: pass any object matching `Palette<Name>` and the
|
|
18
|
-
* return type is inferred from its `colors` keys — no registration needed.
|
|
7
|
+
* Parse a string input that may be a color literal or a palette name.
|
|
8
|
+
* Returns the canonical Rgba, or `null` if nothing matches.
|
|
19
9
|
*/
|
|
10
|
+
function parseInput(input, source) {
|
|
11
|
+
const detected = detectFormat(input);
|
|
12
|
+
if (detected !== 'UNKNOWN')
|
|
13
|
+
return toRgba(input, detected);
|
|
14
|
+
if (source !== undefined && typeof input === 'string') {
|
|
15
|
+
const canonical = getNameIndex(source).get(source.normalize(input));
|
|
16
|
+
if (canonical === undefined)
|
|
17
|
+
return null;
|
|
18
|
+
const hex = source.colors[canonical];
|
|
19
|
+
if (hex === undefined)
|
|
20
|
+
return null;
|
|
21
|
+
return hexToRgba(hex);
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
// Implementation.
|
|
20
26
|
export function identify(input, opts = {}) {
|
|
21
|
-
const
|
|
22
|
-
|
|
27
|
+
const palette = opts.palette ?? web;
|
|
28
|
+
const metric = opts.metric ?? palette.defaultMetric;
|
|
29
|
+
const rgba = parseInput(input, opts.source);
|
|
30
|
+
if (opts.k !== undefined) {
|
|
31
|
+
if (rgba === null)
|
|
32
|
+
return [];
|
|
33
|
+
const raw = nearestAll(rgba, palette, metric, opts.k);
|
|
34
|
+
return raw.map((m) => {
|
|
35
|
+
const value = palette.colors[m.name];
|
|
36
|
+
return { name: m.name, value: value, distance: m.distance };
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (rgba === null)
|
|
23
40
|
return null;
|
|
24
|
-
const
|
|
25
|
-
const rgba = toRgba(input, format);
|
|
26
|
-
const name = nearest(rgba, palette, opts.metric ?? palette.defaultMetric);
|
|
41
|
+
const name = nearest(rgba, palette, metric);
|
|
27
42
|
return name || null;
|
|
28
43
|
}
|
|
29
44
|
//# sourceMappingURL=identify.js.map
|
package/dist/identify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identify.js","sourceRoot":"","sources":["../src/identify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"identify.js","sourceRoot":"","sources":["../src/identify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAkBxC;;;GAGG;AACH,SAAS,UAAU,CAAC,KAA0B,EAAE,MAAgB;IAC9D,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAmB,CAAC,CAAC;IACnD,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAmB,EAAE,QAAQ,CAAC,CAAC;IACzE,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACtD,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACpE,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACzC,MAAM,GAAG,GAAI,MAAM,CAAC,MAAmC,CAAC,SAAS,CAAC,CAAC;QACnE,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACnC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAqCD,kBAAkB;AAClB,MAAM,UAAU,QAAQ,CACtB,KAA0B,EAC1B,IAAI,GAAiF,EAAE;IAEvF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC;IACpC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC;IACpD,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAE5C,IAAI,IAAI,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACtD,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACnB,MAAM,KAAK,GAAI,OAAO,CAAC,MAAmC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACnE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,KAAiB,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5C,OAAO,IAAI,IAAI,IAAI,CAAC;AACtB,CAAC","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { toRgba } from './convert.js';\nimport { detectFormat } from './detectFormat.js';\nimport { getNameIndex, nearest, nearestAll } from './indexing.js';\nimport { web } from './palettes/web.js';\nimport type { ColorInput, DistanceMetric, HexColor, Palette, Rgba } from './types.js';\n\n/** Extract string keys from a Palette's `colors` map. */\ntype PaletteKey<P extends Palette> = Extract<keyof P['colors'], string>;\n\n/**\n * One ranked nearest-match entry when `identify` is called with `k`.\n * `value` is the palette's stored hex for the match (useful for rendering\n * swatches). `distance` is in the selected metric's natural units — ΔE\n * for `deltaE*` metrics, channel-unit Euclidean for the others.\n */\nexport type IdentifyMatch<Name extends string> = {\n readonly name: Name;\n readonly value: HexColor;\n readonly distance: number;\n};\n\n/**\n * Parse a string input that may be a color literal or a palette name.\n * Returns the canonical Rgba, or `null` if nothing matches.\n */\nfunction parseInput(input: ColorInput | string, source?: Palette): Rgba | null {\n const detected = detectFormat(input as ColorInput);\n if (detected !== 'UNKNOWN') return toRgba(input as ColorInput, detected);\n if (source !== undefined && typeof input === 'string') {\n const canonical = getNameIndex(source).get(source.normalize(input));\n if (canonical === undefined) return null;\n const hex = (source.colors as Record<string, HexColor>)[canonical];\n if (hex === undefined) return null;\n return hexToRgba(hex);\n }\n return null;\n}\n\n/**\n * Identify the nearest-named color for any color input (or palette name\n * from another palette via `source`).\n *\n * **Simple case — color in, name out:**\n * identify('#ff0000') → 'red'\n * identify('#ff0000', { palette: pantone }) → '172 C'\n *\n * **Cross-palette — name from one palette, nearest in another:**\n * identify('rebeccapurple', { palette: pantone, source: web }) → '267 C'\n * identify('Razzmatazz', { palette: pantone, source: crayola }) → '213 C'\n *\n * **Top-k ranked matches with ΔE distances:**\n * identify('#ff0000', { palette: pantone, k: 3 })\n * // [{ name: '172 C', value: '#fa4616', distance: 3.2 }, ...]\n *\n * The distance metric defaults to the target palette's `defaultMetric`\n * (web/x11: `deltaE76`, pantone: `deltaE2000`, crayola: `deltaEok`).\n * Override with `metric`.\n *\n * Returns `null` (or `[]` when `k` is set) if the input can't be parsed.\n */\n\n// Overload 1: no `k` — single name (or null). Unchanged legacy shape.\nexport function identify<P extends Palette = typeof web>(\n input: ColorInput | string,\n opts?: { palette?: P; source?: Palette; metric?: DistanceMetric },\n): PaletteKey<P> | null;\n\n// Overload 2: `k` present — ranked Match[] with distances.\nexport function identify<P extends Palette = typeof web>(\n input: ColorInput | string,\n opts: { palette?: P; source?: Palette; metric?: DistanceMetric; k: number },\n): Array<IdentifyMatch<PaletteKey<P>>>;\n\n// Implementation.\nexport function identify(\n input: ColorInput | string,\n opts: { palette?: Palette; source?: Palette; metric?: DistanceMetric; k?: number } = {},\n): string | null | Array<IdentifyMatch<string>> {\n const palette = opts.palette ?? web;\n const metric = opts.metric ?? palette.defaultMetric;\n const rgba = parseInput(input, opts.source);\n\n if (opts.k !== undefined) {\n if (rgba === null) return [];\n const raw = nearestAll(rgba, palette, metric, opts.k);\n return raw.map((m) => {\n const value = (palette.colors as Record<string, HexColor>)[m.name];\n return { name: m.name, value: value as HexColor, distance: m.distance };\n });\n }\n\n if (rgba === null) return null;\n const name = nearest(rgba, palette, metric);\n return name || null;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,11 +5,12 @@ export { pantoneToRgba, rgbaToPantone } from './conversions/pantone.js';
|
|
|
5
5
|
export { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';
|
|
6
6
|
export { convert, fromRgba, toRgba } from './convert.js';
|
|
7
7
|
export { type DetectedFormat, detectFormat, isColor } from './detectFormat.js';
|
|
8
|
-
export { identify } from './identify.js';
|
|
8
|
+
export { type IdentifyMatch, identify } from './identify.js';
|
|
9
|
+
export { type CrayolaColorName, crayola } from './palettes/crayola.js';
|
|
9
10
|
export { type PantoneColorName, pantone } from './palettes/pantone.js';
|
|
10
11
|
export { type WebColorName, web } from './palettes/web.js';
|
|
11
12
|
export { type X11ColorName, x11 } from './palettes/x11.js';
|
|
12
|
-
export { resolve } from './resolve.js';
|
|
13
|
+
export { type ResolveMatch, resolve } from './resolve.js';
|
|
13
14
|
export type { ColorFormat, ColorInput, ColorValue, DistanceMetric, HexColor, HslInput, HslObject, HslString, HsvInput, HsvObject, HsvString, NormalizeFn, Palette, Rgba, RgbaInput, RgbaString, RgbaTuple, RgbInput, RgbObject, RgbString, RgbTuple, } from './types.js';
|
|
14
15
|
export { COLOR_FORMATS } from './types.js';
|
|
15
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,KAAK,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,KAAK,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,KAAK,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,KAAK,YAAY,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC1D,YAAY,EACV,WAAW,EACX,UAAU,EACV,UAAU,EACV,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,EACR,SAAS,EACT,SAAS,EACT,WAAW,EACX,OAAO,EACP,IAAI,EACJ,SAAS,EACT,UAAU,EACV,SAAS,EACT,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,GACT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC","sourcesContent":["// Public API barrel — explicit named re-exports only (tree-shake friendly).\n// No default export; no side effects at module scope.\n\nexport { hexToRgba, rgbaToHex } from './conversions/hex.js';\nexport { hslToRgba, rgbaToHsl } from './conversions/hsl.js';\nexport { hsvToRgba, rgbaToHsv } from './conversions/hsv.js';\nexport { pantoneToRgba, rgbaToPantone } from './conversions/pantone.js';\nexport { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';\nexport { convert, fromRgba, toRgba } from './convert.js';\nexport { type DetectedFormat, detectFormat, isColor } from './detectFormat.js';\nexport { type IdentifyMatch, identify } from './identify.js';\nexport { type CrayolaColorName, crayola } from './palettes/crayola.js';\nexport { type PantoneColorName, pantone } from './palettes/pantone.js';\nexport { type WebColorName, web } from './palettes/web.js';\nexport { type X11ColorName, x11 } from './palettes/x11.js';\nexport { type ResolveMatch, resolve } from './resolve.js';\nexport type {\n ColorFormat,\n ColorInput,\n ColorValue,\n DistanceMetric,\n HexColor,\n HslInput,\n HslObject,\n HslString,\n HsvInput,\n HsvObject,\n HsvString,\n NormalizeFn,\n Palette,\n Rgba,\n RgbaInput,\n RgbaString,\n RgbaTuple,\n RgbInput,\n RgbObject,\n RgbString,\n RgbTuple,\n} from './types.js';\nexport { COLOR_FORMATS } from './types.js';\n"]}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';
|
|
|
8
8
|
export { convert, fromRgba, toRgba } from './convert.js';
|
|
9
9
|
export { detectFormat, isColor } from './detectFormat.js';
|
|
10
10
|
export { identify } from './identify.js';
|
|
11
|
+
export { crayola } from './palettes/crayola.js';
|
|
11
12
|
export { pantone } from './palettes/pantone.js';
|
|
12
13
|
export { web } from './palettes/web.js';
|
|
13
14
|
export { x11 } from './palettes/x11.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,sDAAsD;AAEtD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAuB,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,sDAAsD;AAEtD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAuB,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAsB,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAyB,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAyB,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAqB,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAqB,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAqB,OAAO,EAAE,MAAM,cAAc,CAAC;AAwB1D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC","sourcesContent":["// Public API barrel — explicit named re-exports only (tree-shake friendly).\n// No default export; no side effects at module scope.\n\nexport { hexToRgba, rgbaToHex } from './conversions/hex.js';\nexport { hslToRgba, rgbaToHsl } from './conversions/hsl.js';\nexport { hsvToRgba, rgbaToHsv } from './conversions/hsv.js';\nexport { pantoneToRgba, rgbaToPantone } from './conversions/pantone.js';\nexport { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';\nexport { convert, fromRgba, toRgba } from './convert.js';\nexport { type DetectedFormat, detectFormat, isColor } from './detectFormat.js';\nexport { type IdentifyMatch, identify } from './identify.js';\nexport { type CrayolaColorName, crayola } from './palettes/crayola.js';\nexport { type PantoneColorName, pantone } from './palettes/pantone.js';\nexport { type WebColorName, web } from './palettes/web.js';\nexport { type X11ColorName, x11 } from './palettes/x11.js';\nexport { type ResolveMatch, resolve } from './resolve.js';\nexport type {\n ColorFormat,\n ColorInput,\n ColorValue,\n DistanceMetric,\n HexColor,\n HslInput,\n HslObject,\n HslString,\n HsvInput,\n HsvObject,\n HsvString,\n NormalizeFn,\n Palette,\n Rgba,\n RgbaInput,\n RgbaString,\n RgbaTuple,\n RgbInput,\n RgbObject,\n RgbString,\n RgbTuple,\n} from './types.js';\nexport { COLOR_FORMATS } from './types.js';\n"]}
|
package/dist/indexing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DistanceMetric,
|
|
1
|
+
import type { DistanceMetric, Palette, Rgba } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Per-palette lookup indexes for name resolution and nearest-match.
|
|
4
4
|
*
|
|
@@ -6,8 +6,6 @@ import type { DistanceMetric, NormalizeFn, Palette, Rgba } from './types.js';
|
|
|
6
6
|
* indexes are built once per process and reused by every caller —
|
|
7
7
|
* including BYO palettes the user supplies.
|
|
8
8
|
*/
|
|
9
|
-
export { pantoneNormalize, standardNormalize } from './palettes/normalize.js';
|
|
10
|
-
export type { NormalizeFn };
|
|
11
9
|
type AnyPalette = Palette<string>;
|
|
12
10
|
/**
|
|
13
11
|
* Normalized-name → canonical-key map for a given palette. Built once
|
|
@@ -25,17 +23,6 @@ export declare function getNameIndex(space: AnyPalette): Map<string, string>;
|
|
|
25
23
|
* input with `rgbaToHex(...).toLowerCase()` before lookup.
|
|
26
24
|
*/
|
|
27
25
|
export declare function getReverseNameIndex(space: AnyPalette): Map<string, string>;
|
|
28
|
-
/**
|
|
29
|
-
* Canonical-key → Rgba entries for a given palette, built by parsing
|
|
30
|
-
* each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).
|
|
31
|
-
*/
|
|
32
|
-
export declare function getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]>;
|
|
33
|
-
/**
|
|
34
|
-
* Find the nearest-named entry in a palette to the given Rgba, using
|
|
35
|
-
* squared Euclidean distance in sRGB (alpha ignored). Used by `identify`
|
|
36
|
-
* and `rgbaToPantone` when metric === 'euclidean-srgb'.
|
|
37
|
-
*/
|
|
38
|
-
export declare function nearestByRgb(target: Rgba, space: AnyPalette): string;
|
|
39
26
|
/**
|
|
40
27
|
* Find the nearest-named entry in a palette under the specified distance
|
|
41
28
|
* metric. Dispatches to the right index (rgba / linear-rgb / Lab) and
|
|
@@ -47,4 +34,23 @@ export declare function nearestByRgb(target: Rgba, space: AnyPalette): string;
|
|
|
47
34
|
* hide the distance-metric choice from the hot path.
|
|
48
35
|
*/
|
|
49
36
|
export declare function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string;
|
|
37
|
+
/**
|
|
38
|
+
* Rank every palette entry by distance to `target`. Returns an array of
|
|
39
|
+
* `{ name, distance }` sorted ascending (nearest first). Optional `k`
|
|
40
|
+
* truncates to the top-k matches.
|
|
41
|
+
*
|
|
42
|
+
* Distances are **real** metric values (not squared) — ΔE76 / ΔE94 /
|
|
43
|
+
* ΔE2000 / ΔE-OK are in standard ΔE units (≈1 = just-noticeable for
|
|
44
|
+
* most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`
|
|
45
|
+
* are in their respective channel-unit Euclidean distances.
|
|
46
|
+
*
|
|
47
|
+
* Cost: O(n log n) for the sort vs O(n) for `nearest` — acceptable for
|
|
48
|
+
* top-K queries. For strict "what's the single nearest?" stay on
|
|
49
|
+
* `nearest` / `identify`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function nearestAll(target: Rgba, space: AnyPalette, metric: DistanceMetric, k?: number): Array<{
|
|
52
|
+
name: string;
|
|
53
|
+
distance: number;
|
|
54
|
+
}>;
|
|
55
|
+
export {};
|
|
50
56
|
//# sourceMappingURL=indexing.d.ts.map
|
package/dist/indexing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexing.d.ts","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAY,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvF;;;;;;GAMG;AAKH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,KAAK,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAKlC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAWnE;AAID;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAU1E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAS9E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAYpE;AAsED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAavF","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';\nimport { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';\nimport { squaredDistanceRgb } from './math/euclideanDistance.js';\nimport type { DistanceMetric, HexColor, NormalizeFn, Palette, Rgba } from './types.js';\n\n/**\n * Per-palette lookup indexes for name resolution and nearest-match.\n *\n * Caches are keyed by the Palette wrapper (WeakMap), so each palette's\n * indexes are built once per process and reused by every caller —\n * including BYO palettes the user supplies.\n */\n\n// Re-export normalizers from their own module for backward compat with any\n// caller that expected them here. The functions live in ./palettes/normalize\n// so palette modules can import them without pulling this file's math graph.\nexport { pantoneNormalize, standardNormalize } from './palettes/normalize.js';\nexport type { NormalizeFn };\n\ntype AnyPalette = Palette<string>;\n\nconst nameIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\nconst rgbaIndexCache = new WeakMap<AnyPalette, Array<readonly [string, Rgba]>>();\n\n/**\n * Normalized-name → canonical-key map for a given palette. Built once\n * per palette using its own `normalize` function.\n */\nexport function getNameIndex(space: AnyPalette): Map<string, string> {\n let idx = nameIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n const { normalize } = space;\n for (const key of Object.keys(space.colors)) {\n idx.set(normalize(key), key);\n }\n nameIndexCache.set(space, idx);\n }\n return idx;\n}\n\nconst reverseIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\n\n/**\n * Hex → canonical-key map (reverse of `palette.colors`). Built once per\n * palette. Used by `convert(color, { palette, format: 'NAME' })` for\n * O(1) exact reverse lookup. Returns `undefined` on miss — the caller\n * decides whether that's an error (convert throws; use `identify` for\n * nearest-match semantics).\n *\n * Hex values are lowercased for the key so callers can normalize their\n * input with `rgbaToHex(...).toLowerCase()` before lookup.\n */\nexport function getReverseNameIndex(space: AnyPalette): Map<string, string> {\n let idx = reverseIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n for (const [name, hex] of Object.entries(space.colors)) {\n idx.set((hex as string).toLowerCase(), name);\n }\n reverseIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Canonical-key → Rgba entries for a given palette, built by parsing\n * each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).\n */\nexport function getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]> {\n let idx = rgbaIndexCache.get(space);\n if (idx === undefined) {\n idx = Object.entries(space.colors).map(\n ([name, hex]) => [name, hexToRgba(hex as HexColor)] as const,\n );\n rgbaIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Find the nearest-named entry in a palette to the given Rgba, using\n * squared Euclidean distance in sRGB (alpha ignored). Used by `identify`\n * and `rgbaToPantone` when metric === 'euclidean-srgb'.\n */\nexport function nearestByRgb(target: Rgba, space: AnyPalette): string {\n const entries = getRgbaIndex(space);\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = squaredDistanceRgb(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Precomputed Lab entries for delta-E metrics. Same shape as rgba index.\ntype LabTriple = readonly [number, number, number];\nconst labIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = labIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLab(rgba)] as const);\n labIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed linear-RGB entries for euclidean-linear metric.\nconst linearIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLinearIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = linearIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLinearRgb(rgba)] as const);\n linearIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed OKLAB entries for deltaEok metric.\nconst oklabIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getOklabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = oklabIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToOklab(rgba)] as const);\n oklabIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Generic argmin over an `[name, value]` entries array with a custom\n * distance function. Factored so the three metric branches in `nearest`\n * share one loop body instead of duplicating it three times.\n */\nfunction argminBy<T>(\n entries: Array<readonly [string, T]>,\n target: T,\n dist: (a: T, b: T) => number,\n): string {\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = dist(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Squared Euclidean on a [number, number, number] triple. Used for the\n// linear-RGB metric (sharing shape with Lab but different meaning).\nfunction squaredDistanceTriple(a: LabTriple, b: LabTriple): number {\n const d0 = a[0] - b[0];\n const d1 = a[1] - b[1];\n const d2 = a[2] - b[2];\n return d0 * d0 + d1 * d1 + d2 * d2;\n}\n\n/**\n * Find the nearest-named entry in a palette under the specified distance\n * metric. Dispatches to the right index (rgba / linear-rgb / Lab) and\n * the right distance function. See `DistanceMetric` in types.ts for how\n * to pick a metric.\n *\n * `metric` is required — callers should pass `space.defaultMetric` if\n * they want the palette's declared preference. A silent default would\n * hide the distance-metric choice from the hot path.\n */\nexport function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string {\n if (metric === 'euclidean-srgb') return nearestByRgb(target, space);\n if (metric === 'euclidean-linear') {\n return argminBy(getLinearIndex(space), rgbaToLinearRgb(target), squaredDistanceTriple);\n }\n if (metric === 'deltaEok') {\n return argminBy(getOklabIndex(space), rgbaToOklab(target), deltaEokSquared);\n }\n const labTarget = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') return argminBy(labIndex, labTarget, deltaE76Squared);\n if (metric === 'deltaE94') return argminBy(labIndex, labTarget, deltaE94);\n return argminBy(labIndex, labTarget, deltaE2000);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"indexing.d.ts","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;;GAMG;AAEH,KAAK,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAKlC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAWnE;AAID;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAU1E;AAsGD;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAavF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,IAAI,EACZ,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,cAAc,EACtB,CAAC,CAAC,EAAE,MAAM,GACT,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA2C3C","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';\nimport { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';\nimport { squaredDistanceRgb } from './math/euclideanDistance.js';\nimport type { DistanceMetric, Palette, Rgba } from './types.js';\n\n/**\n * Per-palette lookup indexes for name resolution and nearest-match.\n *\n * Caches are keyed by the Palette wrapper (WeakMap), so each palette's\n * indexes are built once per process and reused by every caller —\n * including BYO palettes the user supplies.\n */\n\ntype AnyPalette = Palette<string>;\n\nconst nameIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\nconst rgbaIndexCache = new WeakMap<AnyPalette, Array<readonly [string, Rgba]>>();\n\n/**\n * Normalized-name → canonical-key map for a given palette. Built once\n * per palette using its own `normalize` function.\n */\nexport function getNameIndex(space: AnyPalette): Map<string, string> {\n let idx = nameIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n const { normalize } = space;\n for (const key of Object.keys(space.colors)) {\n idx.set(normalize(key), key);\n }\n nameIndexCache.set(space, idx);\n }\n return idx;\n}\n\nconst reverseIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\n\n/**\n * Hex → canonical-key map (reverse of `palette.colors`). Built once per\n * palette. Used by `convert(color, { palette, format: 'NAME' })` for\n * O(1) exact reverse lookup. Returns `undefined` on miss — the caller\n * decides whether that's an error (convert throws; use `identify` for\n * nearest-match semantics).\n *\n * Hex values are lowercased for the key so callers can normalize their\n * input with `rgbaToHex(...).toLowerCase()` before lookup.\n */\nexport function getReverseNameIndex(space: AnyPalette): Map<string, string> {\n let idx = reverseIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n for (const [name, hex] of Object.entries(space.colors)) {\n idx.set((hex as string).toLowerCase(), name);\n }\n reverseIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Canonical-key → Rgba entries for a given palette, built by parsing\n * each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).\n */\nfunction getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]> {\n let idx = rgbaIndexCache.get(space);\n if (idx === undefined) {\n idx = Object.entries(space.colors).map(([name, hex]) => [name, hexToRgba(hex)] as const);\n rgbaIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Find the nearest-named entry in a palette to the given Rgba, using\n * squared Euclidean distance in sRGB (alpha ignored). Used by `identify`\n * and `rgbaToPantone` when metric === 'euclidean-srgb'.\n */\nfunction nearestByRgb(target: Rgba, space: AnyPalette): string {\n const entries = getRgbaIndex(space);\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = squaredDistanceRgb(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Precomputed Lab entries for delta-E metrics. Same shape as rgba index.\ntype LabTriple = readonly [number, number, number];\nconst labIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = labIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLab(rgba)] as const);\n labIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed linear-RGB entries for euclidean-linear metric.\nconst linearIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLinearIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = linearIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLinearRgb(rgba)] as const);\n linearIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed OKLAB entries for deltaEok metric.\nconst oklabIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getOklabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = oklabIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToOklab(rgba)] as const);\n oklabIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Generic argmin over an `[name, value]` entries array with a custom\n * distance function. Factored so the three metric branches in `nearest`\n * share one loop body instead of duplicating it three times.\n */\nfunction argminBy<T>(\n entries: Array<readonly [string, T]>,\n target: T,\n dist: (a: T, b: T) => number,\n): string {\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = dist(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Squared Euclidean on a [number, number, number] triple. Used for the\n// linear-RGB metric (sharing shape with Lab but different meaning).\nfunction squaredDistanceTriple(a: LabTriple, b: LabTriple): number {\n const d0 = a[0] - b[0];\n const d1 = a[1] - b[1];\n const d2 = a[2] - b[2];\n return d0 * d0 + d1 * d1 + d2 * d2;\n}\n\n/**\n * Find the nearest-named entry in a palette under the specified distance\n * metric. Dispatches to the right index (rgba / linear-rgb / Lab) and\n * the right distance function. See `DistanceMetric` in types.ts for how\n * to pick a metric.\n *\n * `metric` is required — callers should pass `space.defaultMetric` if\n * they want the palette's declared preference. A silent default would\n * hide the distance-metric choice from the hot path.\n */\nexport function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string {\n if (metric === 'euclidean-srgb') return nearestByRgb(target, space);\n if (metric === 'euclidean-linear') {\n return argminBy(getLinearIndex(space), rgbaToLinearRgb(target), squaredDistanceTriple);\n }\n if (metric === 'deltaEok') {\n return argminBy(getOklabIndex(space), rgbaToOklab(target), deltaEokSquared);\n }\n const labTarget = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') return argminBy(labIndex, labTarget, deltaE76Squared);\n if (metric === 'deltaE94') return argminBy(labIndex, labTarget, deltaE94);\n return argminBy(labIndex, labTarget, deltaE2000);\n}\n\n/**\n * Rank every palette entry by distance to `target`. Returns an array of\n * `{ name, distance }` sorted ascending (nearest first). Optional `k`\n * truncates to the top-k matches.\n *\n * Distances are **real** metric values (not squared) — ΔE76 / ΔE94 /\n * ΔE2000 / ΔE-OK are in standard ΔE units (≈1 = just-noticeable for\n * most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`\n * are in their respective channel-unit Euclidean distances.\n *\n * Cost: O(n log n) for the sort vs O(n) for `nearest` — acceptable for\n * top-K queries. For strict \"what's the single nearest?\" stay on\n * `nearest` / `identify`.\n */\nexport function nearestAll(\n target: Rgba,\n space: AnyPalette,\n metric: DistanceMetric,\n k?: number,\n): Array<{ name: string; distance: number }> {\n const ranked: Array<[string, number]> = [];\n\n if (metric === 'euclidean-srgb') {\n for (const [name, candidate] of getRgbaIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceRgb(target, candidate))]);\n }\n } else if (metric === 'euclidean-linear') {\n const tgt = rgbaToLinearRgb(target);\n for (const [name, candidate] of getLinearIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceTriple(tgt, candidate))]);\n }\n } else if (metric === 'deltaEok') {\n const tgt = rgbaToOklab(target);\n for (const [name, candidate] of getOklabIndex(space)) {\n ranked.push([name, Math.sqrt(deltaEokSquared(tgt, candidate))]);\n }\n } else {\n const tgt = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, Math.sqrt(deltaE76Squared(tgt, candidate))]);\n }\n } else if (metric === 'deltaE94') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE94(tgt, candidate)]);\n }\n } else {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE2000(tgt, candidate)]);\n }\n }\n }\n\n ranked.sort((a, b) => a[1] - b[1]);\n const limit = k === undefined ? ranked.length : Math.min(Math.max(k, 0), ranked.length);\n const result: Array<{ name: string; distance: number }> = new Array(limit);\n for (let i = 0; i < limit; i++) {\n const entry = ranked[i];\n if (entry !== undefined) result[i] = { name: entry[0], distance: entry[1] };\n }\n return result;\n}\n"]}
|
package/dist/indexing.js
CHANGED
|
@@ -2,17 +2,6 @@ import { hexToRgba } from './conversions/hex.js';
|
|
|
2
2
|
import { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';
|
|
3
3
|
import { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';
|
|
4
4
|
import { squaredDistanceRgb } from './math/euclideanDistance.js';
|
|
5
|
-
/**
|
|
6
|
-
* Per-palette lookup indexes for name resolution and nearest-match.
|
|
7
|
-
*
|
|
8
|
-
* Caches are keyed by the Palette wrapper (WeakMap), so each palette's
|
|
9
|
-
* indexes are built once per process and reused by every caller —
|
|
10
|
-
* including BYO palettes the user supplies.
|
|
11
|
-
*/
|
|
12
|
-
// Re-export normalizers from their own module for backward compat with any
|
|
13
|
-
// caller that expected them here. The functions live in ./palettes/normalize
|
|
14
|
-
// so palette modules can import them without pulling this file's math graph.
|
|
15
|
-
export { pantoneNormalize, standardNormalize } from './palettes/normalize.js';
|
|
16
5
|
const nameIndexCache = new WeakMap();
|
|
17
6
|
const rgbaIndexCache = new WeakMap();
|
|
18
7
|
/**
|
|
@@ -57,7 +46,7 @@ export function getReverseNameIndex(space) {
|
|
|
57
46
|
* Canonical-key → Rgba entries for a given palette, built by parsing
|
|
58
47
|
* each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).
|
|
59
48
|
*/
|
|
60
|
-
|
|
49
|
+
function getRgbaIndex(space) {
|
|
61
50
|
let idx = rgbaIndexCache.get(space);
|
|
62
51
|
if (idx === undefined) {
|
|
63
52
|
idx = Object.entries(space.colors).map(([name, hex]) => [name, hexToRgba(hex)]);
|
|
@@ -70,7 +59,7 @@ export function getRgbaIndex(space) {
|
|
|
70
59
|
* squared Euclidean distance in sRGB (alpha ignored). Used by `identify`
|
|
71
60
|
* and `rgbaToPantone` when metric === 'euclidean-srgb'.
|
|
72
61
|
*/
|
|
73
|
-
|
|
62
|
+
function nearestByRgb(target, space) {
|
|
74
63
|
const entries = getRgbaIndex(space);
|
|
75
64
|
let bestName = entries[0]?.[0] ?? '';
|
|
76
65
|
let bestDistance = Number.POSITIVE_INFINITY;
|
|
@@ -164,4 +153,66 @@ export function nearest(target, space, metric) {
|
|
|
164
153
|
return argminBy(labIndex, labTarget, deltaE94);
|
|
165
154
|
return argminBy(labIndex, labTarget, deltaE2000);
|
|
166
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Rank every palette entry by distance to `target`. Returns an array of
|
|
158
|
+
* `{ name, distance }` sorted ascending (nearest first). Optional `k`
|
|
159
|
+
* truncates to the top-k matches.
|
|
160
|
+
*
|
|
161
|
+
* Distances are **real** metric values (not squared) — ΔE76 / ΔE94 /
|
|
162
|
+
* ΔE2000 / ΔE-OK are in standard ΔE units (≈1 = just-noticeable for
|
|
163
|
+
* most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`
|
|
164
|
+
* are in their respective channel-unit Euclidean distances.
|
|
165
|
+
*
|
|
166
|
+
* Cost: O(n log n) for the sort vs O(n) for `nearest` — acceptable for
|
|
167
|
+
* top-K queries. For strict "what's the single nearest?" stay on
|
|
168
|
+
* `nearest` / `identify`.
|
|
169
|
+
*/
|
|
170
|
+
export function nearestAll(target, space, metric, k) {
|
|
171
|
+
const ranked = [];
|
|
172
|
+
if (metric === 'euclidean-srgb') {
|
|
173
|
+
for (const [name, candidate] of getRgbaIndex(space)) {
|
|
174
|
+
ranked.push([name, Math.sqrt(squaredDistanceRgb(target, candidate))]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else if (metric === 'euclidean-linear') {
|
|
178
|
+
const tgt = rgbaToLinearRgb(target);
|
|
179
|
+
for (const [name, candidate] of getLinearIndex(space)) {
|
|
180
|
+
ranked.push([name, Math.sqrt(squaredDistanceTriple(tgt, candidate))]);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (metric === 'deltaEok') {
|
|
184
|
+
const tgt = rgbaToOklab(target);
|
|
185
|
+
for (const [name, candidate] of getOklabIndex(space)) {
|
|
186
|
+
ranked.push([name, Math.sqrt(deltaEokSquared(tgt, candidate))]);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const tgt = rgbaToLab(target);
|
|
191
|
+
const labIndex = getLabIndex(space);
|
|
192
|
+
if (metric === 'deltaE76') {
|
|
193
|
+
for (const [name, candidate] of labIndex) {
|
|
194
|
+
ranked.push([name, Math.sqrt(deltaE76Squared(tgt, candidate))]);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else if (metric === 'deltaE94') {
|
|
198
|
+
for (const [name, candidate] of labIndex) {
|
|
199
|
+
ranked.push([name, deltaE94(tgt, candidate)]);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
for (const [name, candidate] of labIndex) {
|
|
204
|
+
ranked.push([name, deltaE2000(tgt, candidate)]);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
ranked.sort((a, b) => a[1] - b[1]);
|
|
209
|
+
const limit = k === undefined ? ranked.length : Math.min(Math.max(k, 0), ranked.length);
|
|
210
|
+
const result = new Array(limit);
|
|
211
|
+
for (let i = 0; i < limit; i++) {
|
|
212
|
+
const entry = ranked[i];
|
|
213
|
+
if (entry !== undefined)
|
|
214
|
+
result[i] = { name: entry[0], distance: entry[1] };
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
167
218
|
//# sourceMappingURL=indexing.js.map
|
package/dist/indexing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexing.js","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"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","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';\nimport { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';\nimport { squaredDistanceRgb } from './math/euclideanDistance.js';\nimport type { DistanceMetric, HexColor, NormalizeFn, Palette, Rgba } from './types.js';\n\n/**\n * Per-palette lookup indexes for name resolution and nearest-match.\n *\n * Caches are keyed by the Palette wrapper (WeakMap), so each palette's\n * indexes are built once per process and reused by every caller —\n * including BYO palettes the user supplies.\n */\n\n// Re-export normalizers from their own module for backward compat with any\n// caller that expected them here. The functions live in ./palettes/normalize\n// so palette modules can import them without pulling this file's math graph.\nexport { pantoneNormalize, standardNormalize } from './palettes/normalize.js';\nexport type { NormalizeFn };\n\ntype AnyPalette = Palette<string>;\n\nconst nameIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\nconst rgbaIndexCache = new WeakMap<AnyPalette, Array<readonly [string, Rgba]>>();\n\n/**\n * Normalized-name → canonical-key map for a given palette. Built once\n * per palette using its own `normalize` function.\n */\nexport function getNameIndex(space: AnyPalette): Map<string, string> {\n let idx = nameIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n const { normalize } = space;\n for (const key of Object.keys(space.colors)) {\n idx.set(normalize(key), key);\n }\n nameIndexCache.set(space, idx);\n }\n return idx;\n}\n\nconst reverseIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\n\n/**\n * Hex → canonical-key map (reverse of `palette.colors`). Built once per\n * palette. Used by `convert(color, { palette, format: 'NAME' })` for\n * O(1) exact reverse lookup. Returns `undefined` on miss — the caller\n * decides whether that's an error (convert throws; use `identify` for\n * nearest-match semantics).\n *\n * Hex values are lowercased for the key so callers can normalize their\n * input with `rgbaToHex(...).toLowerCase()` before lookup.\n */\nexport function getReverseNameIndex(space: AnyPalette): Map<string, string> {\n let idx = reverseIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n for (const [name, hex] of Object.entries(space.colors)) {\n idx.set((hex as string).toLowerCase(), name);\n }\n reverseIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Canonical-key → Rgba entries for a given palette, built by parsing\n * each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).\n */\nexport function getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]> {\n let idx = rgbaIndexCache.get(space);\n if (idx === undefined) {\n idx = Object.entries(space.colors).map(\n ([name, hex]) => [name, hexToRgba(hex as HexColor)] as const,\n );\n rgbaIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Find the nearest-named entry in a palette to the given Rgba, using\n * squared Euclidean distance in sRGB (alpha ignored). Used by `identify`\n * and `rgbaToPantone` when metric === 'euclidean-srgb'.\n */\nexport function nearestByRgb(target: Rgba, space: AnyPalette): string {\n const entries = getRgbaIndex(space);\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = squaredDistanceRgb(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Precomputed Lab entries for delta-E metrics. Same shape as rgba index.\ntype LabTriple = readonly [number, number, number];\nconst labIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = labIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLab(rgba)] as const);\n labIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed linear-RGB entries for euclidean-linear metric.\nconst linearIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLinearIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = linearIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLinearRgb(rgba)] as const);\n linearIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed OKLAB entries for deltaEok metric.\nconst oklabIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getOklabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = oklabIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToOklab(rgba)] as const);\n oklabIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Generic argmin over an `[name, value]` entries array with a custom\n * distance function. Factored so the three metric branches in `nearest`\n * share one loop body instead of duplicating it three times.\n */\nfunction argminBy<T>(\n entries: Array<readonly [string, T]>,\n target: T,\n dist: (a: T, b: T) => number,\n): string {\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = dist(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Squared Euclidean on a [number, number, number] triple. Used for the\n// linear-RGB metric (sharing shape with Lab but different meaning).\nfunction squaredDistanceTriple(a: LabTriple, b: LabTriple): number {\n const d0 = a[0] - b[0];\n const d1 = a[1] - b[1];\n const d2 = a[2] - b[2];\n return d0 * d0 + d1 * d1 + d2 * d2;\n}\n\n/**\n * Find the nearest-named entry in a palette under the specified distance\n * metric. Dispatches to the right index (rgba / linear-rgb / Lab) and\n * the right distance function. See `DistanceMetric` in types.ts for how\n * to pick a metric.\n *\n * `metric` is required — callers should pass `space.defaultMetric` if\n * they want the palette's declared preference. A silent default would\n * hide the distance-metric choice from the hot path.\n */\nexport function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string {\n if (metric === 'euclidean-srgb') return nearestByRgb(target, space);\n if (metric === 'euclidean-linear') {\n return argminBy(getLinearIndex(space), rgbaToLinearRgb(target), squaredDistanceTriple);\n }\n if (metric === 'deltaEok') {\n return argminBy(getOklabIndex(space), rgbaToOklab(target), deltaEokSquared);\n }\n const labTarget = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') return argminBy(labIndex, labTarget, deltaE76Squared);\n if (metric === 'deltaE94') return argminBy(labIndex, labTarget, deltaE94);\n return argminBy(labIndex, labTarget, deltaE2000);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"indexing.js","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"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","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';\nimport { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';\nimport { squaredDistanceRgb } from './math/euclideanDistance.js';\nimport type { DistanceMetric, Palette, Rgba } from './types.js';\n\n/**\n * Per-palette lookup indexes for name resolution and nearest-match.\n *\n * Caches are keyed by the Palette wrapper (WeakMap), so each palette's\n * indexes are built once per process and reused by every caller —\n * including BYO palettes the user supplies.\n */\n\ntype AnyPalette = Palette<string>;\n\nconst nameIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\nconst rgbaIndexCache = new WeakMap<AnyPalette, Array<readonly [string, Rgba]>>();\n\n/**\n * Normalized-name → canonical-key map for a given palette. Built once\n * per palette using its own `normalize` function.\n */\nexport function getNameIndex(space: AnyPalette): Map<string, string> {\n let idx = nameIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n const { normalize } = space;\n for (const key of Object.keys(space.colors)) {\n idx.set(normalize(key), key);\n }\n nameIndexCache.set(space, idx);\n }\n return idx;\n}\n\nconst reverseIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\n\n/**\n * Hex → canonical-key map (reverse of `palette.colors`). Built once per\n * palette. Used by `convert(color, { palette, format: 'NAME' })` for\n * O(1) exact reverse lookup. Returns `undefined` on miss — the caller\n * decides whether that's an error (convert throws; use `identify` for\n * nearest-match semantics).\n *\n * Hex values are lowercased for the key so callers can normalize their\n * input with `rgbaToHex(...).toLowerCase()` before lookup.\n */\nexport function getReverseNameIndex(space: AnyPalette): Map<string, string> {\n let idx = reverseIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n for (const [name, hex] of Object.entries(space.colors)) {\n idx.set((hex as string).toLowerCase(), name);\n }\n reverseIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Canonical-key → Rgba entries for a given palette, built by parsing\n * each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).\n */\nfunction getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]> {\n let idx = rgbaIndexCache.get(space);\n if (idx === undefined) {\n idx = Object.entries(space.colors).map(([name, hex]) => [name, hexToRgba(hex)] as const);\n rgbaIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Find the nearest-named entry in a palette to the given Rgba, using\n * squared Euclidean distance in sRGB (alpha ignored). Used by `identify`\n * and `rgbaToPantone` when metric === 'euclidean-srgb'.\n */\nfunction nearestByRgb(target: Rgba, space: AnyPalette): string {\n const entries = getRgbaIndex(space);\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = squaredDistanceRgb(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Precomputed Lab entries for delta-E metrics. Same shape as rgba index.\ntype LabTriple = readonly [number, number, number];\nconst labIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = labIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLab(rgba)] as const);\n labIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed linear-RGB entries for euclidean-linear metric.\nconst linearIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLinearIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = linearIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLinearRgb(rgba)] as const);\n linearIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed OKLAB entries for deltaEok metric.\nconst oklabIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getOklabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = oklabIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToOklab(rgba)] as const);\n oklabIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Generic argmin over an `[name, value]` entries array with a custom\n * distance function. Factored so the three metric branches in `nearest`\n * share one loop body instead of duplicating it three times.\n */\nfunction argminBy<T>(\n entries: Array<readonly [string, T]>,\n target: T,\n dist: (a: T, b: T) => number,\n): string {\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = dist(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Squared Euclidean on a [number, number, number] triple. Used for the\n// linear-RGB metric (sharing shape with Lab but different meaning).\nfunction squaredDistanceTriple(a: LabTriple, b: LabTriple): number {\n const d0 = a[0] - b[0];\n const d1 = a[1] - b[1];\n const d2 = a[2] - b[2];\n return d0 * d0 + d1 * d1 + d2 * d2;\n}\n\n/**\n * Find the nearest-named entry in a palette under the specified distance\n * metric. Dispatches to the right index (rgba / linear-rgb / Lab) and\n * the right distance function. See `DistanceMetric` in types.ts for how\n * to pick a metric.\n *\n * `metric` is required — callers should pass `space.defaultMetric` if\n * they want the palette's declared preference. A silent default would\n * hide the distance-metric choice from the hot path.\n */\nexport function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string {\n if (metric === 'euclidean-srgb') return nearestByRgb(target, space);\n if (metric === 'euclidean-linear') {\n return argminBy(getLinearIndex(space), rgbaToLinearRgb(target), squaredDistanceTriple);\n }\n if (metric === 'deltaEok') {\n return argminBy(getOklabIndex(space), rgbaToOklab(target), deltaEokSquared);\n }\n const labTarget = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') return argminBy(labIndex, labTarget, deltaE76Squared);\n if (metric === 'deltaE94') return argminBy(labIndex, labTarget, deltaE94);\n return argminBy(labIndex, labTarget, deltaE2000);\n}\n\n/**\n * Rank every palette entry by distance to `target`. Returns an array of\n * `{ name, distance }` sorted ascending (nearest first). Optional `k`\n * truncates to the top-k matches.\n *\n * Distances are **real** metric values (not squared) — ΔE76 / ΔE94 /\n * ΔE2000 / ΔE-OK are in standard ΔE units (≈1 = just-noticeable for\n * most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`\n * are in their respective channel-unit Euclidean distances.\n *\n * Cost: O(n log n) for the sort vs O(n) for `nearest` — acceptable for\n * top-K queries. For strict \"what's the single nearest?\" stay on\n * `nearest` / `identify`.\n */\nexport function nearestAll(\n target: Rgba,\n space: AnyPalette,\n metric: DistanceMetric,\n k?: number,\n): Array<{ name: string; distance: number }> {\n const ranked: Array<[string, number]> = [];\n\n if (metric === 'euclidean-srgb') {\n for (const [name, candidate] of getRgbaIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceRgb(target, candidate))]);\n }\n } else if (metric === 'euclidean-linear') {\n const tgt = rgbaToLinearRgb(target);\n for (const [name, candidate] of getLinearIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceTriple(tgt, candidate))]);\n }\n } else if (metric === 'deltaEok') {\n const tgt = rgbaToOklab(target);\n for (const [name, candidate] of getOklabIndex(space)) {\n ranked.push([name, Math.sqrt(deltaEokSquared(tgt, candidate))]);\n }\n } else {\n const tgt = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, Math.sqrt(deltaE76Squared(tgt, candidate))]);\n }\n } else if (metric === 'deltaE94') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE94(tgt, candidate)]);\n }\n } else {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE2000(tgt, candidate)]);\n }\n }\n }\n\n ranked.sort((a, b) => a[1] - b[1]);\n const limit = k === undefined ? ranked.length : Math.min(Math.max(k, 0), ranked.length);\n const result: Array<{ name: string; distance: number }> = new Array(limit);\n for (let i = 0; i < limit; i++) {\n const entry = ranked[i];\n if (entry !== undefined) result[i] = { name: entry[0], distance: entry[1] };\n }\n return result;\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Levenshtein edit distance between two strings. Classic dynamic-programming
|
|
3
|
+
* implementation; returns the minimum number of single-character insertions,
|
|
4
|
+
* deletions, or substitutions required to transform `a` into `b`.
|
|
5
|
+
*
|
|
6
|
+
* Used by `resolve(_, { k })` for fuzzy-name matching — operating on the
|
|
7
|
+
* palette's already-normalized keys (lowercase, alphanumeric) so typos,
|
|
8
|
+
* casing, and punctuation all reduce to the same canonical forms before
|
|
9
|
+
* the edit distance is measured.
|
|
10
|
+
*
|
|
11
|
+
* Space-optimized: uses two rolling rows instead of a full n×m matrix
|
|
12
|
+
* (O(min(n,m)) space, O(n·m) time). For palette keys (< 20 chars each)
|
|
13
|
+
* this runs sub-microsecond per pair, so a full scan over 907 Pantone
|
|
14
|
+
* entries fits well inside a millisecond.
|
|
15
|
+
*/
|
|
16
|
+
export declare function levenshtein(a: string, b: string): number;
|
|
17
|
+
//# sourceMappingURL=editDistance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editDistance.d.ts","sourceRoot":"","sources":["../../src/math/editDistance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CA8BxD","sourcesContent":["/**\n * Levenshtein edit distance between two strings. Classic dynamic-programming\n * implementation; returns the minimum number of single-character insertions,\n * deletions, or substitutions required to transform `a` into `b`.\n *\n * Used by `resolve(_, { k })` for fuzzy-name matching — operating on the\n * palette's already-normalized keys (lowercase, alphanumeric) so typos,\n * casing, and punctuation all reduce to the same canonical forms before\n * the edit distance is measured.\n *\n * Space-optimized: uses two rolling rows instead of a full n×m matrix\n * (O(min(n,m)) space, O(n·m) time). For palette keys (< 20 chars each)\n * this runs sub-microsecond per pair, so a full scan over 907 Pantone\n * entries fits well inside a millisecond.\n */\nexport function levenshtein(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n // Ensure `a` is the shorter string so the rolling row is minimal.\n if (a.length > b.length) [a, b] = [b, a];\n\n const aLen = a.length;\n const bLen = b.length;\n\n // `prev[i]` = edit distance between a[0..i] and b[0..j-1] (previous row).\n // `curr[i]` = edit distance between a[0..i] and b[0..j] (current row).\n let prev = new Array<number>(aLen + 1);\n let curr = new Array<number>(aLen + 1);\n for (let i = 0; i <= aLen; i++) prev[i] = i;\n\n for (let j = 1; j <= bLen; j++) {\n curr[0] = j;\n const bj = b.charCodeAt(j - 1);\n for (let i = 1; i <= aLen; i++) {\n const cost = a.charCodeAt(i - 1) === bj ? 0 : 1;\n const deletion = (prev[i] ?? 0) + 1;\n const insertion = (curr[i - 1] ?? 0) + 1;\n const substitution = (prev[i - 1] ?? 0) + cost;\n curr[i] = Math.min(deletion, insertion, substitution);\n }\n [prev, curr] = [curr, prev];\n }\n return prev[aLen] ?? 0;\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Levenshtein edit distance between two strings. Classic dynamic-programming
|
|
3
|
+
* implementation; returns the minimum number of single-character insertions,
|
|
4
|
+
* deletions, or substitutions required to transform `a` into `b`.
|
|
5
|
+
*
|
|
6
|
+
* Used by `resolve(_, { k })` for fuzzy-name matching — operating on the
|
|
7
|
+
* palette's already-normalized keys (lowercase, alphanumeric) so typos,
|
|
8
|
+
* casing, and punctuation all reduce to the same canonical forms before
|
|
9
|
+
* the edit distance is measured.
|
|
10
|
+
*
|
|
11
|
+
* Space-optimized: uses two rolling rows instead of a full n×m matrix
|
|
12
|
+
* (O(min(n,m)) space, O(n·m) time). For palette keys (< 20 chars each)
|
|
13
|
+
* this runs sub-microsecond per pair, so a full scan over 907 Pantone
|
|
14
|
+
* entries fits well inside a millisecond.
|
|
15
|
+
*/
|
|
16
|
+
export function levenshtein(a, b) {
|
|
17
|
+
if (a === b)
|
|
18
|
+
return 0;
|
|
19
|
+
if (a.length === 0)
|
|
20
|
+
return b.length;
|
|
21
|
+
if (b.length === 0)
|
|
22
|
+
return a.length;
|
|
23
|
+
// Ensure `a` is the shorter string so the rolling row is minimal.
|
|
24
|
+
if (a.length > b.length)
|
|
25
|
+
[a, b] = [b, a];
|
|
26
|
+
const aLen = a.length;
|
|
27
|
+
const bLen = b.length;
|
|
28
|
+
// `prev[i]` = edit distance between a[0..i] and b[0..j-1] (previous row).
|
|
29
|
+
// `curr[i]` = edit distance between a[0..i] and b[0..j] (current row).
|
|
30
|
+
let prev = new Array(aLen + 1);
|
|
31
|
+
let curr = new Array(aLen + 1);
|
|
32
|
+
for (let i = 0; i <= aLen; i++)
|
|
33
|
+
prev[i] = i;
|
|
34
|
+
for (let j = 1; j <= bLen; j++) {
|
|
35
|
+
curr[0] = j;
|
|
36
|
+
const bj = b.charCodeAt(j - 1);
|
|
37
|
+
for (let i = 1; i <= aLen; i++) {
|
|
38
|
+
const cost = a.charCodeAt(i - 1) === bj ? 0 : 1;
|
|
39
|
+
const deletion = (prev[i] ?? 0) + 1;
|
|
40
|
+
const insertion = (curr[i - 1] ?? 0) + 1;
|
|
41
|
+
const substitution = (prev[i - 1] ?? 0) + cost;
|
|
42
|
+
curr[i] = Math.min(deletion, insertion, substitution);
|
|
43
|
+
}
|
|
44
|
+
[prev, curr] = [curr, prev];
|
|
45
|
+
}
|
|
46
|
+
return prev[aLen] ?? 0;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=editDistance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editDistance.js","sourceRoot":"","sources":["../../src/math/editDistance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,WAAW,CAAC,CAAS,EAAE,CAAS;IAC9C,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IACpC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IAEpC,kEAAkE;IAClE,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM;QAAE,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEzC,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC;IACtB,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC;IAEtB,0EAA0E;IAC1E,yEAAyE;IACzE,IAAI,IAAI,GAAG,IAAI,KAAK,CAAS,IAAI,GAAG,CAAC,CAAC,CAAC;IACvC,IAAI,IAAI,GAAG,IAAI,KAAK,CAAS,IAAI,GAAG,CAAC,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAE5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACZ,MAAM,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAChD,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YACpC,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YACzC,MAAM,YAAY,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;YAC/C,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;QACxD,CAAC;QACD,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC","sourcesContent":["/**\n * Levenshtein edit distance between two strings. Classic dynamic-programming\n * implementation; returns the minimum number of single-character insertions,\n * deletions, or substitutions required to transform `a` into `b`.\n *\n * Used by `resolve(_, { k })` for fuzzy-name matching — operating on the\n * palette's already-normalized keys (lowercase, alphanumeric) so typos,\n * casing, and punctuation all reduce to the same canonical forms before\n * the edit distance is measured.\n *\n * Space-optimized: uses two rolling rows instead of a full n×m matrix\n * (O(min(n,m)) space, O(n·m) time). For palette keys (< 20 chars each)\n * this runs sub-microsecond per pair, so a full scan over 907 Pantone\n * entries fits well inside a millisecond.\n */\nexport function levenshtein(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n // Ensure `a` is the shorter string so the rolling row is minimal.\n if (a.length > b.length) [a, b] = [b, a];\n\n const aLen = a.length;\n const bLen = b.length;\n\n // `prev[i]` = edit distance between a[0..i] and b[0..j-1] (previous row).\n // `curr[i]` = edit distance between a[0..i] and b[0..j] (current row).\n let prev = new Array<number>(aLen + 1);\n let curr = new Array<number>(aLen + 1);\n for (let i = 0; i <= aLen; i++) prev[i] = i;\n\n for (let j = 1; j <= bLen; j++) {\n curr[0] = j;\n const bj = b.charCodeAt(j - 1);\n for (let i = 1; i <= aLen; i++) {\n const cost = a.charCodeAt(i - 1) === bj ? 0 : 1;\n const deletion = (prev[i] ?? 0) + 1;\n const insertion = (curr[i - 1] ?? 0) + 1;\n const substitution = (prev[i - 1] ?? 0) + cost;\n curr[i] = Math.min(deletion, insertion, substitution);\n }\n [prev, curr] = [curr, prev];\n }\n return prev[aLen] ?? 0;\n}\n"]}
|