bireactive 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/dist/animation/anim.d.ts +57 -0
  4. package/dist/animation/anim.js +318 -0
  5. package/dist/animation/combinators.d.ts +39 -0
  6. package/dist/animation/combinators.js +113 -0
  7. package/dist/animation/easings.d.ts +5 -0
  8. package/dist/animation/easings.js +5 -0
  9. package/dist/animation/index.d.ts +3 -0
  10. package/dist/animation/index.js +3 -0
  11. package/dist/assert/algebra.d.ts +20 -0
  12. package/dist/assert/algebra.js +79 -0
  13. package/dist/assert/claim.d.ts +40 -0
  14. package/dist/assert/claim.js +129 -0
  15. package/dist/assert/index.d.ts +7 -0
  16. package/dist/assert/index.js +19 -0
  17. package/dist/assert/predicates.d.ts +18 -0
  18. package/dist/assert/predicates.js +43 -0
  19. package/dist/assert/record.d.ts +20 -0
  20. package/dist/assert/record.js +78 -0
  21. package/dist/assert/scope.d.ts +42 -0
  22. package/dist/assert/scope.js +233 -0
  23. package/dist/assert/span.d.ts +37 -0
  24. package/dist/assert/span.js +68 -0
  25. package/dist/assert/tree.d.ts +22 -0
  26. package/dist/assert/tree.js +65 -0
  27. package/dist/code/code.d.ts +70 -0
  28. package/dist/code/code.js +361 -0
  29. package/dist/code/index.d.ts +2 -0
  30. package/dist/code/index.js +9 -0
  31. package/dist/code/morph.d.ts +5 -0
  32. package/dist/code/morph.js +194 -0
  33. package/dist/code/tokenize.d.ts +8 -0
  34. package/dist/code/tokenize.js +51 -0
  35. package/dist/constraints/cluster.d.ts +83 -0
  36. package/dist/constraints/cluster.js +213 -0
  37. package/dist/constraints/drivers.d.ts +15 -0
  38. package/dist/constraints/drivers.js +40 -0
  39. package/dist/constraints/factories.d.ts +73 -0
  40. package/dist/constraints/factories.js +248 -0
  41. package/dist/constraints/index.d.ts +11 -0
  42. package/dist/constraints/index.js +39 -0
  43. package/dist/constraints/interaction.d.ts +21 -0
  44. package/dist/constraints/interaction.js +148 -0
  45. package/dist/constraints/linalg.d.ts +18 -0
  46. package/dist/constraints/linalg.js +141 -0
  47. package/dist/constraints/phases.d.ts +21 -0
  48. package/dist/constraints/phases.js +60 -0
  49. package/dist/constraints/physics.d.ts +34 -0
  50. package/dist/constraints/physics.js +128 -0
  51. package/dist/constraints/rigid.d.ts +210 -0
  52. package/dist/constraints/rigid.js +835 -0
  53. package/dist/constraints/solver.d.ts +107 -0
  54. package/dist/constraints/solver.js +510 -0
  55. package/dist/constraints/term.d.ts +50 -0
  56. package/dist/constraints/term.js +80 -0
  57. package/dist/constraints/terms.d.ts +80 -0
  58. package/dist/constraints/terms.js +302 -0
  59. package/dist/constraints/world.d.ts +31 -0
  60. package/dist/constraints/world.js +245 -0
  61. package/dist/core/aggregates.d.ts +64 -0
  62. package/dist/core/aggregates.js +198 -0
  63. package/dist/core/anim.d.ts +84 -0
  64. package/dist/core/anim.js +301 -0
  65. package/dist/core/index.d.ts +38 -0
  66. package/dist/core/index.js +38 -0
  67. package/dist/core/introspect.d.ts +5 -0
  68. package/dist/core/introspect.js +31 -0
  69. package/dist/core/lenses/closed-form-policies.d.ts +64 -0
  70. package/dist/core/lenses/closed-form-policies.js +452 -0
  71. package/dist/core/lenses/domain-aggregates.d.ts +54 -0
  72. package/dist/core/lenses/domain-aggregates.js +259 -0
  73. package/dist/core/lenses/factor-lens.d.ts +42 -0
  74. package/dist/core/lenses/factor-lens.js +419 -0
  75. package/dist/core/lenses/index.d.ts +5 -0
  76. package/dist/core/lenses/index.js +16 -0
  77. package/dist/core/lenses/memory.d.ts +47 -0
  78. package/dist/core/lenses/memory.js +102 -0
  79. package/dist/core/lenses/typed-factor.d.ts +45 -0
  80. package/dist/core/lenses/typed-factor.js +376 -0
  81. package/dist/core/network-utils.d.ts +14 -0
  82. package/dist/core/network-utils.js +62 -0
  83. package/dist/core/new-primitives.d.ts +33 -0
  84. package/dist/core/new-primitives.js +113 -0
  85. package/dist/core/signal.d.ts +254 -0
  86. package/dist/core/signal.js +1349 -0
  87. package/dist/core/traits.d.ts +61 -0
  88. package/dist/core/traits.js +56 -0
  89. package/dist/core/tree.d.ts +23 -0
  90. package/dist/core/tree.js +62 -0
  91. package/dist/core/values/anchor.d.ts +23 -0
  92. package/dist/core/values/anchor.js +23 -0
  93. package/dist/core/values/audio.d.ts +33 -0
  94. package/dist/core/values/audio.js +107 -0
  95. package/dist/core/values/bool.d.ts +37 -0
  96. package/dist/core/values/bool.js +75 -0
  97. package/dist/core/values/box.d.ts +77 -0
  98. package/dist/core/values/box.js +211 -0
  99. package/dist/core/values/canvas.d.ts +71 -0
  100. package/dist/core/values/canvas.js +495 -0
  101. package/dist/core/values/color.d.ts +49 -0
  102. package/dist/core/values/color.js +106 -0
  103. package/dist/core/values/flags.d.ts +18 -0
  104. package/dist/core/values/flags.js +50 -0
  105. package/dist/core/values/gpu.d.ts +74 -0
  106. package/dist/core/values/gpu.js +426 -0
  107. package/dist/core/values/matrix.d.ts +53 -0
  108. package/dist/core/values/matrix.js +140 -0
  109. package/dist/core/values/num.d.ts +62 -0
  110. package/dist/core/values/num.js +166 -0
  111. package/dist/core/values/pose.d.ts +31 -0
  112. package/dist/core/values/pose.js +83 -0
  113. package/dist/core/values/range.d.ts +83 -0
  114. package/dist/core/values/range.js +167 -0
  115. package/dist/core/values/str.d.ts +76 -0
  116. package/dist/core/values/str.js +346 -0
  117. package/dist/core/values/template.d.ts +49 -0
  118. package/dist/core/values/template.js +148 -0
  119. package/dist/core/values/transform.d.ts +49 -0
  120. package/dist/core/values/transform.js +115 -0
  121. package/dist/core/values/tri.d.ts +31 -0
  122. package/dist/core/values/tri.js +95 -0
  123. package/dist/core/values/vec.d.ts +72 -0
  124. package/dist/core/values/vec.js +219 -0
  125. package/dist/core/writable.d.ts +15 -0
  126. package/dist/core/writable.js +29 -0
  127. package/dist/ext/events.d.ts +10 -0
  128. package/dist/ext/events.js +31 -0
  129. package/dist/ext/index.d.ts +4 -0
  130. package/dist/ext/index.js +4 -0
  131. package/dist/ext/snapshot.d.ts +8 -0
  132. package/dist/ext/snapshot.js +29 -0
  133. package/dist/ext/timeline.d.ts +56 -0
  134. package/dist/ext/timeline.js +94 -0
  135. package/dist/ext/waapi.d.ts +25 -0
  136. package/dist/ext/waapi.js +198 -0
  137. package/dist/index.d.ts +8 -0
  138. package/dist/index.js +10 -0
  139. package/dist/propagators/index.d.ts +6 -0
  140. package/dist/propagators/index.js +6 -0
  141. package/dist/propagators/layout.d.ts +68 -0
  142. package/dist/propagators/layout.js +336 -0
  143. package/dist/propagators/network.d.ts +52 -0
  144. package/dist/propagators/network.js +185 -0
  145. package/dist/propagators/propagator.d.ts +12 -0
  146. package/dist/propagators/propagator.js +16 -0
  147. package/dist/propagators/range.d.ts +45 -0
  148. package/dist/propagators/range.js +147 -0
  149. package/dist/propagators/relations.d.ts +60 -0
  150. package/dist/propagators/relations.js +343 -0
  151. package/dist/shapes/annular-sector.d.ts +15 -0
  152. package/dist/shapes/annular-sector.js +64 -0
  153. package/dist/shapes/button.d.ts +14 -0
  154. package/dist/shapes/button.js +31 -0
  155. package/dist/shapes/choreographers.d.ts +22 -0
  156. package/dist/shapes/choreographers.js +69 -0
  157. package/dist/shapes/circle.d.ts +17 -0
  158. package/dist/shapes/circle.js +57 -0
  159. package/dist/shapes/clip.d.ts +5 -0
  160. package/dist/shapes/clip.js +31 -0
  161. package/dist/shapes/connect.d.ts +16 -0
  162. package/dist/shapes/connect.js +70 -0
  163. package/dist/shapes/curve.d.ts +60 -0
  164. package/dist/shapes/curve.js +285 -0
  165. package/dist/shapes/dashed.d.ts +16 -0
  166. package/dist/shapes/dashed.js +142 -0
  167. package/dist/shapes/debug.d.ts +43 -0
  168. package/dist/shapes/debug.js +97 -0
  169. package/dist/shapes/group.d.ts +5 -0
  170. package/dist/shapes/group.js +10 -0
  171. package/dist/shapes/handle.d.ts +32 -0
  172. package/dist/shapes/handle.js +88 -0
  173. package/dist/shapes/index.d.ts +23 -0
  174. package/dist/shapes/index.js +23 -0
  175. package/dist/shapes/interaction.d.ts +32 -0
  176. package/dist/shapes/interaction.js +187 -0
  177. package/dist/shapes/label.d.ts +20 -0
  178. package/dist/shapes/label.js +42 -0
  179. package/dist/shapes/layout.d.ts +29 -0
  180. package/dist/shapes/layout.js +74 -0
  181. package/dist/shapes/line.d.ts +21 -0
  182. package/dist/shapes/line.js +79 -0
  183. package/dist/shapes/list.d.ts +18 -0
  184. package/dist/shapes/list.js +51 -0
  185. package/dist/shapes/mount.d.ts +7 -0
  186. package/dist/shapes/mount.js +10 -0
  187. package/dist/shapes/path.d.ts +77 -0
  188. package/dist/shapes/path.js +227 -0
  189. package/dist/shapes/rect.d.ts +30 -0
  190. package/dist/shapes/rect.js +131 -0
  191. package/dist/shapes/shape.d.ts +132 -0
  192. package/dist/shapes/shape.js +306 -0
  193. package/dist/shapes/text.d.ts +24 -0
  194. package/dist/shapes/text.js +53 -0
  195. package/dist/shapes/tokens.d.ts +28 -0
  196. package/dist/shapes/tokens.js +27 -0
  197. package/dist/shapes/transitions.d.ts +23 -0
  198. package/dist/shapes/transitions.js +62 -0
  199. package/dist/tex/decorations.d.ts +26 -0
  200. package/dist/tex/decorations.js +116 -0
  201. package/dist/tex/index.d.ts +5 -0
  202. package/dist/tex/index.js +5 -0
  203. package/dist/tex/marker.d.ts +17 -0
  204. package/dist/tex/marker.js +63 -0
  205. package/dist/tex/motion.d.ts +43 -0
  206. package/dist/tex/motion.js +290 -0
  207. package/dist/tex/parts.d.ts +65 -0
  208. package/dist/tex/parts.js +149 -0
  209. package/dist/tex/tex.d.ts +45 -0
  210. package/dist/tex/tex.js +244 -0
  211. package/dist/web/attr.d.ts +16 -0
  212. package/dist/web/attr.js +98 -0
  213. package/dist/web/diagram.d.ts +49 -0
  214. package/dist/web/diagram.js +260 -0
  215. package/dist/web/index.d.ts +6 -0
  216. package/dist/web/index.js +6 -0
  217. package/dist/web/md-marker.d.ts +6 -0
  218. package/dist/web/md-marker.js +39 -0
  219. package/dist/web/md-tex.d.ts +6 -0
  220. package/dist/web/md-tex.js +61 -0
  221. package/dist/web/raf.d.ts +6 -0
  222. package/dist/web/raf.js +24 -0
  223. package/dist/web/viewport.d.ts +7 -0
  224. package/dist/web/viewport.js +13 -0
  225. package/package.json +87 -0
@@ -0,0 +1,495 @@
1
+ // canvas.ts — GPU-resident reactive raster (handle-as-value).
2
+ //
3
+ // Every Canvas value is a float texture in the shared WebGL2 context (see
4
+ // gpu.ts); the reactive graph transports only a tiny HEADER {tex, w, h,
5
+ // epoch}, comparing the monotonic epoch, so propagation never reads a pixel
6
+ // and nothing is copied across the bus. Forward lenses render into a per-lens
7
+ // scratch texture (reused across recomputes); the single ownership rule that
8
+ // keeps no-copy safe is the same as on the CPU — a node writes only its own
9
+ // texture, never one another live node still reads, which holds under the
10
+ // engine's glitch-free flush.
11
+ //
12
+ // Tiers mirror the rest of values/:
13
+ // - pure isomorphisms (`invert`, `flipH`) — one pointwise shader each way.
14
+ // - reactive-param invertibles (`brightness(k)`) — read `Val<number>`.
15
+ // - complement projections (`grayscale`, `downsample`) — the lossy view
16
+ // plus a complement texture (chroma / Laplacian residual) recovered on
17
+ // write-back, the raster analog of str.ts's case-preserving lenses.
18
+ // - cross-type lenses (`meanColor` → Color, `brighterThan` → Bool).
19
+ //
20
+ // A backward pass that produces a ROOT cell's next value reads that root and
21
+ // writes a result that becomes its value, so it uses `scratch2` (a feedback-
22
+ // safe pair) to avoid reading and writing the same texture.
23
+ import { Cell, reader } from "../signal.js";
24
+ import { derived } from "../writable.js";
25
+ import { Bool } from "./bool.js";
26
+ import { Color } from "./color.js";
27
+ import { copy, newTex, pass, reduceMean, Spring, scratch, scratch2, } from "./gpu.js";
28
+ import { Vec } from "./vec.js";
29
+ let EPOCH = 0;
30
+ /** Stamp a texture with a fresh epoch — the only way to mint a value. */
31
+ export const stamp = (tex, w, h) => ({
32
+ tex,
33
+ w,
34
+ h,
35
+ epoch: ++EPOCH,
36
+ });
37
+ export const equals = (a, b) => a.epoch === b.epoch;
38
+ const DECONV_ITERS = 18;
39
+ const MAXR = 10;
40
+ const LUMA_W = "vec3(0.299, 0.587, 0.114)";
41
+ const HEAD = `#version 300 es
42
+ precision highp float;
43
+ in vec2 v_uv;
44
+ out vec4 o;`;
45
+ const INVERT = `${HEAD}
46
+ uniform sampler2D u_s;
47
+ void main() { vec4 c = texture(u_s, v_uv); o = vec4(1.0 - c.rgb, c.a); }`;
48
+ const FLIPH = `${HEAD}
49
+ uniform sampler2D u_s;
50
+ void main() { o = texture(u_s, vec2(1.0 - v_uv.x, v_uv.y)); }`;
51
+ const SCALE3 = `${HEAD}
52
+ uniform sampler2D u_s; uniform float u_k;
53
+ void main() { vec4 c = texture(u_s, v_uv); o = vec4(c.rgb * u_k, c.a); }`;
54
+ const LUMA = `${HEAD}
55
+ uniform sampler2D u_s;
56
+ void main() { vec4 c = texture(u_s, v_uv); float y = dot(c.rgb, ${LUMA_W}); o = vec4(y, y, y, c.a); }`;
57
+ const CHROMA = `${HEAD}
58
+ uniform sampler2D u_s;
59
+ void main() { vec4 c = texture(u_s, v_uv); float y = dot(c.rgb, ${LUMA_W}); o = vec4(c.rgb - y, c.a); }`;
60
+ const RECOLOR = `${HEAD}
61
+ uniform sampler2D u_t; uniform sampler2D u_c;
62
+ void main() { float y = texture(u_t, v_uv).r; vec4 c = texture(u_c, v_uv); o = vec4(y + c.rgb, c.a); }`;
63
+ // chroma() view: (r−Y, g−Y, b−Y) lifted by +0.5 so it shows on a mid-grey
64
+ // base; complement is the luma Y, recovered by DELUMA on write-back.
65
+ const CHROMA_VIEW = `${HEAD}
66
+ uniform sampler2D u_s;
67
+ void main() { vec4 c = texture(u_s, v_uv); float y = dot(c.rgb, ${LUMA_W}); o = vec4(c.rgb - y + 0.5, c.a); }`;
68
+ const DELUMA = `${HEAD}
69
+ uniform sampler2D u_t; uniform sampler2D u_c;
70
+ void main() {
71
+ vec4 t = texture(u_t, v_uv);
72
+ vec3 cv = t.rgb - 0.5;
73
+ cv -= dot(cv, ${LUMA_W}); // strip any luma the painted chroma carries
74
+ float y = texture(u_c, v_uv).r; // restore the source's luma exactly
75
+ o = vec4(cv + y, t.a);
76
+ }`;
77
+ const CROP_FWD = `${HEAD}
78
+ uniform sampler2D u_s; uniform vec2 u_off; uniform vec2 u_csize; uniform vec2 u_ssize;
79
+ void main() { vec2 sp = (u_off + v_uv * u_csize) / u_ssize; o = texture(u_s, sp); }`;
80
+ const CROP_BWD = `${HEAD}
81
+ uniform sampler2D u_s; uniform sampler2D u_t; uniform vec2 u_off; uniform vec2 u_csize; uniform vec2 u_ssize;
82
+ void main() {
83
+ vec2 sp = v_uv * u_ssize;
84
+ vec2 rel = sp - u_off;
85
+ if (rel.x >= 0.0 && rel.y >= 0.0 && rel.x < u_csize.x && rel.y < u_csize.y)
86
+ o = texture(u_t, rel / u_csize);
87
+ else o = texture(u_s, v_uv);
88
+ }`;
89
+ const BLUR1D = `${HEAD}
90
+ uniform sampler2D u_s; uniform vec2 u_dir; uniform float u_sigma; uniform int u_r;
91
+ void main() {
92
+ vec3 acc = vec3(0.0); float wsum = 0.0; float a = texture(u_s, v_uv).a;
93
+ for (int k = -${MAXR}; k <= ${MAXR}; k++) {
94
+ if (k < -u_r || k > u_r) continue;
95
+ float wk = exp(-float(k * k) / (2.0 * u_sigma * u_sigma));
96
+ acc += texture(u_s, v_uv + u_dir * float(k)).rgb * wk;
97
+ wsum += wk;
98
+ }
99
+ o = vec4(acc / wsum, a);
100
+ }`;
101
+ // Richardson–Lucy step. RL_RATIO forms target / blur(estimate) (guarded);
102
+ // RL_MUL multiplies the estimate by blur(ratio) and clamps to [0,1]. Both
103
+ // the non-negativity clamp and the multiplicative form keep ringing far
104
+ // below an additive Van-Cittert iteration.
105
+ const RL_RATIO = `${HEAD}
106
+ uniform sampler2D u_t; uniform sampler2D u_est;
107
+ void main() {
108
+ vec4 t = texture(u_t, v_uv); vec3 e = texture(u_est, v_uv).rgb;
109
+ o = vec4(t.rgb / max(e, vec3(1e-3)), t.a);
110
+ }`;
111
+ const RL_MUL = `${HEAD}
112
+ uniform sampler2D u_x; uniform sampler2D u_c;
113
+ void main() {
114
+ vec4 x = texture(u_x, v_uv); vec3 c = texture(u_c, v_uv).rgb;
115
+ o = vec4(clamp(x.rgb * c.rgb, 0.0, 1.0), x.a);
116
+ }`;
117
+ const BOXDOWN = `${HEAD}
118
+ uniform highp sampler2D u_s; uniform int u_f; uniform ivec2 u_ssize;
119
+ void main() {
120
+ ivec2 d = ivec2(gl_FragCoord.xy); vec4 sum = vec4(0.0); float n = 0.0;
121
+ for (int dy = 0; dy < ${MAXR}; dy++) { if (dy >= u_f) break;
122
+ for (int dx = 0; dx < ${MAXR}; dx++) { if (dx >= u_f) break;
123
+ ivec2 sp = clamp(ivec2(d.x * u_f + dx, d.y * u_f + dy), ivec2(0), u_ssize - 1);
124
+ sum += texelFetch(u_s, sp, 0); n += 1.0;
125
+ } }
126
+ o = sum / n;
127
+ }`;
128
+ const UP = `${HEAD}
129
+ uniform highp sampler2D u_small; uniform int u_f; uniform ivec2 u_smallsize;
130
+ void main() {
131
+ ivec2 d = ivec2(gl_FragCoord.xy);
132
+ ivec2 sp = clamp(d / u_f, ivec2(0), u_smallsize - 1);
133
+ o = texelFetch(u_small, sp, 0);
134
+ }`;
135
+ const ADD = `${HEAD}
136
+ uniform sampler2D u_a; uniform sampler2D u_b;
137
+ void main() { o = texture(u_a, v_uv) + texture(u_b, v_uv); }`;
138
+ const SUB = `${HEAD}
139
+ uniform sampler2D u_a; uniform sampler2D u_b;
140
+ void main() { o = texture(u_a, v_uv) - texture(u_b, v_uv); }`;
141
+ const SHIFT = `${HEAD}
142
+ uniform sampler2D u_s; uniform vec3 u_d;
143
+ void main() { vec4 c = texture(u_s, v_uv); o = vec4(c.rgb + u_d, c.a); }`;
144
+ /** Separable Gaussian of `srcTex` → `out` via `tmp` (all distinct). */
145
+ function gauss(srcTex, w, h, radius, tmp, out) {
146
+ const r = Math.min(MAXR, Math.max(0, Math.round(radius)));
147
+ const sigma = Math.max(0.5, radius / 2);
148
+ pass(BLUR1D, tmp, s => {
149
+ s.tex("u_s", 0, srcTex);
150
+ s.v2("u_dir", 1 / w, 0);
151
+ s.f("u_sigma", sigma);
152
+ s.i("u_r", r);
153
+ });
154
+ pass(BLUR1D, out, s => {
155
+ s.tex("u_s", 0, tmp.tex);
156
+ s.v2("u_dir", 0, 1 / h);
157
+ s.f("u_sigma", sigma);
158
+ s.i("u_r", r);
159
+ });
160
+ }
161
+ export class Canvas extends Cell {
162
+ static traits = { equals };
163
+ constructor(v = { tex: null, w: 0, h: 0, epoch: 0 }) {
164
+ super(v, { equals });
165
+ }
166
+ /** Per-channel invert (alpha preserved). Involution. */
167
+ invert() {
168
+ const sf = scratch();
169
+ const sb = scratch();
170
+ const run = (alloc) => (v) => {
171
+ const out = alloc(v.w, v.h);
172
+ pass(INVERT, out, s => s.tex("u_s", 0, v.tex));
173
+ return stamp(out.tex, v.w, v.h);
174
+ };
175
+ return this.lens(run(sf), run(sb));
176
+ }
177
+ /** Horizontal flip. Involution. */
178
+ flipH() {
179
+ const sf = scratch();
180
+ const sb = scratch();
181
+ const run = (alloc) => (v) => {
182
+ const out = alloc(v.w, v.h);
183
+ pass(FLIPH, out, s => s.tex("u_s", 0, v.tex));
184
+ return stamp(out.tex, v.w, v.h);
185
+ };
186
+ return this.lens(run(sf), run(sb));
187
+ }
188
+ /** Multiply RGB by reactive `k` (alpha preserved). Invertible while
189
+ * k ≠ 0. */
190
+ brightness(k) {
191
+ const kf = reader(k);
192
+ const sf = scratch();
193
+ const sb = scratch();
194
+ const run = (alloc, gain) => (v) => {
195
+ const out = alloc(v.w, v.h);
196
+ pass(SCALE3, out, s => {
197
+ s.tex("u_s", 0, v.tex);
198
+ s.f("u_k", gain());
199
+ });
200
+ return stamp(out.tex, v.w, v.h);
201
+ };
202
+ return this.lens(run(sf, () => kf()), run(sb, () => 1 / kf()));
203
+ }
204
+ /** Grayscale (Rec.601 luma) view; complement is the per-pixel chroma
205
+ * residual `(r−Y, g−Y, b−Y)`, so editing the gray view recolours the
206
+ * source. The raster analog of `str.lowercase()`. */
207
+ grayscale() {
208
+ const sc = scratch();
209
+ const sf = scratch();
210
+ const sb = scratch();
211
+ const chromaOf = (v) => {
212
+ const c = sc(v.w, v.h);
213
+ pass(CHROMA, c, s => s.tex("u_s", 0, v.tex));
214
+ return c;
215
+ };
216
+ const self = this;
217
+ return Canvas.lens([self], {
218
+ init: ([s]) => chromaOf(s),
219
+ step: ([s], c, external) => (external ? chromaOf(s) : c),
220
+ fwd: ([s]) => {
221
+ const out = sf(s.w, s.h);
222
+ pass(LUMA, out, x => x.tex("u_s", 0, s.tex));
223
+ return stamp(out.tex, s.w, s.h);
224
+ },
225
+ bwd: (target, [s], c) => {
226
+ const out = sb(s.w, s.h);
227
+ pass(RECOLOR, out, x => {
228
+ x.tex("u_t", 0, target.tex);
229
+ x.tex("u_c", 1, c.tex);
230
+ });
231
+ return { updates: [stamp(out.tex, s.w, s.h)], complement: c };
232
+ },
233
+ });
234
+ }
235
+ /** Chroma view (the dual of `grayscale`): `(r−Y, g−Y, b−Y)` on a mid-grey
236
+ * base, complement is the luma `Y`. Editing the colour re-lights nothing —
237
+ * it rewrites hue while keeping the original brightness. */
238
+ chroma() {
239
+ const sc = scratch();
240
+ const sf = scratch();
241
+ const sb = scratch();
242
+ const lumaOf = (v) => {
243
+ const c = sc(v.w, v.h);
244
+ pass(LUMA, c, s => s.tex("u_s", 0, v.tex));
245
+ return c;
246
+ };
247
+ const self = this;
248
+ return Canvas.lens([self], {
249
+ init: ([s]) => lumaOf(s),
250
+ step: ([s], c, external) => (external ? lumaOf(s) : c),
251
+ fwd: ([s]) => {
252
+ const out = sf(s.w, s.h);
253
+ pass(CHROMA_VIEW, out, x => x.tex("u_s", 0, s.tex));
254
+ return stamp(out.tex, s.w, s.h);
255
+ },
256
+ bwd: (target, [s], c) => {
257
+ const out = sb(s.w, s.h);
258
+ pass(DELUMA, out, x => {
259
+ x.tex("u_t", 0, target.tex);
260
+ x.tex("u_c", 1, c.tex);
261
+ });
262
+ return { updates: [stamp(out.tex, s.w, s.h)], complement: c };
263
+ },
264
+ });
265
+ }
266
+ /** Sub-rectangle view (reactive `x,y,w,h`). Editing the crop composites
267
+ * back into the source; the surround reads straight from the parent. */
268
+ crop(x, y, w, h) {
269
+ const xf = reader(x);
270
+ const yf = reader(y);
271
+ const wf = reader(w);
272
+ const hf = reader(h);
273
+ const sf = scratch();
274
+ const sb = scratch2();
275
+ const self = this;
276
+ return Canvas.lens(self, v => {
277
+ const cw = Math.max(1, wf() | 0);
278
+ const ch = Math.max(1, hf() | 0);
279
+ const out = sf(cw, ch);
280
+ pass(CROP_FWD, out, s => {
281
+ s.tex("u_s", 0, v.tex);
282
+ s.v2("u_off", xf() | 0, yf() | 0);
283
+ s.v2("u_csize", cw, ch);
284
+ s.v2("u_ssize", v.w, v.h);
285
+ });
286
+ return stamp(out.tex, cw, ch);
287
+ }, (target, v) => {
288
+ const out = sb(v.w, v.h, v.tex);
289
+ pass(CROP_BWD, out, s => {
290
+ s.tex("u_s", 0, v.tex);
291
+ s.tex("u_t", 1, target.tex);
292
+ s.v2("u_off", xf() | 0, yf() | 0);
293
+ s.v2("u_csize", target.w, target.h);
294
+ s.v2("u_ssize", v.w, v.h);
295
+ });
296
+ return stamp(out.tex, v.w, v.h);
297
+ });
298
+ }
299
+ /** Box-downsampled thumbnail (integer `factor`). Complement is the
300
+ * Laplacian residual `source − up(down(source))`; editing the thumbnail
301
+ * reconstructs full-res detail on top of the edit. */
302
+ downsample(factor) {
303
+ const f = Math.max(1, Math.floor(factor));
304
+ const dw = (n) => Math.max(1, Math.floor(n / f));
305
+ const sdF = scratch();
306
+ const sdR = scratch();
307
+ const suR = scratch();
308
+ const sc = scratch();
309
+ const suB = scratch();
310
+ const sb = scratch();
311
+ const down = (alloc, srcTex, sw, sh) => {
312
+ const small = alloc(dw(sw), dw(sh));
313
+ pass(BOXDOWN, small, x => {
314
+ x.tex("u_s", 0, srcTex);
315
+ x.i("u_f", f);
316
+ x.i2("u_ssize", sw, sh);
317
+ });
318
+ return small;
319
+ };
320
+ const residualOf = (s) => {
321
+ const small = down(sdR, s.tex, s.w, s.h);
322
+ const up = suR(s.w, s.h);
323
+ pass(UP, up, x => {
324
+ x.tex("u_small", 0, small.tex);
325
+ x.i("u_f", f);
326
+ x.i2("u_smallsize", small.w, small.h);
327
+ });
328
+ const res = sc(s.w, s.h);
329
+ pass(SUB, res, x => {
330
+ x.tex("u_a", 0, s.tex);
331
+ x.tex("u_b", 1, up.tex);
332
+ });
333
+ return res;
334
+ };
335
+ const self = this;
336
+ return Canvas.lens([self], {
337
+ init: ([s]) => residualOf(s),
338
+ step: ([s], c, external) => (external ? residualOf(s) : c),
339
+ fwd: ([s]) => {
340
+ const small = down(sdF, s.tex, s.w, s.h);
341
+ return stamp(small.tex, small.w, small.h);
342
+ },
343
+ bwd: (target, [s], c) => {
344
+ const up = suB(s.w, s.h);
345
+ pass(UP, up, x => {
346
+ x.tex("u_small", 0, target.tex);
347
+ x.i("u_f", f);
348
+ x.i2("u_smallsize", target.w, target.h);
349
+ });
350
+ const out = sb(s.w, s.h);
351
+ pass(ADD, out, x => {
352
+ x.tex("u_a", 0, up.tex);
353
+ x.tex("u_b", 1, c.tex);
354
+ });
355
+ return { updates: [stamp(out.tex, s.w, s.h)], complement: c };
356
+ },
357
+ });
358
+ }
359
+ /** Gaussian blur (reactive `radius`). Writable: the backward direction
360
+ * runs an iterated Richardson–Lucy deconvolution seeded from the source.
361
+ * Each step is `x ← x · H(target / H(x))`, non-negative and multiplicative,
362
+ * so untouched regions stay fixed (their ratio is 1) while a stroke
363
+ * back-solves to a sharp pre-image with far less ringing than an additive
364
+ * solve. PutGet, not exact GetPut — the residual is the honest signature
365
+ * of an ill-posed inverse. */
366
+ blur(radius) {
367
+ const rf = reader(radius);
368
+ const fTmp = scratch();
369
+ const fOut = scratch();
370
+ const xa = scratch();
371
+ const xb = scratch();
372
+ const gTmp = scratch();
373
+ const est = scratch();
374
+ const ratio = scratch();
375
+ const corr = scratch();
376
+ return this.lens(v => {
377
+ const tmp = fTmp(v.w, v.h);
378
+ const out = fOut(v.w, v.h);
379
+ gauss(v.tex, v.w, v.h, rf(), tmp, out);
380
+ return stamp(out.tex, v.w, v.h);
381
+ }, (target, v) => {
382
+ const A = xa(v.w, v.h);
383
+ const B = xb(v.w, v.h);
384
+ let cur = v.tex === A.tex ? B : A;
385
+ let other = cur === A ? B : A;
386
+ copy(v.tex, cur);
387
+ const r = rf();
388
+ for (let it = 0; it < DECONV_ITERS; it++) {
389
+ const e = est(v.w, v.h);
390
+ gauss(cur.tex, v.w, v.h, r, gTmp(v.w, v.h), e);
391
+ const ra = ratio(v.w, v.h);
392
+ pass(RL_RATIO, ra, s => {
393
+ s.tex("u_t", 0, target.tex);
394
+ s.tex("u_est", 1, e.tex);
395
+ });
396
+ const co = corr(v.w, v.h);
397
+ gauss(ra.tex, v.w, v.h, r, gTmp(v.w, v.h), co);
398
+ pass(RL_MUL, other, s => {
399
+ s.tex("u_x", 0, cur.tex);
400
+ s.tex("u_c", 1, co.tex);
401
+ });
402
+ const t = cur;
403
+ cur = other;
404
+ other = t;
405
+ }
406
+ return stamp(cur.tex, v.w, v.h);
407
+ });
408
+ }
409
+ /** Mean colour (0–1) as a writable `Color`; the GPU reduces, the write
410
+ * shifts every pixel by the delta (a rigid translate in RGB). */
411
+ meanColor() {
412
+ const self = this;
413
+ const sb = scratch2();
414
+ return Color.lens(self, v => {
415
+ const [r, g, b, a] = reduceMean({ tex: v.tex, w: v.w, h: v.h });
416
+ return { r, g, b, a };
417
+ }, (target, v) => {
418
+ const [mr, mg, mb] = reduceMean({ tex: v.tex, w: v.w, h: v.h });
419
+ const out = sb(v.w, v.h, v.tex);
420
+ pass(SHIFT, out, s => {
421
+ s.tex("u_s", 0, v.tex);
422
+ s.v3("u_d", target.r - mr, target.g - mg, target.b - mb);
423
+ });
424
+ return stamp(out.tex, v.w, v.h);
425
+ });
426
+ }
427
+ /** Mean luma ≥ reactive `threshold` (0–1) as a writable `Bool`; flipping
428
+ * the bit auto-exposes — iterate the gain (clipping caps a single step)
429
+ * until the mean crosses the line. */
430
+ brighterThan(threshold) {
431
+ const tf = reader(threshold);
432
+ const self = this;
433
+ const xa = scratch();
434
+ const xb = scratch();
435
+ const meanLuma = (t, w, h) => {
436
+ const m = reduceMean({ tex: t, w, h });
437
+ return 0.299 * m[0] + 0.587 * m[1] + 0.114 * m[2];
438
+ };
439
+ return Bool.lens(self, v => meanLuma(v.tex, v.w, v.h) >= tf(), (target, v) => {
440
+ const t = tf();
441
+ const want = target ? t + 0.03 : t - 0.03;
442
+ const A = xa(v.w, v.h);
443
+ const B = xb(v.w, v.h);
444
+ let cur = v.tex === A.tex ? B : A;
445
+ let other = cur === A ? B : A;
446
+ copy(v.tex, cur);
447
+ for (let it = 0; it < 40; it++) {
448
+ const Y = meanLuma(cur.tex, v.w, v.h);
449
+ if (target ? Y >= t : Y < t)
450
+ break;
451
+ const k = Y > 0.002 ? want / Y : target ? 2 : 0;
452
+ if (Math.abs(k - 1) < 1e-3)
453
+ break;
454
+ pass(SCALE3, other, s => {
455
+ s.tex("u_s", 0, cur.tex);
456
+ s.f("u_k", k);
457
+ });
458
+ const tmp = cur;
459
+ cur = other;
460
+ other = tmp;
461
+ }
462
+ return stamp(cur.tex, v.w, v.h);
463
+ });
464
+ }
465
+ /** Dimensions `(w, h)` as a read-only `Vec`. */
466
+ get dimensions() {
467
+ return derived(this, "dimensions", Vec, v => ({ x: v.w, y: v.h }));
468
+ }
469
+ /** A GPU per-pixel spring driver seeded from this value's texture. The
470
+ * host steps it and writes `current()` back into a root cell each frame;
471
+ * settle is the GPU energy reduction. */
472
+ spring(opts) {
473
+ const s = new Spring(this.value.w, this.value.h, opts);
474
+ s.seed(this.value.tex);
475
+ return s;
476
+ }
477
+ }
478
+ /** Writable `Canvas` of size `w×h`. `painter` fills it pixel-by-pixel
479
+ * (RGBA, 0–255); omit for transparent black. Uploaded once to a texture. */
480
+ export function canvas(w, h, painter) {
481
+ const f = new Float32Array(w * h * 4);
482
+ if (painter !== undefined) {
483
+ let o = 0;
484
+ for (let y = 0; y < h; y++) {
485
+ for (let x = 0; x < w; x++) {
486
+ const [r, g, b, a] = painter(x, y);
487
+ f[o++] = r / 255;
488
+ f[o++] = g / 255;
489
+ f[o++] = b / 255;
490
+ f[o++] = a / 255;
491
+ }
492
+ }
493
+ }
494
+ return new Canvas(stamp(newTex(w, h, f), w, h));
495
+ }
@@ -0,0 +1,49 @@
1
+ import type { Easing } from "../../animation/index.js";
2
+ import { type Tween } from "../anim.js";
3
+ import { Cell, type Init, type Val, type Writable } from "../signal.js";
4
+ import type { Linear, Pack } from "../traits.js";
5
+ import { Num } from "./num.js";
6
+ type V = {
7
+ r: number;
8
+ g: number;
9
+ b: number;
10
+ a: number;
11
+ };
12
+ export declare const add: (a: V, b: V) => V;
13
+ export declare const sub: (a: V, b: V) => V;
14
+ export declare const scale: (a: V, k: number) => V;
15
+ export declare const lerp: (a: V, b: V, t: number) => V;
16
+ export declare const equals: (a: V, b: V) => boolean;
17
+ /** L2 distance in RGBA-space. */
18
+ export declare const metric: (a: V, b: V) => number;
19
+ export declare class Color extends Cell<V> {
20
+ static traits: {
21
+ linear: Linear<V>;
22
+ lerp: (a: V, b: V, t: number) => V;
23
+ metric: (a: V, b: V) => number;
24
+ equals: (a: V, b: V) => boolean;
25
+ pack: Pack<V>;
26
+ };
27
+ readonly _t: typeof Color.traits;
28
+ constructor(v?: V);
29
+ add(b: Val<V>): this;
30
+ sub(b: Val<V>): this;
31
+ scale(k: Val<number>): this;
32
+ lerp(b: Val<V>, t: Val<number>): Color;
33
+ get r(): this extends import("../index.js").WritableBrand ? Writable<Num> : Num;
34
+ get g(): this extends import("../index.js").WritableBrand ? Writable<Num> : Num;
35
+ get b(): this extends import("../index.js").WritableBrand ? Writable<Num> : Num;
36
+ get a(): this extends import("../index.js").WritableBrand ? Writable<Num> : Num;
37
+ get luminance(): Num;
38
+ get css(): Cell<string>;
39
+ /** Tween-builder, implied by the lerp trait. */
40
+ to(this: Writable<Color>, target: V, dur: Val<number>, ease?: Easing): Tween<V>;
41
+ }
42
+ /** Writable `Color` from RGB channels (alpha = 1). Each channel is a
43
+ * literal `number` (lifted to a fresh seed) or an existing `Writable<Num>`
44
+ * (identity passthrough). All-literal inputs take a fast path; mixed
45
+ * inputs build a lens so channel-writes round-trip to source. */
46
+ export declare function rgb(r: Init<Num>, g: Init<Num>, b: Init<Num>): Writable<Color>;
47
+ /** Writable `Color` from RGBA channels. Same lift rule as `rgb`. */
48
+ export declare function rgba(r: Init<Num>, g: Init<Num>, b: Init<Num>, a: Init<Num>): Writable<Color>;
49
+ export {};
@@ -0,0 +1,106 @@
1
+ // color.ts — reactive RGBA color.
2
+ //
3
+ // Invertibles (`add`, `sub`, `scale`) return `: this` and ride on
4
+ // `Cell#lens(fwd, bwd)`. Chained calls compose into a lens chain.
5
+ import { tween } from "../anim.js";
6
+ import { Cell, derive, lazy, reader, readNow } from "../signal.js";
7
+ import { derived, field } from "../writable.js";
8
+ import { Num, num } from "./num.js";
9
+ export const add = (a, b) => ({ r: a.r + b.r, g: a.g + b.g, b: a.b + b.b, a: a.a + b.a });
10
+ export const sub = (a, b) => ({ r: a.r - b.r, g: a.g - b.g, b: a.b - b.b, a: a.a - b.a });
11
+ export const scale = (a, k) => ({ r: a.r * k, g: a.g * k, b: a.b * k, a: a.a * k });
12
+ export const lerp = (a, b, t) => ({
13
+ r: a.r + (b.r - a.r) * t,
14
+ g: a.g + (b.g - a.g) * t,
15
+ b: a.b + (b.b - a.b) * t,
16
+ a: a.a + (b.a - a.a) * t,
17
+ });
18
+ export const equals = (a, b) => a === b || (a.r === b.r && a.g === b.g && a.b === b.b && a.a === b.a);
19
+ /** L2 distance in RGBA-space. */
20
+ export const metric = (a, b) => Math.hypot(a.r - b.r, a.g - b.g, a.b - b.b, a.a - b.a);
21
+ const linearImpl = { add, sub, scale };
22
+ const packImpl = {
23
+ dim: 4,
24
+ read: (v, a, o) => {
25
+ a[o] = v.r;
26
+ a[o + 1] = v.g;
27
+ a[o + 2] = v.b;
28
+ a[o + 3] = v.a;
29
+ },
30
+ write: (a, o) => ({ r: a[o], g: a[o + 1], b: a[o + 2], a: a[o + 3] }),
31
+ };
32
+ export class Color extends Cell {
33
+ static traits = {
34
+ linear: linearImpl,
35
+ lerp,
36
+ metric,
37
+ equals,
38
+ pack: packImpl,
39
+ };
40
+ constructor(v = { r: 0, g: 0, b: 0, a: 1 }) {
41
+ super(v, { equals });
42
+ }
43
+ add(b) {
44
+ const bf = reader(b);
45
+ return this.lens(v => add(v, bf()), n => sub(n, bf()));
46
+ }
47
+ sub(b) {
48
+ const bf = reader(b);
49
+ return this.lens(v => sub(v, bf()), n => add(n, bf()));
50
+ }
51
+ scale(k) {
52
+ const kf = reader(k);
53
+ return this.lens(v => scale(v, kf()), n => scale(n, 1 / kf()));
54
+ }
55
+ lerp(b, t) {
56
+ return Color.derive(() => lerp(this.value, readNow(b), readNow(t)));
57
+ }
58
+ get r() {
59
+ return field(this, "r", Num);
60
+ }
61
+ get g() {
62
+ return field(this, "g", Num);
63
+ }
64
+ get b() {
65
+ return field(this, "b", Num);
66
+ }
67
+ get a() {
68
+ return field(this, "a", Num);
69
+ }
70
+ get luminance() {
71
+ return derived(this, "luminance", Num, c => 0.299 * c.r + 0.587 * c.g + 0.114 * c.b);
72
+ }
73
+ get css() {
74
+ return lazy(this, "css", () => derive(() => {
75
+ const c = this.value;
76
+ const r = Math.round(c.r * 255);
77
+ const g = Math.round(c.g * 255);
78
+ const b = Math.round(c.b * 255);
79
+ return `rgba(${r}, ${g}, ${b}, ${c.a})`;
80
+ }));
81
+ }
82
+ /** Tween-builder, implied by the lerp trait. */
83
+ to(target, dur, ease) {
84
+ return tween(this, target, dur, ease);
85
+ }
86
+ }
87
+ /** Writable `Color` from RGB channels (alpha = 1). Each channel is a
88
+ * literal `number` (lifted to a fresh seed) or an existing `Writable<Num>`
89
+ * (identity passthrough). All-literal inputs take a fast path; mixed
90
+ * inputs build a lens so channel-writes round-trip to source. */
91
+ export function rgb(r, g, b) {
92
+ if (typeof r === "number" && typeof g === "number" && typeof b === "number") {
93
+ return new Color({ r, g, b, a: 1 });
94
+ }
95
+ return Color.lens([num(r), num(g), num(b)], ([r, g, b]) => ({ r, g, b, a: 1 }), target => [target.r, target.g, target.b]);
96
+ }
97
+ /** Writable `Color` from RGBA channels. Same lift rule as `rgb`. */
98
+ export function rgba(r, g, b, a) {
99
+ if (typeof r === "number" &&
100
+ typeof g === "number" &&
101
+ typeof b === "number" &&
102
+ typeof a === "number") {
103
+ return new Color({ r, g, b, a });
104
+ }
105
+ return Color.lens([num(r), num(g), num(b), num(a)], ([r, g, b, a]) => ({ r, g, b, a }), target => [target.r, target.g, target.b, target.a]);
106
+ }
@@ -0,0 +1,18 @@
1
+ import { Cell, type Writable } from "../signal.js";
2
+ import { Bool } from "./bool.js";
3
+ export declare class Flags<K extends string> extends Cell<number> {
4
+ #private;
5
+ readonly names: readonly K[];
6
+ static traits: {
7
+ equals: (a: number, b: number) => boolean;
8
+ };
9
+ readonly _t: typeof Flags.traits;
10
+ constructor(names: readonly K[], v?: number);
11
+ /** Bit lens for `name`; set/clear round-trips through the packed mask.
12
+ * Cached per name so repeated calls return the same lens. */
13
+ flag<F extends K>(name: F): Writable<Bool>;
14
+ }
15
+ /** Writable `Flags` from variadic bit names (bit `i` = the i-th name), or
16
+ * from an object of name→default (keys are the bits in insertion order). */
17
+ export declare function flags<const N extends readonly string[]>(...names: N): Writable<Flags<N[number]>>;
18
+ export declare function flags<const R extends Record<string, boolean>>(defaults: R): Writable<Flags<keyof R & string>>;