@oxyhq/bloom 0.36.0 → 0.37.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.
Files changed (235) hide show
  1. package/lib/commonjs/theme/build-theme.js +31 -43
  2. package/lib/commonjs/theme/build-theme.js.map +1 -1
  3. package/lib/commonjs/theme/color-engine/cam16.js +161 -0
  4. package/lib/commonjs/theme/color-engine/cam16.js.map +1 -0
  5. package/lib/commonjs/theme/color-engine/color-role.js +183 -0
  6. package/lib/commonjs/theme/color-engine/color-role.js.map +1 -0
  7. package/lib/commonjs/theme/color-engine/color-roles.js +416 -0
  8. package/lib/commonjs/theme/color-engine/color-roles.js.map +1 -0
  9. package/lib/commonjs/theme/color-engine/color-utils.js +157 -0
  10. package/lib/commonjs/theme/color-engine/color-utils.js.map +1 -0
  11. package/lib/commonjs/theme/color-engine/contrast-curve.js +32 -0
  12. package/lib/commonjs/theme/color-engine/contrast-curve.js.map +1 -0
  13. package/lib/commonjs/theme/color-engine/contrast.js +70 -0
  14. package/lib/commonjs/theme/color-engine/contrast.js.map +1 -0
  15. package/lib/commonjs/theme/color-engine/dislike-analyzer.js +26 -0
  16. package/lib/commonjs/theme/color-engine/dislike-analyzer.js.map +1 -0
  17. package/lib/commonjs/theme/color-engine/dynamic-scheme.js +70 -0
  18. package/lib/commonjs/theme/color-engine/dynamic-scheme.js.map +1 -0
  19. package/lib/commonjs/theme/color-engine/hct-solver.js +236 -0
  20. package/lib/commonjs/theme/color-engine/hct-solver.js.map +1 -0
  21. package/lib/commonjs/theme/color-engine/hct.js +67 -0
  22. package/lib/commonjs/theme/color-engine/hct.js.map +1 -0
  23. package/lib/commonjs/theme/color-engine/index.js +173 -0
  24. package/lib/commonjs/theme/color-engine/index.js.map +1 -0
  25. package/lib/commonjs/theme/color-engine/lab-point-provider.js +32 -0
  26. package/lib/commonjs/theme/color-engine/lab-point-provider.js.map +1 -0
  27. package/lib/commonjs/theme/color-engine/math-utils.js +77 -0
  28. package/lib/commonjs/theme/color-engine/math-utils.js.map +1 -0
  29. package/lib/commonjs/theme/color-engine/quantizer-map.js +24 -0
  30. package/lib/commonjs/theme/color-engine/quantizer-map.js.map +1 -0
  31. package/lib/commonjs/theme/color-engine/quantizer-wu.js +266 -0
  32. package/lib/commonjs/theme/color-engine/quantizer-wu.js.map +1 -0
  33. package/lib/commonjs/theme/color-engine/scheme-variants.js +102 -0
  34. package/lib/commonjs/theme/color-engine/scheme-variants.js.map +1 -0
  35. package/lib/commonjs/theme/color-engine/score.js +80 -0
  36. package/lib/commonjs/theme/color-engine/score.js.map +1 -0
  37. package/lib/commonjs/theme/color-engine/seed-from-image.js +73 -0
  38. package/lib/commonjs/theme/color-engine/seed-from-image.js.map +1 -0
  39. package/lib/commonjs/theme/color-engine/tonal-palette.js +95 -0
  40. package/lib/commonjs/theme/color-engine/tonal-palette.js.map +1 -0
  41. package/lib/commonjs/theme/color-engine/tone-delta-pair.js +26 -0
  42. package/lib/commonjs/theme/color-engine/tone-delta-pair.js.map +1 -0
  43. package/lib/commonjs/theme/color-engine/variant.js +26 -0
  44. package/lib/commonjs/theme/color-engine/variant.js.map +1 -0
  45. package/lib/commonjs/theme/color-engine/viewing-conditions.js +61 -0
  46. package/lib/commonjs/theme/color-engine/viewing-conditions.js.map +1 -0
  47. package/lib/commonjs/theme/color-presets.js +44 -572
  48. package/lib/commonjs/theme/color-presets.js.map +1 -1
  49. package/lib/commonjs/theme/preset-vars.js +64 -47
  50. package/lib/commonjs/theme/preset-vars.js.map +1 -1
  51. package/lib/module/theme/build-theme.js +32 -44
  52. package/lib/module/theme/build-theme.js.map +1 -1
  53. package/lib/module/theme/color-engine/cam16.js +155 -0
  54. package/lib/module/theme/color-engine/cam16.js.map +1 -0
  55. package/lib/module/theme/color-engine/color-role.js +177 -0
  56. package/lib/module/theme/color-engine/color-role.js.map +1 -0
  57. package/lib/module/theme/color-engine/color-roles.js +411 -0
  58. package/lib/module/theme/color-engine/color-roles.js.map +1 -0
  59. package/lib/module/theme/color-engine/color-utils.js +135 -0
  60. package/lib/module/theme/color-engine/color-utils.js.map +1 -0
  61. package/lib/module/theme/color-engine/contrast-curve.js +26 -0
  62. package/lib/module/theme/color-engine/contrast-curve.js.map +1 -0
  63. package/lib/module/theme/color-engine/contrast.js +61 -0
  64. package/lib/module/theme/color-engine/contrast.js.map +1 -0
  65. package/lib/module/theme/color-engine/dislike-analyzer.js +20 -0
  66. package/lib/module/theme/color-engine/dislike-analyzer.js.map +1 -0
  67. package/lib/module/theme/color-engine/dynamic-scheme.js +65 -0
  68. package/lib/module/theme/color-engine/dynamic-scheme.js.map +1 -0
  69. package/lib/module/theme/color-engine/hct-solver.js +231 -0
  70. package/lib/module/theme/color-engine/hct-solver.js.map +1 -0
  71. package/lib/module/theme/color-engine/hct.js +61 -0
  72. package/lib/module/theme/color-engine/hct.js.map +1 -0
  73. package/lib/module/theme/color-engine/index.js +57 -0
  74. package/lib/module/theme/color-engine/index.js.map +1 -0
  75. package/lib/module/theme/color-engine/lab-point-provider.js +25 -0
  76. package/lib/module/theme/color-engine/lab-point-provider.js.map +1 -0
  77. package/lib/module/theme/color-engine/math-utils.js +65 -0
  78. package/lib/module/theme/color-engine/math-utils.js.map +1 -0
  79. package/lib/module/theme/color-engine/quantizer-map.js +20 -0
  80. package/lib/module/theme/color-engine/quantizer-map.js.map +1 -0
  81. package/lib/module/theme/color-engine/quantizer-wu.js +261 -0
  82. package/lib/module/theme/color-engine/quantizer-wu.js.map +1 -0
  83. package/lib/module/theme/color-engine/scheme-variants.js +93 -0
  84. package/lib/module/theme/color-engine/scheme-variants.js.map +1 -0
  85. package/lib/module/theme/color-engine/score.js +75 -0
  86. package/lib/module/theme/color-engine/score.js.map +1 -0
  87. package/lib/module/theme/color-engine/seed-from-image.js +67 -0
  88. package/lib/module/theme/color-engine/seed-from-image.js.map +1 -0
  89. package/lib/module/theme/color-engine/tonal-palette.js +89 -0
  90. package/lib/module/theme/color-engine/tonal-palette.js.map +1 -0
  91. package/lib/module/theme/color-engine/tone-delta-pair.js +21 -0
  92. package/lib/module/theme/color-engine/tone-delta-pair.js.map +1 -0
  93. package/lib/module/theme/color-engine/variant.js +22 -0
  94. package/lib/module/theme/color-engine/variant.js.map +1 -0
  95. package/lib/module/theme/color-engine/viewing-conditions.js +55 -0
  96. package/lib/module/theme/color-engine/viewing-conditions.js.map +1 -0
  97. package/lib/module/theme/color-presets.js +44 -572
  98. package/lib/module/theme/color-presets.js.map +1 -1
  99. package/lib/module/theme/preset-vars.js +64 -46
  100. package/lib/module/theme/preset-vars.js.map +1 -1
  101. package/lib/typescript/commonjs/theme/build-theme.d.ts.map +1 -1
  102. package/lib/typescript/commonjs/theme/color-engine/cam16.d.ts +26 -0
  103. package/lib/typescript/commonjs/theme/color-engine/cam16.d.ts.map +1 -0
  104. package/lib/typescript/commonjs/theme/color-engine/color-role.d.ts +36 -0
  105. package/lib/typescript/commonjs/theme/color-engine/color-role.d.ts.map +1 -0
  106. package/lib/typescript/commonjs/theme/color-engine/color-roles.d.ts +58 -0
  107. package/lib/typescript/commonjs/theme/color-engine/color-roles.d.ts.map +1 -0
  108. package/lib/typescript/commonjs/theme/color-engine/color-utils.d.ts +32 -0
  109. package/lib/typescript/commonjs/theme/color-engine/color-utils.d.ts.map +1 -0
  110. package/lib/typescript/commonjs/theme/color-engine/contrast-curve.d.ts +9 -0
  111. package/lib/typescript/commonjs/theme/color-engine/contrast-curve.d.ts.map +1 -0
  112. package/lib/typescript/commonjs/theme/color-engine/contrast.d.ts +12 -0
  113. package/lib/typescript/commonjs/theme/color-engine/contrast.d.ts.map +1 -0
  114. package/lib/typescript/commonjs/theme/color-engine/dislike-analyzer.d.ts +11 -0
  115. package/lib/typescript/commonjs/theme/color-engine/dislike-analyzer.d.ts.map +1 -0
  116. package/lib/typescript/commonjs/theme/color-engine/dynamic-scheme.d.ts +47 -0
  117. package/lib/typescript/commonjs/theme/color-engine/dynamic-scheme.d.ts.map +1 -0
  118. package/lib/typescript/commonjs/theme/color-engine/hct-solver.d.ts +6 -0
  119. package/lib/typescript/commonjs/theme/color-engine/hct-solver.d.ts.map +1 -0
  120. package/lib/typescript/commonjs/theme/color-engine/hct.d.ts +20 -0
  121. package/lib/typescript/commonjs/theme/color-engine/hct.d.ts.map +1 -0
  122. package/lib/typescript/commonjs/theme/color-engine/index.d.ts +44 -0
  123. package/lib/typescript/commonjs/theme/color-engine/index.d.ts.map +1 -0
  124. package/lib/typescript/commonjs/theme/color-engine/lab-point-provider.d.ts +6 -0
  125. package/lib/typescript/commonjs/theme/color-engine/lab-point-provider.d.ts.map +1 -0
  126. package/lib/typescript/commonjs/theme/color-engine/math-utils.d.ts +46 -0
  127. package/lib/typescript/commonjs/theme/color-engine/math-utils.d.ts.map +1 -0
  128. package/lib/typescript/commonjs/theme/color-engine/quantizer-map.d.ts +3 -0
  129. package/lib/typescript/commonjs/theme/color-engine/quantizer-map.d.ts.map +1 -0
  130. package/lib/typescript/commonjs/theme/color-engine/quantizer-wu.d.ts +3 -0
  131. package/lib/typescript/commonjs/theme/color-engine/quantizer-wu.d.ts.map +1 -0
  132. package/lib/typescript/commonjs/theme/color-engine/scheme-variants.d.ts +23 -0
  133. package/lib/typescript/commonjs/theme/color-engine/scheme-variants.d.ts.map +1 -0
  134. package/lib/typescript/commonjs/theme/color-engine/score.d.ts +8 -0
  135. package/lib/typescript/commonjs/theme/color-engine/score.d.ts.map +1 -0
  136. package/lib/typescript/commonjs/theme/color-engine/seed-from-image.d.ts +13 -0
  137. package/lib/typescript/commonjs/theme/color-engine/seed-from-image.d.ts.map +1 -0
  138. package/lib/typescript/commonjs/theme/color-engine/tonal-palette.d.ts +24 -0
  139. package/lib/typescript/commonjs/theme/color-engine/tonal-palette.d.ts.map +1 -0
  140. package/lib/typescript/commonjs/theme/color-engine/tone-delta-pair.d.ts +19 -0
  141. package/lib/typescript/commonjs/theme/color-engine/tone-delta-pair.d.ts.map +1 -0
  142. package/lib/typescript/commonjs/theme/color-engine/variant.d.ts +19 -0
  143. package/lib/typescript/commonjs/theme/color-engine/variant.d.ts.map +1 -0
  144. package/lib/typescript/commonjs/theme/color-engine/viewing-conditions.d.ts +18 -0
  145. package/lib/typescript/commonjs/theme/color-engine/viewing-conditions.d.ts.map +1 -0
  146. package/lib/typescript/commonjs/theme/color-presets.d.ts +25 -20
  147. package/lib/typescript/commonjs/theme/color-presets.d.ts.map +1 -1
  148. package/lib/typescript/commonjs/theme/preset-vars.d.ts +16 -12
  149. package/lib/typescript/commonjs/theme/preset-vars.d.ts.map +1 -1
  150. package/lib/typescript/module/theme/build-theme.d.ts.map +1 -1
  151. package/lib/typescript/module/theme/color-engine/cam16.d.ts +26 -0
  152. package/lib/typescript/module/theme/color-engine/cam16.d.ts.map +1 -0
  153. package/lib/typescript/module/theme/color-engine/color-role.d.ts +36 -0
  154. package/lib/typescript/module/theme/color-engine/color-role.d.ts.map +1 -0
  155. package/lib/typescript/module/theme/color-engine/color-roles.d.ts +58 -0
  156. package/lib/typescript/module/theme/color-engine/color-roles.d.ts.map +1 -0
  157. package/lib/typescript/module/theme/color-engine/color-utils.d.ts +32 -0
  158. package/lib/typescript/module/theme/color-engine/color-utils.d.ts.map +1 -0
  159. package/lib/typescript/module/theme/color-engine/contrast-curve.d.ts +9 -0
  160. package/lib/typescript/module/theme/color-engine/contrast-curve.d.ts.map +1 -0
  161. package/lib/typescript/module/theme/color-engine/contrast.d.ts +12 -0
  162. package/lib/typescript/module/theme/color-engine/contrast.d.ts.map +1 -0
  163. package/lib/typescript/module/theme/color-engine/dislike-analyzer.d.ts +11 -0
  164. package/lib/typescript/module/theme/color-engine/dislike-analyzer.d.ts.map +1 -0
  165. package/lib/typescript/module/theme/color-engine/dynamic-scheme.d.ts +47 -0
  166. package/lib/typescript/module/theme/color-engine/dynamic-scheme.d.ts.map +1 -0
  167. package/lib/typescript/module/theme/color-engine/hct-solver.d.ts +6 -0
  168. package/lib/typescript/module/theme/color-engine/hct-solver.d.ts.map +1 -0
  169. package/lib/typescript/module/theme/color-engine/hct.d.ts +20 -0
  170. package/lib/typescript/module/theme/color-engine/hct.d.ts.map +1 -0
  171. package/lib/typescript/module/theme/color-engine/index.d.ts +44 -0
  172. package/lib/typescript/module/theme/color-engine/index.d.ts.map +1 -0
  173. package/lib/typescript/module/theme/color-engine/lab-point-provider.d.ts +6 -0
  174. package/lib/typescript/module/theme/color-engine/lab-point-provider.d.ts.map +1 -0
  175. package/lib/typescript/module/theme/color-engine/math-utils.d.ts +46 -0
  176. package/lib/typescript/module/theme/color-engine/math-utils.d.ts.map +1 -0
  177. package/lib/typescript/module/theme/color-engine/quantizer-map.d.ts +3 -0
  178. package/lib/typescript/module/theme/color-engine/quantizer-map.d.ts.map +1 -0
  179. package/lib/typescript/module/theme/color-engine/quantizer-wu.d.ts +3 -0
  180. package/lib/typescript/module/theme/color-engine/quantizer-wu.d.ts.map +1 -0
  181. package/lib/typescript/module/theme/color-engine/scheme-variants.d.ts +23 -0
  182. package/lib/typescript/module/theme/color-engine/scheme-variants.d.ts.map +1 -0
  183. package/lib/typescript/module/theme/color-engine/score.d.ts +8 -0
  184. package/lib/typescript/module/theme/color-engine/score.d.ts.map +1 -0
  185. package/lib/typescript/module/theme/color-engine/seed-from-image.d.ts +13 -0
  186. package/lib/typescript/module/theme/color-engine/seed-from-image.d.ts.map +1 -0
  187. package/lib/typescript/module/theme/color-engine/tonal-palette.d.ts +24 -0
  188. package/lib/typescript/module/theme/color-engine/tonal-palette.d.ts.map +1 -0
  189. package/lib/typescript/module/theme/color-engine/tone-delta-pair.d.ts +19 -0
  190. package/lib/typescript/module/theme/color-engine/tone-delta-pair.d.ts.map +1 -0
  191. package/lib/typescript/module/theme/color-engine/variant.d.ts +19 -0
  192. package/lib/typescript/module/theme/color-engine/variant.d.ts.map +1 -0
  193. package/lib/typescript/module/theme/color-engine/viewing-conditions.d.ts +18 -0
  194. package/lib/typescript/module/theme/color-engine/viewing-conditions.d.ts.map +1 -0
  195. package/lib/typescript/module/theme/color-presets.d.ts +25 -20
  196. package/lib/typescript/module/theme/color-presets.d.ts.map +1 -1
  197. package/lib/typescript/module/theme/preset-vars.d.ts +16 -12
  198. package/lib/typescript/module/theme/preset-vars.d.ts.map +1 -1
  199. package/package.json +1 -1
  200. package/src/__tests__/BloomThemeProvider.test.tsx +9 -4
  201. package/src/__tests__/native-root-vars.test.ts +8 -4
  202. package/src/__tests__/preset-vars.test.ts +66 -37
  203. package/src/__tests__/theme.test.ts +20 -12
  204. package/src/theme/__tests__/__fixtures__/golden-resolved-tokens.json +509 -509
  205. package/src/theme/__tests__/__snapshots__/visual-gallery.test.tsx.snap +613 -613
  206. package/src/theme/__tests__/color-engine-golden.test.ts +81 -0
  207. package/src/theme/__tests__/fixtures/color-engine-golden.json +1 -0
  208. package/src/theme/__tests__/golden-tokens.test.ts +6 -7
  209. package/src/theme/__tests__/runtime-contract.test.ts +8 -8
  210. package/src/theme/__tests__/theme-colors-parity.test.ts +44 -88
  211. package/src/theme/build-theme.ts +32 -44
  212. package/src/theme/color-engine/cam16.ts +179 -0
  213. package/src/theme/color-engine/color-role.ts +217 -0
  214. package/src/theme/color-engine/color-roles.ts +435 -0
  215. package/src/theme/color-engine/color-utils.ts +157 -0
  216. package/src/theme/color-engine/contrast-curve.ts +25 -0
  217. package/src/theme/color-engine/contrast.ts +59 -0
  218. package/src/theme/color-engine/dislike-analyzer.ts +19 -0
  219. package/src/theme/color-engine/dynamic-scheme.ts +90 -0
  220. package/src/theme/color-engine/hct-solver.ts +263 -0
  221. package/src/theme/color-engine/hct.ts +69 -0
  222. package/src/theme/color-engine/index.ts +73 -0
  223. package/src/theme/color-engine/lab-point-provider.ts +26 -0
  224. package/src/theme/color-engine/math-utils.ts +69 -0
  225. package/src/theme/color-engine/quantizer-map.ts +17 -0
  226. package/src/theme/color-engine/quantizer-wu.ts +320 -0
  227. package/src/theme/color-engine/scheme-variants.ts +113 -0
  228. package/src/theme/color-engine/score.ts +82 -0
  229. package/src/theme/color-engine/seed-from-image.ts +64 -0
  230. package/src/theme/color-engine/tonal-palette.ts +94 -0
  231. package/src/theme/color-engine/tone-delta-pair.ts +21 -0
  232. package/src/theme/color-engine/variant.ts +18 -0
  233. package/src/theme/color-engine/viewing-conditions.ts +77 -0
  234. package/src/theme/color-presets.ts +54 -631
  235. package/src/theme/preset-vars.ts +65 -49
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Bloom colour engine — CAM16 colour appearance model.
3
+ *
4
+ * Faithful port of Material Color Utilities' `Cam16` (Apache-2.0) into Bloom's
5
+ * own code. A colour is defined by its ARGB AND the viewing conditions; CAM16
6
+ * also exposes CAM16-UCS coordinates (jstar/astar/bstar) for perceptual
7
+ * distance. Underpins the HCT colour space.
8
+ */
9
+ import { argbFromXyz, linearized } from './color-utils';
10
+ import { signum } from './math-utils';
11
+ import { ViewingConditions } from './viewing-conditions';
12
+
13
+ export class Cam16 {
14
+ constructor(
15
+ readonly hue: number,
16
+ readonly chroma: number,
17
+ readonly j: number,
18
+ readonly q: number,
19
+ readonly m: number,
20
+ readonly s: number,
21
+ readonly jstar: number,
22
+ readonly astar: number,
23
+ readonly bstar: number,
24
+ ) {}
25
+
26
+ /** CAM16-UCS perceptual distance to another colour. */
27
+ distance(other: Cam16): number {
28
+ const dJ = this.jstar - other.jstar;
29
+ const dA = this.astar - other.astar;
30
+ const dB = this.bstar - other.bstar;
31
+ const dEPrime = Math.sqrt(dJ * dJ + dA * dA + dB * dB);
32
+ return 1.41 * Math.pow(dEPrime, 0.63);
33
+ }
34
+
35
+ static fromInt(argb: number): Cam16 {
36
+ return Cam16.fromIntInViewingConditions(argb, ViewingConditions.DEFAULT);
37
+ }
38
+
39
+ static fromIntInViewingConditions(argb: number, vc: ViewingConditions): Cam16 {
40
+ const red = (argb & 0x00ff0000) >> 16;
41
+ const green = (argb & 0x0000ff00) >> 8;
42
+ const blue = argb & 0x000000ff;
43
+ const redL = linearized(red);
44
+ const greenL = linearized(green);
45
+ const blueL = linearized(blue);
46
+ const x = 0.41233895 * redL + 0.35762064 * greenL + 0.18051042 * blueL;
47
+ const y = 0.2126 * redL + 0.7152 * greenL + 0.0722 * blueL;
48
+ const z = 0.01932141 * redL + 0.11916382 * greenL + 0.95034478 * blueL;
49
+ return Cam16.fromXyzInViewingConditions(x, y, z, vc);
50
+ }
51
+
52
+ static fromXyzInViewingConditions(
53
+ x: number,
54
+ y: number,
55
+ z: number,
56
+ vc: ViewingConditions,
57
+ ): Cam16 {
58
+ const rC = 0.401288 * x + 0.650173 * y - 0.051461 * z;
59
+ const gC = -0.250268 * x + 1.204414 * y + 0.045854 * z;
60
+ const bC = -0.002079 * x + 0.048952 * y + 0.953127 * z;
61
+ const rD = vc.rgbD[0] * rC;
62
+ const gD = vc.rgbD[1] * gC;
63
+ const bD = vc.rgbD[2] * bC;
64
+ const rAF = Math.pow((vc.fl * Math.abs(rD)) / 100.0, 0.42);
65
+ const gAF = Math.pow((vc.fl * Math.abs(gD)) / 100.0, 0.42);
66
+ const bAF = Math.pow((vc.fl * Math.abs(bD)) / 100.0, 0.42);
67
+ const rA = (signum(rD) * 400.0 * rAF) / (rAF + 27.13);
68
+ const gA = (signum(gD) * 400.0 * gAF) / (gAF + 27.13);
69
+ const bA = (signum(bD) * 400.0 * bAF) / (bAF + 27.13);
70
+ const a = (11.0 * rA + -12.0 * gA + bA) / 11.0;
71
+ const b = (rA + gA - 2.0 * bA) / 9.0;
72
+ const u = (20.0 * rA + 20.0 * gA + 21.0 * bA) / 20.0;
73
+ const p2 = (40.0 * rA + 20.0 * gA + bA) / 20.0;
74
+ const atan2 = Math.atan2(b, a);
75
+ const atanDegrees = (atan2 * 180.0) / Math.PI;
76
+ const hue =
77
+ atanDegrees < 0 ? atanDegrees + 360.0 : atanDegrees >= 360 ? atanDegrees - 360 : atanDegrees;
78
+ const hueRadians = (hue * Math.PI) / 180.0;
79
+ const ac = p2 * vc.nbb;
80
+ const J = 100.0 * Math.pow(ac / vc.aw, vc.c * vc.z);
81
+ const Q = (4.0 / vc.c) * Math.sqrt(J / 100.0) * (vc.aw + 4.0) * vc.fLRoot;
82
+ const huePrime = hue < 20.14 ? hue + 360 : hue;
83
+ const eHue = (1.0 / 4.0) * (Math.cos((huePrime * Math.PI) / 180.0 + 2.0) + 3.8);
84
+ const p1 = ((50000.0 / 13.0) * eHue * vc.nc * vc.ncb);
85
+ const t = (p1 * Math.sqrt(a * a + b * b)) / (u + 0.305);
86
+ const alpha = Math.pow(t, 0.9) * Math.pow(1.64 - Math.pow(0.29, vc.n), 0.73);
87
+ const C = alpha * Math.sqrt(J / 100.0);
88
+ const M = C * vc.fLRoot;
89
+ const s = 50.0 * Math.sqrt((alpha * vc.c) / (vc.aw + 4.0));
90
+ const jstar = ((1.0 + 100.0 * 0.007) * J) / (1.0 + 0.007 * J);
91
+ const mstar = Math.log(1.0 + 0.0228 * M) / 0.0228;
92
+ const astar = mstar * Math.cos(hueRadians);
93
+ const bstar = mstar * Math.sin(hueRadians);
94
+ return new Cam16(hue, C, J, Q, M, s, jstar, astar, bstar);
95
+ }
96
+
97
+ static fromJch(j: number, c: number, h: number): Cam16 {
98
+ return Cam16.fromJchInViewingConditions(j, c, h, ViewingConditions.DEFAULT);
99
+ }
100
+
101
+ static fromJchInViewingConditions(
102
+ j: number,
103
+ c: number,
104
+ h: number,
105
+ vc: ViewingConditions,
106
+ ): Cam16 {
107
+ const q = (4.0 / vc.c) * Math.sqrt(j / 100.0) * (vc.aw + 4.0) * vc.fLRoot;
108
+ const m = c * vc.fLRoot;
109
+ const alpha = c / Math.sqrt(j / 100.0);
110
+ const s = 50.0 * Math.sqrt((alpha * vc.c) / (vc.aw + 4.0));
111
+ const hueRadians = (h * Math.PI) / 180.0;
112
+ const jstar = ((1.0 + 100.0 * 0.007) * j) / (1.0 + 0.007 * j);
113
+ const mstar = (1.0 / 0.0228) * Math.log(1.0 + 0.0228 * m);
114
+ const astar = mstar * Math.cos(hueRadians);
115
+ const bstar = mstar * Math.sin(hueRadians);
116
+ return new Cam16(h, c, j, q, m, s, jstar, astar, bstar);
117
+ }
118
+
119
+ static fromUcs(jstar: number, astar: number, bstar: number): Cam16 {
120
+ return Cam16.fromUcsInViewingConditions(jstar, astar, bstar, ViewingConditions.DEFAULT);
121
+ }
122
+
123
+ static fromUcsInViewingConditions(
124
+ jstar: number,
125
+ astar: number,
126
+ bstar: number,
127
+ vc: ViewingConditions,
128
+ ): Cam16 {
129
+ const a = astar;
130
+ const b = bstar;
131
+ const m = Math.sqrt(a * a + b * b);
132
+ const M = (Math.exp(m * 0.0228) - 1.0) / 0.0228;
133
+ const c = M / vc.fLRoot;
134
+ let h = Math.atan2(b, a) * (180.0 / Math.PI);
135
+ if (h < 0.0) h += 360.0;
136
+ const j = jstar / (1 - (jstar - 100) * 0.007);
137
+ return Cam16.fromJchInViewingConditions(j, c, h, vc);
138
+ }
139
+
140
+ toInt(): number {
141
+ return this.viewed(ViewingConditions.DEFAULT);
142
+ }
143
+
144
+ viewed(vc: ViewingConditions): number {
145
+ const xyz = this.xyzInViewingConditions(vc);
146
+ return argbFromXyz(xyz[0], xyz[1], xyz[2]);
147
+ }
148
+
149
+ xyzInViewingConditions(vc: ViewingConditions): [number, number, number] {
150
+ const alpha = this.chroma === 0.0 || this.j === 0.0 ? 0.0 : this.chroma / Math.sqrt(this.j / 100.0);
151
+ const t = Math.pow(alpha / Math.pow(1.64 - Math.pow(0.29, vc.n), 0.73), 1.0 / 0.9);
152
+ const hRad = (this.hue * Math.PI) / 180.0;
153
+ const eHue = 0.25 * (Math.cos(hRad + 2.0) + 3.8);
154
+ const ac = vc.aw * Math.pow(this.j / 100.0, 1.0 / vc.c / vc.z);
155
+ const p1 = eHue * (50000.0 / 13.0) * vc.nc * vc.ncb;
156
+ const p2 = ac / vc.nbb;
157
+ const hSin = Math.sin(hRad);
158
+ const hCos = Math.cos(hRad);
159
+ const gamma = (23.0 * (p2 + 0.305) * t) / (23.0 * p1 + 11.0 * t * hCos + 108.0 * t * hSin);
160
+ const a = gamma * hCos;
161
+ const b = gamma * hSin;
162
+ const rA = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0;
163
+ const gA = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0;
164
+ const bA = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0;
165
+ const rCBase = Math.max(0, (27.13 * Math.abs(rA)) / (400.0 - Math.abs(rA)));
166
+ const rC = signum(rA) * (100.0 / vc.fl) * Math.pow(rCBase, 1.0 / 0.42);
167
+ const gCBase = Math.max(0, (27.13 * Math.abs(gA)) / (400.0 - Math.abs(gA)));
168
+ const gC = signum(gA) * (100.0 / vc.fl) * Math.pow(gCBase, 1.0 / 0.42);
169
+ const bCBase = Math.max(0, (27.13 * Math.abs(bA)) / (400.0 - Math.abs(bA)));
170
+ const bC = signum(bA) * (100.0 / vc.fl) * Math.pow(bCBase, 1.0 / 0.42);
171
+ const rF = rC / vc.rgbD[0];
172
+ const gF = gC / vc.rgbD[1];
173
+ const bF = bC / vc.rgbD[2];
174
+ const x = 1.86206786 * rF - 1.01125463 * gF + 0.14918677 * bF;
175
+ const y = 0.38752654 * rF + 0.62144744 * gF - 0.00897398 * bF;
176
+ const z = -0.0158415 * rF - 0.03412294 * gF + 1.04996444 * bF;
177
+ return [x, y, z];
178
+ }
179
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Bloom colour engine — a colour role.
3
+ *
4
+ * Faithful port of Material Color Utilities' `DynamicColor` (Apache-2.0),
5
+ * renamed to Bloom's own `ColorRole`. A role is a *recipe*, not a fixed colour:
6
+ * a palette + a base tone + (optionally) a background to contrast against and a
7
+ * paired role to keep tonal distance from. `getArgb(scheme)` resolves the recipe
8
+ * against a scheme's palettes, dark mode and contrast level into a concrete
9
+ * colour — guaranteeing legibility. This is the heart of the system.
10
+ */
11
+ import { darker, darkerUnsafe, lighter, lighterUnsafe, ratioOfTones } from './contrast';
12
+ import type { ContrastCurve } from './contrast-curve';
13
+ import type { DynamicScheme } from './dynamic-scheme';
14
+ import { Hct } from './hct';
15
+ import { clampDouble } from './math-utils';
16
+ import type { TonalPalette } from './tonal-palette';
17
+ import type { ToneDeltaPair } from './tone-delta-pair';
18
+
19
+ export interface ColorRoleArgs {
20
+ name?: string;
21
+ palette: (scheme: DynamicScheme) => TonalPalette;
22
+ tone: (scheme: DynamicScheme) => number;
23
+ isBackground?: boolean;
24
+ background?: (scheme: DynamicScheme) => ColorRole | undefined;
25
+ secondBackground?: (scheme: DynamicScheme) => ColorRole | undefined;
26
+ contrastCurve?: ContrastCurve;
27
+ toneDeltaPair?: (scheme: DynamicScheme) => ToneDeltaPair;
28
+ }
29
+
30
+ export class ColorRole {
31
+ readonly name: string;
32
+ readonly palette: (scheme: DynamicScheme) => TonalPalette;
33
+ readonly tone: (scheme: DynamicScheme) => number;
34
+ readonly isBackground: boolean;
35
+ readonly background?: (scheme: DynamicScheme) => ColorRole | undefined;
36
+ readonly secondBackground?: (scheme: DynamicScheme) => ColorRole | undefined;
37
+ readonly contrastCurve?: ContrastCurve;
38
+ readonly toneDeltaPair?: (scheme: DynamicScheme) => ToneDeltaPair;
39
+ private readonly hctCache = new Map<DynamicScheme, Hct>();
40
+
41
+ static fromPalette(args: ColorRoleArgs): ColorRole {
42
+ return new ColorRole(args);
43
+ }
44
+
45
+ constructor(args: ColorRoleArgs) {
46
+ this.name = args.name ?? '';
47
+ this.palette = args.palette;
48
+ this.tone = args.tone;
49
+ this.isBackground = args.isBackground ?? false;
50
+ this.background = args.background;
51
+ this.secondBackground = args.secondBackground;
52
+ this.contrastCurve = args.contrastCurve;
53
+ this.toneDeltaPair = args.toneDeltaPair;
54
+ if (!args.background && args.secondBackground) {
55
+ throw new Error(`Color ${this.name} has secondBackground defined, but background is not.`);
56
+ }
57
+ if (!args.background && args.contrastCurve) {
58
+ throw new Error(`Color ${this.name} has contrastCurve defined, but background is not.`);
59
+ }
60
+ if (args.background && !args.contrastCurve) {
61
+ throw new Error(`Color ${this.name} has background defined, but contrastCurve is not.`);
62
+ }
63
+ }
64
+
65
+ getArgb(scheme: DynamicScheme): number {
66
+ return this.getHct(scheme).toInt();
67
+ }
68
+
69
+ getHct(scheme: DynamicScheme): Hct {
70
+ const cached = this.hctCache.get(scheme);
71
+ if (cached != null) return cached;
72
+ const tone = this.getTone(scheme);
73
+ const answer = this.palette(scheme).getHct(tone);
74
+ if (this.hctCache.size > 4) this.hctCache.clear();
75
+ this.hctCache.set(scheme, answer);
76
+ return answer;
77
+ }
78
+
79
+ getTone(scheme: DynamicScheme): number {
80
+ const decreasingContrast = scheme.contrastLevel < 0;
81
+ if (this.toneDeltaPair) {
82
+ const pair = this.toneDeltaPair(scheme);
83
+ const { roleA, roleB, delta, polarity, stayTogether } = pair;
84
+ const bg = this.background!(scheme)!;
85
+ const bgTone = bg.getTone(scheme);
86
+ const aIsNearer =
87
+ polarity === 'nearer' ||
88
+ (polarity === 'lighter' && !scheme.isDark) ||
89
+ (polarity === 'darker' && scheme.isDark);
90
+ const nearer = aIsNearer ? roleA : roleB;
91
+ const farther = aIsNearer ? roleB : roleA;
92
+ const amNearer = this.name === nearer.name;
93
+ const expansionDir = scheme.isDark ? 1 : -1;
94
+ const nContrast = nearer.contrastCurve!.get(scheme.contrastLevel);
95
+ const fContrast = farther.contrastCurve!.get(scheme.contrastLevel);
96
+ const nInitialTone = nearer.tone(scheme);
97
+ let nTone =
98
+ ratioOfTones(bgTone, nInitialTone) >= nContrast
99
+ ? nInitialTone
100
+ : ColorRole.foregroundTone(bgTone, nContrast);
101
+ const fInitialTone = farther.tone(scheme);
102
+ let fTone =
103
+ ratioOfTones(bgTone, fInitialTone) >= fContrast
104
+ ? fInitialTone
105
+ : ColorRole.foregroundTone(bgTone, fContrast);
106
+ if (decreasingContrast) {
107
+ nTone = ColorRole.foregroundTone(bgTone, nContrast);
108
+ fTone = ColorRole.foregroundTone(bgTone, fContrast);
109
+ }
110
+ if ((fTone - nTone) * expansionDir >= delta) {
111
+ // constraint already satisfied
112
+ } else {
113
+ fTone = clampDouble(0, 100, nTone + delta * expansionDir);
114
+ if ((fTone - nTone) * expansionDir >= delta) {
115
+ // satisfied after expanding farther
116
+ } else {
117
+ nTone = clampDouble(0, 100, fTone - delta * expansionDir);
118
+ }
119
+ }
120
+ if (50 <= nTone && nTone < 60) {
121
+ if (expansionDir > 0) {
122
+ nTone = 60;
123
+ fTone = Math.max(fTone, nTone + delta * expansionDir);
124
+ } else {
125
+ nTone = 49;
126
+ fTone = Math.min(fTone, nTone + delta * expansionDir);
127
+ }
128
+ } else if (50 <= fTone && fTone < 60) {
129
+ if (stayTogether) {
130
+ if (expansionDir > 0) {
131
+ nTone = 60;
132
+ fTone = Math.max(fTone, nTone + delta * expansionDir);
133
+ } else {
134
+ nTone = 49;
135
+ fTone = Math.min(fTone, nTone + delta * expansionDir);
136
+ }
137
+ } else if (expansionDir > 0) {
138
+ fTone = 60;
139
+ } else {
140
+ fTone = 49;
141
+ }
142
+ }
143
+ return amNearer ? nTone : fTone;
144
+ }
145
+
146
+ let answer = this.tone(scheme);
147
+ if (this.background == null) return answer;
148
+ const bgTone = this.background(scheme)!.getTone(scheme);
149
+ const desiredRatio = this.contrastCurve!.get(scheme.contrastLevel);
150
+ if (ratioOfTones(bgTone, answer) >= desiredRatio) {
151
+ // already contrasts enough
152
+ } else {
153
+ answer = ColorRole.foregroundTone(bgTone, desiredRatio);
154
+ }
155
+ if (decreasingContrast) {
156
+ answer = ColorRole.foregroundTone(bgTone, desiredRatio);
157
+ }
158
+ if (this.isBackground && 50 <= answer && answer < 60) {
159
+ answer = ratioOfTones(49, bgTone) >= desiredRatio ? 49 : 60;
160
+ }
161
+ if (this.secondBackground) {
162
+ const bg1 = this.background;
163
+ const bg2 = this.secondBackground;
164
+ const bgTone1 = bg1(scheme)!.getTone(scheme);
165
+ const bgTone2 = bg2(scheme)!.getTone(scheme);
166
+ const upper = Math.max(bgTone1, bgTone2);
167
+ const lower = Math.min(bgTone1, bgTone2);
168
+ if (ratioOfTones(upper, answer) >= desiredRatio && ratioOfTones(lower, answer) >= desiredRatio) {
169
+ return answer;
170
+ }
171
+ const lightOption = lighter(upper, desiredRatio);
172
+ const darkOption = darker(lower, desiredRatio);
173
+ const availables: number[] = [];
174
+ if (lightOption !== -1) availables.push(lightOption);
175
+ if (darkOption !== -1) availables.push(darkOption);
176
+ const prefersLight =
177
+ ColorRole.tonePrefersLightForeground(bgTone1) ||
178
+ ColorRole.tonePrefersLightForeground(bgTone2);
179
+ if (prefersLight) return lightOption < 0 ? 100 : lightOption;
180
+ const onlyAvailable = availables[0];
181
+ if (availables.length === 1 && onlyAvailable !== undefined) return onlyAvailable;
182
+ return darkOption < 0 ? 0 : darkOption;
183
+ }
184
+ return answer;
185
+ }
186
+
187
+ static foregroundTone(bgTone: number, ratio: number): number {
188
+ const lighterTone = lighterUnsafe(bgTone, ratio);
189
+ const darkerTone = darkerUnsafe(bgTone, ratio);
190
+ const lighterRatio = ratioOfTones(lighterTone, bgTone);
191
+ const darkerRatio = ratioOfTones(darkerTone, bgTone);
192
+ const preferLighter = ColorRole.tonePrefersLightForeground(bgTone);
193
+ if (preferLighter) {
194
+ const negligibleDifference =
195
+ Math.abs(lighterRatio - darkerRatio) < 0.1 && lighterRatio < ratio && darkerRatio < ratio;
196
+ return lighterRatio >= ratio || lighterRatio >= darkerRatio || negligibleDifference
197
+ ? lighterTone
198
+ : darkerTone;
199
+ }
200
+ return darkerRatio >= ratio || darkerRatio >= lighterRatio ? darkerTone : lighterTone;
201
+ }
202
+
203
+ static tonePrefersLightForeground(tone: number): boolean {
204
+ return Math.round(tone) < 60.0;
205
+ }
206
+
207
+ static toneAllowsLightForeground(tone: number): boolean {
208
+ return Math.round(tone) <= 49.0;
209
+ }
210
+
211
+ static enableLightForeground(tone: number): number {
212
+ if (ColorRole.tonePrefersLightForeground(tone) && !ColorRole.toneAllowsLightForeground(tone)) {
213
+ return 49.0;
214
+ }
215
+ return tone;
216
+ }
217
+ }