color-map 2.2.0 → 2.3.1

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.
Files changed (366) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/.codex-plugin/plugin.json +12 -0
  3. package/.cursor-plugin/plugin.json +12 -0
  4. package/CHANGELOG.md +100 -0
  5. package/README.md +71 -8
  6. package/bin/cli-CELmx3Y9.js +6414 -0
  7. package/bin/color-map.js +9 -0
  8. package/bin/index.js +1 -0
  9. package/cjs/color/index.d.ts +5 -0
  10. package/cjs/color/index.js +52 -0
  11. package/cjs/color-wNrkrhC7.js +374 -0
  12. package/cjs/color-wNrkrhC7.js.map +1 -0
  13. package/cjs/contrast-XjF5Rwui.js +293 -0
  14. package/cjs/contrast-XjF5Rwui.js.map +1 -0
  15. package/cjs/gamut-CG1J2aat.d.ts +158 -0
  16. package/cjs/gamut-CG1J2aat.d.ts.map +1 -0
  17. package/cjs/index-BcEqCPHz.d.ts +137 -0
  18. package/cjs/index-BcEqCPHz.d.ts.map +1 -0
  19. package/cjs/index-CGKCqRqD.d.ts +111 -0
  20. package/cjs/index-CGKCqRqD.d.ts.map +1 -0
  21. package/cjs/index-Cv3iUUYE.d.ts +171 -0
  22. package/cjs/index-Cv3iUUYE.d.ts.map +1 -0
  23. package/cjs/index-Dw43Hj4v.d.ts +41 -0
  24. package/cjs/index-Dw43Hj4v.d.ts.map +1 -0
  25. package/cjs/index-Dy8UARqO.d.ts +72 -0
  26. package/cjs/index-Dy8UARqO.d.ts.map +1 -0
  27. package/cjs/index.d.ts +26 -36
  28. package/cjs/index.d.ts.map +1 -1
  29. package/cjs/index.js +83 -62
  30. package/cjs/index.js.map +1 -1
  31. package/cjs/legacy/index.d.ts +2 -0
  32. package/cjs/legacy/index.js +75 -0
  33. package/cjs/legacy/index.js.map +1 -0
  34. package/cjs/mix-Cs4owSbW.js +158 -0
  35. package/cjs/mix-Cs4owSbW.js.map +1 -0
  36. package/cjs/mix-DGlKoUw4.d.ts +31 -0
  37. package/cjs/mix-DGlKoUw4.d.ts.map +1 -0
  38. package/cjs/palette/index.d.ts +3 -0
  39. package/cjs/palette/index.js +8 -0
  40. package/cjs/palette-DyFV_KoJ.js +332 -0
  41. package/cjs/palette-DyFV_KoJ.js.map +1 -0
  42. package/cjs/parse-8mt2OMKf.js +1508 -0
  43. package/cjs/parse-8mt2OMKf.js.map +1 -0
  44. package/cjs/scale/index.d.ts +2 -0
  45. package/cjs/scale/index.js +11 -0
  46. package/cjs/scale-Cq9pXrc1.js +318 -0
  47. package/cjs/scale-Cq9pXrc1.js.map +1 -0
  48. package/cjs/semantic-CrEIo8tS.d.ts +52 -0
  49. package/cjs/semantic-CrEIo8tS.d.ts.map +1 -0
  50. package/cjs/serialize-C69DAmoE.d.ts +25 -0
  51. package/cjs/serialize-C69DAmoE.d.ts.map +1 -0
  52. package/cjs/serialize-Ci2X0cPN.js +103 -0
  53. package/cjs/serialize-Ci2X0cPN.js.map +1 -0
  54. package/cjs/tokens/index.d.ts +2 -0
  55. package/cjs/tokens/index.js +6 -0
  56. package/cjs/tokens-CzXxaMgM.js +168 -0
  57. package/cjs/tokens-CzXxaMgM.js.map +1 -0
  58. package/cjs/tone-BAhu-QzQ.js +118 -0
  59. package/cjs/tone-BAhu-QzQ.js.map +1 -0
  60. package/dist/color-map.es5.js.map +1 -1
  61. package/esm/color/a98-rgb.d.ts +7 -0
  62. package/esm/color/a98-rgb.d.ts.map +1 -0
  63. package/esm/color/a98-rgb.js +13 -0
  64. package/esm/color/a98-rgb.js.map +1 -0
  65. package/esm/color/argb.d.ts +6 -0
  66. package/esm/color/argb.d.ts.map +1 -0
  67. package/esm/color/argb.js +25 -0
  68. package/esm/color/argb.js.map +1 -0
  69. package/esm/color/calc.js +82 -0
  70. package/esm/color/calc.js.map +1 -0
  71. package/esm/color/compare.d.ts +20 -0
  72. package/esm/color/compare.d.ts.map +1 -0
  73. package/esm/color/compare.js +46 -0
  74. package/esm/color/compare.js.map +1 -0
  75. package/esm/color/contrast.d.ts +107 -0
  76. package/esm/color/contrast.d.ts.map +1 -0
  77. package/esm/color/contrast.js +242 -0
  78. package/esm/color/contrast.js.map +1 -0
  79. package/esm/color/convert.d.ts +9 -0
  80. package/esm/color/convert.d.ts.map +1 -0
  81. package/esm/color/convert.js +45 -0
  82. package/esm/color/convert.js.map +1 -0
  83. package/esm/color/display-p3-linear.d.ts +7 -0
  84. package/esm/color/display-p3-linear.d.ts.map +1 -0
  85. package/esm/color/display-p3-linear.js +12 -0
  86. package/esm/color/display-p3-linear.js.map +1 -0
  87. package/esm/color/display-p3.d.ts +7 -0
  88. package/esm/color/display-p3.d.ts.map +1 -0
  89. package/esm/color/display-p3.js +12 -0
  90. package/esm/color/display-p3.js.map +1 -0
  91. package/esm/color/expr.d.ts +43 -0
  92. package/esm/color/expr.d.ts.map +1 -0
  93. package/esm/color/expr.js +160 -0
  94. package/esm/color/expr.js.map +1 -0
  95. package/esm/color/gamut.d.ts +31 -0
  96. package/esm/color/gamut.d.ts.map +1 -0
  97. package/esm/color/gamut.js +210 -0
  98. package/esm/color/gamut.js.map +1 -0
  99. package/esm/color/hsl.d.ts +7 -0
  100. package/esm/color/hsl.d.ts.map +1 -0
  101. package/esm/color/hsl.js +67 -0
  102. package/esm/color/hsl.js.map +1 -0
  103. package/esm/color/hwb.d.ts +7 -0
  104. package/esm/color/hwb.d.ts.map +1 -0
  105. package/esm/color/hwb.js +40 -0
  106. package/esm/color/hwb.js.map +1 -0
  107. package/esm/color/index.d.ts +28 -0
  108. package/esm/color/index.js +27 -0
  109. package/esm/color/lab.d.ts +7 -0
  110. package/esm/color/lab.d.ts.map +1 -0
  111. package/esm/color/lab.js +33 -0
  112. package/esm/color/lab.js.map +1 -0
  113. package/esm/color/lch.d.ts +7 -0
  114. package/esm/color/lch.d.ts.map +1 -0
  115. package/esm/color/lch.js +11 -0
  116. package/esm/color/lch.js.map +1 -0
  117. package/esm/color/math.js +32 -0
  118. package/esm/color/math.js.map +1 -0
  119. package/esm/color/matrices.js +180 -0
  120. package/esm/color/matrices.js.map +1 -0
  121. package/esm/color/mix.d.ts +30 -0
  122. package/esm/color/mix.d.ts.map +1 -0
  123. package/esm/color/mix.js +152 -0
  124. package/esm/color/mix.js.map +1 -0
  125. package/esm/color/named.js +11 -0
  126. package/esm/color/named.js.map +1 -0
  127. package/esm/color/oklab.d.ts +7 -0
  128. package/esm/color/oklab.d.ts.map +1 -0
  129. package/esm/color/oklab.js +21 -0
  130. package/esm/color/oklab.js.map +1 -0
  131. package/esm/color/oklch.d.ts +7 -0
  132. package/esm/color/oklch.d.ts.map +1 -0
  133. package/esm/color/oklch.js +11 -0
  134. package/esm/color/oklch.js.map +1 -0
  135. package/esm/color/parse.d.ts +9 -0
  136. package/esm/color/parse.d.ts.map +1 -0
  137. package/esm/color/parse.js +452 -0
  138. package/esm/color/parse.js.map +1 -0
  139. package/esm/color/polar.js +27 -0
  140. package/esm/color/polar.js.map +1 -0
  141. package/esm/color/prophoto-rgb.d.ts +7 -0
  142. package/esm/color/prophoto-rgb.d.ts.map +1 -0
  143. package/esm/color/prophoto-rgb.js +18 -0
  144. package/esm/color/prophoto-rgb.js.map +1 -0
  145. package/esm/color/rec2020.d.ts +7 -0
  146. package/esm/color/rec2020.d.ts.map +1 -0
  147. package/esm/color/rec2020.js +13 -0
  148. package/esm/color/rec2020.js.map +1 -0
  149. package/esm/color/resolve.d.ts +18 -0
  150. package/esm/color/resolve.d.ts.map +1 -0
  151. package/esm/color/resolve.js +98 -0
  152. package/esm/color/resolve.js.map +1 -0
  153. package/esm/color/rgb.js +20 -0
  154. package/esm/color/rgb.js.map +1 -0
  155. package/esm/color/serialize.d.ts +24 -0
  156. package/esm/color/serialize.d.ts.map +1 -0
  157. package/esm/color/serialize.js +88 -0
  158. package/esm/color/serialize.js.map +1 -0
  159. package/esm/color/spaces.d.ts +6 -0
  160. package/esm/color/spaces.d.ts.map +1 -0
  161. package/esm/color/spaces.js +42 -0
  162. package/esm/color/spaces.js.map +1 -0
  163. package/esm/color/srgb-linear.d.ts +7 -0
  164. package/esm/color/srgb-linear.d.ts.map +1 -0
  165. package/esm/color/srgb-linear.js +12 -0
  166. package/esm/color/srgb-linear.js.map +1 -0
  167. package/esm/color/srgb.d.ts +7 -0
  168. package/esm/color/srgb.d.ts.map +1 -0
  169. package/esm/color/srgb.js +12 -0
  170. package/esm/color/srgb.js.map +1 -0
  171. package/esm/color/transfer.js +11 -0
  172. package/esm/color/transfer.js.map +1 -0
  173. package/esm/color/types.d.ts +23 -0
  174. package/esm/color/types.d.ts.map +1 -0
  175. package/esm/color/unknown-name.js +16 -0
  176. package/esm/color/unknown-name.js.map +1 -0
  177. package/esm/color/xyz-d50.d.ts +7 -0
  178. package/esm/color/xyz-d50.d.ts.map +1 -0
  179. package/esm/color/xyz-d50.js +15 -0
  180. package/esm/color/xyz-d50.js.map +1 -0
  181. package/esm/color/xyz-d65.d.ts +4 -0
  182. package/esm/color/xyz-d65.d.ts.map +1 -0
  183. package/esm/color/xyz-d65.js +10 -0
  184. package/esm/color/xyz-d65.js.map +1 -0
  185. package/esm/index.d.ts +63 -6
  186. package/esm/index.d.ts.map +1 -0
  187. package/esm/index.js +54 -5
  188. package/esm/index.js.map +1 -0
  189. package/esm/legacy/createColors.d.ts +9 -0
  190. package/esm/legacy/createColors.d.ts.map +1 -0
  191. package/esm/{createColors.js → legacy/createColors.js} +7 -2
  192. package/esm/legacy/createColors.js.map +1 -0
  193. package/esm/legacy/createColorsFromMap.d.ts +9 -0
  194. package/esm/legacy/createColorsFromMap.d.ts.map +1 -0
  195. package/esm/{createColorsFromMap.js → legacy/createColorsFromMap.js} +4 -2
  196. package/esm/legacy/createColorsFromMap.js.map +1 -0
  197. package/esm/legacy/index.d.ts +6 -0
  198. package/esm/legacy/index.js +5 -0
  199. package/esm/legacy/interfaces.d.ts +19 -0
  200. package/esm/legacy/interfaces.d.ts.map +1 -0
  201. package/esm/{rgbHex.d.ts → legacy/rgbHex.d.ts} +1 -4
  202. package/esm/legacy/rgbHex.d.ts.map +1 -0
  203. package/esm/{rgbHex.js → legacy/rgbHex.js} +1 -2
  204. package/esm/legacy/rgbHex.js.map +1 -0
  205. package/esm/legacy/rgbaString.d.ts +4 -0
  206. package/esm/{rgbaString.d.ts.map → legacy/rgbaString.d.ts.map} +1 -1
  207. package/esm/legacy/rgbaString.js +8 -0
  208. package/esm/legacy/rgbaString.js.map +1 -0
  209. package/esm/palette/dark.d.ts +23 -0
  210. package/esm/palette/dark.d.ts.map +1 -0
  211. package/esm/palette/dark.js +54 -0
  212. package/esm/palette/dark.js.map +1 -0
  213. package/esm/palette/harmony.d.ts +56 -0
  214. package/esm/palette/harmony.d.ts.map +1 -0
  215. package/esm/palette/harmony.js +115 -0
  216. package/esm/palette/harmony.js.map +1 -0
  217. package/esm/palette/index.d.ts +5 -0
  218. package/esm/palette/index.js +5 -0
  219. package/esm/palette/semantic.d.ts +53 -0
  220. package/esm/palette/semantic.d.ts.map +1 -0
  221. package/esm/palette/semantic.js +96 -0
  222. package/esm/palette/semantic.js.map +1 -0
  223. package/esm/palette/validate.d.ts +39 -0
  224. package/esm/palette/validate.d.ts.map +1 -0
  225. package/esm/palette/validate.js +56 -0
  226. package/esm/palette/validate.js.map +1 -0
  227. package/esm/scale/brand.d.ts +21 -0
  228. package/esm/scale/brand.d.ts.map +1 -0
  229. package/esm/scale/brand.js +63 -0
  230. package/esm/scale/brand.js.map +1 -0
  231. package/esm/scale/contrast-scale.d.ts +33 -0
  232. package/esm/scale/contrast-scale.d.ts.map +1 -0
  233. package/esm/scale/contrast-scale.js +26 -0
  234. package/esm/scale/contrast-scale.js.map +1 -0
  235. package/esm/scale/gradient.d.ts +38 -0
  236. package/esm/scale/gradient.d.ts.map +1 -0
  237. package/esm/scale/gradient.js +73 -0
  238. package/esm/scale/gradient.js.map +1 -0
  239. package/esm/scale/index.d.ts +6 -0
  240. package/esm/scale/index.js +6 -0
  241. package/esm/scale/scale.d.ts +31 -0
  242. package/esm/scale/scale.d.ts.map +1 -0
  243. package/esm/scale/scale.js +152 -0
  244. package/esm/scale/scale.js.map +1 -0
  245. package/esm/scale/tone.d.ts +25 -0
  246. package/esm/scale/tone.d.ts.map +1 -0
  247. package/esm/scale/tone.js +96 -0
  248. package/esm/scale/tone.js.map +1 -0
  249. package/esm/tokens/css.d.ts +18 -0
  250. package/esm/tokens/css.d.ts.map +1 -0
  251. package/esm/tokens/css.js +23 -0
  252. package/esm/tokens/css.js.map +1 -0
  253. package/esm/tokens/dtcg.d.ts +33 -0
  254. package/esm/tokens/dtcg.d.ts.map +1 -0
  255. package/esm/tokens/dtcg.js +42 -0
  256. package/esm/tokens/dtcg.js.map +1 -0
  257. package/esm/tokens/index.d.ts +5 -0
  258. package/esm/tokens/index.js +4 -0
  259. package/esm/tokens/tailwind.d.ts +19 -0
  260. package/esm/tokens/tailwind.d.ts.map +1 -0
  261. package/esm/tokens/tailwind.js +19 -0
  262. package/esm/tokens/tailwind.js.map +1 -0
  263. package/esm/tokens/tokens.d.ts +11 -0
  264. package/esm/tokens/tokens.d.ts.map +1 -0
  265. package/esm/tokens/tokens.js +77 -0
  266. package/esm/tokens/tokens.js.map +1 -0
  267. package/llms.txt +48 -0
  268. package/package.json +277 -64
  269. package/plugin.json +18 -0
  270. package/schemas/contrast.schema.json +24 -0
  271. package/schemas/convert.schema.json +60 -0
  272. package/schemas/export.schema.json +106 -0
  273. package/schemas/find-contrast.schema.json +51 -0
  274. package/schemas/harmony.schema.json +58 -0
  275. package/schemas/mix.schema.json +72 -0
  276. package/schemas/palette.schema.json +56 -0
  277. package/schemas/scale.schema.json +86 -0
  278. package/skills/color-map/SKILL.md +38 -0
  279. package/src/cli/bin.ts +11 -0
  280. package/src/cli/cli.ts +40 -0
  281. package/src/cli/commands/contrast.ts +36 -0
  282. package/src/cli/commands/convert.ts +37 -0
  283. package/src/cli/commands/export.ts +102 -0
  284. package/src/cli/commands/find-contrast.ts +37 -0
  285. package/src/cli/commands/harmony.ts +30 -0
  286. package/src/cli/commands/mix.ts +29 -0
  287. package/src/cli/commands/palette.ts +66 -0
  288. package/src/cli/commands/scale.ts +75 -0
  289. package/src/cli/index.ts +3 -0
  290. package/src/cli/schema-command.ts +31 -0
  291. package/src/cli/schemas.ts +86 -0
  292. package/src/cli/shared.ts +72 -0
  293. package/src/cli/testing.ts +27 -0
  294. package/src/color/a98-rgb.ts +11 -0
  295. package/src/color/argb.ts +21 -0
  296. package/src/color/calc.ts +84 -0
  297. package/src/color/compare.ts +53 -0
  298. package/src/color/contrast.ts +277 -0
  299. package/src/color/convert.ts +43 -0
  300. package/src/color/display-p3-linear.ts +9 -0
  301. package/src/color/display-p3.ts +9 -0
  302. package/src/color/expr.ts +158 -0
  303. package/src/color/gamut.ts +215 -0
  304. package/src/color/hsl.ts +57 -0
  305. package/src/color/hwb.ts +26 -0
  306. package/src/color/index.ts +43 -0
  307. package/src/color/lab.ts +29 -0
  308. package/src/color/lch.ts +8 -0
  309. package/src/color/math.ts +30 -0
  310. package/src/color/matrices.ts +91 -0
  311. package/src/color/mix.ts +158 -0
  312. package/src/color/named.ts +13 -0
  313. package/src/color/oklab.ts +19 -0
  314. package/src/color/oklch.ts +8 -0
  315. package/src/color/parse.ts +355 -0
  316. package/src/color/polar.ts +17 -0
  317. package/src/color/prophoto-rgb.ts +18 -0
  318. package/src/color/rec2020.ts +11 -0
  319. package/src/color/resolve.ts +87 -0
  320. package/src/color/rgb.ts +20 -0
  321. package/src/color/serialize.ts +96 -0
  322. package/src/color/spaces.ts +42 -0
  323. package/src/color/srgb-linear.ts +9 -0
  324. package/src/color/srgb.ts +9 -0
  325. package/src/color/test-utils.ts +10 -0
  326. package/src/color/transfer.ts +14 -0
  327. package/src/color/types.ts +41 -0
  328. package/src/color/unknown-name.ts +15 -0
  329. package/src/color/xyz-d50.ts +13 -0
  330. package/src/color/xyz-d65.ts +8 -0
  331. package/src/index.ts +37 -0
  332. package/{ts → src/legacy}/createColors.ts +6 -0
  333. package/{ts → src/legacy}/createColorsFromMap.ts +3 -0
  334. package/{ts → src/legacy}/interfaces.ts +4 -0
  335. package/{ts → src/legacy}/rgbaString.ts +1 -0
  336. package/src/palette/dark.ts +55 -0
  337. package/src/palette/harmony.ts +107 -0
  338. package/src/palette/index.ts +18 -0
  339. package/src/palette/semantic.ts +120 -0
  340. package/src/palette/validate.ts +82 -0
  341. package/src/scale/brand.ts +50 -0
  342. package/src/scale/contrast-scale.ts +48 -0
  343. package/src/scale/gradient.ts +91 -0
  344. package/src/scale/index.ts +5 -0
  345. package/src/scale/scale.ts +127 -0
  346. package/src/scale/tone.ts +69 -0
  347. package/src/tokens/css.ts +32 -0
  348. package/src/tokens/dtcg.ts +62 -0
  349. package/src/tokens/index.ts +11 -0
  350. package/src/tokens/tailwind.ts +24 -0
  351. package/src/tokens/tokens.ts +82 -0
  352. package/esm/createColors.d.ts +0 -6
  353. package/esm/createColors.d.ts.map +0 -1
  354. package/esm/createColors.js.map +0 -1
  355. package/esm/createColorsFromMap.d.ts +0 -9
  356. package/esm/createColorsFromMap.d.ts.map +0 -1
  357. package/esm/createColorsFromMap.js.map +0 -1
  358. package/esm/interfaces.d.ts +0 -18
  359. package/esm/interfaces.d.ts.map +0 -1
  360. package/esm/rgbHex.d.ts.map +0 -1
  361. package/esm/rgbHex.js.map +0 -1
  362. package/esm/rgbaString.d.ts +0 -6
  363. package/esm/rgbaString.js +0 -8
  364. package/esm/rgbaString.js.map +0 -1
  365. /package/{ts → src/legacy}/index.ts +0 -0
  366. /package/{ts → src/legacy}/rgbHex.ts +0 -0
@@ -0,0 +1,43 @@
1
+ import { HueMethod } from "./mix.js";
2
+ import { Color, ColorSpaceId } from "./types.js";
3
+ /** A color that needs a context to become concrete: see {@link resolve}. */
4
+ export type ColorExpr = Color | {
5
+ type: 'currentcolor';
6
+ } | {
7
+ type: 'system';
8
+ name: string;
9
+ } | {
10
+ type: 'light-dark';
11
+ light: ColorExpr;
12
+ dark: ColorExpr;
13
+ } | {
14
+ type: 'contrast-color';
15
+ color: ColorExpr;
16
+ } | {
17
+ type: 'color-mix';
18
+ space: ColorSpaceId;
19
+ hue?: HueMethod;
20
+ /** Exactly two. `percent` is 0 to 100, absent when not written. */
21
+ items: {
22
+ color: ColorExpr;
23
+ percent?: number;
24
+ }[];
25
+ } | {
26
+ /** `rgb(from red r g b)` and the like. The channels are kept as written, for `resolve` to evaluate. */
27
+ type: 'relative';
28
+ /** The function name: `rgb`, `hsl`, `hwb`, `lab`, `lch`, `oklab`, `oklch` or `color`. */
29
+ fn: string;
30
+ space: ColorSpaceId;
31
+ from: ColorExpr;
32
+ channels: [string, string, string];
33
+ alpha?: string;
34
+ };
35
+ /**
36
+ * Parses any CSS Color 4 or 5 color into a {@link ColorExpr}: everything
37
+ * `parse` takes, plus `currentcolor`, system colors, `light-dark()`,
38
+ * `color-mix()`, `contrast-color()` and relative colors such as
39
+ * `oklch(from #3b82f6 calc(l + 0.1) c h)`. A concrete color comes back as a
40
+ * plain {@link Color}; the rest are nodes that {@link resolve} turns into one.
41
+ */
42
+ export declare function parseExpr(input: string): ColorExpr;
43
+ //# sourceMappingURL=expr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expr.d.ts","names":[],"sources":["../../src/color/expr.ts"],"mappings":";;;YAKY,YACT;EACE;;EACA;EAAgB;;EAChB;EAAoB,OAAO;EAAW,MAAM;;EAC5C;EAAwB,OAAO;;EAEjC;EACA,OAAO;EACP,MAAM;;EAEN;IAAS,OAAO;IAAW;;;;EAI3B;;EAEA;EACA,OAAO;EACP,MAAM;EACN;EACA;;;;;;;;;wBAgHa,UAAU,gBAAgB"}
@@ -0,0 +1,160 @@
1
+ import { COLOR_SPACES, FUNCTIONS, VALUES_NEEDING_RESOLVE, fail, parse } from "./parse.js";
2
+ // src/color/expr.ts
3
+ const INTERPOLATION_SPACES = {
4
+ __proto__: null,
5
+ ...COLOR_SPACES,
6
+ hsl: "hsl",
7
+ hwb: "hwb",
8
+ lab: "lab",
9
+ lch: "lch",
10
+ oklab: "oklab",
11
+ oklch: "oklch"
12
+ };
13
+ const HUE_METHODS = /* @__PURE__ */ new Set([
14
+ "shorter",
15
+ "longer",
16
+ "increasing",
17
+ "decreasing"
18
+ ]);
19
+ const RELATIVE = /* @__PURE__ */ new Set([
20
+ "rgb",
21
+ "rgba",
22
+ "hsl",
23
+ "hsla",
24
+ "hwb",
25
+ "lab",
26
+ "lch",
27
+ "oklab",
28
+ "oklch",
29
+ "color"
30
+ ]);
31
+ const PERCENT = /^[+-]?(?:\d+\.?\d*|\.\d+)%$/;
32
+ /** Splits at top-level commas, or at top-level whitespace with `/` as its own item. */
33
+ function split(text, commas, input) {
34
+ const out = [];
35
+ let depth = 0;
36
+ let current = "";
37
+ const flush = () => {
38
+ if (commas || current) out.push(current.trim());
39
+ current = "";
40
+ };
41
+ for (const ch of text) {
42
+ if (ch === "(") depth++;
43
+ else if (ch === ")") depth--;
44
+ if (depth === 0 && (commas ? ch === "," : /\s|\//.test(ch))) {
45
+ flush();
46
+ if (ch === "/") out.push("/");
47
+ } else current += ch;
48
+ }
49
+ if (depth !== 0) return fail(input, "unbalanced parentheses");
50
+ flush();
51
+ return out;
52
+ }
53
+ /** Splits `name(body)`, or returns undefined when the text is not one call that ends the text. */
54
+ function call(text) {
55
+ const open = text.indexOf("(");
56
+ if (open < 1 || !text.endsWith(")") || !/^[a-z-]+$/.test(text.slice(0, open))) return void 0;
57
+ let depth = 0;
58
+ for (let i = open; i < text.length; i++) if (text[i] === "(") depth++;
59
+ else if (text[i] === ")" && --depth === 0 && i < text.length - 1) return void 0;
60
+ return [text.slice(0, open), text.slice(open + 1, -1)];
61
+ }
62
+ function colorMix(body, input) {
63
+ const [head, ...rest] = split(body, true, input);
64
+ const words = head.split(/\s+/);
65
+ if (words[0] !== "in" || !INTERPOLATION_SPACES[words[1]]) return fail(input, "color-mix() needs \"in <space>\"", words[1]);
66
+ const space = INTERPOLATION_SPACES[words[1]];
67
+ let hue;
68
+ if (words.length > 2) {
69
+ if (words.length !== 4 || words[3] !== "hue" || !HUE_METHODS.has(words[2])) return fail(input, "invalid hue interpolation", words.slice(2).join(" "));
70
+ hue = words[2];
71
+ }
72
+ if (rest.length !== 2) return fail(input, `color-mix() needs two colors, got ${rest.length}`);
73
+ const items = rest.map((text) => {
74
+ const parts = split(text, false, input);
75
+ const percents = parts.filter((p) => PERCENT.test(p));
76
+ if (parts.length - percents.length !== 1 || percents.length > 1) return fail(input, "invalid color-mix() item", text);
77
+ const percent = percents.length === 1 ? Number.parseFloat(percents[0]) : void 0;
78
+ if (percent !== void 0 && percent < 0) return fail(input, "negative percentage", text);
79
+ const color = parseExpr(parts.find((p) => !PERCENT.test(p)));
80
+ return percent === void 0 ? { color } : {
81
+ color,
82
+ percent
83
+ };
84
+ });
85
+ return hue === void 0 ? {
86
+ type: "color-mix",
87
+ space,
88
+ items
89
+ } : {
90
+ type: "color-mix",
91
+ space,
92
+ hue,
93
+ items
94
+ };
95
+ }
96
+ function relative(name, words, input) {
97
+ const from = words[1] === void 0 ? fail(input, "missing origin color") : parseExpr(words[1]);
98
+ let space;
99
+ let at = 2;
100
+ const fn = name === "rgba" ? "rgb" : name === "hsla" ? "hsl" : name;
101
+ if (fn === "color") {
102
+ const id = COLOR_SPACES[words[2]];
103
+ if (!id) return fail(input, "unknown color space", words[2]);
104
+ space = id;
105
+ at = 3;
106
+ } else space = FUNCTIONS[fn].space;
107
+ const channels = words.slice(at, at + 3);
108
+ const tail = words.slice(at + 3);
109
+ if (channels.length !== 3) return fail(input, `expected 3 components, got ${channels.length}`);
110
+ if (tail.length > 0 && (tail[0] !== "/" || tail.length !== 2)) return fail(input, "expected \"/ alpha\" after the components");
111
+ const node = {
112
+ type: "relative",
113
+ fn,
114
+ space,
115
+ from,
116
+ channels
117
+ };
118
+ if (tail.length === 2) node.alpha = tail[1];
119
+ return node;
120
+ }
121
+ /**
122
+ * Parses any CSS Color 4 or 5 color into a {@link ColorExpr}: everything
123
+ * `parse` takes, plus `currentcolor`, system colors, `light-dark()`,
124
+ * `color-mix()`, `contrast-color()` and relative colors such as
125
+ * `oklch(from #3b82f6 calc(l + 0.1) c h)`. A concrete color comes back as a
126
+ * plain {@link Color}; the rest are nodes that {@link resolve} turns into one.
127
+ */
128
+ function parseExpr(input) {
129
+ const text = input.trim().toLowerCase();
130
+ if (text === "currentcolor") return { type: "currentcolor" };
131
+ if (VALUES_NEEDING_RESOLVE.test(text)) return {
132
+ type: "system",
133
+ name: text
134
+ };
135
+ const parts = call(text);
136
+ if (!parts) return parse(text);
137
+ const [name, body] = parts;
138
+ if (name === "color-mix") return colorMix(body, input);
139
+ if (name === "light-dark") {
140
+ const list = split(body, true, input);
141
+ if (list.length !== 2) return fail(input, `light-dark() needs two colors, got ${list.length}`);
142
+ return {
143
+ type: "light-dark",
144
+ light: parseExpr(list[0]),
145
+ dark: parseExpr(list[1])
146
+ };
147
+ }
148
+ if (name === "contrast-color") return {
149
+ type: "contrast-color",
150
+ color: parseExpr(body)
151
+ };
152
+ if (RELATIVE.has(name)) {
153
+ const words = split(body, false, input);
154
+ if (words[0] === "from") return relative(name, words, input);
155
+ }
156
+ return parse(text);
157
+ }
158
+ export { parseExpr };
159
+
160
+ //# sourceMappingURL=expr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expr.js","names":[],"sources":["../../src/color/expr.ts"],"sourcesContent":["import type { HueMethod } from './mix.js'\nimport { COLOR_SPACES, FUNCTIONS, fail, parse, VALUES_NEEDING_RESOLVE } from './parse.js'\nimport type { Color, ColorSpaceId } from './types.js'\n\n/** A color that needs a context to become concrete: see {@link resolve}. */\nexport type ColorExpr =\n\t| Color\n\t| { type: 'currentcolor' }\n\t| { type: 'system'; name: string }\n\t| { type: 'light-dark'; light: ColorExpr; dark: ColorExpr }\n\t| { type: 'contrast-color'; color: ColorExpr }\n\t| {\n\t\t\ttype: 'color-mix'\n\t\t\tspace: ColorSpaceId\n\t\t\thue?: HueMethod\n\t\t\t/** Exactly two. `percent` is 0 to 100, absent when not written. */\n\t\t\titems: { color: ColorExpr; percent?: number }[]\n\t }\n\t| {\n\t\t\t/** `rgb(from red r g b)` and the like. The channels are kept as written, for `resolve` to evaluate. */\n\t\t\ttype: 'relative'\n\t\t\t/** The function name: `rgb`, `hsl`, `hwb`, `lab`, `lch`, `oklab`, `oklch` or `color`. */\n\t\t\tfn: string\n\t\t\tspace: ColorSpaceId\n\t\t\tfrom: ColorExpr\n\t\t\tchannels: [string, string, string]\n\t\t\talpha?: string\n\t }\n\nconst INTERPOLATION_SPACES: Record<string, ColorSpaceId> = {\n\t__proto__: null as never,\n\t...COLOR_SPACES,\n\thsl: 'hsl',\n\thwb: 'hwb',\n\tlab: 'lab',\n\tlch: 'lch',\n\toklab: 'oklab',\n\toklch: 'oklch'\n}\nconst HUE_METHODS = new Set(['shorter', 'longer', 'increasing', 'decreasing'])\nconst RELATIVE = new Set(['rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'lab', 'lch', 'oklab', 'oklch', 'color'])\nconst PERCENT = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)%$/\n\n/** Splits at top-level commas, or at top-level whitespace with `/` as its own item. */\nfunction split(text: string, commas: boolean, input: string): string[] {\n\tconst out: string[] = []\n\tlet depth = 0\n\tlet current = ''\n\tconst flush = (): void => {\n\t\tif (commas || current) out.push(current.trim())\n\t\tcurrent = ''\n\t}\n\tfor (const ch of text) {\n\t\tif (ch === '(') depth++\n\t\telse if (ch === ')') depth--\n\t\tif (depth === 0 && (commas ? ch === ',' : /\\s|\\//.test(ch))) {\n\t\t\tflush()\n\t\t\tif (ch === '/') out.push('/')\n\t\t} else current += ch\n\t}\n\tif (depth !== 0) return fail(input, 'unbalanced parentheses')\n\tflush()\n\treturn out\n}\n\n/** Splits `name(body)`, or returns undefined when the text is not one call that ends the text. */\nfunction call(text: string): [string, string] | undefined {\n\tconst open = text.indexOf('(')\n\tif (open < 1 || !text.endsWith(')') || !/^[a-z-]+$/.test(text.slice(0, open))) return undefined\n\tlet depth = 0\n\tfor (let i = open; i < text.length; i++) {\n\t\tif (text[i] === '(') depth++\n\t\telse if (text[i] === ')' && --depth === 0 && i < text.length - 1) return undefined\n\t}\n\treturn [text.slice(0, open), text.slice(open + 1, -1)]\n}\n\nfunction colorMix(body: string, input: string): ColorExpr {\n\tconst [head, ...rest] = split(body, true, input)\n\tconst words = head.split(/\\s+/)\n\tif (words[0] !== 'in' || !INTERPOLATION_SPACES[words[1]])\n\t\treturn fail(input, 'color-mix() needs \"in <space>\"', words[1])\n\tconst space = INTERPOLATION_SPACES[words[1]]\n\tlet hue: HueMethod | undefined\n\tif (words.length > 2) {\n\t\tif (words.length !== 4 || words[3] !== 'hue' || !HUE_METHODS.has(words[2])) {\n\t\t\treturn fail(input, 'invalid hue interpolation', words.slice(2).join(' '))\n\t\t}\n\t\thue = words[2] as HueMethod\n\t}\n\tif (rest.length !== 2) return fail(input, `color-mix() needs two colors, got ${rest.length}`)\n\tconst items = rest.map((text) => {\n\t\tconst parts = split(text, false, input)\n\t\tconst percents = parts.filter((p) => PERCENT.test(p))\n\t\tif (parts.length - percents.length !== 1 || percents.length > 1)\n\t\t\treturn fail(input, 'invalid color-mix() item', text)\n\t\tconst percent = percents.length === 1 ? Number.parseFloat(percents[0]) : undefined\n\t\tif (percent !== undefined && percent < 0) return fail(input, 'negative percentage', text)\n\t\tconst color = parseExpr(parts.find((p) => !PERCENT.test(p)) as string)\n\t\treturn percent === undefined ? { color } : { color, percent }\n\t})\n\treturn hue === undefined ? { type: 'color-mix', space, items } : { type: 'color-mix', space, hue, items }\n}\n\nfunction relative(name: string, words: string[], input: string): ColorExpr {\n\tconst from = words[1] === undefined ? fail(input, 'missing origin color') : parseExpr(words[1])\n\tlet space: ColorSpaceId\n\tlet at = 2\n\tconst fn = name === 'rgba' ? 'rgb' : name === 'hsla' ? 'hsl' : name\n\tif (fn === 'color') {\n\t\tconst id = COLOR_SPACES[words[2]]\n\t\tif (!id) return fail(input, 'unknown color space', words[2])\n\t\tspace = id\n\t\tat = 3\n\t} else space = FUNCTIONS[fn].space\n\tconst channels = words.slice(at, at + 3)\n\tconst tail = words.slice(at + 3)\n\tif (channels.length !== 3) return fail(input, `expected 3 components, got ${channels.length}`)\n\tif (tail.length > 0 && (tail[0] !== '/' || tail.length !== 2))\n\t\treturn fail(input, 'expected \"/ alpha\" after the components')\n\tconst node: Extract<ColorExpr, { type: 'relative' }> = {\n\t\ttype: 'relative',\n\t\tfn,\n\t\tspace,\n\t\tfrom,\n\t\tchannels: channels as [string, string, string]\n\t}\n\tif (tail.length === 2) node.alpha = tail[1]\n\treturn node\n}\n\n/**\n * Parses any CSS Color 4 or 5 color into a {@link ColorExpr}: everything\n * `parse` takes, plus `currentcolor`, system colors, `light-dark()`,\n * `color-mix()`, `contrast-color()` and relative colors such as\n * `oklch(from #3b82f6 calc(l + 0.1) c h)`. A concrete color comes back as a\n * plain {@link Color}; the rest are nodes that {@link resolve} turns into one.\n */\nexport function parseExpr(input: string): ColorExpr {\n\tconst text = input.trim().toLowerCase()\n\tif (text === 'currentcolor') return { type: 'currentcolor' }\n\tif (VALUES_NEEDING_RESOLVE.test(text)) return { type: 'system', name: text }\n\tconst parts = call(text)\n\tif (!parts) return parse(text)\n\tconst [name, body] = parts\n\tif (name === 'color-mix') return colorMix(body, input)\n\tif (name === 'light-dark') {\n\t\tconst list = split(body, true, input)\n\t\tif (list.length !== 2) return fail(input, `light-dark() needs two colors, got ${list.length}`)\n\t\treturn { type: 'light-dark', light: parseExpr(list[0]), dark: parseExpr(list[1]) }\n\t}\n\tif (name === 'contrast-color') return { type: 'contrast-color', color: parseExpr(body) }\n\tif (RELATIVE.has(name)) {\n\t\tconst words = split(body, false, input)\n\t\tif (words[0] === 'from') return relative(name, words, input)\n\t}\n\treturn parse(text)\n}\n"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { Color, Space } from "./types.js";
2
+ /** The bounded RGB spaces a color can be tested or mapped against. */
3
+ export type GamutId = 'srgb' | 'srgb-linear' | 'display-p3' | 'display-p3-linear' | 'a98-rgb' | 'prophoto-rgb' | 'rec2020';
4
+ /** True when every channel of `color`, seen in `gamut`, is within 0 to 1 (± `epsilon`). */
5
+ export declare function inGamut(color: Color, gamut?: GamutId, epsilon?: number): boolean;
6
+ /**
7
+ * Builds {@link inGamut} over the spaces given, plus `srgb`, so a bundle
8
+ * contains only those. Give the gamut too when it is not `srgb`. A color or
9
+ * gamut in a space not given throws, naming that space.
10
+ */
11
+ export declare function createInGamut(...spaces: Space[]): (color: Color, gamut?: GamutId, epsilon?: number) => boolean;
12
+ /** Options for `toGamut` and the function `createToGamut` returns. */
13
+ export interface ToGamutOptions {
14
+ /** The gamut to fit into. Default `srgb`. */
15
+ gamut?: GamutId;
16
+ /** `css` is the CSS Color 4 OKLCH chroma reduction. `clip` clamps each channel. Default `css`. */
17
+ method?: 'css' | 'clip';
18
+ }
19
+ /**
20
+ * Maps a color into a gamut and returns it in its original space. `css` follows
21
+ * CSS Color 4 section 13.2: reduce OKLCH chroma until the clipped color is
22
+ * within a just-noticeable ΔEOK of 0.02.
23
+ */
24
+ export declare function toGamut(color: Color, options?: ToGamutOptions): Color;
25
+ /**
26
+ * Builds {@link toGamut} over the spaces given, plus `srgb`, `oklch` and
27
+ * `oklab`, which the CSS method works in. Give the gamut too when it is not
28
+ * `srgb`. A color or gamut in a space not given throws, naming that space.
29
+ */
30
+ export declare function createToGamut(...spaces: Space[]): (color: Color, options?: ToGamutOptions) => Color;
31
+ //# sourceMappingURL=gamut.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gamut.d.ts","names":[],"sources":["../../src/color/gamut.ts"],"mappings":";;YAYY;;wBAaI,QAAQ,OAAO,OAAO,QAAO,SAAkB;;;;;;wBAS/C,iBAAiB,QAAQ,WAAW,OAAO,OAAO,QAAQ,SAAS;;iBAMlE;;EAEhB,QAAQ;;EAER;;;;;;;wBAgBe,QAAQ,OAAO,OAAO,UAAS,iBAAsB;;;;;;wBASrD,iBAAiB,QAAQ,WAAW,OAAO,OAAO,UAAU,mBAAmB"}
@@ -0,0 +1,210 @@
1
+ import { deltaEOkWith } from "./compare.js";
2
+ import { componentsOver, convertWith } from "./convert.js";
3
+ import { product } from "./math.js";
4
+ import { lmsToOklab, lmsToXyz, oklabToLms, srgbToXyz, xyzToLms, xyzToSrgb } from "./matrices.js";
5
+ import { oklab } from "./oklab.js";
6
+ import { oklch } from "./oklch.js";
7
+ import { componentsIn } from "./spaces.js";
8
+ import { srgb } from "./srgb.js";
9
+ import { srgbToGamma } from "./transfer.js";
10
+ // src/color/gamut.ts
11
+ const within = (components, epsilon) => components.every((c) => c >= -epsilon && c <= 1 + epsilon);
12
+ /** True when every channel of `color`, seen in `gamut`, is within 0 to 1 (± `epsilon`). */
13
+ function inGamut(color, gamut = "srgb", epsilon = 1e-6) {
14
+ return within(componentsIn(color, gamut), epsilon);
15
+ }
16
+ /**
17
+ * Builds {@link inGamut} over the spaces given, plus `srgb`, so a bundle
18
+ * contains only those. Give the gamut too when it is not `srgb`. A color or
19
+ * gamut in a space not given throws, naming that space.
20
+ */
21
+ function createInGamut(...spaces) {
22
+ const over = componentsOver([srgb, ...spaces]);
23
+ return (color, gamut = "srgb", epsilon = 1e-6) => within(over(color, gamut), epsilon);
24
+ }
25
+ /** `color` with `components`, which are in `gamut`, clamped to 0 to 1: what `convert` then a clamp gives. */
26
+ function clipped(color, gamut, components) {
27
+ return withAlpha({
28
+ colorSpace: gamut,
29
+ components: components.map((c) => Math.min(1, Math.max(0, c)))
30
+ }, color);
31
+ }
32
+ /**
33
+ * Maps a color into a gamut and returns it in its original space. `css` follows
34
+ * CSS Color 4 section 13.2: reduce OKLCH chroma until the clipped color is
35
+ * within a just-noticeable ΔEOK of 0.02.
36
+ */
37
+ function toGamut(color, options = {}) {
38
+ return toGamutWith(componentsIn, color, options);
39
+ }
40
+ /**
41
+ * Builds {@link toGamut} over the spaces given, plus `srgb`, `oklch` and
42
+ * `oklab`, which the CSS method works in. Give the gamut too when it is not
43
+ * `srgb`. A color or gamut in a space not given throws, naming that space.
44
+ */
45
+ function createToGamut(...spaces) {
46
+ const over = componentsOver([
47
+ srgb,
48
+ oklch,
49
+ oklab,
50
+ ...spaces
51
+ ]);
52
+ return (color, options = {}) => toGamutWith(over, color, options);
53
+ }
54
+ /** {@link toGamut} over the spaces `componentsIn` knows, which must include `oklch` and `oklab`. */
55
+ function toGamutWith(componentsIn, color, options) {
56
+ const { gamut = "srgb", method = "css" } = options;
57
+ const deltaEOk = (a, b) => deltaEOkWith(componentsIn, a, b);
58
+ const back = (c) => convertWith(componentsIn, c, color.colorSpace);
59
+ const fit = componentsIn(color, gamut);
60
+ if (within(fit, 1e-6)) return color;
61
+ if (method === "clip") return back(clipped(color, gamut, fit));
62
+ const origin = convertWith(componentsIn, color, "oklch");
63
+ const [lightness, chroma, hue] = origin.components;
64
+ if (lightness >= 1) return back(withAlpha({
65
+ colorSpace: gamut,
66
+ components: [
67
+ 1,
68
+ 1,
69
+ 1
70
+ ]
71
+ }, color));
72
+ if (lightness <= 0) return back(withAlpha({
73
+ colorSpace: gamut,
74
+ components: [
75
+ 0,
76
+ 0,
77
+ 0
78
+ ]
79
+ }, color));
80
+ const JND = .02;
81
+ const EPSILON = 1e-4;
82
+ if (gamut === "srgb") return back(reduceChromaInSrgb(origin, lightness, chroma, hue));
83
+ let min = 0;
84
+ let max = chroma;
85
+ let minInGamut = true;
86
+ let clip = clipped(origin, gamut, componentsIn(origin, gamut));
87
+ if (deltaEOk(clip, origin) < JND) return back(clip);
88
+ while (max - min > EPSILON) {
89
+ const mid = (min + max) / 2;
90
+ const current = withAlpha({
91
+ colorSpace: "oklch",
92
+ components: [
93
+ lightness,
94
+ mid,
95
+ hue
96
+ ]
97
+ }, origin);
98
+ const inGamutSpace = componentsIn(current, gamut);
99
+ if (minInGamut && within(inGamutSpace, 1e-6)) {
100
+ min = mid;
101
+ continue;
102
+ }
103
+ clip = clipped(current, gamut, inGamutSpace);
104
+ const e = deltaEOk(clip, current);
105
+ if (e < JND) {
106
+ if (JND - e < EPSILON) return back(clip);
107
+ minInGamut = false;
108
+ min = mid;
109
+ } else max = mid;
110
+ }
111
+ /* v8 ignore next */
112
+ return back(clip);
113
+ }
114
+ function withAlpha(c, source) {
115
+ if (source.alpha !== void 0) c.alpha = source.alpha;
116
+ return c;
117
+ }
118
+ let lmsToLinear;
119
+ let linearToLms;
120
+ const LOW = -1e-6 / 12.92;
121
+ const HIGH = ((1 + 1e-6 + .055) / 1.055) ** 2.4;
122
+ const encode = (n) => n === 0 || n === 1 ? n : srgbToGamma(n);
123
+ const clamp01 = (n) => Math.min(1, Math.max(0, n));
124
+ /**
125
+ * The CSS Color 4 section 13.2 search of `toGamut` for the `srgb` gamut, done in
126
+ * linear sRGB so the loop has no hub conversion, `**` or trigonometry. Clipping
127
+ * to 0 to 1 commutes with the monotone transfer curve, so it finds what the
128
+ * generic path finds, up to rounding. Returns the mapped color in `srgb`.
129
+ */
130
+ function reduceChromaInSrgb(origin, lightness, chroma, hue) {
131
+ const m = lmsToLinear ??= product(xyzToSrgb, lmsToXyz);
132
+ const n = linearToLms ??= product(xyzToLms, srgbToXyz);
133
+ const o = oklabToLms;
134
+ const k = lmsToOklab;
135
+ const l = lightness;
136
+ const radians = (hue ?? 0) * Math.PI / 180;
137
+ const cos = Math.cos(radians);
138
+ const sin = Math.sin(radians);
139
+ let r = 0;
140
+ let g = 0;
141
+ let b = 0;
142
+ let inside = false;
143
+ /** Puts the candidate of chroma `c` in linear sRGB into r, g, b (unclipped), and says if it is within the gamut. */
144
+ const place = (c) => {
145
+ const a = c * cos;
146
+ const bb = c * sin;
147
+ const x = l + o[1] * a + o[2] * bb;
148
+ const y = l + o[4] * a + o[5] * bb;
149
+ const z = l + o[7] * a + o[8] * bb;
150
+ const x3 = x * x * x;
151
+ const y3 = y * y * y;
152
+ const z3 = z * z * z;
153
+ r = m[0] * x3 + m[1] * y3 + m[2] * z3;
154
+ g = m[3] * x3 + m[4] * y3 + m[5] * z3;
155
+ b = m[6] * x3 + m[7] * y3 + m[8] * z3;
156
+ inside = r >= LOW && r <= HIGH && g >= LOW && g <= HIGH && b >= LOW && b <= HIGH;
157
+ };
158
+ /** Clips r, g, b and returns its ΔEOK to the candidate of chroma `c`. */
159
+ const clipAndMeasure = (c) => {
160
+ r = clamp01(r);
161
+ g = clamp01(g);
162
+ b = clamp01(b);
163
+ const x = Math.cbrt(n[0] * r + n[1] * g + n[2] * b);
164
+ const y = Math.cbrt(n[3] * r + n[4] * g + n[5] * b);
165
+ const z = Math.cbrt(n[6] * r + n[7] * g + n[8] * b);
166
+ const dl = k[0] * x + k[1] * y + k[2] * z - l;
167
+ const da = k[3] * x + k[4] * y + k[5] * z - c * cos;
168
+ const db = k[6] * x + k[7] * y + k[8] * z - c * sin;
169
+ return Math.sqrt(dl * dl + da * da + db * db);
170
+ };
171
+ const JND = .02;
172
+ const EPSILON = 1e-4;
173
+ let min = 0;
174
+ let max = chroma;
175
+ let minInGamut = true;
176
+ place(chroma ?? 0);
177
+ let clip;
178
+ let e = clipAndMeasure(chroma ?? 0);
179
+ clip = [
180
+ r,
181
+ g,
182
+ b
183
+ ];
184
+ if (e >= JND) while (max - min > EPSILON) {
185
+ const mid = (min + max) / 2;
186
+ place(mid);
187
+ if (minInGamut && inside) {
188
+ min = mid;
189
+ continue;
190
+ }
191
+ e = clipAndMeasure(mid);
192
+ clip = [
193
+ r,
194
+ g,
195
+ b
196
+ ];
197
+ if (e < JND) {
198
+ if (JND - e < EPSILON) break;
199
+ minInGamut = false;
200
+ min = mid;
201
+ } else max = mid;
202
+ }
203
+ return withAlpha({
204
+ colorSpace: "srgb",
205
+ components: clip.map(encode)
206
+ }, origin);
207
+ }
208
+ export { createInGamut, createToGamut, inGamut, toGamut, toGamutWith };
209
+
210
+ //# sourceMappingURL=gamut.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gamut.js","names":[],"sources":["../../src/color/gamut.ts"],"sourcesContent":["import { deltaEOkWith } from './compare.js'\nimport { type ComponentsIn, componentsOver, convertWith } from './convert.js'\nimport { type Matrix, product } from './math.js'\nimport { lmsToOklab, lmsToXyz, oklabToLms, srgbToXyz, xyzToLms, xyzToSrgb } from './matrices.js'\nimport { oklab } from './oklab.js'\nimport { oklch } from './oklch.js'\nimport { componentsIn } from './spaces.js'\nimport { srgb } from './srgb.js'\nimport { srgbToGamma } from './transfer.js'\nimport type { Color, ColorSpaceId, Component, Space, Vec3 } from './types.js'\n\n/** The bounded RGB spaces a color can be tested or mapped against. */\nexport type GamutId =\n\t| 'srgb'\n\t| 'srgb-linear'\n\t| 'display-p3'\n\t| 'display-p3-linear'\n\t| 'a98-rgb'\n\t| 'prophoto-rgb'\n\t| 'rec2020'\n\nconst within = (components: Component[], epsilon: number): boolean =>\n\tcomponents.every((c) => (c as number) >= -epsilon && (c as number) <= 1 + epsilon)\n\n/** True when every channel of `color`, seen in `gamut`, is within 0 to 1 (± `epsilon`). */\nexport function inGamut(color: Color, gamut: GamutId = 'srgb', epsilon = 1e-6): boolean {\n\treturn within(componentsIn(color, gamut), epsilon)\n}\n\n/**\n * Builds {@link inGamut} over the spaces given, plus `srgb`, so a bundle\n * contains only those. Give the gamut too when it is not `srgb`. A color or\n * gamut in a space not given throws, naming that space.\n */\nexport function createInGamut(...spaces: Space[]): (color: Color, gamut?: GamutId, epsilon?: number) => boolean {\n\tconst over = componentsOver([srgb, ...spaces])\n\treturn (color, gamut = 'srgb', epsilon = 1e-6) => within(over(color, gamut), epsilon)\n}\n\n/** Options for `toGamut` and the function `createToGamut` returns. */\nexport interface ToGamutOptions {\n\t/** The gamut to fit into. Default `srgb`. */\n\tgamut?: GamutId\n\t/** `css` is the CSS Color 4 OKLCH chroma reduction. `clip` clamps each channel. Default `css`. */\n\tmethod?: 'css' | 'clip'\n}\n\n/** `color` with `components`, which are in `gamut`, clamped to 0 to 1: what `convert` then a clamp gives. */\nfunction clipped(color: Color, gamut: GamutId, components: Component[]): Color {\n\treturn withAlpha(\n\t\t{ colorSpace: gamut, components: components.map((c) => Math.min(1, Math.max(0, c as number))) },\n\t\tcolor\n\t)\n}\n\n/**\n * Maps a color into a gamut and returns it in its original space. `css` follows\n * CSS Color 4 section 13.2: reduce OKLCH chroma until the clipped color is\n * within a just-noticeable ΔEOK of 0.02.\n */\nexport function toGamut(color: Color, options: ToGamutOptions = {}): Color {\n\treturn toGamutWith(componentsIn, color, options)\n}\n\n/**\n * Builds {@link toGamut} over the spaces given, plus `srgb`, `oklch` and\n * `oklab`, which the CSS method works in. Give the gamut too when it is not\n * `srgb`. A color or gamut in a space not given throws, naming that space.\n */\nexport function createToGamut(...spaces: Space[]): (color: Color, options?: ToGamutOptions) => Color {\n\tconst over = componentsOver([srgb, oklch, oklab, ...spaces])\n\treturn (color, options = {}) => toGamutWith(over, color, options)\n}\n\n/** {@link toGamut} over the spaces `componentsIn` knows, which must include `oklch` and `oklab`. */\nexport function toGamutWith(componentsIn: ComponentsIn, color: Color, options: ToGamutOptions): Color {\n\tconst { gamut = 'srgb', method = 'css' } = options\n\tconst deltaEOk = (a: Color, b: Color): number => deltaEOkWith(componentsIn, a, b)\n\tconst back = (c: Color): Color => convertWith(componentsIn, c, color.colorSpace as ColorSpaceId)\n\t// Each candidate is converted to the gamut once, then tested and clipped from that.\n\tconst fit = componentsIn(color, gamut)\n\tif (within(fit, 1e-6)) return color\n\tif (method === 'clip') return back(clipped(color, gamut, fit))\n\n\tconst origin = convertWith(componentsIn, color, 'oklch')\n\tconst [lightness, chroma, hue] = origin.components as [number, number, Component]\n\tif (lightness >= 1) return back(withAlpha({ colorSpace: gamut, components: [1, 1, 1] }, color))\n\tif (lightness <= 0) return back(withAlpha({ colorSpace: gamut, components: [0, 0, 0] }, color))\n\n\tconst JND = 0.02\n\tconst EPSILON = 0.0001\n\tif (gamut === 'srgb') return back(reduceChromaInSrgb(origin, lightness, chroma, hue))\n\tlet min = 0\n\tlet max = chroma\n\tlet minInGamut = true\n\tlet clip = clipped(origin, gamut, componentsIn(origin, gamut))\n\tif (deltaEOk(clip, origin) < JND) return back(clip)\n\twhile (max - min > EPSILON) {\n\t\tconst mid = (min + max) / 2\n\t\tconst current = withAlpha({ colorSpace: 'oklch', components: [lightness, mid, hue] }, origin)\n\t\tconst inGamutSpace = componentsIn(current, gamut)\n\t\tif (minInGamut && within(inGamutSpace, 1e-6)) {\n\t\t\tmin = mid\n\t\t\tcontinue\n\t\t}\n\t\tclip = clipped(current, gamut, inGamutSpace)\n\t\tconst e = deltaEOk(clip, current)\n\t\tif (e < JND) {\n\t\t\tif (JND - e < EPSILON) return back(clip)\n\t\t\tminInGamut = false\n\t\t\tmin = mid\n\t\t} else {\n\t\t\tmax = mid\n\t\t}\n\t}\n\t// The search ended by narrowing to EPSILON rather than by an early return. A\n\t// sweep of 88,000 colors never gets here, but the algorithm allows it.\n\t/* v8 ignore next */\n\treturn back(clip)\n}\n\nfunction withAlpha(c: Color, source: Color): Color {\n\tif (source.alpha !== undefined) c.alpha = source.alpha\n\treturn c\n}\n\nlet lmsToLinear: Matrix | undefined\nlet linearToLms: Matrix | undefined\n// The gamma-space tolerance of 1e-6 on each side, as a linear one: the transfer curve is monotone.\nconst LOW = -1e-6 / 12.92\nconst HIGH = ((1 + 1e-6 + 0.055) / 1.055) ** 2.4\n\n// A clipped channel of exactly 0 or 1 stays exactly that, as clipping after the curve gives.\nconst encode = (n: number): number => (n === 0 || n === 1 ? n : srgbToGamma(n))\nconst clamp01 = (n: number): number => Math.min(1, Math.max(0, n))\n\n/**\n * The CSS Color 4 section 13.2 search of `toGamut` for the `srgb` gamut, done in\n * linear sRGB so the loop has no hub conversion, `**` or trigonometry. Clipping\n * to 0 to 1 commutes with the monotone transfer curve, so it finds what the\n * generic path finds, up to rounding. Returns the mapped color in `srgb`.\n */\nfunction reduceChromaInSrgb(origin: Color, lightness: number, chroma: number, hue: Component): Color {\n\t// Composed on first use, so a bundle without this path does not pay for them at load.\n\tconst m = (lmsToLinear ??= product(xyzToSrgb, lmsToXyz))\n\tconst n = (linearToLms ??= product(xyzToLms, srgbToXyz))\n\tconst o = oklabToLms\n\tconst k = lmsToOklab\n\t// A `none` hue or chroma reads as 0, as it does in a conversion. A `none` lightness never gets here.\n\tconst l = lightness\n\tconst radians = ((hue ?? 0) * Math.PI) / 180\n\tconst cos = Math.cos(radians)\n\tconst sin = Math.sin(radians)\n\tlet r = 0\n\tlet g = 0\n\tlet b = 0\n\tlet inside = false\n\t/** Puts the candidate of chroma `c` in linear sRGB into r, g, b (unclipped), and says if it is within the gamut. */\n\tconst place = (c: number): void => {\n\t\tconst a = c * cos\n\t\tconst bb = c * sin\n\t\tconst x = l + o[1] * a + o[2] * bb\n\t\tconst y = l + o[4] * a + o[5] * bb\n\t\tconst z = l + o[7] * a + o[8] * bb\n\t\tconst x3 = x * x * x\n\t\tconst y3 = y * y * y\n\t\tconst z3 = z * z * z\n\t\tr = m[0] * x3 + m[1] * y3 + m[2] * z3\n\t\tg = m[3] * x3 + m[4] * y3 + m[5] * z3\n\t\tb = m[6] * x3 + m[7] * y3 + m[8] * z3\n\t\tinside = r >= LOW && r <= HIGH && g >= LOW && g <= HIGH && b >= LOW && b <= HIGH\n\t}\n\t/** Clips r, g, b and returns its ΔEOK to the candidate of chroma `c`. */\n\tconst clipAndMeasure = (c: number): number => {\n\t\tr = clamp01(r)\n\t\tg = clamp01(g)\n\t\tb = clamp01(b)\n\t\tconst x = Math.cbrt(n[0] * r + n[1] * g + n[2] * b)\n\t\tconst y = Math.cbrt(n[3] * r + n[4] * g + n[5] * b)\n\t\tconst z = Math.cbrt(n[6] * r + n[7] * g + n[8] * b)\n\t\tconst dl = k[0] * x + k[1] * y + k[2] * z - l\n\t\tconst da = k[3] * x + k[4] * y + k[5] * z - c * cos\n\t\tconst db = k[6] * x + k[7] * y + k[8] * z - c * sin\n\t\treturn Math.sqrt(dl * dl + da * da + db * db)\n\t}\n\tconst JND = 0.02\n\tconst EPSILON = 0.0001\n\tlet min = 0\n\tlet max = chroma\n\tlet minInGamut = true\n\tplace(chroma ?? 0)\n\tlet clip: Vec3\n\tlet e = clipAndMeasure(chroma ?? 0)\n\tclip = [r, g, b]\n\tif (e >= JND) {\n\t\twhile (max - min > EPSILON) {\n\t\t\tconst mid = (min + max) / 2\n\t\t\tplace(mid)\n\t\t\tif (minInGamut && inside) {\n\t\t\t\tmin = mid\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\te = clipAndMeasure(mid)\n\t\t\tclip = [r, g, b]\n\t\t\tif (e < JND) {\n\t\t\t\tif (JND - e < EPSILON) break\n\t\t\t\tminInGamut = false\n\t\t\t\tmin = mid\n\t\t\t} else {\n\t\t\t\tmax = mid\n\t\t\t}\n\t\t}\n\t}\n\treturn withAlpha({ colorSpace: 'srgb', components: clip.map(encode) }, origin)\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Space } from "./types.js";
2
+ /**
3
+ * The CSS `hsl` space. Components are hue in degrees, then saturation and lightness in 0 to 100.
4
+ * Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.
5
+ */
6
+ export declare const hsl: Space;
7
+ //# sourceMappingURL=hsl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hsl.d.ts","names":[],"sources":["../../src/color/hsl.ts"],"mappings":";;;;;qBAiDa,KAAK"}
@@ -0,0 +1,67 @@
1
+ import { normalizeHue } from "./math.js";
2
+ import { srgb } from "./srgb.js";
3
+ // src/color/hsl.ts
4
+ function channel(t1, t2, sextant) {
5
+ const h = (sextant + 6) % 6;
6
+ if (h < 1) return (t2 - t1) * h + t1;
7
+ if (h < 3) return t2;
8
+ if (h < 4) return (t2 - t1) * (4 - h) + t1;
9
+ return t1;
10
+ }
11
+ /** CSS Color 4 hslToRgb. Hue in degrees, s and l in 0 to 1. */
12
+ function hslToSrgb(h, s, l) {
13
+ const t2 = l <= .5 ? l * (s + 1) : l + s - l * s;
14
+ const t1 = l * 2 - t2;
15
+ const hue = normalizeHue(h) / 60;
16
+ return [
17
+ channel(t1, t2, hue + 2),
18
+ channel(t1, t2, hue),
19
+ channel(t1, t2, hue - 2)
20
+ ];
21
+ }
22
+ /**
23
+ * CSS Color 4 rgbToHsl. Returns hue in degrees, s and l in 0 to 1. For
24
+ * out-of-gamut input the saturation can come out negative; it is made positive
25
+ * and the hue turned by 180 degrees, unless `flipNegative` is false.
26
+ */
27
+ function srgbToHsl([r, g, b], flipNegative = true) {
28
+ const max = Math.max(r, g, b);
29
+ const min = Math.min(r, g, b);
30
+ const l = (min + max) / 2;
31
+ const d = max - min;
32
+ let h = 0;
33
+ let s = 0;
34
+ if (d > 1e-9) {
35
+ s = l === 0 || l === 1 ? 0 : (max - l) / Math.min(l, 1 - l);
36
+ h = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
37
+ h *= 60;
38
+ }
39
+ if (s < 0) {
40
+ if (flipNegative) h += 180;
41
+ s = -s;
42
+ }
43
+ return [
44
+ normalizeHue(h),
45
+ s,
46
+ l
47
+ ];
48
+ }
49
+ /**
50
+ * The CSS `hsl` space. Components are hue in degrees, then saturation and lightness in 0 to 100.
51
+ * Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.
52
+ */
53
+ const hsl = {
54
+ id: "hsl",
55
+ toXyz: ([h, s, l]) => srgb.toXyz(hslToSrgb(h, s / 100, l / 100)),
56
+ fromXyz: (xyz) => {
57
+ const [h, s, l] = srgbToHsl(srgb.fromXyz(xyz));
58
+ return [
59
+ h,
60
+ s * 100,
61
+ l * 100
62
+ ];
63
+ }
64
+ };
65
+ export { hsl, hslToSrgb, srgbToHsl };
66
+
67
+ //# sourceMappingURL=hsl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hsl.js","names":[],"sources":["../../src/color/hsl.ts"],"sourcesContent":["import { normalizeHue } from './math.js'\nimport { srgb } from './srgb.js'\nimport type { Space, Vec3 } from './types.js'\n\nfunction channel(t1: number, t2: number, sextant: number): number {\n\tconst h = (sextant + 6) % 6\n\tif (h < 1) return (t2 - t1) * h + t1\n\tif (h < 3) return t2\n\tif (h < 4) return (t2 - t1) * (4 - h) + t1\n\treturn t1\n}\n\n/** CSS Color 4 hslToRgb. Hue in degrees, s and l in 0 to 1. */\nexport function hslToSrgb(h: number, s: number, l: number): Vec3 {\n\tconst t2 = l <= 0.5 ? l * (s + 1) : l + s - l * s\n\tconst t1 = l * 2 - t2\n\tconst hue = normalizeHue(h) / 60\n\treturn [channel(t1, t2, hue + 2), channel(t1, t2, hue), channel(t1, t2, hue - 2)]\n}\n\n/**\n * CSS Color 4 rgbToHsl. Returns hue in degrees, s and l in 0 to 1. For\n * out-of-gamut input the saturation can come out negative; it is made positive\n * and the hue turned by 180 degrees, unless `flipNegative` is false.\n */\nexport function srgbToHsl([r, g, b]: Vec3, flipNegative = true): Vec3 {\n\tconst max = Math.max(r, g, b)\n\tconst min = Math.min(r, g, b)\n\tconst l = (min + max) / 2\n\tconst d = max - min\n\tlet h = 0\n\tlet s = 0\n\t// A spread of rounding noise is a gray: a round trip leaves white at 1 - 1e-16.\n\tif (d > 1e-9) {\n\t\ts = l === 0 || l === 1 ? 0 : (max - l) / Math.min(l, 1 - l)\n\t\th = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4\n\t\th *= 60\n\t}\n\tif (s < 0) {\n\t\tif (flipNegative) h += 180\n\t\ts = -s\n\t}\n\treturn [normalizeHue(h), s, l]\n}\n\n/**\n * The CSS `hsl` space. Components are hue in degrees, then saturation and lightness in 0 to 100.\n * Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.\n */\nexport const hsl: Space = {\n\tid: 'hsl',\n\ttoXyz: ([h, s, l]) => srgb.toXyz(hslToSrgb(h, s / 100, l / 100)),\n\tfromXyz: (xyz) => {\n\t\tconst [h, s, l] = srgbToHsl(srgb.fromXyz(xyz))\n\t\treturn [h, s * 100, l * 100]\n\t}\n}\n"],"mappings":";;;AAIA,SAAS,QAAQ,IAAY,IAAY,SAAyB;CACjE,MAAM,KAAK,UAAU,KAAK;CAC1B,IAAI,IAAI,GAAG,QAAQ,KAAK,MAAM,IAAI;CAClC,IAAI,IAAI,GAAG,OAAO;CAClB,IAAI,IAAI,GAAG,QAAQ,KAAK,OAAO,IAAI,KAAK;CACxC,OAAO;AACR;;AAGA,SAAgB,UAAU,GAAW,GAAW,GAAiB;CAChE,MAAM,KAAK,KAAK,KAAM,KAAK,IAAI,KAAK,IAAI,IAAI,IAAI;CAChD,MAAM,KAAK,IAAI,IAAI;CACnB,MAAM,MAAM,aAAa,CAAC,IAAI;CAC9B,OAAO;EAAC,QAAQ,IAAI,IAAI,MAAM,CAAC;EAAG,QAAQ,IAAI,IAAI,GAAG;EAAG,QAAQ,IAAI,IAAI,MAAM,CAAC;CAAC;AACjF;;;;;;AAOA,SAAgB,UAAU,CAAC,GAAG,GAAG,IAAU,eAAe,MAAY;CACrE,MAAM,MAAM,KAAK,IAAI,GAAG,GAAG,CAAC;CAC5B,MAAM,MAAM,KAAK,IAAI,GAAG,GAAG,CAAC;CAC5B,MAAM,KAAK,MAAM,OAAO;CACxB,MAAM,IAAI,MAAM;CAChB,IAAI,IAAI;CACR,IAAI,IAAI;CAER,IAAI,IAAI,MAAM;EACb,IAAI,MAAM,KAAK,MAAM,IAAI,KAAK,MAAM,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;EAC1D,IAAI,QAAQ,KAAK,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,QAAQ,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI;EAC5F,KAAK;CACN;CACA,IAAI,IAAI,GAAG;EACV,IAAI,cAAc,KAAK;EACvB,IAAI,CAAC;CACN;CACA,OAAO;EAAC,aAAa,CAAC;EAAG;EAAG;CAAC;AAC9B;;;;;AAMA,MAAa,MAAa;CACzB,IAAI;CACJ,QAAQ,CAAC,GAAG,GAAG,OAAO,KAAK,MAAM,UAAU,GAAG,IAAI,KAAK,IAAI,GAAG,CAAC;CAC/D,UAAU,QAAQ;EACjB,MAAM,CAAC,GAAG,GAAG,KAAK,UAAU,KAAK,QAAQ,GAAG,CAAC;EAC7C,OAAO;GAAC;GAAG,IAAI;GAAK,IAAI;EAAG;CAC5B;AACD"}
@@ -0,0 +1,7 @@
1
+ import { Space } from "./types.js";
2
+ /**
3
+ * The CSS `hwb` space. Components are hue in degrees, then whiteness and blackness in 0 to 100.
4
+ * Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.
5
+ */
6
+ export declare const hwb: Space;
7
+ //# sourceMappingURL=hwb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hwb.d.ts","names":[],"sources":["../../src/color/hwb.ts"],"mappings":";;;;;qBAQa,KAAK"}
@@ -0,0 +1,40 @@
1
+ import { hslToSrgb, srgbToHsl } from "./hsl.js";
2
+ import { srgb } from "./srgb.js";
3
+ // src/color/hwb.ts
4
+ /**
5
+ * The CSS `hwb` space. Components are hue in degrees, then whiteness and blackness in 0 to 100.
6
+ * Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.
7
+ */
8
+ const hwb = {
9
+ id: "hwb",
10
+ toXyz: ([h, w, b]) => {
11
+ const white = w / 100;
12
+ const black = b / 100;
13
+ if (white + black >= 1) {
14
+ const gray = white / (white + black);
15
+ return srgb.toXyz([
16
+ gray,
17
+ gray,
18
+ gray
19
+ ]);
20
+ }
21
+ const k = 1 - white - black;
22
+ const [red, green, blue] = hslToSrgb(h, 1, .5);
23
+ return srgb.toXyz([
24
+ red * k + white,
25
+ green * k + white,
26
+ blue * k + white
27
+ ]);
28
+ },
29
+ fromXyz: (xyz) => {
30
+ const rgb = srgb.fromXyz(xyz);
31
+ return [
32
+ srgbToHsl(rgb, false)[0],
33
+ Math.min(...rgb) * 100,
34
+ (1 - Math.max(...rgb)) * 100
35
+ ];
36
+ }
37
+ };
38
+ export { hwb };
39
+
40
+ //# sourceMappingURL=hwb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hwb.js","names":[],"sources":["../../src/color/hwb.ts"],"sourcesContent":["import { hslToSrgb, srgbToHsl } from './hsl.js'\nimport { srgb } from './srgb.js'\nimport type { Space } from './types.js'\n\n/**\n * The CSS `hwb` space. Components are hue in degrees, then whiteness and blackness in 0 to 100.\n * Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.\n */\nexport const hwb: Space = {\n\tid: 'hwb',\n\ttoXyz: ([h, w, b]) => {\n\t\tconst white = w / 100\n\t\tconst black = b / 100\n\t\tif (white + black >= 1) {\n\t\t\tconst gray = white / (white + black)\n\t\t\treturn srgb.toXyz([gray, gray, gray])\n\t\t}\n\t\tconst k = 1 - white - black\n\t\tconst [red, green, blue] = hslToSrgb(h, 1, 0.5)\n\t\treturn srgb.toXyz([red * k + white, green * k + white, blue * k + white])\n\t},\n\tfromXyz: (xyz) => {\n\t\tconst rgb = srgb.fromXyz(xyz)\n\t\treturn [srgbToHsl(rgb, false)[0], Math.min(...rgb) * 100, (1 - Math.max(...rgb)) * 100]\n\t}\n}\n"],"mappings":";;;;;;;AAQA,MAAa,MAAa;CACzB,IAAI;CACJ,QAAQ,CAAC,GAAG,GAAG,OAAO;EACrB,MAAM,QAAQ,IAAI;EAClB,MAAM,QAAQ,IAAI;EAClB,IAAI,QAAQ,SAAS,GAAG;GACvB,MAAM,OAAO,SAAS,QAAQ;GAC9B,OAAO,KAAK,MAAM;IAAC;IAAM;IAAM;GAAI,CAAC;EACrC;EACA,MAAM,IAAI,IAAI,QAAQ;EACtB,MAAM,CAAC,KAAK,OAAO,QAAQ,UAAU,GAAG,GAAG,EAAG;EAC9C,OAAO,KAAK,MAAM;GAAC,MAAM,IAAI;GAAO,QAAQ,IAAI;GAAO,OAAO,IAAI;EAAK,CAAC;CACzE;CACA,UAAU,QAAQ;EACjB,MAAM,MAAM,KAAK,QAAQ,GAAG;EAC5B,OAAO;GAAC,UAAU,KAAK,KAAK,CAAC,CAAC;GAAI,KAAK,IAAI,GAAG,GAAG,IAAI;IAAM,IAAI,KAAK,IAAI,GAAG,GAAG,KAAK;EAAG;CACvF;AACD"}