chromonym 1.4.0 → 2.0.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 -4
- package/README.md +240 -91
- package/dist/conversions/hex.d.ts +1 -1
- package/dist/conversions/hex.d.ts.map +1 -1
- package/dist/conversions/hex.js +2 -2
- package/dist/conversions/hex.js.map +1 -1
- package/dist/conversions/hsl.d.ts +1 -1
- package/dist/conversions/hsl.d.ts.map +1 -1
- package/dist/conversions/hsl.js +2 -2
- package/dist/conversions/hsl.js.map +1 -1
- package/dist/conversions/hsv.d.ts +1 -1
- package/dist/conversions/hsv.d.ts.map +1 -1
- package/dist/conversions/hsv.js +2 -2
- package/dist/conversions/hsv.js.map +1 -1
- package/dist/conversions/index.d.ts +5 -5
- package/dist/conversions/index.d.ts.map +1 -1
- package/dist/conversions/index.js +5 -5
- package/dist/conversions/index.js.map +1 -1
- package/dist/conversions/pantone.d.ts +9 -7
- package/dist/conversions/pantone.d.ts.map +1 -1
- package/dist/conversions/pantone.js +11 -10
- package/dist/conversions/pantone.js.map +1 -1
- package/dist/conversions/rgb.d.ts +1 -1
- package/dist/conversions/rgb.d.ts.map +1 -1
- package/dist/conversions/rgb.js +1 -1
- package/dist/conversions/rgb.js.map +1 -1
- package/dist/convert.d.ts +45 -5
- package/dist/convert.d.ts.map +1 -1
- package/dist/convert.js +68 -17
- package/dist/convert.js.map +1 -1
- package/dist/detectFormat.d.ts +1 -1
- package/dist/detectFormat.d.ts.map +1 -1
- package/dist/detectFormat.js +2 -6
- package/dist/detectFormat.js.map +1 -1
- package/dist/identify.d.ts +13 -16
- package/dist/identify.d.ts.map +1 -1
- package/dist/identify.js +12 -12
- package/dist/identify.js.map +1 -1
- package/dist/index.d.ts +14 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -13
- package/dist/index.js.map +1 -1
- package/dist/indexing.d.ts +30 -24
- package/dist/indexing.d.ts.map +1 -1
- package/dist/indexing.js +49 -22
- package/dist/indexing.js.map +1 -1
- package/dist/math/clamp.d.ts.map +1 -1
- package/dist/math/clamp.js.map +1 -1
- package/dist/math/colorSpace.d.ts +1 -1
- package/dist/math/colorSpace.d.ts.map +1 -1
- package/dist/math/colorSpace.js.map +1 -1
- package/dist/math/deltaE.d.ts.map +1 -1
- package/dist/math/deltaE.js.map +1 -1
- package/dist/math/euclideanDistance.d.ts +1 -1
- package/dist/math/euclideanDistance.d.ts.map +1 -1
- package/dist/math/euclideanDistance.js.map +1 -1
- package/dist/math/hueSector.d.ts.map +1 -1
- package/dist/math/hueSector.js.map +1 -1
- package/dist/palettes/index.d.ts +4 -0
- package/dist/palettes/index.d.ts.map +1 -0
- package/dist/palettes/index.js +4 -0
- package/dist/palettes/index.js.map +1 -0
- package/dist/palettes/normalize.d.ts +17 -0
- package/dist/palettes/normalize.d.ts.map +1 -0
- package/dist/palettes/normalize.js +19 -0
- package/dist/palettes/normalize.js.map +1 -0
- package/dist/palettes/pantone.d.ts +1834 -0
- package/dist/palettes/pantone.d.ts.map +1 -0
- package/dist/palettes/pantone.js +925 -0
- package/dist/palettes/pantone.js.map +1 -0
- package/dist/palettes/web.d.ts +320 -0
- package/dist/palettes/web.d.ts.map +1 -0
- package/dist/{colorspaces → palettes}/web.js +18 -3
- package/dist/palettes/web.js.map +1 -0
- package/dist/palettes/x11.d.ts +1346 -0
- package/dist/palettes/x11.d.ts.map +1 -0
- package/dist/palettes/x11.js +686 -0
- package/dist/palettes/x11.js.map +1 -0
- package/dist/resolve.d.ts +9 -7
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +18 -17
- package/dist/resolve.js.map +1 -1
- package/dist/types.d.ts +10 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -1
- package/dist/types.js.map +1 -1
- package/package.json +32 -18
- package/dist/colorspaces/index.d.ts +0 -4
- package/dist/colorspaces/index.d.ts.map +0 -1
- package/dist/colorspaces/index.js +0 -4
- package/dist/colorspaces/index.js.map +0 -1
- package/dist/colorspaces/pantone.d.ts +0 -919
- package/dist/colorspaces/pantone.d.ts.map +0 -1
- package/dist/colorspaces/pantone.js +0 -918
- package/dist/colorspaces/pantone.js.map +0 -1
- package/dist/colorspaces/registry.d.ts +0 -26
- package/dist/colorspaces/registry.d.ts.map +0 -1
- package/dist/colorspaces/registry.js +0 -36
- package/dist/colorspaces/registry.js.map +0 -1
- package/dist/colorspaces/web.d.ts +0 -156
- package/dist/colorspaces/web.d.ts.map +0 -1
- package/dist/colorspaces/web.js.map +0 -1
- package/dist/colorspaces/x11.d.ts +0 -667
- package/dist/colorspaces/x11.d.ts.map +0 -1
- package/dist/colorspaces/x11.js +0 -666
- package/dist/colorspaces/x11.js.map +0 -1
package/NOTICE.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
**not affiliated with, endorsed by, licensed by, or certified by Pantone
|
|
7
7
|
LLC**.
|
|
8
8
|
|
|
9
|
-
The `pantone`
|
|
9
|
+
The `pantone` palette in this library ships **community-derived sRGB
|
|
10
10
|
approximations** of the Pantone Coated (C) color set, sourced from
|
|
11
11
|
[`color_library`](https://github.com/draganradu/color_library) (MIT © Radu
|
|
12
12
|
Dragan). These values:
|
|
@@ -16,21 +16,49 @@ Dragan). These values:
|
|
|
16
16
|
exactly.
|
|
17
17
|
- Are intended for on-screen reference, nearest-name identification, and
|
|
18
18
|
general tooling — **not for print color specification**.
|
|
19
|
-
- Use standard color codes (e.g. `
|
|
19
|
+
- Use standard color codes (e.g. `185 C`) as **nominative references** to
|
|
20
20
|
publicly identified Pantone colors. No endorsement or claim of accuracy
|
|
21
21
|
is implied.
|
|
22
22
|
|
|
23
|
+
### Accuracy envelope
|
|
24
|
+
|
|
25
|
+
The approximations are closest for mid-chroma colors under a D65 / 2°
|
|
26
|
+
observer. As a rough guide:
|
|
27
|
+
|
|
28
|
+
- **Mid-chroma Pantones**: ΔE*00 ≲ 5 versus Pantone's own sRGB previews.
|
|
29
|
+
Usable for nearest-name identification and comparative swatch UI.
|
|
30
|
+
- **Saturated edges**: ΔE*00 commonly 5–10, higher in the deep-blue and
|
|
31
|
+
blue-violet region (~2736 C through ~2746 C). `identify` will still
|
|
32
|
+
converge on a plausible neighbor, but the *which* can depend on metric
|
|
33
|
+
choice — see the "Distance metrics" section of the README.
|
|
34
|
+
- **Fluorescents (801 C – 814 C)** and the **metallic series (8xxx C)**
|
|
35
|
+
are defined outside the sRGB gamut. Their sRGB approximations are
|
|
36
|
+
perceptually wrong by construction — the physical inks can't be
|
|
37
|
+
reproduced on a standard monitor. Do not use these values as a
|
|
38
|
+
reference for brand-critical matching; identify carefully or skip.
|
|
39
|
+
- **Neons / warm reds** clipping against the sRGB gamut boundary (Pantone
|
|
40
|
+
Red 032 / Warm Red / Red 021 area) are approximated via hard clip.
|
|
41
|
+
|
|
42
|
+
### What `identify` does *not* guarantee
|
|
43
|
+
|
|
44
|
+
`identify(sampledRgba, { palette: pantone })` returns the nearest
|
|
45
|
+
entry **in the shipped sRGB approximation set**. It does **not** imply
|
|
46
|
+
that the physical ink bearing that Pantone code would match your sampled
|
|
47
|
+
color in a print workflow, under a different light source, or on
|
|
48
|
+
different paper stock. Pantone is a spectral specification; any
|
|
49
|
+
color-library lookup that rounds it through sRGB loses information.
|
|
50
|
+
|
|
23
51
|
If you need Pantone-certified color values, consult Pantone directly:
|
|
24
52
|
<https://www.pantone.com/>.
|
|
25
53
|
|
|
26
54
|
## X11 / X.Org
|
|
27
55
|
|
|
28
|
-
The `x11`
|
|
56
|
+
The `x11` palette is derived from the X.Org `rgb.txt` file, which is in
|
|
29
57
|
the public domain.
|
|
30
58
|
|
|
31
59
|
## CSS / SVG named colors
|
|
32
60
|
|
|
33
|
-
The `web`
|
|
61
|
+
The `web` palette mirrors the CSS Color Module Level 4 named-color
|
|
34
62
|
set, which is a public specification.
|
|
35
63
|
|
|
36
64
|
## Upstream dependencies
|
package/README.md
CHANGED
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
# chromonym
|
|
12
12
|
|
|
13
13
|
[](https://www.npmjs.com/package/chromonym)
|
|
14
|
-
[](https://packagephobia.com/result?p=chromonym)
|
|
15
|
-
[](https://packagephobia.com/result?p=chromonym)
|
|
16
14
|
[](https://www.npmjs.com/package/chromonym)
|
|
17
15
|
[](https://github.com/simiancraft/chromonym/actions/workflows/ci.yml)
|
|
18
|
-
[](https://codecov.io/gh/simiancraft/chromonym)
|
|
19
|
-
[](https://opensource.org/licenses/MIT)
|
|
20
|
-
[](https://github.com/semantic-release/semantic-release)
|
|
21
|
-
[](https://biomejs.dev)
|
|
22
16
|
|
|
23
|
-
|
|
17
|
+
<sub>[bundle size](https://packagephobia.com/result?p=chromonym) • [coverage](https://codecov.io/github/simiancraft/chromonym) • [MIT license](https://opensource.org/licenses/MIT)</sub>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<code>identify</code> • <code>resolve</code> • <code>convert</code>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
**The last color-naming library you'll ever need.** One API — `identify`, `resolve`, `convert` — works against any palette you throw at it. Ships CSS, X11, and Pantone out of the box; bring your own (brand colors, paint chips, game factions, chart themes) with full TypeScript inference and zero registration. Perceptual accuracy via six distance metrics. Tree-shakes to the bone — you only pay for palettes you import.
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
26
|
<a href="https://simiancraft.github.io/chromonym/">
|
|
@@ -28,29 +28,51 @@
|
|
|
28
28
|
</a>
|
|
29
29
|
</p>
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
```ts
|
|
34
|
-
import { identify, resolve, convert } from 'chromonym';
|
|
35
|
-
|
|
36
|
-
// What Pantone is this brand color closest to? (perceptually, via CIEDE2000)
|
|
37
|
-
identify('#E20074', { colorspace: 'pantone' }) // '213C'
|
|
31
|
+
Because "it's sort of magenta-ish, maybe?" doesn't copy-paste into code — and naming a color is the same problem whether your palette is CSS, Pantone, your company's brand kit, or a homebrew set of faction colors. Chromonym treats every palette as a first-class `Palette<Name>` object and runs the same nearest-neighbor machinery over all of them.
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
resolve('Pantone 185 C', { colorspace: 'pantone' })// '#e4002b'
|
|
33
|
+
For color *manipulation* (mixing, scales, gamut mapping), reach for [`chroma-js`](https://gka.github.io/chroma.js/) or [`color.js`](https://colorjs.io/) — chromonym is the tool for *naming*.
|
|
41
34
|
|
|
42
|
-
|
|
35
|
+
```ts
|
|
36
|
+
import { identify, resolve, convert, pantone, type Palette } from 'chromonym';
|
|
37
|
+
|
|
38
|
+
// Your palette — defined inline, type-checked, no registration. Works the
|
|
39
|
+
// same as the built-in ones because that's the whole point.
|
|
40
|
+
const brand = {
|
|
41
|
+
name: 'acme-brand',
|
|
42
|
+
colors: { 'acme red': '#ff2a3b', 'acme ink': '#0a0f2c', 'acme mist': '#e6ecf5' },
|
|
43
|
+
normalize: (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, ''),
|
|
44
|
+
defaultMetric: 'deltaEok',
|
|
45
|
+
} as const satisfies Palette;
|
|
46
|
+
|
|
47
|
+
identify('#ff2b3c', { palette: brand }) // 'acme red'
|
|
48
|
+
resolve('Acme Ink', { palette: brand }) // '#0a0f2c'
|
|
49
|
+
|
|
50
|
+
// Built-in palettes follow the identical shape — CSS, X11, Pantone out of the box.
|
|
51
|
+
identify('#ff8080') // 'lightcoral' (web is the default)
|
|
52
|
+
identify('#663399') // 'rebeccapurple'
|
|
53
|
+
identify('#E20074', { palette: pantone }) // '213 C' — T-Mobile magenta
|
|
54
|
+
resolve('Pantone 185 C', { palette: pantone }) // '#e4002b'
|
|
55
|
+
|
|
56
|
+
// Format conversion: HEX / RGB / RGBA / HSL / HSV (palette-free, tree-shakes cleanly)
|
|
43
57
|
convert('#ff0000', { format: 'HSL' }) // 'hsl(0, 100%, 50%)'
|
|
44
58
|
convert({ h: 120, s: 100, l: 50 }, { format: 'HEX' }) // '#00ff00'
|
|
45
59
|
|
|
46
|
-
//
|
|
47
|
-
|
|
60
|
+
// Same convert API understands palette names both ways when you pass one:
|
|
61
|
+
// name in → any structural format out; color in → exact palette key out.
|
|
62
|
+
// `'NAME'` is an exact reverse lookup (throws on miss; use `identify` for fuzzy).
|
|
63
|
+
convert('185 C', { palette: pantone, format: 'HSL' }) // 'hsl(349, 100%, 45%)'
|
|
64
|
+
convert('#e4002b', { palette: pantone, format: 'NAME' }) // '185 C' (exact match)
|
|
65
|
+
convert('acme red', { palette: brand, format: 'RGB' }) // 'rgb(255, 42, 59)'
|
|
48
66
|
```
|
|
49
67
|
|
|
68
|
+
The identification machinery is palette-agnostic — RAL, HKS, NCS, Munsell, or a bespoke in-house set are the same amount of work (a small file each, or a single object literal). PRs welcome; meanwhile, BYO lets you ship today.
|
|
69
|
+
|
|
50
70
|
## Install
|
|
51
71
|
|
|
52
72
|
```sh
|
|
53
73
|
bun add chromonym
|
|
74
|
+
pnpm add chromonym
|
|
75
|
+
yarn add chromonym
|
|
54
76
|
npm install chromonym
|
|
55
77
|
```
|
|
56
78
|
|
|
@@ -60,52 +82,58 @@ Three primary functions. All accept an optional `opts` object; defaults fire whe
|
|
|
60
82
|
|
|
61
83
|
### `identify(input, opts?)`
|
|
62
84
|
|
|
63
|
-
**Color → name.** Finds the nearest-match name in the chosen
|
|
85
|
+
**Color → name.** Finds the nearest-match name in the chosen palette using the selected perceptual distance metric.
|
|
64
86
|
|
|
65
87
|
```ts
|
|
66
|
-
function identify(
|
|
88
|
+
function identify<C extends Palette = typeof web>(
|
|
67
89
|
input: ColorInput,
|
|
68
|
-
opts?: {
|
|
69
|
-
): string | null
|
|
90
|
+
opts?: { palette?: C; metric?: DistanceMetric },
|
|
91
|
+
): Extract<keyof C['colors'], string> | null
|
|
70
92
|
```
|
|
71
93
|
|
|
72
|
-
- **Default
|
|
73
|
-
- **Default metric**:
|
|
94
|
+
- **Default palette**: the built-in `web` palette (148 entries, small — included if you call `identify` without a palette).
|
|
95
|
+
- **Default metric**: read from the palette's own `defaultMetric` — `'deltaE76'` for web and x11 (well-separated palettes), `'deltaE2000'` for pantone (dense, perceptually-packed). Override freely via `metric`.
|
|
74
96
|
- Returns the matched name, or `null` if input is unrecognized (`detectFormat` returns `'UNKNOWN'`).
|
|
75
|
-
-
|
|
97
|
+
- Return type is inferred from the palette's `colors` keys — so passing `{ palette: pantone }` narrows to `PantoneColorName | null`, and a BYO palette with a literal key union narrows to that union.
|
|
98
|
+
- Ties go to whichever color was defined first — deterministic across runs, but not semantically meaningful.
|
|
76
99
|
|
|
77
100
|
```ts
|
|
78
|
-
identify
|
|
79
|
-
|
|
80
|
-
identify(
|
|
81
|
-
identify(
|
|
82
|
-
identify(
|
|
83
|
-
identify('#
|
|
84
|
-
identify('#
|
|
101
|
+
import { identify, pantone, x11 } from 'chromonym';
|
|
102
|
+
|
|
103
|
+
identify('#ff0000') // 'red' (web default)
|
|
104
|
+
identify([255, 0, 0]) // 'red'
|
|
105
|
+
identify({ r: 250, g: 20, b: 60 }) // 'crimson' (nearest)
|
|
106
|
+
identify('#ff0000', { palette: x11 }) // 'red' (x11 also has 'red 1' through 'red 4')
|
|
107
|
+
identify('#e4002b', { palette: pantone }) // '185 C' (exact match — Target/Coca-Cola red)
|
|
108
|
+
identify('#ff0000', { palette: pantone }) // '172 C' (nearest — pure red isn't a Pantone)
|
|
109
|
+
identify('#ff0080', { metric: 'deltaE2000' }) // force perceptual-accurate match
|
|
110
|
+
identify('#ff0000', { metric: 'euclidean-srgb' }) // force fastest (non-perceptual) match
|
|
85
111
|
```
|
|
86
112
|
|
|
87
113
|
### `resolve(name, opts?)`
|
|
88
114
|
|
|
89
|
-
**Name → color.** Normalizes the input (lowercases it, strips all non-alphanumeric characters), looks up the canonical key in the chosen
|
|
115
|
+
**Name → color.** Normalizes the input (lowercases it, strips all non-alphanumeric characters), looks up the canonical key in the chosen palette, and returns the value in the chosen format.
|
|
90
116
|
|
|
91
117
|
```ts
|
|
92
118
|
function resolve(
|
|
93
119
|
name: string,
|
|
94
|
-
opts?: {
|
|
120
|
+
opts?: { palette?: Palette; format?: ColorFormat },
|
|
95
121
|
): ColorValue | null
|
|
96
122
|
```
|
|
97
123
|
|
|
98
|
-
- **Default
|
|
124
|
+
- **Default palette**: the built-in `web` palette.
|
|
99
125
|
- **Default format**: `'HEX'`.
|
|
100
|
-
- Returns `null` if the normalized name isn't in the
|
|
101
|
-
- Normalization
|
|
126
|
+
- Returns `null` if the normalized name isn't in the palette.
|
|
127
|
+
- Normalization is the palette's own `normalize` function — web/x11 lowercase + strip non-alphanumeric, pantone also strips a `Pantone` / `PMS` prefix. So `'Alice Blue'`, `'alice-blue'`, `'ALICEBLUE'`, `'Pantone 185 C'` all resolve identically in their respective palettes.
|
|
102
128
|
|
|
103
129
|
```ts
|
|
130
|
+
import { resolve, pantone } from 'chromonym';
|
|
131
|
+
|
|
104
132
|
resolve('crimson') // '#dc143c'
|
|
105
133
|
resolve('Alice Blue') // '#f0f8ff'
|
|
106
134
|
resolve('alice-blue!') // '#f0f8ff'
|
|
107
|
-
resolve('185 C', {
|
|
108
|
-
resolve('Pantone 185 C', {
|
|
135
|
+
resolve('185 C', { palette: pantone }) // '#e4002b'
|
|
136
|
+
resolve('Pantone 185 C', { palette: pantone }) // '#e4002b' (same, normalized)
|
|
109
137
|
resolve('crimson', { format: 'RGB' }) // 'rgb(220, 20, 60)'
|
|
110
138
|
resolve('crimson', { format: 'RGBA' }) // { r: 220, g: 20, b: 60, a: 1 }
|
|
111
139
|
resolve('not-a-color') // null
|
|
@@ -113,77 +141,198 @@ resolve('not-a-color') // null
|
|
|
113
141
|
|
|
114
142
|
### `convert(input, opts?)`
|
|
115
143
|
|
|
116
|
-
**Color → color in a different format.** Detects the input format, normalizes to the canonical internal `Rgba
|
|
144
|
+
**Color → color in a different format.** Detects the input format, normalizes to the canonical internal `Rgba`, and returns the value in the target format.
|
|
145
|
+
|
|
146
|
+
Structural on its own (HEX ↔ RGB ↔ RGBA ↔ HSL ↔ HSV, no palette data bundled). Pass an optional `palette` and the same one API understands palette names both ways — input as a name, or `format: 'NAME'` output for exact reverse lookup.
|
|
117
147
|
|
|
118
148
|
```ts
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
)
|
|
149
|
+
// Structural (palette-free, tree-shakes to ~5 KB min)
|
|
150
|
+
function convert(input: ColorInput, opts?: { format?: ColorFormat }): ColorValue;
|
|
151
|
+
|
|
152
|
+
// With a palette — input can be a palette name, output can be 'NAME' (exact key)
|
|
153
|
+
function convert<P extends Palette>(
|
|
154
|
+
input: ColorInput | string,
|
|
155
|
+
opts: { palette: P; format: 'NAME' },
|
|
156
|
+
): PaletteKey<P>;
|
|
157
|
+
function convert<P extends Palette>(
|
|
158
|
+
input: ColorInput | string,
|
|
159
|
+
opts: { palette: P; format?: ColorFormat },
|
|
160
|
+
): ColorValue;
|
|
123
161
|
```
|
|
124
162
|
|
|
125
163
|
- **Default format**: `'HEX'`.
|
|
126
|
-
- Throws
|
|
127
|
-
- Alpha
|
|
164
|
+
- Throws on unrecognized input (parser-flavored — `identify` / `resolve` return `null` instead).
|
|
165
|
+
- Alpha is preserved only in `'RGBA'` output (canonical `{ r, g, b, a }` object); dropped by `'HEX'` (6-digit), `'RGB'`, `'HSL'`, `'HSV'`, and `'NAME'`.
|
|
166
|
+
- **Palette is caller-supplied**: no palette is pulled unless you import one and pass it. Tree-shake stays intact — `convert`-only bundles include zero palette data.
|
|
167
|
+
- `format: 'NAME'` is **exact-match only**; it throws if the input isn't a pixel-exact palette entry. Use `identify` for nearest-match.
|
|
168
|
+
- Priority: if the input is structurally valid (hex, rgb, hsl, …), that wins over palette-name lookup. So `convert('#ff0000', { palette: brand })` always means "parse as hex."
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
// Structural — no palette imported, nothing bundled
|
|
172
|
+
convert('#ff0000') // '#ff0000' (identity)
|
|
173
|
+
convert('#ff0000', { format: 'RGB' }) // 'rgb(255, 0, 0)'
|
|
174
|
+
convert([255, 0, 0], { format: 'HEX' }) // '#ff0000'
|
|
175
|
+
convert({ h: 0, s: 100, l: 50 }, { format: 'HEX' }) // '#ff0000'
|
|
176
|
+
|
|
177
|
+
// With a palette — palette-name in, any format out
|
|
178
|
+
import { pantone } from 'chromonym';
|
|
179
|
+
convert('185 C', { palette: pantone }) // '#e4002b'
|
|
180
|
+
convert('Pantone 185 C', { palette: pantone }) // '#e4002b' (normalizer strips prefix)
|
|
181
|
+
convert('185 C', { palette: pantone, format: 'HSL' }) // 'hsl(349, 100%, 45%)'
|
|
182
|
+
|
|
183
|
+
// With a palette — color in, exact palette key out
|
|
184
|
+
convert('#e4002b', { palette: pantone, format: 'NAME' }) // '185 C'
|
|
185
|
+
convert('#663399', { palette: web, format: 'NAME' }) // 'rebeccapurple'
|
|
186
|
+
convert('#ff0000', { palette: pantone, format: 'NAME' }) // throws — use identify for nearest
|
|
187
|
+
|
|
188
|
+
// BYO palettes work the same way
|
|
189
|
+
convert('acme red', { palette: brand, format: 'RGB' }) // 'rgb(255, 42, 59)'
|
|
190
|
+
convert('#ff2a3b', { palette: brand, format: 'NAME' }) // 'acme red'
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The low-level `pantoneToRgba` / `rgbaToPantone` functions (from `chromonym` or `chromonym/conversions/pantone`) remain available for single-purpose Pantone work when you don't need the full `convert` ceremony.
|
|
194
|
+
|
|
195
|
+
## Palettes
|
|
196
|
+
|
|
197
|
+
Every palette — yours or ours — is a `Palette<Name>` object. Define it once, pass it anywhere the three verbs accept one.
|
|
128
198
|
|
|
129
199
|
```ts
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
convert('185 C', { format: 'HEX' }) // '#e4002b'
|
|
137
|
-
convert('#e4002b', { format: 'PANTONE' }) // '185C' (nearest Pantone C)
|
|
200
|
+
type Palette<Name extends string = string> = {
|
|
201
|
+
readonly name: string; // human-readable label
|
|
202
|
+
readonly colors: Record<Name, HexColor>; // the lookup data
|
|
203
|
+
readonly normalize: (s: string) => string; // user-input → canonical key
|
|
204
|
+
readonly defaultMetric: DistanceMetric; // used by identify when no metric override
|
|
205
|
+
};
|
|
138
206
|
```
|
|
139
207
|
|
|
140
|
-
|
|
208
|
+
### Bring your own
|
|
209
|
+
|
|
210
|
+
The primary shape. Any object matching `Palette<Name>` works — no registry, no plugin, no side effects. Bundlers only include the palettes you actually import.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { identify, resolve, type Palette } from 'chromonym';
|
|
214
|
+
|
|
215
|
+
const warhammer = {
|
|
216
|
+
name: 'warhammer40k',
|
|
217
|
+
colors: {
|
|
218
|
+
'world eaters red': '#8b1a1a',
|
|
219
|
+
'adeptus red': '#652022',
|
|
220
|
+
'sons of malice white': '#e8e4d8',
|
|
221
|
+
'the flawless host purple': '#6b2d7d',
|
|
222
|
+
'nurgle green': '#748c3f',
|
|
223
|
+
'alpha legion teal': '#2a6d7a',
|
|
224
|
+
},
|
|
225
|
+
normalize: (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, ''),
|
|
226
|
+
defaultMetric: 'deltaE2000',
|
|
227
|
+
} as const satisfies Palette;
|
|
228
|
+
|
|
229
|
+
identify('#750c0c', { palette: warhammer })
|
|
230
|
+
// → 'world eaters red' (return type narrows to the palette's key union)
|
|
231
|
+
|
|
232
|
+
resolve('Nurgle Green', { palette: warhammer })
|
|
233
|
+
// → '#748c3f' (your normalizer handles case + punctuation)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### The three we ship
|
|
141
237
|
|
|
142
238
|
| Name | Entries | Source |
|
|
143
239
|
|---|---|---|
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
240
|
+
| `web` (default) | 148 | CSS Color Module Level 4 named colors |
|
|
241
|
+
| `x11` | 658 | X.Org `rgb.txt` (public domain) |
|
|
242
|
+
| `pantone` | 907 | Pantone Coated (C) — community approximations (not Pantone-licensed) |
|
|
147
243
|
|
|
148
|
-
|
|
244
|
+
Importable directly, or via subpath exports for stricter tree-shaking:
|
|
149
245
|
|
|
150
246
|
```ts
|
|
151
247
|
import { web, x11, pantone } from 'chromonym';
|
|
152
|
-
|
|
248
|
+
// or: import { pantone } from 'chromonym/pantone';
|
|
249
|
+
|
|
250
|
+
web.colors.crimson // '#dc143c'
|
|
251
|
+
web.colors.aliceblue // '#f0f8ff' (CSS keyword form — paste-compatible)
|
|
252
|
+
pantone.colors['185 C'] // '#e4002b'
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Each palette's keys follow its own domain convention — so `identify`'s output pastes back into whatever ecosystem the name came from. `web` uses the CSS spec's single-word form (`rebeccapurple`), `x11` uses the X.Org docs' spaced form (`'antique white 1'`), `pantone` uses the official print notation (`'185 C'`).
|
|
256
|
+
|
|
257
|
+
*Trivia:* `web.colors.rebeccapurple` (`#663399`) entered CSS Color 4 in 2014 in memory of [Rebecca Meyer](https://meyerweb.com/eric/thoughts/2014/06/19/rebeccapurple/). The X11 set ships every gray name twice (`gray`, `grey`) plus numbered variants up to 99, so `x11.colors['gray 73']` is a real key (`#bababa`). Yes, these are Greys. No, not the kind that visit during sleep paralysis.
|
|
258
|
+
|
|
259
|
+
### Cross-palette translation
|
|
260
|
+
|
|
261
|
+
"What's this CSS color in X11?" / "What's our brand red in Pantone?" is a *nearest-match* question — palettes almost never share exact hex values, so there's no strict round-trip. That's `identify`'s job. Chain `convert` (strict name → hex) with `identify` (fuzzy hex → nearest name):
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { convert, identify, web, x11, pantone } from 'chromonym';
|
|
265
|
+
|
|
266
|
+
// web → x11
|
|
267
|
+
identify(convert('rebeccapurple', { palette: web }), { palette: x11 })
|
|
268
|
+
// → 'dark orchid 4'
|
|
269
|
+
|
|
270
|
+
identify(convert('dodgerblue', { palette: web }), { palette: x11 })
|
|
271
|
+
// → 'dodger blue' (hex shared across both palettes — exact hit)
|
|
272
|
+
|
|
273
|
+
// x11 → pantone
|
|
274
|
+
identify(convert('dark slate gray 4', { palette: x11 }), { palette: pantone })
|
|
275
|
+
// → '5483 C'
|
|
276
|
+
|
|
277
|
+
identify(convert('debian red', { palette: x11 }), { palette: pantone })
|
|
278
|
+
// → '1925 C'
|
|
279
|
+
|
|
280
|
+
// web → pantone (the classic brand-matching case)
|
|
281
|
+
identify(convert('hotpink', { palette: web }), { palette: pantone }) // → '812 C'
|
|
282
|
+
identify(convert('dodgerblue', { palette: web }), { palette: pantone }) // → '279 C'
|
|
283
|
+
|
|
284
|
+
// BYO → pantone (ship your brand palette, find the closest Pantone for print)
|
|
285
|
+
identify(convert('acme red', { palette: brand }), { palette: pantone }) // → '1788 C'
|
|
286
|
+
|
|
287
|
+
// Override the metric for the *nearest-match* leg if you want — input parsing
|
|
288
|
+
// is always exact, so only the second call takes `metric`.
|
|
289
|
+
identify(convert('rebeccapurple', { palette: web }), { palette: pantone, metric: 'deltaEok' })
|
|
290
|
+
// → '526 C' (OKLAB disagrees with ΔE2000's '267 C' in the saturated-purple region)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Why two calls instead of one: `convert` is the strict verb (throws on a name miss), `identify` is the fuzzy one (always returns a nearest). Keeping them separate lets you decide — per call — whether you want a hard error on an unknown input or a best-effort neighbor.
|
|
294
|
+
|
|
295
|
+
If this chain shows up often in your code, wrap it in your own helper:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
const translate = (name: string, from: Palette, to: Palette) =>
|
|
299
|
+
identify(convert(name, { palette: from }), { palette: to });
|
|
153
300
|
```
|
|
154
301
|
|
|
155
302
|
## Distance metrics
|
|
156
303
|
|
|
157
|
-
|
|
304
|
+
*Six ways to ask "how different are two colors?"*
|
|
305
|
+
|
|
306
|
+
`identify` picks the nearest-named color by comparing your input to every entry in the chosen palette. *How* it measures "nearest" is controlled by `metric`. Six options, roughly ordered from fast-and-approximate to slow-and-correct:
|
|
158
307
|
|
|
159
308
|
| Metric | What it does | When to use |
|
|
160
309
|
|---|---|---|
|
|
161
310
|
| `'euclidean-srgb'` | Raw `sqrt(Δr² + Δg² + Δb²)` in sRGB. Fastest. Not perceptually uniform — a unit of distance doesn't mean a unit of "visual difference." | Tight perf budgets; well-separated palettes where you just need the obvious answer. |
|
|
162
311
|
| `'euclidean-linear'` | Same math, but on linearized (gamma-removed) RGB. Somewhat better in dark regions. Still not perceptual. | Physical-light mixing contexts; incremental upgrade from sRGB Euclidean. |
|
|
163
|
-
| `'deltaE76'` | Euclidean in CIELAB (CIE 1976). Simple perceptual metric. 1 ΔE ≈ "just noticeable difference" for most of the gamut. | **Default for web and x11.**
|
|
312
|
+
| `'deltaE76'` | Euclidean in CIELAB (CIE 1976). Simple perceptual metric. 1 ΔE ≈ "just noticeable difference" for most of the gamut. | **Default for web and x11.** Meaningful perceptual accuracy at low cost. |
|
|
164
313
|
| `'deltaE94'` | ΔE76 + chroma/hue weighting (CIE 1994). Fixes "saturated colors feel too far apart." | When ΔE76 is over-penalizing saturated matches in your use case. |
|
|
165
314
|
| `'deltaE2000'` | Full CIEDE2000 formula with blue/purple rotation correction. Industry standard for print, design tools, Pantone workflows. | **Default for pantone.** Use whenever accuracy matters, especially in the blue region where ΔE76/94 break down. |
|
|
166
|
-
| `'deltaEok'` | Euclidean distance in OKLAB (Björn Ottosson, 2020). OKLAB is perceptually uniform by construction — no weighting formula needed. Strictly more uniform than CIELAB; often a better nearest-match than ΔE2000 in saturated-blue regions, and cheaper to compute. |
|
|
315
|
+
| `'deltaEok'` | Euclidean distance in OKLAB (Björn Ottosson, 2020). OKLAB is perceptually uniform by construction — no weighting formula needed. Strictly more uniform than CIELAB; often a better nearest-match than ΔE2000 in saturated-blue regions, and cheaper to compute. | Use when you want perceptual accuracy without the CIEDE2000 rotation-term complexity. |
|
|
316
|
+
|
|
317
|
+
Each metric trades cost for accuracy — the full scan over 907 Pantone entries is well under 1 ms even with ΔE2000. For interactive UIs, cost is usually irrelevant; drop to a cheaper metric when batch-processing millions of colors or when you need specific tie-breaking behavior.
|
|
167
318
|
|
|
168
|
-
|
|
319
|
+
> **Note on x11's default (ΔE76):** x11 has 658 entries, many of them saturated blue/purple ramps where ΔE76 is a known weak spot. The default optimizes for UI scrubbing speed (~5 µs vs ~90 µs for ΔE2000). If perceptual accuracy matters more than latency, override per call with `{ metric: 'deltaEok' }` — same cost as ΔE76 on cached Lab/OKLAB indexes, better separation in blues.
|
|
169
320
|
|
|
170
321
|
```ts
|
|
171
|
-
// Defaults:
|
|
322
|
+
// Defaults: read from the palette's own `defaultMetric`.
|
|
172
323
|
identify('#ff8080') // deltaE76 (web default)
|
|
173
|
-
identify('#ff8080', {
|
|
324
|
+
identify('#ff8080', { palette: pantone }) // deltaE2000 (pantone default)
|
|
174
325
|
|
|
175
326
|
// Override per-call:
|
|
176
327
|
identify('#ff8080', { metric: 'euclidean-srgb' }) // fastest
|
|
177
328
|
identify('#ff8080', { metric: 'deltaE2000' }) // most accurate
|
|
178
|
-
identify('#ff8080', {
|
|
329
|
+
identify('#ff8080', { palette: pantone, metric: 'euclidean-srgb' }) // force fast for Pantone
|
|
179
330
|
```
|
|
180
331
|
|
|
181
|
-
The low-level `rgbaToPantone`
|
|
332
|
+
The low-level `rgbaToPantone` reads `pantone.defaultMetric` (currently `'deltaE2000'`).
|
|
182
333
|
|
|
183
334
|
## Formats
|
|
184
335
|
|
|
185
|
-
Format keys are SCREAMING_CAPS — they are used as dispatch keys internally.
|
|
186
|
-
|
|
187
336
|
| Key | Input accepted | Output shape |
|
|
188
337
|
|---|---|---|
|
|
189
338
|
| `'HEX'` | `'#rgb'`, `'#rrggbb'`, `'#rrggbbaa'` | `'#rrggbb'` string |
|
|
@@ -191,13 +340,10 @@ Format keys are SCREAMING_CAPS — they are used as dispatch keys internally.
|
|
|
191
340
|
| `'RGBA'` | `'rgba(r, g, b, a)'`, `[r, g, b, a]`, `{ r, g, b, a }` | `{ r, g, b, a }` object |
|
|
192
341
|
| `'HSL'` | `'hsl(h, s%, l%)'`, `{ h, s, l }` | `'hsl(h, s%, l%)'` string |
|
|
193
342
|
| `'HSV'` | `'hsv(h, s%, v%)'`, `{ h, s, v }` | `'hsv(h, s%, v%)'` string |
|
|
194
|
-
| `'PANTONE'` | `'185 C'`, `'185C'`, `'Pantone 185 C'`, etc. | `'185C'`-style string |
|
|
195
|
-
|
|
196
|
-
The runtime set `COLOR_FORMATS` is exported for enumeration / membership checks.
|
|
197
343
|
|
|
198
344
|
## Error handling
|
|
199
345
|
|
|
200
|
-
The three dispatchers are asymmetric
|
|
346
|
+
The three dispatchers are asymmetric:
|
|
201
347
|
|
|
202
348
|
| Function | Unrecognized / unknown input | Rationale |
|
|
203
349
|
|---|---|---|
|
|
@@ -205,7 +351,7 @@ The three dispatchers are asymmetric — chosen deliberately, but worth knowing:
|
|
|
205
351
|
| `resolve(name)` | returns `null` | lookup-flavored; name may legitimately not exist |
|
|
206
352
|
| `convert(input)` | **throws** | parser-flavored; malformed color is a caller error |
|
|
207
353
|
|
|
208
|
-
Low-level converters (`hexToRgba`, `rgbToRgba`, `hslToRgba`, `hsvToRgba`, `pantoneToRgba`) all **throw** on malformed input. The `rgbaTo*` direction never throws.
|
|
354
|
+
Low-level converters (`hexToRgba`, `rgbToRgba`, `hslToRgba`, `hsvToRgba`, `pantoneToRgba`) all **throw** on malformed input. The `rgbaTo*` direction never throws. `rgbaToPantone` is the special case: it always returns a Pantone code regardless of how distant the nearest match is, so don't treat the return as evidence the input was Pantone-like — check the ΔE yourself if that matters.
|
|
209
355
|
|
|
210
356
|
If you want a `null`-returning variant of `convert`, wrap it:
|
|
211
357
|
|
|
@@ -218,6 +364,7 @@ const tryConvert = (input: ColorInput, opts = {}) => {
|
|
|
218
364
|
## Utilities
|
|
219
365
|
|
|
220
366
|
- **`detectFormat(input): DetectedFormat`** — runtime format detection. Returns one of the format keys or `'UNKNOWN'`.
|
|
367
|
+
- **`isColor(input): boolean`** — type-guard wrapper around `detectFormat`. Returns `true` if the input is a recognized structural color shape.
|
|
221
368
|
- **`COLOR_FORMATS: ReadonlySet<ColorFormat>`** — the Set of valid format keys.
|
|
222
369
|
- **Per-format converters** (tree-shakeable) — `hexToRgba`, `rgbaToHex`, `rgbToRgba`, `rgbaToRgb`, `hslToRgba`, `rgbaToHsl`, `hsvToRgba`, `rgbaToHsv`, `pantoneToRgba`, `rgbaToPantone`. Use these directly if you only need one conversion and want maximum tree-shaking.
|
|
223
370
|
|
|
@@ -226,20 +373,22 @@ const tryConvert = (input: ColorInput, opts = {}) => {
|
|
|
226
373
|
- `sideEffects: false` in `package.json`.
|
|
227
374
|
- All exports are named; no default export.
|
|
228
375
|
- Barrel uses explicit re-exports.
|
|
229
|
-
-
|
|
376
|
+
- `identify` / `resolve` take the palette **as an object**, not a string key. You only pay for palettes you actually import: calling `identify(hex)` with no override pulls in `web` (148 entries) and nothing else; passing `{ palette: pantone }` adds pantone. X11 and pantone are *not* bundled unless you reference them.
|
|
377
|
+
- BYO palettes have zero library cost beyond your own data.
|
|
378
|
+
- Subpath exports (`chromonym/web`, `chromonym/x11`, `chromonym/pantone`, `chromonym/conversions/hex`, `chromonym/math/deltaE`, etc.) let you import a single palette or converter without going through the root barrel.
|
|
230
379
|
|
|
231
380
|
## Types
|
|
232
381
|
|
|
233
|
-
|
|
382
|
+
Re-exported from the root barrel — `import type { ... } from 'chromonym'`:
|
|
234
383
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
384
|
+
| Category | Types |
|
|
385
|
+
|---|---|
|
|
386
|
+
| Input / output unions | `ColorInput`, `ColorValue` |
|
|
387
|
+
| Format keys | `ColorFormat` |
|
|
388
|
+
| Per-format shapes | `HexColor`, `Rgba`, `RgbInput`, `RgbaInput`, `HslInput`, `HsvInput` |
|
|
389
|
+
| Palette container | `Palette<Name>`, `NormalizeFn` |
|
|
390
|
+
| Distance selector | `DistanceMetric` |
|
|
391
|
+
| Color-name unions | `WebColorName`, `X11ColorName`, `PantoneColorName` |
|
|
243
392
|
|
|
244
393
|
## Development
|
|
245
394
|
|
|
@@ -251,19 +400,17 @@ bun test
|
|
|
251
400
|
bun run build
|
|
252
401
|
```
|
|
253
402
|
|
|
254
|
-
Regenerating
|
|
403
|
+
Regenerating palette data:
|
|
255
404
|
|
|
256
405
|
```sh
|
|
257
406
|
bun run scripts/generate-x11.ts # reads /usr/share/X11/rgb.txt
|
|
258
|
-
bun run scripts/generate-pantone.ts # reads color_library (MIT
|
|
407
|
+
bun run scripts/generate-pantone.ts # reads the color_library npm package (MIT © Radu Dragan)
|
|
259
408
|
```
|
|
260
409
|
|
|
261
410
|
## Coverage
|
|
262
411
|
|
|
263
412
|
[](https://codecov.io/github/simiancraft/chromonym)
|
|
264
413
|
|
|
265
|
-
Each ring is a directory; each leaf is a file. Green is covered, red is gaps.
|
|
266
|
-
|
|
267
414
|
## See also
|
|
268
415
|
|
|
269
416
|
chromonym deliberately limits its scope to color *naming* (resolving and identifying) and lightweight conversions. For other color-related work, reach for tools that do those things well:
|
|
@@ -276,8 +423,10 @@ chromonym deliberately limits its scope to color *naming* (resolving and identif
|
|
|
276
423
|
|
|
277
424
|
MIT © [the-simian](https://github.com/the-simian)
|
|
278
425
|
|
|
426
|
+
<p align="center"><sub>Crafted with care by <a href="https://simiancraft.com">Simiancraft</a>.</sub></p>
|
|
427
|
+
|
|
279
428
|
---
|
|
280
429
|
|
|
281
430
|
> ### Pantone® trademark notice
|
|
282
431
|
>
|
|
283
|
-
> **Pantone®** is a registered trademark of **Pantone LLC**. Chromonym is **not affiliated with, endorsed by, or certified by Pantone LLC**. The `pantone`
|
|
432
|
+
> **Pantone®** is a registered trademark of **Pantone LLC**. Chromonym is **not affiliated with, endorsed by, or certified by Pantone LLC**. The `pantone` palette ships **community-derived sRGB approximations** of the Pantone Coated (C) set. Values will **not** match a licensed Pantone reference exactly and are unsuitable for print color specification. See [`NOTICE.md`](./NOTICE.md) for full text.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hex.d.ts","sourceRoot":"","sources":["../../src/conversions/hex.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"hex.d.ts","sourceRoot":"","sources":["../../src/conversions/hex.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAElD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAsB7C;AAMD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ,CAE9C","sourcesContent":["import { HEX_RE } from '../detectFormat.js';\nimport { clamp } from '../math/clamp.js';\nimport type { HexColor, Rgba } from '../types.js';\n\n/**\n * Parse a hex color string into the canonical Rgba representation.\n * Accepts 3-digit (`#rgb`), 6-digit (`#rrggbb`), and 8-digit (`#rrggbbaa`).\n * Throws on any other input shape.\n */\nexport function hexToRgba(hex: HexColor): Rgba {\n const match = HEX_RE.exec(hex);\n const body = match?.[1];\n if (!body) throw new Error(`Invalid hex color: ${hex}`);\n\n if (body.length === 3) {\n const r = body.charAt(0);\n const g = body.charAt(1);\n const b = body.charAt(2);\n return {\n r: parseInt(r + r, 16),\n g: parseInt(g + g, 16),\n b: parseInt(b + b, 16),\n a: 1,\n };\n }\n\n const r = parseInt(body.slice(0, 2), 16);\n const g = parseInt(body.slice(2, 4), 16);\n const b = parseInt(body.slice(4, 6), 16);\n const a = body.length === 8 ? parseInt(body.slice(6, 8), 16) / 255 : 1;\n return { r, g, b, a };\n}\n\nfunction channelToHex(n: number): string {\n return clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0');\n}\n\n/**\n * Emit a 6-digit lowercase hex string. Alpha is dropped; out-of-range\n * channel values are clamped to 0..255; fractional values are rounded.\n */\nexport function rgbaToHex(rgba: Rgba): HexColor {\n return `#${channelToHex(rgba.r)}${channelToHex(rgba.g)}${channelToHex(rgba.b)}` as HexColor;\n}\n"]}
|
package/dist/conversions/hex.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HEX_RE } from '../detectFormat';
|
|
2
|
-
import { clamp } from '../math/clamp';
|
|
1
|
+
import { HEX_RE } from '../detectFormat.js';
|
|
2
|
+
import { clamp } from '../math/clamp.js';
|
|
3
3
|
/**
|
|
4
4
|
* Parse a hex color string into the canonical Rgba representation.
|
|
5
5
|
* Accepts 3-digit (`#rgb`), 6-digit (`#rrggbb`), and 8-digit (`#rrggbbaa`).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hex.js","sourceRoot":"","sources":["../../src/conversions/hex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"hex.js","sourceRoot":"","sources":["../../src/conversions/hex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAGzC;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,GAAa;IACrC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,EAAE,CAAC,CAAC;IAExD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACzB,OAAO;YACL,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACtB,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACtB,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACtB,CAAC,EAAE,CAAC;SACL,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AACxB,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAU;IAClC,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAc,CAAC;AAC9F,CAAC","sourcesContent":["import { HEX_RE } from '../detectFormat.js';\nimport { clamp } from '../math/clamp.js';\nimport type { HexColor, Rgba } from '../types.js';\n\n/**\n * Parse a hex color string into the canonical Rgba representation.\n * Accepts 3-digit (`#rgb`), 6-digit (`#rrggbb`), and 8-digit (`#rrggbbaa`).\n * Throws on any other input shape.\n */\nexport function hexToRgba(hex: HexColor): Rgba {\n const match = HEX_RE.exec(hex);\n const body = match?.[1];\n if (!body) throw new Error(`Invalid hex color: ${hex}`);\n\n if (body.length === 3) {\n const r = body.charAt(0);\n const g = body.charAt(1);\n const b = body.charAt(2);\n return {\n r: parseInt(r + r, 16),\n g: parseInt(g + g, 16),\n b: parseInt(b + b, 16),\n a: 1,\n };\n }\n\n const r = parseInt(body.slice(0, 2), 16);\n const g = parseInt(body.slice(2, 4), 16);\n const b = parseInt(body.slice(4, 6), 16);\n const a = body.length === 8 ? parseInt(body.slice(6, 8), 16) / 255 : 1;\n return { r, g, b, a };\n}\n\nfunction channelToHex(n: number): string {\n return clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0');\n}\n\n/**\n * Emit a 6-digit lowercase hex string. Alpha is dropped; out-of-range\n * channel values are clamped to 0..255; fractional values are rounded.\n */\nexport function rgbaToHex(rgba: Rgba): HexColor {\n return `#${channelToHex(rgba.r)}${channelToHex(rgba.g)}${channelToHex(rgba.b)}` as HexColor;\n}\n"]}
|