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.
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/animation/anim.d.ts +57 -0
- package/dist/animation/anim.js +318 -0
- package/dist/animation/combinators.d.ts +39 -0
- package/dist/animation/combinators.js +113 -0
- package/dist/animation/easings.d.ts +5 -0
- package/dist/animation/easings.js +5 -0
- package/dist/animation/index.d.ts +3 -0
- package/dist/animation/index.js +3 -0
- package/dist/assert/algebra.d.ts +20 -0
- package/dist/assert/algebra.js +79 -0
- package/dist/assert/claim.d.ts +40 -0
- package/dist/assert/claim.js +129 -0
- package/dist/assert/index.d.ts +7 -0
- package/dist/assert/index.js +19 -0
- package/dist/assert/predicates.d.ts +18 -0
- package/dist/assert/predicates.js +43 -0
- package/dist/assert/record.d.ts +20 -0
- package/dist/assert/record.js +78 -0
- package/dist/assert/scope.d.ts +42 -0
- package/dist/assert/scope.js +233 -0
- package/dist/assert/span.d.ts +37 -0
- package/dist/assert/span.js +68 -0
- package/dist/assert/tree.d.ts +22 -0
- package/dist/assert/tree.js +65 -0
- package/dist/code/code.d.ts +70 -0
- package/dist/code/code.js +361 -0
- package/dist/code/index.d.ts +2 -0
- package/dist/code/index.js +9 -0
- package/dist/code/morph.d.ts +5 -0
- package/dist/code/morph.js +194 -0
- package/dist/code/tokenize.d.ts +8 -0
- package/dist/code/tokenize.js +51 -0
- package/dist/constraints/cluster.d.ts +83 -0
- package/dist/constraints/cluster.js +213 -0
- package/dist/constraints/drivers.d.ts +15 -0
- package/dist/constraints/drivers.js +40 -0
- package/dist/constraints/factories.d.ts +73 -0
- package/dist/constraints/factories.js +248 -0
- package/dist/constraints/index.d.ts +11 -0
- package/dist/constraints/index.js +39 -0
- package/dist/constraints/interaction.d.ts +21 -0
- package/dist/constraints/interaction.js +148 -0
- package/dist/constraints/linalg.d.ts +18 -0
- package/dist/constraints/linalg.js +141 -0
- package/dist/constraints/phases.d.ts +21 -0
- package/dist/constraints/phases.js +60 -0
- package/dist/constraints/physics.d.ts +34 -0
- package/dist/constraints/physics.js +128 -0
- package/dist/constraints/rigid.d.ts +210 -0
- package/dist/constraints/rigid.js +835 -0
- package/dist/constraints/solver.d.ts +107 -0
- package/dist/constraints/solver.js +510 -0
- package/dist/constraints/term.d.ts +50 -0
- package/dist/constraints/term.js +80 -0
- package/dist/constraints/terms.d.ts +80 -0
- package/dist/constraints/terms.js +302 -0
- package/dist/constraints/world.d.ts +31 -0
- package/dist/constraints/world.js +245 -0
- package/dist/core/aggregates.d.ts +64 -0
- package/dist/core/aggregates.js +198 -0
- package/dist/core/anim.d.ts +84 -0
- package/dist/core/anim.js +301 -0
- package/dist/core/index.d.ts +38 -0
- package/dist/core/index.js +38 -0
- package/dist/core/introspect.d.ts +5 -0
- package/dist/core/introspect.js +31 -0
- package/dist/core/lenses/closed-form-policies.d.ts +64 -0
- package/dist/core/lenses/closed-form-policies.js +452 -0
- package/dist/core/lenses/domain-aggregates.d.ts +54 -0
- package/dist/core/lenses/domain-aggregates.js +259 -0
- package/dist/core/lenses/factor-lens.d.ts +42 -0
- package/dist/core/lenses/factor-lens.js +419 -0
- package/dist/core/lenses/index.d.ts +5 -0
- package/dist/core/lenses/index.js +16 -0
- package/dist/core/lenses/memory.d.ts +47 -0
- package/dist/core/lenses/memory.js +102 -0
- package/dist/core/lenses/typed-factor.d.ts +45 -0
- package/dist/core/lenses/typed-factor.js +376 -0
- package/dist/core/network-utils.d.ts +14 -0
- package/dist/core/network-utils.js +62 -0
- package/dist/core/new-primitives.d.ts +33 -0
- package/dist/core/new-primitives.js +113 -0
- package/dist/core/signal.d.ts +254 -0
- package/dist/core/signal.js +1349 -0
- package/dist/core/traits.d.ts +61 -0
- package/dist/core/traits.js +56 -0
- package/dist/core/tree.d.ts +23 -0
- package/dist/core/tree.js +62 -0
- package/dist/core/values/anchor.d.ts +23 -0
- package/dist/core/values/anchor.js +23 -0
- package/dist/core/values/audio.d.ts +33 -0
- package/dist/core/values/audio.js +107 -0
- package/dist/core/values/bool.d.ts +37 -0
- package/dist/core/values/bool.js +75 -0
- package/dist/core/values/box.d.ts +77 -0
- package/dist/core/values/box.js +211 -0
- package/dist/core/values/canvas.d.ts +71 -0
- package/dist/core/values/canvas.js +495 -0
- package/dist/core/values/color.d.ts +49 -0
- package/dist/core/values/color.js +106 -0
- package/dist/core/values/flags.d.ts +18 -0
- package/dist/core/values/flags.js +50 -0
- package/dist/core/values/gpu.d.ts +74 -0
- package/dist/core/values/gpu.js +426 -0
- package/dist/core/values/matrix.d.ts +53 -0
- package/dist/core/values/matrix.js +140 -0
- package/dist/core/values/num.d.ts +62 -0
- package/dist/core/values/num.js +166 -0
- package/dist/core/values/pose.d.ts +31 -0
- package/dist/core/values/pose.js +83 -0
- package/dist/core/values/range.d.ts +83 -0
- package/dist/core/values/range.js +167 -0
- package/dist/core/values/str.d.ts +76 -0
- package/dist/core/values/str.js +346 -0
- package/dist/core/values/template.d.ts +49 -0
- package/dist/core/values/template.js +148 -0
- package/dist/core/values/transform.d.ts +49 -0
- package/dist/core/values/transform.js +115 -0
- package/dist/core/values/tri.d.ts +31 -0
- package/dist/core/values/tri.js +95 -0
- package/dist/core/values/vec.d.ts +72 -0
- package/dist/core/values/vec.js +219 -0
- package/dist/core/writable.d.ts +15 -0
- package/dist/core/writable.js +29 -0
- package/dist/ext/events.d.ts +10 -0
- package/dist/ext/events.js +31 -0
- package/dist/ext/index.d.ts +4 -0
- package/dist/ext/index.js +4 -0
- package/dist/ext/snapshot.d.ts +8 -0
- package/dist/ext/snapshot.js +29 -0
- package/dist/ext/timeline.d.ts +56 -0
- package/dist/ext/timeline.js +94 -0
- package/dist/ext/waapi.d.ts +25 -0
- package/dist/ext/waapi.js +198 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +10 -0
- package/dist/propagators/index.d.ts +6 -0
- package/dist/propagators/index.js +6 -0
- package/dist/propagators/layout.d.ts +68 -0
- package/dist/propagators/layout.js +336 -0
- package/dist/propagators/network.d.ts +52 -0
- package/dist/propagators/network.js +185 -0
- package/dist/propagators/propagator.d.ts +12 -0
- package/dist/propagators/propagator.js +16 -0
- package/dist/propagators/range.d.ts +45 -0
- package/dist/propagators/range.js +147 -0
- package/dist/propagators/relations.d.ts +60 -0
- package/dist/propagators/relations.js +343 -0
- package/dist/shapes/annular-sector.d.ts +15 -0
- package/dist/shapes/annular-sector.js +64 -0
- package/dist/shapes/button.d.ts +14 -0
- package/dist/shapes/button.js +31 -0
- package/dist/shapes/choreographers.d.ts +22 -0
- package/dist/shapes/choreographers.js +69 -0
- package/dist/shapes/circle.d.ts +17 -0
- package/dist/shapes/circle.js +57 -0
- package/dist/shapes/clip.d.ts +5 -0
- package/dist/shapes/clip.js +31 -0
- package/dist/shapes/connect.d.ts +16 -0
- package/dist/shapes/connect.js +70 -0
- package/dist/shapes/curve.d.ts +60 -0
- package/dist/shapes/curve.js +285 -0
- package/dist/shapes/dashed.d.ts +16 -0
- package/dist/shapes/dashed.js +142 -0
- package/dist/shapes/debug.d.ts +43 -0
- package/dist/shapes/debug.js +97 -0
- package/dist/shapes/group.d.ts +5 -0
- package/dist/shapes/group.js +10 -0
- package/dist/shapes/handle.d.ts +32 -0
- package/dist/shapes/handle.js +88 -0
- package/dist/shapes/index.d.ts +23 -0
- package/dist/shapes/index.js +23 -0
- package/dist/shapes/interaction.d.ts +32 -0
- package/dist/shapes/interaction.js +187 -0
- package/dist/shapes/label.d.ts +20 -0
- package/dist/shapes/label.js +42 -0
- package/dist/shapes/layout.d.ts +29 -0
- package/dist/shapes/layout.js +74 -0
- package/dist/shapes/line.d.ts +21 -0
- package/dist/shapes/line.js +79 -0
- package/dist/shapes/list.d.ts +18 -0
- package/dist/shapes/list.js +51 -0
- package/dist/shapes/mount.d.ts +7 -0
- package/dist/shapes/mount.js +10 -0
- package/dist/shapes/path.d.ts +77 -0
- package/dist/shapes/path.js +227 -0
- package/dist/shapes/rect.d.ts +30 -0
- package/dist/shapes/rect.js +131 -0
- package/dist/shapes/shape.d.ts +132 -0
- package/dist/shapes/shape.js +306 -0
- package/dist/shapes/text.d.ts +24 -0
- package/dist/shapes/text.js +53 -0
- package/dist/shapes/tokens.d.ts +28 -0
- package/dist/shapes/tokens.js +27 -0
- package/dist/shapes/transitions.d.ts +23 -0
- package/dist/shapes/transitions.js +62 -0
- package/dist/tex/decorations.d.ts +26 -0
- package/dist/tex/decorations.js +116 -0
- package/dist/tex/index.d.ts +5 -0
- package/dist/tex/index.js +5 -0
- package/dist/tex/marker.d.ts +17 -0
- package/dist/tex/marker.js +63 -0
- package/dist/tex/motion.d.ts +43 -0
- package/dist/tex/motion.js +290 -0
- package/dist/tex/parts.d.ts +65 -0
- package/dist/tex/parts.js +149 -0
- package/dist/tex/tex.d.ts +45 -0
- package/dist/tex/tex.js +244 -0
- package/dist/web/attr.d.ts +16 -0
- package/dist/web/attr.js +98 -0
- package/dist/web/diagram.d.ts +49 -0
- package/dist/web/diagram.js +260 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +6 -0
- package/dist/web/md-marker.d.ts +6 -0
- package/dist/web/md-marker.js +39 -0
- package/dist/web/md-tex.d.ts +6 -0
- package/dist/web/md-tex.js +61 -0
- package/dist/web/raf.d.ts +6 -0
- package/dist/web/raf.js +24 -0
- package/dist/web/viewport.d.ts +7 -0
- package/dist/web/viewport.js +13 -0
- 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>>;
|