chromonym 3.1.1 → 3.2.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 +243 -1
- package/README.md +47 -42
- package/dist/convert.d.ts +73 -21
- package/dist/convert.d.ts.map +1 -1
- package/dist/convert.js +13 -2
- package/dist/convert.js.map +1 -1
- package/dist/defineColorPalette.d.ts +53 -0
- package/dist/defineColorPalette.d.ts.map +1 -0
- package/dist/defineColorPalette.js +103 -0
- package/dist/defineColorPalette.js.map +1 -0
- package/dist/detectFormat.d.ts +26 -3
- package/dist/detectFormat.d.ts.map +1 -1
- package/dist/detectFormat.js +20 -0
- package/dist/detectFormat.js.map +1 -1
- package/dist/identify.d.ts +63 -17
- package/dist/identify.d.ts.map +1 -1
- package/dist/identify.js.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/indexing.d.ts +13 -3
- package/dist/indexing.d.ts.map +1 -1
- package/dist/indexing.js +67 -19
- package/dist/indexing.js.map +1 -1
- package/dist/palettes/crayola.d.ts +16 -0
- package/dist/palettes/crayola.d.ts.map +1 -1
- package/dist/palettes/crayola.js +16 -0
- package/dist/palettes/crayola.js.map +1 -1
- package/dist/palettes/fs595b.d.ts +467 -0
- package/dist/palettes/fs595b.d.ts.map +1 -0
- package/dist/palettes/fs595b.js +256 -0
- package/dist/palettes/fs595b.js.map +1 -0
- package/dist/palettes/fs595c.d.ts +1224 -0
- package/dist/palettes/fs595c.d.ts.map +1 -0
- package/dist/palettes/fs595c.js +633 -0
- package/dist/palettes/fs595c.js.map +1 -0
- package/dist/palettes/isccNbs.d.ts +562 -0
- package/dist/palettes/isccNbs.d.ts.map +1 -0
- package/dist/palettes/isccNbs.js +300 -0
- package/dist/palettes/isccNbs.js.map +1 -0
- package/dist/palettes/nbs.d.ts +578 -0
- package/dist/palettes/nbs.d.ts.map +1 -0
- package/dist/palettes/nbs.js +309 -0
- package/dist/palettes/nbs.js.map +1 -0
- package/dist/palettes/ncs.d.ts +3949 -0
- package/dist/palettes/ncs.d.ts.map +1 -0
- package/dist/palettes/ncs.js +2011 -0
- package/dist/palettes/ncs.js.map +1 -0
- package/dist/palettes/ntc.d.ts +3170 -0
- package/dist/palettes/ntc.d.ts.map +1 -0
- package/dist/palettes/ntc.js +1602 -0
- package/dist/palettes/ntc.js.map +1 -0
- package/dist/palettes/pantone.d.ts +18 -1
- package/dist/palettes/pantone.d.ts.map +1 -1
- package/dist/palettes/pantone.js +18 -1
- package/dist/palettes/pantone.js.map +1 -1
- package/dist/palettes/resene.d.ts +2802 -0
- package/dist/palettes/resene.d.ts.map +1 -0
- package/dist/palettes/resene.js +1422 -0
- package/dist/palettes/resene.js.map +1 -0
- package/dist/palettes/web.d.ts +10 -0
- package/dist/palettes/web.d.ts.map +1 -1
- package/dist/palettes/web.js +10 -0
- package/dist/palettes/web.js.map +1 -1
- package/dist/palettes/x11.d.ts +15 -10
- package/dist/palettes/x11.d.ts.map +1 -1
- package/dist/palettes/x11.js +15 -10
- package/dist/palettes/x11.js.map +1 -1
- package/dist/palettes/xkcd.d.ts +1880 -0
- package/dist/palettes/xkcd.d.ts.map +1 -0
- package/dist/palettes/xkcd.js +955 -0
- package/dist/palettes/xkcd.js.map +1 -0
- package/dist/resolve.d.ts +58 -14
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +32 -16
- package/dist/resolve.js.map +1 -1
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +43 -4
package/NOTICE.md
CHANGED
|
@@ -67,7 +67,7 @@ Crayola's published approximations.
|
|
|
67
67
|
|
|
68
68
|
### Scope
|
|
69
69
|
|
|
70
|
-
The shipped set is
|
|
70
|
+
The shipped set is 84 entries: the current 64-standard box, the
|
|
71
71
|
iconic fluorescent "Neon" range, and widely-recognized modern
|
|
72
72
|
additions. It is not exhaustive: historical, retired, specialty-box
|
|
73
73
|
(Gem Tones, Heads 'N Tails, Multicultural, etc.), and regional-variant
|
|
@@ -93,6 +93,248 @@ the public domain.
|
|
|
93
93
|
The `web` palette mirrors the CSS Color Module Level 4 named-color
|
|
94
94
|
set, which is a public specification.
|
|
95
95
|
|
|
96
|
+
## Resene
|
|
97
|
+
|
|
98
|
+
The `resene` palette (1378 entries) is derived from the Raku
|
|
99
|
+
module **`Color::Names::Resene`** in the
|
|
100
|
+
[`thundergnat/Color-Names`](https://github.com/thundergnat/Color-Names)
|
|
101
|
+
repository, which digitizes the named-color catalog published by
|
|
102
|
+
**Resene Paints Ltd.** (New Zealand). Resene's naming system has
|
|
103
|
+
distinctly local flavor, including te reo Māori and New Zealand
|
|
104
|
+
place names ("Pohutukawa," "Acapulco," "Afghan Tan").
|
|
105
|
+
|
|
106
|
+
- Chromonym uses **nominative references** to Resene's published
|
|
107
|
+
color names; no trademark claim or endorsement is implied.
|
|
108
|
+
- Canonical keys are the lowercase single-word form the source
|
|
109
|
+
module uses (`treepoppy` for "Tree Poppy"); the display form
|
|
110
|
+
and Resene's own Munsell-style code (e.g. `BR32-011-076`) are
|
|
111
|
+
captured as trailing comments on each palette entry for human
|
|
112
|
+
readers.
|
|
113
|
+
- sRGB hex values are taken verbatim from the upstream module's
|
|
114
|
+
RGB byte triplets. Resene's physical paint samples under
|
|
115
|
+
their own published lighting conditions are the authoritative
|
|
116
|
+
reference; this digital approximation is provided for
|
|
117
|
+
comparative / tooling use, not for paint specification.
|
|
118
|
+
- If Resene Paints Ltd. objects to this palette's inclusion,
|
|
119
|
+
open an issue and we will revisit.
|
|
120
|
+
|
|
121
|
+
## Natural Colour System (NCS)
|
|
122
|
+
|
|
123
|
+
The `ncs` palette (1950 entries) is derived from the Raku module
|
|
124
|
+
**`Color::Names::NCS`** in the
|
|
125
|
+
[`thundergnat/Color-Names`](https://github.com/thundergnat/Color-Names)
|
|
126
|
+
repository, which digitizes the **Natural Colour System®** Atlas.
|
|
127
|
+
NCS is a Swedish perceptual color system based on Hering's
|
|
128
|
+
opponent-process theory; its notation (e.g. `S 2030-R80B`)
|
|
129
|
+
describes each color by its blackness, chromaticness, and hue
|
|
130
|
+
position between the four elementary chromatics.
|
|
131
|
+
|
|
132
|
+
- **NCS® is a registered trademark of NCS Colour AB** (Stockholm).
|
|
133
|
+
Chromonym is **not affiliated with, endorsed by, licensed by,
|
|
134
|
+
or certified by NCS Colour AB**. The shipped palette is a
|
|
135
|
+
community approximation intended for on-screen reference and
|
|
136
|
+
nearest-name identification; it is **not suitable for paint
|
|
137
|
+
specification or color matching** against the official NCS
|
|
138
|
+
Atlas.
|
|
139
|
+
- NCS is **device-independent by design**. Any sRGB approximation
|
|
140
|
+
is lossy by construction. The values shipped here come from
|
|
141
|
+
thundergnat's Raku module's own digitization; different NCS
|
|
142
|
+
publications give slightly different sRGB values for the same
|
|
143
|
+
code.
|
|
144
|
+
- Canonical keys are the NCS codes themselves (e.g. `2030-R80B`).
|
|
145
|
+
The palette's custom normalizer accepts optional `NCS ` and
|
|
146
|
+
`S ` prefixes, so `NCS S 2030-R80B`, `S 2030-R80B`,
|
|
147
|
+
`2030-R80B`, and `2030r80b` all resolve identically. `S` is
|
|
148
|
+
only treated as a prefix when followed by a digit, so inputs
|
|
149
|
+
that don't match an NCS code aren't silently rewritten.
|
|
150
|
+
|
|
151
|
+
### Accuracy envelope
|
|
152
|
+
|
|
153
|
+
NCS specifies colors by percentages on an opponent-process model;
|
|
154
|
+
those percentages must be translated to sRGB through a calibration
|
|
155
|
+
that differs between publications. As a rough guide for the
|
|
156
|
+
digitization chromonym ships:
|
|
157
|
+
|
|
158
|
+
- **Mid-chroma / mid-value codes** (blackness and chromaticness
|
|
159
|
+
both between ~20 and ~60) are the best-behaved; the upstream
|
|
160
|
+
digitization is usually within a few ΔE2000 of any reasonable
|
|
161
|
+
alternate rendering.
|
|
162
|
+
- **High chromaticness, low blackness** codes (e.g. `S 1080-Y90R`,
|
|
163
|
+
`S 0580-G`) sit at or outside the sRGB gamut boundary. The hex
|
|
164
|
+
shipped here is gamut-clipped; a different digitization may
|
|
165
|
+
clip differently, producing visibly different hex values for
|
|
166
|
+
the same NCS code.
|
|
167
|
+
- **Very low-blackness neutrals** (e.g. the `0300-N` / `0500-N`
|
|
168
|
+
range) are close to sRGB white; fine-grained differences between
|
|
169
|
+
digitizations are largely imperceptible.
|
|
170
|
+
|
|
171
|
+
If brand-critical or specification-critical NCS matching is needed,
|
|
172
|
+
consult NCS Colour AB directly: <https://ncscolour.com/>.
|
|
173
|
+
|
|
174
|
+
## NBS (thundergnat digitization)
|
|
175
|
+
|
|
176
|
+
The `nbs` palette (267 entries) is a **second digitization of the
|
|
177
|
+
same 1955 NBS Method of Designating Colors** covered by the
|
|
178
|
+
`isccNbs` palette above; it is sourced from the Raku module
|
|
179
|
+
**`Color::Names::NBS`** in
|
|
180
|
+
[`thundergnat/Color-Names`](https://github.com/thundergnat/Color-Names).
|
|
181
|
+
Ships alongside `isccNbs` (not as a replacement) because the two
|
|
182
|
+
digitizations produce materially different sRGB values for the
|
|
183
|
+
same named blocks: average ΔE2000 of ~4.6 across the 260 shared
|
|
184
|
+
names, max ΔE2000 of 31 for the most saturated blocks like
|
|
185
|
+
"Vivid bluish green."
|
|
186
|
+
|
|
187
|
+
The two palettes serve different reference workflows:
|
|
188
|
+
|
|
189
|
+
- **`isccNbs`** (Paul Centore) computes Munsell-centroid CIELAB
|
|
190
|
+
then maps to sRGB. "Vivid" blocks land near the sRGB saturation
|
|
191
|
+
edge; seven blocks are left as gaps because their centroids
|
|
192
|
+
fall outside the sRGB gamut.
|
|
193
|
+
- **`nbs`** (thundergnat) appears to use the sample chip RGBs
|
|
194
|
+
from the original NBS publication, which were already gamut-
|
|
195
|
+
compressed by the physical chip material. "Vivid" blocks read
|
|
196
|
+
as more muted, and all 267 entries are covered — including
|
|
197
|
+
the seven blocks whose Munsell centroids fall outside the sRGB
|
|
198
|
+
gamut and are therefore omitted from `isccNbs`. `nbs` does not
|
|
199
|
+
"recover" the out-of-gamut centroids; it ships the already-
|
|
200
|
+
compressed physical chip approximation instead, which is a
|
|
201
|
+
different kind of lossy.
|
|
202
|
+
|
|
203
|
+
Pick `nbs` for historical / swatch-book matching; pick `isccNbs`
|
|
204
|
+
for modern digital design work. The name vocabulary (the 1955
|
|
205
|
+
NBS publication itself) is a US-government document not subject
|
|
206
|
+
to copyright restrictions when faithfully reproduced; the
|
|
207
|
+
specific sRGB values in each palette are credited to their
|
|
208
|
+
respective digitizers.
|
|
209
|
+
|
|
210
|
+
## ISCC-NBS Colour System
|
|
211
|
+
|
|
212
|
+
The `isccNbs` palette (260 entries) is the **ISCC-NBS Method of
|
|
213
|
+
Designating Colors**, a 1955 joint publication of the
|
|
214
|
+
**Inter-Society Color Council** and the US **National Bureau of
|
|
215
|
+
Standards** (now NIST). The system partitions the Munsell color
|
|
216
|
+
solid into 267 named blocks at Level 3 resolution with canonical
|
|
217
|
+
names like "Vivid pink," "Strong purplish red," and "Dark
|
|
218
|
+
yellowish brown."
|
|
219
|
+
|
|
220
|
+
sRGB centroids for each named block are scraped from **Paul
|
|
221
|
+
Centore's** digitization at
|
|
222
|
+
<https://www.munsellcolorscienceforpainters.com/ISCCNBS/ISCCNBSSystem.html>,
|
|
223
|
+
part of his *Munsell Colour Science for Painters* site.
|
|
224
|
+
|
|
225
|
+
**Why 260 and not 267:** Seven ISCC-NBS blocks sit outside the
|
|
226
|
+
sRGB gamut (notably the most-saturated greens, blues, and
|
|
227
|
+
orange-yellows): `Deep green`, `Deep bluish green`, `Vivid
|
|
228
|
+
greenish blue`, `Deep greenish blue`, `Brilliant orange`, `Deep
|
|
229
|
+
olive green`, and `Vivid orange yellow`. Centore's source marks
|
|
230
|
+
these as `rgb(NaN,NaN,NaN)`; chromonym skips them rather than
|
|
231
|
+
ship a fabricated sRGB approximation.
|
|
232
|
+
|
|
233
|
+
The ISCC-NBS name system itself is a US-government publication
|
|
234
|
+
and is not subject to copyright restrictions when faithfully
|
|
235
|
+
reproduced. Paul Centore's specific sRGB-centroid computations
|
|
236
|
+
are credited here; if the author objects to Chromonym's use of
|
|
237
|
+
his digitization, open an issue and we will revisit or redo the
|
|
238
|
+
centroids from the primary Munsell data.
|
|
239
|
+
|
|
240
|
+
## Federal Standard 595 (FS595B and FS595C)
|
|
241
|
+
|
|
242
|
+
The `fs595b` (209 entries) and `fs595c` (589 entries) palettes
|
|
243
|
+
are derived from the Raku modules **`Color::Names::FS595B`** and
|
|
244
|
+
**`Color::Names::FS595C`** in the
|
|
245
|
+
[`thundergnat/Color-Names`](https://github.com/thundergnat/Color-Names)
|
|
246
|
+
repository, which digitize the US Federal Government's
|
|
247
|
+
**Federal Standard 595** paint-color specification (used for
|
|
248
|
+
military, aviation, and government-equipment coatings). FS595B
|
|
249
|
+
is the 1989 revision, superseded by FS595C in 2008. Both are
|
|
250
|
+
still in active use depending on which revision a given piece of
|
|
251
|
+
documentation or equipment references. The Federal Standard
|
|
252
|
+
itself is a US-government-published specification and is not
|
|
253
|
+
subject to copyright restrictions when faithfully reproduced.
|
|
254
|
+
|
|
255
|
+
- Canonical keys in both palettes are the 5-digit FS codes
|
|
256
|
+
prefixed with `FS ` (e.g. `FS 11136`). Common names attached
|
|
257
|
+
to many chips in the source (e.g. "Insignia Red") are captured
|
|
258
|
+
as trailing code comments on each palette entry for human
|
|
259
|
+
readers but are **not** used as lookup keys because the spec
|
|
260
|
+
reuses many across multiple chips with different hex values
|
|
261
|
+
(18–20 different "Green"s, 11 "Tan"s, etc., depending on
|
|
262
|
+
revision).
|
|
263
|
+
- Same-coded chips can differ in hex between the B and C
|
|
264
|
+
revisions; for example `FS 11136` ("Insignia Red") is
|
|
265
|
+
`#9b2f25` in FS595B versus `#a32b25` in FS595C. Pick the
|
|
266
|
+
palette that matches your reference documentation.
|
|
267
|
+
- sRGB hex values are taken verbatim from the upstream modules'
|
|
268
|
+
RGB byte triplets; the published standard specifies color
|
|
269
|
+
chips under controlled lighting, and all digital sRGB
|
|
270
|
+
approximations including these are necessarily simplified.
|
|
271
|
+
- Chromonym is **not affiliated with** any US Government agency
|
|
272
|
+
or defense contractor; no endorsement is implied.
|
|
273
|
+
|
|
274
|
+
## XKCD color survey
|
|
275
|
+
|
|
276
|
+
The `xkcd` palette (923 entries after normalization-collision
|
|
277
|
+
dedupe) is derived from **Randall Munroe's 2010 XKCD color
|
|
278
|
+
survey results**, published at <https://xkcd.com/color/rgb.txt>
|
|
279
|
+
and released to the **public domain under CC0 1.0 Universal**.
|
|
280
|
+
Chromonym reproduces the dataset verbatim apart from:
|
|
281
|
+
|
|
282
|
+
- Hex casing normalized to lowercase.
|
|
283
|
+
- Entries whose names collapse to the same canonical key under
|
|
284
|
+
`standardNormalize` are deduped first-seen wins (e.g. the
|
|
285
|
+
survey's `darkblue` and `dark blue` entries both normalize to
|
|
286
|
+
`darkblue`; only the first is retained).
|
|
287
|
+
|
|
288
|
+
The XKCD color-survey names are crowd-sourced and include
|
|
289
|
+
colorful non-standard descriptors. Chromonym takes no editorial
|
|
290
|
+
stance on the names and ships them as-is.
|
|
291
|
+
|
|
292
|
+
## Name That Color (NTC)
|
|
293
|
+
|
|
294
|
+
The `ntc` palette (1566 entries) is derived from Chirag Mehta's
|
|
295
|
+
**"Name That Color"** dataset, first published at
|
|
296
|
+
<https://chir.ag/projects/ntc/>. Chromonym consumes the dataset via
|
|
297
|
+
the MIT-licensed **`colorjs/color-namer`** npm package
|
|
298
|
+
(<https://github.com/colorjs/color-namer>), which redistributes the
|
|
299
|
+
list in a machine-readable form. The shipped palette was generated
|
|
300
|
+
against commit `abb3d184ab63db9327908319cc45b55c91493bb7` of the
|
|
301
|
+
upstream repository, so the entry set is deterministic and
|
|
302
|
+
reproducible.
|
|
303
|
+
|
|
304
|
+
- Names are used as **nominative references** to the publicly
|
|
305
|
+
distributed NTC dataset; no endorsement or trademark claim is
|
|
306
|
+
implied.
|
|
307
|
+
- sRGB hex values are taken verbatim from the upstream list.
|
|
308
|
+
- No modifications are made beyond casing normalization of the hex
|
|
309
|
+
string and sorting by display name.
|
|
310
|
+
|
|
311
|
+
The NTC dataset has been widely redistributed since 2006 under
|
|
312
|
+
terms allowing free reuse with attribution. If the upstream author
|
|
313
|
+
objects to Chromonym's inclusion of the list, open an issue and we
|
|
314
|
+
will revisit.
|
|
315
|
+
|
|
316
|
+
## Bauhaus Modern (demo site typeface)
|
|
317
|
+
|
|
318
|
+
The demo site at <https://simiancraft.github.io/chromonym/> embeds
|
|
319
|
+
**Bauhaus Modern**, a display typeface designed by **Nils Kähler**
|
|
320
|
+
(2016). The font file is hosted under
|
|
321
|
+
[`demo/public/fonts/`](./demo/public/fonts/) and is used only as
|
|
322
|
+
display type for the demo wordmark.
|
|
323
|
+
|
|
324
|
+
- Source: <https://www.dafont.com/bauhaus-modern.font>
|
|
325
|
+
- License terms: "Free for personal use" (per the dafont listing)
|
|
326
|
+
- Use for this demo was arranged with the designer's knowledge via
|
|
327
|
+
email; a donation was made, and the use case (MIT-licensed library
|
|
328
|
+
demo, display-type only, with visible attribution) was described.
|
|
329
|
+
See [`demo/public/fonts/README.txt`](./demo/public/fonts/README.txt)
|
|
330
|
+
for the full note.
|
|
331
|
+
|
|
332
|
+
The font is not part of the published npm package (`files` allowlist
|
|
333
|
+
ships only the library, not the demo site). Downstream users of the
|
|
334
|
+
`chromonym` package do not redistribute Bauhaus Modern by installing
|
|
335
|
+
the library. Please do not repackage or redistribute the font file
|
|
336
|
+
outside this demo without following up with the designer directly.
|
|
337
|
+
|
|
96
338
|
## Upstream dependencies
|
|
97
339
|
|
|
98
340
|
See `package.json` for runtime and development dependencies. All are MIT
|
package/README.md
CHANGED
|
@@ -20,7 +20,11 @@
|
|
|
20
20
|
<code>identify</code> • <code>resolve</code> • <code>convert</code>
|
|
21
21
|
</p>
|
|
22
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, Pantone,
|
|
23
|
+
**The last color-naming library you'll ever need.** One API — `identify`, `resolve`, `convert` — works against any palette you throw at it. Ships twelve palettes out of the box (CSS, X11, Pantone, Crayola, NTC, XKCD, Resene, NCS, ISCC-NBS, NBS, FS 595 B, FS 595 C); bring your own (brand colors, paint chips, game factions, chart themes) with full TypeScript inference and zero registration. Six distance metrics — from fast Euclidean to industry-standard CIEDE2000 and OKLAB. Tree-shakes to the bone — you only pay for palettes you import.
|
|
24
|
+
|
|
25
|
+
<sub>*chromonym* (n.): a name for a color; *chroma* + *-onym*. Also the library.</sub>
|
|
26
|
+
|
|
27
|
+
<sub>Every public export ships with JSDoc and a runnable `@example`; autocomplete and hover do the teaching.</sub>
|
|
24
28
|
|
|
25
29
|
<p align="center">
|
|
26
30
|
<a href="https://simiancraft.github.io/chromonym/">
|
|
@@ -80,18 +84,6 @@ yarn add chromonym
|
|
|
80
84
|
npm install chromonym
|
|
81
85
|
```
|
|
82
86
|
|
|
83
|
-
## Migrating from 1.x
|
|
84
|
-
|
|
85
|
-
v2 collapsed the five-verb draft API (`identify`, `identifyAll`, `translate`, `resolve`, `convert`) into **three verbs** by folding the duplicates into options on existing verbs. Mechanical rewrites:
|
|
86
|
-
|
|
87
|
-
```ts
|
|
88
|
-
// v1 → v2
|
|
89
|
-
identifyAll(color, { palette, k: 3 }) // → identify(color, { palette, k: 3 })
|
|
90
|
-
translate(name, { from: web, to: pantone }) // → identify(name, { palette: pantone, source: web })
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
`resolve` and `convert` are unchanged. If you were only using those two, no edits are needed. See [`CHANGELOG.md`](CHANGELOG.md) for the full list of v3.0 breaking changes.
|
|
94
|
-
|
|
95
87
|
## API
|
|
96
88
|
|
|
97
89
|
Three verbs. All accept an optional `opts` object; defaults fire when omitted.
|
|
@@ -273,22 +265,24 @@ type Palette<Name extends string = string> = {
|
|
|
273
265
|
|
|
274
266
|
The primary shape. Any object matching `Palette<Name>` works — no registry, no plugin, no side effects. Bundlers only include the palettes you actually import.
|
|
275
267
|
|
|
268
|
+
The recommended pattern is `defineColorPalette`: it accepts mixed color-value formats (paste whatever came out of your design tool), normalizes everything to hex once at module load, and preserves the literal-key union for full narrowing through `identify` / `resolve` / `convert`.
|
|
269
|
+
|
|
276
270
|
```ts
|
|
277
|
-
import { identify, resolve
|
|
271
|
+
import { defineColorPalette, identify, resolve } from 'chromonym';
|
|
278
272
|
|
|
279
|
-
const warhammer = {
|
|
273
|
+
const warhammer = defineColorPalette({
|
|
280
274
|
name: 'warhammer40k',
|
|
281
275
|
colors: {
|
|
282
|
-
'world eaters red': '#8b1a1a',
|
|
283
|
-
'adeptus red': '
|
|
284
|
-
'sons of malice white':
|
|
285
|
-
'the flawless host purple':
|
|
286
|
-
'nurgle green': '
|
|
276
|
+
'world eaters red': '#8b1a1a', // hex
|
|
277
|
+
'adeptus red': 'rgb(101, 32, 34)', // rgb string
|
|
278
|
+
'sons of malice white': { r: 232, g: 228, b: 216 }, // rgb object
|
|
279
|
+
'the flawless host purple': [107, 45, 125], // rgb tuple
|
|
280
|
+
'nurgle green': 'hsl(78, 38%, 39%)', // hsl string
|
|
287
281
|
'alpha legion teal': '#2a6d7a',
|
|
288
282
|
},
|
|
289
|
-
normalize: (s
|
|
283
|
+
normalize: (s) => s.toLowerCase().replace(/[^a-z0-9]/g, ''),
|
|
290
284
|
defaultMetric: 'deltaE2000',
|
|
291
|
-
}
|
|
285
|
+
});
|
|
292
286
|
|
|
293
287
|
identify('#750c0c', { palette: warhammer })
|
|
294
288
|
// → 'world eaters red' (return type narrows to the palette's key union)
|
|
@@ -297,14 +291,26 @@ resolve('Nurgle Green', { palette: warhammer })
|
|
|
297
291
|
// → '#748c3f' (your normalizer handles case + punctuation)
|
|
298
292
|
```
|
|
299
293
|
|
|
300
|
-
|
|
294
|
+
Unparseable values are dropped with a `console.warn` naming the palette, the offending key, and the bad value; the rest of the palette loads normally. This keeps a single typo out of your brand kit from taking down the whole module.
|
|
295
|
+
|
|
296
|
+
If you prefer zero runtime and already have hex-only values, the `as const satisfies Palette<'A' | 'B' | ...>` pattern still works — list the key union once in the generic and it narrows identically.
|
|
297
|
+
|
|
298
|
+
### The twelve we ship
|
|
301
299
|
|
|
302
300
|
| Name | Entries | Source |
|
|
303
301
|
|---|---|---|
|
|
304
302
|
| `web` (default) | 148 | CSS Color Module Level 4 named colors |
|
|
305
303
|
| `x11` | 658 | X.Org `rgb.txt` (public domain) |
|
|
306
304
|
| `pantone` | 907 | Pantone Coated (C) — community approximations (not Pantone-licensed) |
|
|
307
|
-
| `crayola` |
|
|
305
|
+
| `crayola` | 84 | Crayola crayon colors — community approximations (not Crayola-licensed) |
|
|
306
|
+
| `ntc` | 1566 | Chirag Mehta's "Name That Color" dataset (broad coverage of design / UI / hardware names) |
|
|
307
|
+
| `xkcd` | 923 | XKCD 2010 color-survey results (CC0; crowd-sourced common-speech names) |
|
|
308
|
+
| `fs595b` | 209 | Federal Standard 595B paint chips (1989 revision; modelers / historical equipment) |
|
|
309
|
+
| `fs595c` | 589 | Federal Standard 595C paint chips (US Government spec; military / aviation coatings) |
|
|
310
|
+
| `isccNbs` | 260 | ISCC-NBS Method of Designating Colors (1955 US standard; Munsell-partitioned named blocks) |
|
|
311
|
+
| `nbs` | 267 | Second digitization of the same 1955 standard; different sRGB (matches 1955 chip book; covers the 7 blocks `isccNbs` leaves as gaps) |
|
|
312
|
+
| `resene` | 1378 | Resene Paints (NZ) catalog; te reo Māori and NZ place-names in the naming |
|
|
313
|
+
| `ncs` | 1950 | Natural Colour System (Sweden) — perceptual, structured notation (`2030-R80B`), not NCS-licensed |
|
|
308
314
|
|
|
309
315
|
Importable directly, or via subpath exports for stricter tree-shaking:
|
|
310
316
|
|
|
@@ -321,7 +327,7 @@ crayola.colors['Granny Smith Apple'] // '#a8e4a0'
|
|
|
321
327
|
|
|
322
328
|
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'`), `crayola` uses the Title Case form printed on the crayon wrapper (`'Granny Smith Apple'`).
|
|
323
329
|
|
|
324
|
-
*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
|
|
330
|
+
*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 100, so `x11.colors['gray 73']` is a real key (`#bababa`). Yes, these are Greys. No, not the kind that visit during sleep paralysis.
|
|
325
331
|
|
|
326
332
|
### Cross-palette translation
|
|
327
333
|
|
|
@@ -382,7 +388,7 @@ Rule of thumb: use `source` when you want one clean call and `null` on miss; use
|
|
|
382
388
|
|
|
383
389
|
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.
|
|
384
390
|
|
|
385
|
-
> **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' }`
|
|
391
|
+
> **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' }` for better separation in the blue and purple region.
|
|
386
392
|
|
|
387
393
|
```ts
|
|
388
394
|
// Defaults: read from the palette's own `defaultMetric`.
|
|
@@ -443,6 +449,20 @@ const tryConvert = (input: ColorInput, opts = {}) => {
|
|
|
443
449
|
- BYO palettes have zero library cost beyond your own data.
|
|
444
450
|
- 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.
|
|
445
451
|
|
|
452
|
+
### What you actually ship
|
|
453
|
+
|
|
454
|
+
The published tarball is **~557 kB gzipped / ~2.5 MB unpacked** because it carries twelve palettes and ~9000 named-color entries. That's the number `packagephobia` reports — it measures the install footprint on disk. Your **bundle** pays only for what you actually import, measured with `esbuild --minify`:
|
|
455
|
+
|
|
456
|
+
| Import | min | gzip |
|
|
457
|
+
|---|---|---|
|
|
458
|
+
| `import { identify, web } from 'chromonym'` | 10.9 kB | **4.7 kB** |
|
|
459
|
+
| `import { identify, pantone } from 'chromonym'` | 27 kB | **11.3 kB** |
|
|
460
|
+
| `import { identify, ntc } from 'chromonym'` (one of the bigger palettes) | 43 kB | **20 kB** |
|
|
461
|
+
| `import { defineColorPalette } from 'chromonym'` (pure BYO; no built-in) | 3.8 kB | **1.5 kB** |
|
|
462
|
+
| `import { pantoneToRgba } from 'chromonym'` (single conversion) | 17.5 kB | **6.8 kB** |
|
|
463
|
+
|
|
464
|
+
The install-size number is a shipping concern, not a bundle-size one. If you're evaluating chromonym against a size budget, the gzip column above is what actually lands in your production build.
|
|
465
|
+
|
|
446
466
|
## Types
|
|
447
467
|
|
|
448
468
|
Re-exported from the root barrel — `import type { ... } from 'chromonym'`:
|
|
@@ -466,17 +486,6 @@ bun test
|
|
|
466
486
|
bun run build
|
|
467
487
|
```
|
|
468
488
|
|
|
469
|
-
Regenerating palette data:
|
|
470
|
-
|
|
471
|
-
```sh
|
|
472
|
-
bun run scripts/generate-x11.ts # reads /usr/share/X11/rgb.txt
|
|
473
|
-
bun run scripts/generate-pantone.ts # reads the color_library npm package (MIT © Radu Dragan)
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
## Coverage
|
|
477
|
-
|
|
478
|
-
[](https://codecov.io/github/simiancraft/chromonym)
|
|
479
|
-
|
|
480
489
|
## See also
|
|
481
490
|
|
|
482
491
|
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:
|
|
@@ -491,8 +500,4 @@ MIT © [the-simian](https://github.com/the-simian)
|
|
|
491
500
|
|
|
492
501
|
<p align="center"><sub>Crafted with care by <a href="https://simiancraft.com">Simiancraft</a>.</sub></p>
|
|
493
502
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
> ### Pantone® trademark notice
|
|
497
|
-
>
|
|
498
|
-
> **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.
|
|
503
|
+
<p align="center"><sub>Pantone® and Crayola® are trademarks of their respective owners; Chromonym is not affiliated. Palette values are community approximations. Demo typeface <a href="https://www.dafont.com/bauhaus-modern.font">Bauhaus Modern</a> © Nils Kähler. Full attributions: <a href="./NOTICE.md">NOTICE.md</a>.</sub></p>
|
package/dist/convert.d.ts
CHANGED
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
import { type DetectedFormat } from './detectFormat.js';
|
|
2
|
-
import type { ColorFormat, ColorInput, ColorValue, Palette, Rgba } from './types.js';
|
|
2
|
+
import type { ColorFormat, ColorInput, ColorValue, Palette, PaletteKey, Rgba } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Normalize any recognized color input to canonical `Rgba`.
|
|
5
5
|
* Pass `knownFormat` when the caller has already run `detectFormat` to skip
|
|
6
6
|
* the redundant detection. Throws on `UNKNOWN` input.
|
|
7
7
|
*
|
|
8
|
-
* Palette names (Pantone codes, BYO keys) are not accepted here
|
|
8
|
+
* Palette names (Pantone codes, BYO keys) are not accepted here; they
|
|
9
9
|
* require palette data to parse. Use `convert(name, { palette })` or
|
|
10
10
|
* `resolve(name, { palette })` at the higher level.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* toRgba('#ff0000'); // { r: 255, g: 0, b: 0, a: 1 }
|
|
14
|
+
* toRgba('rgb(255, 0, 0)'); // { r: 255, g: 0, b: 0, a: 1 }
|
|
15
|
+
* toRgba([255, 0, 0]); // { r: 255, g: 0, b: 0, a: 1 }
|
|
16
|
+
* toRgba({ h: 0, s: 100, l: 50 }); // { r: 255, g: 0, b: 0, a: 1 }
|
|
11
17
|
*/
|
|
12
18
|
export declare function toRgba(input: ColorInput, knownFormat?: DetectedFormat): Rgba;
|
|
13
19
|
/**
|
|
14
20
|
* Emit a canonical `Rgba` in the target structural format. Palette-independent.
|
|
15
21
|
*
|
|
16
22
|
* For palette-key output (e.g. nearest Pantone code, brand name), use
|
|
17
|
-
* `convert(rgba, { format: 'NAME', palette })
|
|
23
|
+
* `convert(rgba, { format: 'NAME', palette })`; or `identify` for fuzzy,
|
|
18
24
|
* `rgbaToPantone` for the low-level Pantone-only path.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* fromRgba({ r: 255, g: 0, b: 0, a: 1 }); // '#ff0000'
|
|
28
|
+
* fromRgba({ r: 255, g: 0, b: 0, a: 1 }, 'HSL'); // 'hsl(0, 100%, 50%)'
|
|
29
|
+
* fromRgba({ r: 255, g: 0, b: 0, a: 0.5 }, 'RGB'); // 'rgb(255, 0, 0)' (alpha dropped)
|
|
19
30
|
*/
|
|
20
31
|
export declare function fromRgba(rgba: Rgba, format?: ColorFormat): ColorValue;
|
|
21
|
-
/**
|
|
22
|
-
* Extract string keys from a Palette's `colors` map.
|
|
23
|
-
* Matches the `PaletteKey<P>` helper in `identify.ts` — kept local so
|
|
24
|
-
* `convert` doesn't depend on `identify`.
|
|
25
|
-
*/
|
|
26
|
-
type PaletteKey<P extends Palette> = Extract<keyof P['colors'], string>;
|
|
27
32
|
/**
|
|
28
33
|
* Detect the input color format, normalize to Rgba, and emit the result
|
|
29
34
|
* in the requested output format.
|
|
@@ -46,16 +51,63 @@ type PaletteKey<P extends Palette> = Extract<keyof P['colors'], string>;
|
|
|
46
51
|
* Throws on unrecognized input (parser-flavored — `identify`/`resolve`
|
|
47
52
|
* are the `null`-returning alternatives).
|
|
48
53
|
*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Options for the structural (palette-free) variant of {@link convert}.
|
|
56
|
+
* Use when you only need to translate between HEX / RGB / RGBA / HSL / HSV.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* convert('#ff0000', { format: 'HSL' }); // 'hsl(0, 100%, 50%)'
|
|
60
|
+
*/
|
|
61
|
+
export type ConvertStructuralOptions = {
|
|
62
|
+
/**
|
|
63
|
+
* Target output format. Defaults to `'HEX'`.
|
|
64
|
+
*/
|
|
65
|
+
readonly format?: ColorFormat;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Options for {@link convert} when reverse-looking-up a palette name
|
|
69
|
+
* from an exact color value. Generic so the return narrows to the
|
|
70
|
+
* palette's literal-key union.
|
|
71
|
+
*
|
|
72
|
+
* Throws at runtime if the input isn't a pixel-exact match in the
|
|
73
|
+
* palette; use `identify` for nearest-match semantics.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* convert('#e4002b', { palette: pantone, format: 'NAME' }); // '185 C'
|
|
77
|
+
* convert('#663399', { palette: web, format: 'NAME' }); // 'rebeccapurple'
|
|
78
|
+
*/
|
|
79
|
+
export type ConvertNameOptions<P extends Palette> = {
|
|
80
|
+
/**
|
|
81
|
+
* Palette to reverse-look-up against. Required when `format` is `'NAME'`.
|
|
82
|
+
*/
|
|
83
|
+
readonly palette: P;
|
|
84
|
+
/**
|
|
85
|
+
* Output discriminator. `'NAME'` returns the palette's canonical key
|
|
86
|
+
* for the input color.
|
|
87
|
+
*/
|
|
88
|
+
readonly format: 'NAME';
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Options for {@link convert} when a palette is provided but the output
|
|
92
|
+
* is a structural format (HEX / RGB / HSL / HSV). Unlocks palette-name
|
|
93
|
+
* *input*: the `input` may be `'185 C'`, `'Acme Red'`, etc., which is
|
|
94
|
+
* resolved through `palette.normalize` before conversion.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* convert('185 C', { palette: pantone }); // '#e4002b'
|
|
98
|
+
* convert('185 C', { palette: pantone, format: 'HSL' }); // 'hsl(349, 100%, 45%)'
|
|
99
|
+
*/
|
|
100
|
+
export type ConvertPaletteOptions<P extends Palette> = {
|
|
101
|
+
/**
|
|
102
|
+
* Palette used to parse name-shaped inputs.
|
|
103
|
+
*/
|
|
104
|
+
readonly palette: P;
|
|
105
|
+
/**
|
|
106
|
+
* Target output format. Defaults to `'HEX'`.
|
|
107
|
+
*/
|
|
108
|
+
readonly format?: ColorFormat;
|
|
109
|
+
};
|
|
110
|
+
export declare function convert(input: ColorInput, opts?: ConvertStructuralOptions): ColorValue;
|
|
111
|
+
export declare function convert<P extends Palette>(input: ColorInput | string, opts: ConvertNameOptions<P>): PaletteKey<P>;
|
|
112
|
+
export declare function convert<P extends Palette>(input: ColorInput | string, opts: ConvertPaletteOptions<P>): ColorValue;
|
|
61
113
|
//# sourceMappingURL=convert.d.ts.map
|
package/dist/convert.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,cAAc,EAAgB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,UAAU,EAIV,OAAO,EACP,IAAI,EAGL,MAAM,YAAY,CAAC;AAYpB;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,cAAc,GAAG,IAAI,CAgB5E;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,GAAE,WAAmB,GAAG,UAAU,CAqB5E;AAED;;;;GAIG;AACH,KAAK,UAAU,CAAC,CAAC,SAAS,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC;AAExE;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAAG,UAAU,CAAC;AAGxF,wBAAgB,OAAO,CAAC,CAAC,SAAS,OAAO,EACvC,KAAK,EAAE,UAAU,GAAG,MAAM,EAC1B,IAAI,EAAE;IAAE,OAAO,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC,UAAU,CAAC,CAAC,CAAC,CAAC;AAGjB,wBAAgB,OAAO,CAAC,CAAC,SAAS,OAAO,EACvC,KAAK,EAAE,UAAU,GAAG,MAAM,EAC1B,IAAI,EAAE;IAAE,OAAO,EAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GACzC,UAAU,CAAC","sourcesContent":["import { hexToRgba, rgbaToHex } from './conversions/hex.js';\nimport { hslToRgba, rgbaToHsl } from './conversions/hsl.js';\nimport { hsvToRgba, rgbaToHsv } from './conversions/hsv.js';\nimport { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';\nimport { type DetectedFormat, detectFormat } from './detectFormat.js';\nimport { getNameIndex, getReverseNameIndex } from './indexing.js';\nimport type {\n ColorFormat,\n ColorInput,\n ColorValue,\n HexColor,\n HslInput,\n HsvInput,\n Palette,\n Rgba,\n RgbaInput,\n RgbInput,\n} from './types.js';\n\nfunction safeStringify(x: unknown): string {\n if (x === undefined) return 'undefined';\n try {\n const s = JSON.stringify(x);\n return s === undefined ? String(x) : s;\n } catch {\n return Object.prototype.toString.call(x);\n }\n}\n\n/**\n * Normalize any recognized color input to canonical `Rgba`.\n * Pass `knownFormat` when the caller has already run `detectFormat` to skip\n * the redundant detection. Throws on `UNKNOWN` input.\n *\n * Palette names (Pantone codes, BYO keys) are not accepted here — they\n * require palette data to parse. Use `convert(name, { palette })` or\n * `resolve(name, { palette })` at the higher level.\n */\nexport function toRgba(input: ColorInput, knownFormat?: DetectedFormat): Rgba {\n const format = knownFormat ?? detectFormat(input);\n switch (format) {\n case 'HEX':\n return hexToRgba(input as HexColor);\n case 'RGB':\n return rgbToRgba(input as RgbInput);\n case 'RGBA':\n return rgbToRgba(input as RgbaInput);\n case 'HSL':\n return hslToRgba(input as HslInput);\n case 'HSV':\n return hsvToRgba(input as HsvInput);\n default:\n throw new Error(`Unrecognized color input: ${safeStringify(input)}`);\n }\n}\n\n/**\n * Emit a canonical `Rgba` in the target structural format. Palette-independent.\n *\n * For palette-key output (e.g. nearest Pantone code, brand name), use\n * `convert(rgba, { format: 'NAME', palette })` — or `identify` for fuzzy,\n * `rgbaToPantone` for the low-level Pantone-only path.\n */\nexport function fromRgba(rgba: Rgba, format: ColorFormat = 'HEX'): ColorValue {\n switch (format) {\n case 'HEX':\n return rgbaToHex(rgba);\n case 'RGB':\n return rgbaToRgb(rgba);\n case 'RGBA':\n return rgba;\n case 'HSL':\n return rgbaToHsl(rgba);\n case 'HSV':\n return rgbaToHsv(rgba);\n default: {\n // Runtime safety for JS callers bypassing the ColorFormat union.\n const hint =\n format === ('PANTONE' as unknown)\n ? ` — use convert(rgba, { palette: pantone, format: 'NAME' }) or rgbaToPantone from 'chromonym/conversions/pantone'`\n : '';\n throw new Error(`Unsupported format: ${safeStringify(format)}${hint}`);\n }\n }\n}\n\n/**\n * Extract string keys from a Palette's `colors` map.\n * Matches the `PaletteKey<P>` helper in `identify.ts` — kept local so\n * `convert` doesn't depend on `identify`.\n */\ntype PaletteKey<P extends Palette> = Extract<keyof P['colors'], string>;\n\n/**\n * Detect the input color format, normalize to Rgba, and emit the result\n * in the requested output format.\n *\n * Without a `palette` option, `convert` is strictly structural —\n * HEX ↔ RGB ↔ RGBA ↔ HSL ↔ HSV, no palette data ever touched, tree-shakes\n * to the bone.\n *\n * With a `palette` option, `convert` understands the palette's naming\n * scheme both ways:\n * - Input can be a palette name (`'185 C'`, `'Acme Red'`) — parsed via\n * `palette.normalize` and looked up in `palette.colors`.\n * - Output can be `format: 'NAME'` — the exact canonical key from\n * `palette.colors`, throws if the color isn't a pixel-exact match (use\n * `identify` for nearest-match).\n *\n * The palette data comes from the caller's own import, so tree-shaking\n * is preserved: callers who don't pass `palette` never pull a palette.\n *\n * Throws on unrecognized input (parser-flavored — `identify`/`resolve`\n * are the `null`-returning alternatives).\n */\n\n// Overload 1: no palette — structural only. Input is strictly ColorInput.\nexport function convert(input: ColorInput, opts?: { format?: ColorFormat }): ColorValue;\n\n// Overload 2: palette present, format 'NAME' — returns a palette key.\nexport function convert<P extends Palette>(\n input: ColorInput | string,\n opts: { palette: P; format: 'NAME' },\n): PaletteKey<P>;\n\n// Overload 3: palette present, structural format — returns ColorValue.\nexport function convert<P extends Palette>(\n input: ColorInput | string,\n opts: { palette: P; format?: ColorFormat },\n): ColorValue;\n\n// Implementation\nexport function convert(\n input: ColorInput | string,\n opts: { format?: ColorFormat | 'NAME'; palette?: Palette } = {},\n): ColorValue | string {\n const { format = 'HEX', palette } = opts;\n\n // --- Parse input → Rgba ---\n // Structural detection first; palette lookup only as a fallback. This\n // way `convert('#ff0000', { palette: brand })` always means \"parse hex,\"\n // even if the brand palette happens to have a key that normalizes to\n // \"ff0000\".\n const detected = detectFormat(input as ColorInput);\n let rgba: Rgba;\n if (detected !== 'UNKNOWN') {\n rgba = toRgba(input as ColorInput, detected);\n } else if (palette !== undefined && typeof input === 'string') {\n const canonical = getNameIndex(palette).get(palette.normalize(input));\n if (canonical === undefined) {\n throw new Error(\n `Unrecognized color input: ${safeStringify(input)} (not structural, and not found in palette '${palette.name}')`,\n );\n }\n const hex = (palette.colors as Record<string, HexColor>)[canonical];\n if (hex === undefined) {\n throw new Error(`Unrecognized color input: ${safeStringify(input)}`);\n }\n rgba = hexToRgba(hex);\n } else {\n throw new Error(`Unrecognized color input: ${safeStringify(input)}`);\n }\n\n // --- Emit ---\n if (format === 'NAME') {\n if (palette === undefined) {\n throw new Error(`convert(_, { format: 'NAME' }) requires a 'palette' option`);\n }\n // Palette colors are alpha = 1 by construction; a partially-transparent\n // input isn't \"that named color\" in the strict-convert sense. Reject\n // here rather than silently drop alpha during the reverse lookup.\n if (rgba.a !== 1) {\n throw new Error(\n `format: 'NAME' requires fully-opaque input (a === 1). Got a=${rgba.a}. ` +\n `Strip alpha first or use identify() for fuzzy matching.`,\n );\n }\n const hex = rgbaToHex(rgba).toLowerCase();\n const name = getReverseNameIndex(palette).get(hex);\n if (name === undefined) {\n throw new Error(\n `No exact match for ${hex} in palette '${palette.name}'. Use identify() for nearest-match semantics.`,\n );\n }\n return name;\n }\n return fromRgba(rgba, format);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,cAAc,EAAgB,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,UAAU,EAIV,OAAO,EACP,UAAU,EACV,IAAI,EAGL,MAAM,YAAY,CAAC;AAYpB;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,cAAc,GAAG,IAAI,CAgB5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,GAAE,WAAmB,GAAG,UAAU,CAqB5E;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,OAAO,IAAI;IAClD;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,OAAO,IAAI;IACrD;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B,CAAC;AAGF,wBAAgB,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,wBAAwB,GAAG,UAAU,CAAC;AAGxF,wBAAgB,OAAO,CAAC,CAAC,SAAS,OAAO,EACvC,KAAK,EAAE,UAAU,GAAG,MAAM,EAC1B,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAC1B,UAAU,CAAC,CAAC,CAAC,CAAC;AAGjB,wBAAgB,OAAO,CAAC,CAAC,SAAS,OAAO,EACvC,KAAK,EAAE,UAAU,GAAG,MAAM,EAC1B,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAC7B,UAAU,CAAC","sourcesContent":["import { hexToRgba, rgbaToHex } from './conversions/hex.js';\nimport { hslToRgba, rgbaToHsl } from './conversions/hsl.js';\nimport { hsvToRgba, rgbaToHsv } from './conversions/hsv.js';\nimport { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';\nimport { type DetectedFormat, detectFormat } from './detectFormat.js';\nimport { getNameIndex, getReverseNameIndex } from './indexing.js';\nimport type {\n ColorFormat,\n ColorInput,\n ColorValue,\n HexColor,\n HslInput,\n HsvInput,\n Palette,\n PaletteKey,\n Rgba,\n RgbaInput,\n RgbInput,\n} from './types.js';\n\nfunction safeStringify(x: unknown): string {\n if (x === undefined) return 'undefined';\n try {\n const s = JSON.stringify(x);\n return s === undefined ? String(x) : s;\n } catch {\n return Object.prototype.toString.call(x);\n }\n}\n\n/**\n * Normalize any recognized color input to canonical `Rgba`.\n * Pass `knownFormat` when the caller has already run `detectFormat` to skip\n * the redundant detection. Throws on `UNKNOWN` input.\n *\n * Palette names (Pantone codes, BYO keys) are not accepted here; they\n * require palette data to parse. Use `convert(name, { palette })` or\n * `resolve(name, { palette })` at the higher level.\n *\n * @example\n * toRgba('#ff0000'); // { r: 255, g: 0, b: 0, a: 1 }\n * toRgba('rgb(255, 0, 0)'); // { r: 255, g: 0, b: 0, a: 1 }\n * toRgba([255, 0, 0]); // { r: 255, g: 0, b: 0, a: 1 }\n * toRgba({ h: 0, s: 100, l: 50 }); // { r: 255, g: 0, b: 0, a: 1 }\n */\nexport function toRgba(input: ColorInput, knownFormat?: DetectedFormat): Rgba {\n const format = knownFormat ?? detectFormat(input);\n switch (format) {\n case 'HEX':\n return hexToRgba(input as HexColor);\n case 'RGB':\n return rgbToRgba(input as RgbInput);\n case 'RGBA':\n return rgbToRgba(input as RgbaInput);\n case 'HSL':\n return hslToRgba(input as HslInput);\n case 'HSV':\n return hsvToRgba(input as HsvInput);\n default:\n throw new Error(`Unrecognized color input: ${safeStringify(input)}`);\n }\n}\n\n/**\n * Emit a canonical `Rgba` in the target structural format. Palette-independent.\n *\n * For palette-key output (e.g. nearest Pantone code, brand name), use\n * `convert(rgba, { format: 'NAME', palette })`; or `identify` for fuzzy,\n * `rgbaToPantone` for the low-level Pantone-only path.\n *\n * @example\n * fromRgba({ r: 255, g: 0, b: 0, a: 1 }); // '#ff0000'\n * fromRgba({ r: 255, g: 0, b: 0, a: 1 }, 'HSL'); // 'hsl(0, 100%, 50%)'\n * fromRgba({ r: 255, g: 0, b: 0, a: 0.5 }, 'RGB'); // 'rgb(255, 0, 0)' (alpha dropped)\n */\nexport function fromRgba(rgba: Rgba, format: ColorFormat = 'HEX'): ColorValue {\n switch (format) {\n case 'HEX':\n return rgbaToHex(rgba);\n case 'RGB':\n return rgbaToRgb(rgba);\n case 'RGBA':\n return rgba;\n case 'HSL':\n return rgbaToHsl(rgba);\n case 'HSV':\n return rgbaToHsv(rgba);\n default: {\n // Runtime safety for JS callers bypassing the ColorFormat union.\n const hint =\n format === ('PANTONE' as unknown)\n ? ` — use convert(rgba, { palette: pantone, format: 'NAME' }) or rgbaToPantone from 'chromonym/conversions/pantone'`\n : '';\n throw new Error(`Unsupported format: ${safeStringify(format)}${hint}`);\n }\n }\n}\n\n/**\n * Detect the input color format, normalize to Rgba, and emit the result\n * in the requested output format.\n *\n * Without a `palette` option, `convert` is strictly structural —\n * HEX ↔ RGB ↔ RGBA ↔ HSL ↔ HSV, no palette data ever touched, tree-shakes\n * to the bone.\n *\n * With a `palette` option, `convert` understands the palette's naming\n * scheme both ways:\n * - Input can be a palette name (`'185 C'`, `'Acme Red'`) — parsed via\n * `palette.normalize` and looked up in `palette.colors`.\n * - Output can be `format: 'NAME'` — the exact canonical key from\n * `palette.colors`, throws if the color isn't a pixel-exact match (use\n * `identify` for nearest-match).\n *\n * The palette data comes from the caller's own import, so tree-shaking\n * is preserved: callers who don't pass `palette` never pull a palette.\n *\n * Throws on unrecognized input (parser-flavored — `identify`/`resolve`\n * are the `null`-returning alternatives).\n */\n\n/**\n * Options for the structural (palette-free) variant of {@link convert}.\n * Use when you only need to translate between HEX / RGB / RGBA / HSL / HSV.\n *\n * @example\n * convert('#ff0000', { format: 'HSL' }); // 'hsl(0, 100%, 50%)'\n */\nexport type ConvertStructuralOptions = {\n /**\n * Target output format. Defaults to `'HEX'`.\n */\n readonly format?: ColorFormat;\n};\n\n/**\n * Options for {@link convert} when reverse-looking-up a palette name\n * from an exact color value. Generic so the return narrows to the\n * palette's literal-key union.\n *\n * Throws at runtime if the input isn't a pixel-exact match in the\n * palette; use `identify` for nearest-match semantics.\n *\n * @example\n * convert('#e4002b', { palette: pantone, format: 'NAME' }); // '185 C'\n * convert('#663399', { palette: web, format: 'NAME' }); // 'rebeccapurple'\n */\nexport type ConvertNameOptions<P extends Palette> = {\n /**\n * Palette to reverse-look-up against. Required when `format` is `'NAME'`.\n */\n readonly palette: P;\n /**\n * Output discriminator. `'NAME'` returns the palette's canonical key\n * for the input color.\n */\n readonly format: 'NAME';\n};\n\n/**\n * Options for {@link convert} when a palette is provided but the output\n * is a structural format (HEX / RGB / HSL / HSV). Unlocks palette-name\n * *input*: the `input` may be `'185 C'`, `'Acme Red'`, etc., which is\n * resolved through `palette.normalize` before conversion.\n *\n * @example\n * convert('185 C', { palette: pantone }); // '#e4002b'\n * convert('185 C', { palette: pantone, format: 'HSL' }); // 'hsl(349, 100%, 45%)'\n */\nexport type ConvertPaletteOptions<P extends Palette> = {\n /**\n * Palette used to parse name-shaped inputs.\n */\n readonly palette: P;\n /**\n * Target output format. Defaults to `'HEX'`.\n */\n readonly format?: ColorFormat;\n};\n\n// Overload 1: no palette — structural only. Input is strictly ColorInput.\nexport function convert(input: ColorInput, opts?: ConvertStructuralOptions): ColorValue;\n\n// Overload 2: palette present, format 'NAME' — returns a palette key.\nexport function convert<P extends Palette>(\n input: ColorInput | string,\n opts: ConvertNameOptions<P>,\n): PaletteKey<P>;\n\n// Overload 3: palette present, structural format — returns ColorValue.\nexport function convert<P extends Palette>(\n input: ColorInput | string,\n opts: ConvertPaletteOptions<P>,\n): ColorValue;\n\n// Implementation\nexport function convert(\n input: ColorInput | string,\n opts: { format?: ColorFormat | 'NAME'; palette?: Palette } = {},\n): ColorValue | string {\n const { format = 'HEX', palette } = opts;\n\n // --- Parse input → Rgba ---\n // Structural detection first; palette lookup only as a fallback. This\n // way `convert('#ff0000', { palette: brand })` always means \"parse hex,\"\n // even if the brand palette happens to have a key that normalizes to\n // \"ff0000\".\n const detected = detectFormat(input as ColorInput);\n let rgba: Rgba;\n if (detected !== 'UNKNOWN') {\n rgba = toRgba(input as ColorInput, detected);\n } else if (palette !== undefined && typeof input === 'string') {\n const canonical = getNameIndex(palette).get(palette.normalize(input));\n if (canonical === undefined) {\n throw new Error(\n `Unrecognized color input: ${safeStringify(input)} (not structural, and not found in palette '${palette.name}')`,\n );\n }\n const hex = (palette.colors as Record<string, HexColor>)[canonical];\n if (hex === undefined) {\n throw new Error(`Unrecognized color input: ${safeStringify(input)}`);\n }\n rgba = hexToRgba(hex);\n } else {\n throw new Error(`Unrecognized color input: ${safeStringify(input)}`);\n }\n\n // --- Emit ---\n if (format === 'NAME') {\n if (palette === undefined) {\n throw new Error(`convert(_, { format: 'NAME' }) requires a 'palette' option`);\n }\n // Palette colors are alpha = 1 by construction; a partially-transparent\n // input isn't \"that named color\" in the strict-convert sense. Reject\n // here rather than silently drop alpha during the reverse lookup.\n if (rgba.a !== 1) {\n throw new Error(\n `format: 'NAME' requires fully-opaque input (a === 1). Got a=${rgba.a}. ` +\n `Strip alpha first or use identify() for fuzzy matching.`,\n );\n }\n const hex = rgbaToHex(rgba).toLowerCase();\n const name = getReverseNameIndex(palette).get(hex);\n if (name === undefined) {\n throw new Error(\n `No exact match for ${hex} in palette '${palette.name}'. Use identify() for nearest-match semantics.`,\n );\n }\n return name;\n }\n return fromRgba(rgba, format);\n}\n"]}
|
package/dist/convert.js
CHANGED
|
@@ -20,9 +20,15 @@ function safeStringify(x) {
|
|
|
20
20
|
* Pass `knownFormat` when the caller has already run `detectFormat` to skip
|
|
21
21
|
* the redundant detection. Throws on `UNKNOWN` input.
|
|
22
22
|
*
|
|
23
|
-
* Palette names (Pantone codes, BYO keys) are not accepted here
|
|
23
|
+
* Palette names (Pantone codes, BYO keys) are not accepted here; they
|
|
24
24
|
* require palette data to parse. Use `convert(name, { palette })` or
|
|
25
25
|
* `resolve(name, { palette })` at the higher level.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* toRgba('#ff0000'); // { r: 255, g: 0, b: 0, a: 1 }
|
|
29
|
+
* toRgba('rgb(255, 0, 0)'); // { r: 255, g: 0, b: 0, a: 1 }
|
|
30
|
+
* toRgba([255, 0, 0]); // { r: 255, g: 0, b: 0, a: 1 }
|
|
31
|
+
* toRgba({ h: 0, s: 100, l: 50 }); // { r: 255, g: 0, b: 0, a: 1 }
|
|
26
32
|
*/
|
|
27
33
|
export function toRgba(input, knownFormat) {
|
|
28
34
|
const format = knownFormat ?? detectFormat(input);
|
|
@@ -45,8 +51,13 @@ export function toRgba(input, knownFormat) {
|
|
|
45
51
|
* Emit a canonical `Rgba` in the target structural format. Palette-independent.
|
|
46
52
|
*
|
|
47
53
|
* For palette-key output (e.g. nearest Pantone code, brand name), use
|
|
48
|
-
* `convert(rgba, { format: 'NAME', palette })
|
|
54
|
+
* `convert(rgba, { format: 'NAME', palette })`; or `identify` for fuzzy,
|
|
49
55
|
* `rgbaToPantone` for the low-level Pantone-only path.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* fromRgba({ r: 255, g: 0, b: 0, a: 1 }); // '#ff0000'
|
|
59
|
+
* fromRgba({ r: 255, g: 0, b: 0, a: 1 }, 'HSL'); // 'hsl(0, 100%, 50%)'
|
|
60
|
+
* fromRgba({ r: 255, g: 0, b: 0, a: 0.5 }, 'RGB'); // 'rgb(255, 0, 0)' (alpha dropped)
|
|
50
61
|
*/
|
|
51
62
|
export function fromRgba(rgba, format = 'HEX') {
|
|
52
63
|
switch (format) {
|