blob-tracker 0.1.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.
@@ -0,0 +1,355 @@
1
+ import { jsxs as Q, jsx as N } from "react/jsx-runtime";
2
+ import { useRef as $, useState as V, useEffect as Z, useCallback as tt } from "react";
3
+ function et(c, i, s = 30) {
4
+ const a = c.width, h = c.height, r = new Uint8Array(a * h), l = c.data, o = i.data;
5
+ for (let w = 0, f = 0; w < l.length; w += 4, f++) {
6
+ const g = Math.abs(l[w] - o[w]), m = Math.abs(l[w + 1] - o[w + 1]), y = Math.abs(l[w + 2] - o[w + 2]);
7
+ (g > m ? g > y ? g : y : m > y ? m : y) > s && (r[f] = 1);
8
+ }
9
+ return { data: r, width: a, height: h };
10
+ }
11
+ function nt(c) {
12
+ const { data: i, width: s, height: a } = c, h = new Uint8Array(s * a);
13
+ for (let r = 1; r < a - 1; r++)
14
+ for (let l = 1; l < s - 1; l++) {
15
+ const o = r * s + l;
16
+ i[o] && i[o - 1] && i[o + 1] && i[o - s] && i[o + s] && (h[o] = 1);
17
+ }
18
+ return { data: h, width: s, height: a };
19
+ }
20
+ function ot(c) {
21
+ const { data: i, width: s, height: a } = c, h = new Uint8Array(s * a);
22
+ for (let r = 1; r < a - 1; r++)
23
+ for (let l = 1; l < s - 1; l++) {
24
+ const o = r * s + l;
25
+ (i[o] || i[o - 1] || i[o + 1] || i[o - s] || i[o + s]) && (h[o] = 1);
26
+ }
27
+ return { data: h, width: s, height: a };
28
+ }
29
+ function st(c) {
30
+ return ot(nt(c));
31
+ }
32
+ function rt(c) {
33
+ const { data: i, width: s, height: a } = c, h = [], r = s - 1, l = a - 1, o = /* @__PURE__ */ new Set(), w = (e, n) => e < 0 || e >= s || n < 0 || n >= a ? 0 : i[n * s + e], f = (e, n) => w(e, n) << 3 | w(e + 1, n) << 2 | w(e + 1, n + 1) << 1 | w(e, n + 1), g = (e, n, b) => {
34
+ switch (b) {
35
+ case 0:
36
+ return { x: e + 0.5, y: n };
37
+ // top
38
+ case 1:
39
+ return { x: e + 1, y: n + 0.5 };
40
+ // right
41
+ case 2:
42
+ return { x: e + 0.5, y: n + 1 };
43
+ // bottom
44
+ case 3:
45
+ return { x: e, y: n + 0.5 };
46
+ // left
47
+ default:
48
+ return { x: e + 0.5, y: n + 0.5 };
49
+ }
50
+ }, m = [
51
+ [],
52
+ // 0: no edges
53
+ [[2, 3]],
54
+ // 1: BL
55
+ [[1, 2]],
56
+ // 2: BR
57
+ [[1, 3]],
58
+ // 3: BL+BR
59
+ [[0, 1]],
60
+ // 4: TR
61
+ [[0, 3], [1, 2]],
62
+ // 5: TR+BL (saddle)
63
+ [[0, 2]],
64
+ // 6: TR+BR
65
+ [[0, 3]],
66
+ // 7: TR+BR+BL
67
+ [[0, 3]],
68
+ // 8: TL
69
+ [[0, 2]],
70
+ // 9: TL+BL
71
+ [[0, 1], [2, 3]],
72
+ // 10: TL+BR (saddle)
73
+ [[0, 1]],
74
+ // 11: TL+BL+BR
75
+ [[1, 3]],
76
+ // 12: TL+TR
77
+ [[1, 2]],
78
+ // 13: TL+TR+BL
79
+ [[2, 3]],
80
+ // 14: TL+TR+BR
81
+ []
82
+ // 15: all filled, no boundary
83
+ ], y = [2, 3, 0, 1], X = (e, n, b) => {
84
+ switch (b) {
85
+ case 0:
86
+ return [e, n - 1];
87
+ // exit top → cell above
88
+ case 1:
89
+ return [e + 1, n];
90
+ // exit right → cell to right
91
+ case 2:
92
+ return [e, n + 1];
93
+ // exit bottom → cell below
94
+ case 3:
95
+ return [e - 1, n];
96
+ // exit left → cell to left
97
+ default:
98
+ return [e, n];
99
+ }
100
+ }, B = (e, n, b) => (n * r + e) * 4 + b, L = (e, n) => {
101
+ const b = m[e];
102
+ for (const t of b) {
103
+ if (t[0] === n) return t[1];
104
+ if (t[1] === n) return t[0];
105
+ }
106
+ return -1;
107
+ };
108
+ for (let e = 0; e < l; e++)
109
+ for (let n = 0; n < r; n++) {
110
+ const b = f(n, e);
111
+ if (b === 0 || b === 15) continue;
112
+ const t = m[b];
113
+ for (const F of t) {
114
+ const E = F[0], D = B(n, e, E);
115
+ if (o.has(D)) continue;
116
+ const M = [];
117
+ let u = n, p = e, v = E, k = !1;
118
+ for (let C = 0; C < 5e4; C++) {
119
+ const R = f(u, p);
120
+ if (R === 0 || R === 15) break;
121
+ const T = L(R, v);
122
+ if (T === -1) break;
123
+ o.add(B(u, p, v)), o.add(B(u, p, T)), M.push(g(u, p, T));
124
+ const [Y, d] = X(u, p, T);
125
+ if (Y === n && d === e && y[T] === E) {
126
+ k = !0;
127
+ break;
128
+ }
129
+ if (Y < 0 || Y >= r || d < 0 || d >= l) break;
130
+ u = Y, p = d, v = y[T];
131
+ }
132
+ M.length >= 3 && h.push({ points: M, isClosed: k });
133
+ }
134
+ }
135
+ return h;
136
+ }
137
+ function at(c, i = 15) {
138
+ const { data: s, width: a, height: h } = c, r = new Int32Array(a * h);
139
+ let l = 1;
140
+ const o = [], w = [1, -1, 0, 0], f = [0, 0, 1, -1];
141
+ for (let g = 0; g < h; g++)
142
+ for (let m = 0; m < a; m++) {
143
+ const y = g * a + m;
144
+ if (s[y] === 0 || r[y] !== 0) continue;
145
+ const X = l++, B = [y];
146
+ r[y] = X;
147
+ let L = m, e = m, n = g, b = g, t = 0, F = 0, E = 0, D = 0;
148
+ for (; D < B.length; ) {
149
+ const M = B[D++], u = M % a, p = (M - u) / a;
150
+ E++, t += u, F += p, u < L && (L = u), u > e && (e = u), p < n && (n = p), p > b && (b = p);
151
+ for (let v = 0; v < 4; v++) {
152
+ const k = u + w[v], C = p + f[v];
153
+ if (k < 0 || k >= a || C < 0 || C >= h) continue;
154
+ const R = C * a + k;
155
+ s[R] === 1 && r[R] === 0 && (r[R] = X, B.push(R));
156
+ }
157
+ }
158
+ E >= i && o.push({
159
+ id: o.length,
160
+ boundingBox: { x: L, y: n, w: e - L + 1, h: b - n + 1 },
161
+ centroid: { x: t / E, y: F / E },
162
+ area: E
163
+ });
164
+ }
165
+ return o;
166
+ }
167
+ function it(c, i, s) {
168
+ const a = s?.threshold ?? 30, h = s?.minBlobArea ?? 15;
169
+ let r = et(c, i, a);
170
+ r = st(r);
171
+ const l = rt(r), o = at(r, h);
172
+ return { contours: l, blobs: o, mask: r };
173
+ }
174
+ function ct(c, i) {
175
+ const s = $(null), a = $(i?.onFrame);
176
+ a.current = i?.onFrame;
177
+ const [h, r] = V({
178
+ contours: [],
179
+ blobs: [],
180
+ mask: null
181
+ }), { resolution: l = 120, threshold: o, minBlobArea: w } = i ?? {};
182
+ return Z(() => {
183
+ const f = c.current, g = s.current;
184
+ if (!f || !g) return;
185
+ const m = g.getContext("2d"), y = document.createElement("canvas"), X = y.getContext("2d"), B = () => {
186
+ const t = g.getBoundingClientRect(), F = window.devicePixelRatio;
187
+ g.width = t.width * F, g.height = t.height * F;
188
+ };
189
+ B(), window.addEventListener("resize", B);
190
+ let L = null, e, n = !1;
191
+ const b = () => {
192
+ if (n || f.videoWidth === 0) return;
193
+ n = !0;
194
+ const t = l, F = Math.round(t * (f.videoWidth / f.videoHeight)) || Math.round(t * (16 / 9));
195
+ y.width = F, y.height = t;
196
+ const E = () => {
197
+ if (f.paused || f.ended) {
198
+ e = requestAnimationFrame(E);
199
+ return;
200
+ }
201
+ const D = g.width, M = g.height, u = f.videoWidth / f.videoHeight, p = D / M;
202
+ let v, k, C, R;
203
+ u > p ? (v = D, k = D / u, C = 0, R = (M - k) / 2) : (k = M, v = M * u, C = (D - v) / 2, R = 0), m.fillStyle = "black", m.fillRect(0, 0, D, M), m.globalAlpha = 0.4, m.drawImage(f, C, R, v, k), m.globalAlpha = 1, X.drawImage(f, 0, 0, F, t);
204
+ const T = X.getImageData(0, 0, F, t);
205
+ if (L) {
206
+ const Y = it(T, L, { threshold: o, minBlobArea: w });
207
+ r(Y);
208
+ const d = {
209
+ dw: D,
210
+ dh: M,
211
+ offsetX: C,
212
+ offsetY: R,
213
+ scaleX: v / F,
214
+ scaleY: k / t
215
+ };
216
+ a.current?.(m, Y, d);
217
+ }
218
+ L = T, e = requestAnimationFrame(E);
219
+ };
220
+ E();
221
+ };
222
+ return f.videoWidth > 0 && b(), f.addEventListener("loadedmetadata", b), () => {
223
+ cancelAnimationFrame(e), window.removeEventListener("resize", B), f.removeEventListener("loadedmetadata", b);
224
+ };
225
+ }, [c, l, o, w]), { canvasRef: s, ...h };
226
+ }
227
+ function J(c) {
228
+ return `rgba(${c.r}, ${c.g}, ${c.b}, ${c.a})`;
229
+ }
230
+ const lt = { r: 179, g: 162, b: 255, a: 1 }, dt = { r: 0, g: 255, b: 255, a: 1 };
231
+ function ht({
232
+ src: c,
233
+ showEdges: i = !0,
234
+ showBlobs: s = !0,
235
+ showDiagnostic: a = !1,
236
+ edgeColor: h = lt,
237
+ blobColor: r = dt,
238
+ style: l,
239
+ resolution: o,
240
+ threshold: w,
241
+ minBlobArea: f
242
+ }) {
243
+ const g = $(null), m = $(i);
244
+ m.current = i;
245
+ const y = $(s);
246
+ y.current = s;
247
+ const X = $(a);
248
+ X.current = a;
249
+ const B = $(h);
250
+ B.current = h;
251
+ const L = $(r);
252
+ L.current = r;
253
+ const e = $(null), n = tt((t, F, E) => {
254
+ const { contours: D, blobs: M, mask: u } = F, { offsetX: p, offsetY: v, scaleX: k, scaleY: C, dw: R } = E, T = u.width, Y = u.height;
255
+ if (m.current) {
256
+ const d = [];
257
+ for (const A of D)
258
+ A.points.length >= 2 && d.push(A.points);
259
+ if (d.length > 0) {
260
+ const A = new Uint8Array(d.length), O = [];
261
+ A[0] = 1, O.push(d[0]);
262
+ for (let x = 1; x < d.length; x++) {
263
+ const I = O[O.length - 1], W = I[I.length - 1];
264
+ let P = 1 / 0, U = -1, q = !1;
265
+ for (let _ = 0; _ < d.length; _++) {
266
+ if (A[_]) continue;
267
+ const H = d[_], z = (H[0].x - W.x) ** 2 + (H[0].y - W.y) ** 2, G = (H[H.length - 1].x - W.x) ** 2 + (H[H.length - 1].y - W.y) ** 2, K = Math.min(z, G);
268
+ K < P && (P = K, U = _, q = G < z);
269
+ }
270
+ U !== -1 && (A[U] = 1, O.push(q ? [...d[U]].reverse() : d[U]));
271
+ }
272
+ const S = [];
273
+ for (const x of O)
274
+ for (const I of x) S.push(I);
275
+ t.save();
276
+ const j = S.length * 7.31;
277
+ t.strokeStyle = J(B.current), t.lineWidth = 1.5, t.beginPath(), t.moveTo(p + S[0].x * k, v + S[0].y * C);
278
+ for (let x = 1; x < S.length; x++) {
279
+ const I = Math.sin(x * 3.17 + j) * 0.4, W = Math.cos(x * 2.73 + j) * 0.4;
280
+ t.lineTo(p + S[x].x * k + I, v + S[x].y * C + W);
281
+ }
282
+ t.stroke(), t.restore();
283
+ }
284
+ }
285
+ if (y.current) {
286
+ t.save();
287
+ const d = J(L.current);
288
+ t.strokeStyle = d, t.lineWidth = 1.5, t.font = "11px monospace", t.fillStyle = d;
289
+ for (const A of M) {
290
+ const O = p + A.boundingBox.x * k, S = v + A.boundingBox.y * C, j = A.boundingBox.w * k, x = A.boundingBox.h * C;
291
+ t.strokeRect(O, S, j, x);
292
+ const I = (p + A.centroid.x * k).toFixed(5), W = (v + A.centroid.y * C).toFixed(4);
293
+ t.fillText(`${I}, ${W}`, O + j / 2, S + x / 2 + 4);
294
+ }
295
+ t.restore();
296
+ }
297
+ if (X.current) {
298
+ const d = R * 0.18, A = d * (Y / T), O = R - d - 16, S = 16;
299
+ e.current || (e.current = document.createElement("canvas"));
300
+ const j = e.current;
301
+ j.width = T, j.height = Y;
302
+ const x = j.getContext("2d"), I = x.createImageData(T, Y);
303
+ for (let W = 0; W < u.data.length; W++) {
304
+ const P = W * 4, U = u.data[W] * 255;
305
+ I.data[P] = U, I.data[P + 1] = U, I.data[P + 2] = U, I.data[P + 3] = 255;
306
+ }
307
+ x.putImageData(I, 0, 0), t.save(), t.globalAlpha = 0.85, t.drawImage(j, O, S, d, A), t.globalAlpha = 1, t.strokeStyle = "#00ffff", t.lineWidth = 1, t.strokeRect(O, S, d, A), t.font = "10px monospace", t.fillStyle = "#00ffff", t.fillText("MOTION MASK", O, S - 4), t.restore();
308
+ }
309
+ }, []), { canvasRef: b } = ct(g, {
310
+ resolution: o,
311
+ threshold: w,
312
+ minBlobArea: f,
313
+ onFrame: n
314
+ });
315
+ return /* @__PURE__ */ Q(
316
+ "div",
317
+ {
318
+ style: {
319
+ width: "100%",
320
+ height: "100%",
321
+ background: "black",
322
+ overflow: "hidden",
323
+ position: "relative",
324
+ ...l
325
+ },
326
+ children: [
327
+ /* @__PURE__ */ N(
328
+ "video",
329
+ {
330
+ ref: g,
331
+ src: c,
332
+ loop: !0,
333
+ muted: !0,
334
+ playsInline: !0,
335
+ autoPlay: !0,
336
+ style: { display: "none" }
337
+ }
338
+ ),
339
+ /* @__PURE__ */ N(
340
+ "canvas",
341
+ {
342
+ ref: b,
343
+ style: { width: "100%", height: "100%" }
344
+ }
345
+ )
346
+ ]
347
+ }
348
+ );
349
+ }
350
+ export {
351
+ ht as BlobTracker,
352
+ it as processFrame,
353
+ ct as useBlobTracker
354
+ };
355
+ //# sourceMappingURL=blob-tracker.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blob-tracker.es.js","sources":["../src/lib/cv/binaryMask.ts","../src/lib/cv/marchingSquares.ts","../src/lib/cv/connectedComponents.ts","../src/lib/cv/processFrame.ts","../src/useBlobTracker.ts","../src/lib/cv/color.ts","../src/BlobTracker.tsx"],"sourcesContent":["export interface BinaryMask {\n data: Uint8Array;\n width: number;\n height: number;\n}\n\n/**\n * Create a binary motion mask from two frames using max-channel difference.\n */\nexport function createMotionMask(\n current: ImageData,\n previous: ImageData,\n threshold: number = 30\n): BinaryMask {\n const w = current.width;\n const h = current.height;\n const mask = new Uint8Array(w * h);\n const cd = current.data;\n const pd = previous.data;\n\n for (let i = 0, p = 0; i < cd.length; i += 4, p++) {\n const dr = Math.abs(cd[i] - pd[i]);\n const dg = Math.abs(cd[i + 1] - pd[i + 1]);\n const db = Math.abs(cd[i + 2] - pd[i + 2]);\n const maxDiff = dr > dg ? (dr > db ? dr : db) : (dg > db ? dg : db);\n if (maxDiff > threshold) {\n mask[p] = 1;\n }\n }\n\n return { data: mask, width: w, height: h };\n}\n\n/**\n * Erode mask with a 3x3 structuring element.\n * A pixel survives only if all 4 cardinal neighbors are also 1.\n */\nexport function erodeMask(mask: BinaryMask): BinaryMask {\n const { data, width: w, height: h } = mask;\n const out = new Uint8Array(w * h);\n\n for (let y = 1; y < h - 1; y++) {\n for (let x = 1; x < w - 1; x++) {\n const i = y * w + x;\n if (\n data[i] &&\n data[i - 1] &&\n data[i + 1] &&\n data[i - w] &&\n data[i + w]\n ) {\n out[i] = 1;\n }\n }\n }\n\n return { data: out, width: w, height: h };\n}\n\n/**\n * Dilate mask with a 3x3 structuring element.\n * A pixel becomes 1 if any of its 4 cardinal neighbors is 1.\n */\nexport function dilateMask(mask: BinaryMask): BinaryMask {\n const { data, width: w, height: h } = mask;\n const out = new Uint8Array(w * h);\n\n for (let y = 1; y < h - 1; y++) {\n for (let x = 1; x < w - 1; x++) {\n const i = y * w + x;\n if (\n data[i] ||\n data[i - 1] ||\n data[i + 1] ||\n data[i - w] ||\n data[i + w]\n ) {\n out[i] = 1;\n }\n }\n }\n\n return { data: out, width: w, height: h };\n}\n\n/**\n * Morphological open: erode then dilate. Removes small noise specks.\n */\nexport function morphOpen(mask: BinaryMask): BinaryMask {\n return dilateMask(erodeMask(mask));\n}\n","import type { BinaryMask } from \"./binaryMask\";\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface Contour {\n points: Point[];\n isClosed: boolean;\n}\n\n/**\n * Marching squares contour extraction.\n * Walks cell-to-cell along boundaries in the binary mask.\n * Returns ordered polylines tracing the edges of foreground regions.\n */\nexport function extractContours(mask: BinaryMask): Contour[] {\n const { data, width: w, height: h } = mask;\n const contours: Contour[] = [];\n\n // Grid of cells is (w-1) x (h-1) — each cell looks at a 2x2 pixel block\n const cw = w - 1;\n const ch = h - 1;\n // Track visited edges: for each cell, track which edges have been traced\n // We use a Set of \"cellIndex:edgeDir\" strings\n const visitedEdges = new Set<number>();\n\n // Helper: get pixel value (0 or 1), with out-of-bounds = 0\n const px = (x: number, y: number): number => {\n if (x < 0 || x >= w || y < 0 || y >= h) return 0;\n return data[y * w + x];\n };\n\n // Compute the 4-bit case for the 2x2 cell at (cx, cy)\n // Top-left = (cx, cy), Top-right = (cx+1, cy)\n // Bottom-left = (cx, cy+1), Bottom-right = (cx+1, cy+1)\n const cellCase = (cx: number, cy: number): number => {\n return (\n (px(cx, cy) << 3) |\n (px(cx + 1, cy) << 2) |\n (px(cx + 1, cy + 1) << 1) |\n px(cx, cy + 1)\n );\n };\n\n // Edge midpoints for a cell at (cx, cy):\n // top: (cx+0.5, cy), right: (cx+1, cy+0.5), bottom: (cx+0.5, cy+1), left: (cx, cy+0.5)\n const edgeMidpoint = (cx: number, cy: number, edge: number): Point => {\n switch (edge) {\n case 0: return { x: cx + 0.5, y: cy }; // top\n case 1: return { x: cx + 1, y: cy + 0.5 }; // right\n case 2: return { x: cx + 0.5, y: cy + 1 }; // bottom\n case 3: return { x: cx, y: cy + 0.5 }; // left\n default: return { x: cx + 0.5, y: cy + 0.5 };\n }\n };\n\n // Lookup table: for each of the 16 cases, list pairs of edges that get connected\n // Each entry is [entryEdge, exitEdge] — edges: 0=top, 1=right, 2=bottom, 3=left\n // For saddle cases (5, 10), we use a default disambiguation\n const edgePairs: number[][][] = [\n [], // 0: no edges\n [[2, 3]], // 1: BL\n [[1, 2]], // 2: BR\n [[1, 3]], // 3: BL+BR\n [[0, 1]], // 4: TR\n [[0, 3], [1, 2]], // 5: TR+BL (saddle)\n [[0, 2]], // 6: TR+BR\n [[0, 3]], // 7: TR+BR+BL\n [[0, 3]], // 8: TL\n [[0, 2]], // 9: TL+BL\n [[0, 1], [2, 3]], // 10: TL+BR (saddle)\n [[0, 1]], // 11: TL+BL+BR\n [[1, 3]], // 12: TL+TR\n [[1, 2]], // 13: TL+TR+BL\n [[2, 3]], // 14: TL+TR+BR\n [], // 15: all filled, no boundary\n ];\n\n // Opposite edge: top<->bottom, right<->left\n const oppositeEdge = [2, 3, 0, 1];\n\n // Neighbor cell when exiting through an edge\n const neighborCell = (cx: number, cy: number, edge: number): [number, number] => {\n switch (edge) {\n case 0: return [cx, cy - 1]; // exit top → cell above\n case 1: return [cx + 1, cy]; // exit right → cell to right\n case 2: return [cx, cy + 1]; // exit bottom → cell below\n case 3: return [cx - 1, cy]; // exit left → cell to left\n default: return [cx, cy];\n }\n };\n\n // Encode cell+edge into a unique integer for visited tracking\n const edgeKey = (cx: number, cy: number, edge: number): number => {\n return (cy * cw + cx) * 4 + edge;\n };\n\n // Find the exit edge for a given cell case when entering from a specific edge\n const findExit = (caseVal: number, entryEdge: number): number => {\n const pairs = edgePairs[caseVal];\n // entryEdge is the edge we entered from (i.e., the opposite of the exit edge of the previous cell)\n for (const pair of pairs) {\n if (pair[0] === entryEdge) return pair[1];\n if (pair[1] === entryEdge) return pair[0];\n }\n return -1;\n };\n\n // Scan for contour starting points\n for (let cy = 0; cy < ch; cy++) {\n for (let cx = 0; cx < cw; cx++) {\n const c = cellCase(cx, cy);\n if (c === 0 || c === 15) continue;\n\n const pairs = edgePairs[c];\n for (const pair of pairs) {\n const startEdge = pair[0];\n const key = edgeKey(cx, cy, startEdge);\n if (visitedEdges.has(key)) continue;\n\n // Trace a contour starting from this cell/edge\n const points: Point[] = [];\n let curCx = cx;\n let curCy = cy;\n let curEntry = startEdge;\n let isClosed = false;\n\n for (let safety = 0; safety < 50000; safety++) {\n const curCase = cellCase(curCx, curCy);\n if (curCase === 0 || curCase === 15) break;\n\n const exitEdge = findExit(curCase, curEntry);\n if (exitEdge === -1) break;\n\n // Mark this edge pair as visited\n visitedEdges.add(edgeKey(curCx, curCy, curEntry));\n visitedEdges.add(edgeKey(curCx, curCy, exitEdge));\n\n // Add the midpoint of the exit edge\n points.push(edgeMidpoint(curCx, curCy, exitEdge));\n\n // Move to neighbor cell through the exit edge\n const [nx, ny] = neighborCell(curCx, curCy, exitEdge);\n\n // Check if we've looped back to start\n if (nx === cx && ny === cy) {\n const reentryEdge = oppositeEdge[exitEdge];\n if (reentryEdge === startEdge) {\n isClosed = true;\n break;\n }\n }\n\n // Check bounds\n if (nx < 0 || nx >= cw || ny < 0 || ny >= ch) break;\n\n curCx = nx;\n curCy = ny;\n curEntry = oppositeEdge[exitEdge]; // entry edge is opposite of exit\n }\n\n if (points.length >= 3) {\n contours.push({ points, isClosed });\n }\n }\n }\n }\n\n return contours;\n}\n","import type { BinaryMask } from \"./binaryMask\";\n\nexport interface BoundingBox {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\nexport interface Blob {\n id: number;\n boundingBox: BoundingBox;\n centroid: { x: number; y: number };\n area: number;\n}\n\n/**\n * Label connected components using flood-fill BFS.\n * Returns blobs with bounding boxes, centroids, and areas.\n * Filters out components smaller than minArea.\n */\nexport function labelConnectedComponents(\n mask: BinaryMask,\n minArea: number = 15\n): Blob[] {\n const { data, width: w, height: h } = mask;\n const labels = new Int32Array(w * h); // 0 = unlabeled\n let nextLabel = 1;\n const blobs: Blob[] = [];\n\n // 4-connected BFS neighbors\n const dx = [1, -1, 0, 0];\n const dy = [0, 0, 1, -1];\n\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const idx = y * w + x;\n if (data[idx] === 0 || labels[idx] !== 0) continue;\n\n // BFS flood fill\n const label = nextLabel++;\n const queue: number[] = [idx];\n labels[idx] = label;\n\n let minX = x, maxX = x, minY = y, maxY = y;\n let sumX = 0, sumY = 0, area = 0;\n\n let head = 0;\n while (head < queue.length) {\n const ci = queue[head++];\n const cx = ci % w;\n const cy = (ci - cx) / w;\n\n area++;\n sumX += cx;\n sumY += cy;\n if (cx < minX) minX = cx;\n if (cx > maxX) maxX = cx;\n if (cy < minY) minY = cy;\n if (cy > maxY) maxY = cy;\n\n for (let d = 0; d < 4; d++) {\n const nx = cx + dx[d];\n const ny = cy + dy[d];\n if (nx < 0 || nx >= w || ny < 0 || ny >= h) continue;\n const ni = ny * w + nx;\n if (data[ni] === 1 && labels[ni] === 0) {\n labels[ni] = label;\n queue.push(ni);\n }\n }\n }\n\n if (area >= minArea) {\n blobs.push({\n id: blobs.length,\n boundingBox: { x: minX, y: minY, w: maxX - minX + 1, h: maxY - minY + 1 },\n centroid: { x: sumX / area, y: sumY / area },\n area,\n });\n }\n }\n }\n\n return blobs;\n}\n","import { createMotionMask, morphOpen, type BinaryMask } from \"./binaryMask\";\nimport { extractContours, type Contour } from \"./marchingSquares\";\nimport { labelConnectedComponents, type Blob } from \"./connectedComponents\";\n\nexport interface ProcessFrameOptions {\n /** Pixel difference threshold for motion detection (0-255). Default: 30 */\n threshold?: number;\n /** Minimum blob area in pixels to keep. Default: 15 */\n minBlobArea?: number;\n}\n\nexport interface FrameResult {\n contours: Contour[];\n blobs: Blob[];\n mask: BinaryMask;\n}\n\n/**\n * Run the full blob-tracking pipeline on two consecutive frames.\n * Framework-agnostic — works with raw ImageData from any canvas.\n *\n * Pipeline: frame diff → binary mask → morphological open → contour extraction + blob labeling\n */\nexport function processFrame(\n current: ImageData,\n previous: ImageData,\n options?: ProcessFrameOptions\n): FrameResult {\n const threshold = options?.threshold ?? 30;\n const minBlobArea = options?.minBlobArea ?? 15;\n\n let mask = createMotionMask(current, previous, threshold);\n mask = morphOpen(mask);\n\n const contours = extractContours(mask);\n const blobs = labelConnectedComponents(mask, minBlobArea);\n\n return { contours, blobs, mask };\n}\n","import { useRef, useEffect, useState } from \"react\";\nimport { processFrame, type FrameResult, type ProcessFrameOptions } from \"./lib/cv/processFrame\";\nimport type { Contour } from \"./lib/cv/marchingSquares\";\nimport type { Blob } from \"./lib/cv/connectedComponents\";\nimport type { BinaryMask } from \"./lib/cv/binaryMask\";\n\nexport interface FrameLayout {\n /** Display canvas width */\n dw: number;\n /** Display canvas height */\n dh: number;\n /** Letterbox offset X */\n offsetX: number;\n /** Letterbox offset Y */\n offsetY: number;\n /** Scale factor: logic → display X */\n scaleX: number;\n /** Scale factor: logic → display Y */\n scaleY: number;\n}\n\nexport interface UseBlobTrackerOptions extends ProcessFrameOptions {\n /** Processing resolution height in pixels. Width computed from video aspect ratio. Default: 120 */\n resolution?: number;\n /** Called each frame after background is drawn. Draw your overlays here. */\n onFrame?: (ctx: CanvasRenderingContext2D, result: FrameResult, layout: FrameLayout) => void;\n}\n\nexport interface BlobTrackerState {\n contours: Contour[];\n blobs: Blob[];\n mask: BinaryMask | null;\n}\n\n/**\n * Hook that runs real-time blob tracking on a video element.\n * Returns a canvas ref for display + live contour/blob data each frame.\n *\n * @example\n * ```tsx\n * const videoRef = useRef<HTMLVideoElement>(null);\n * const { canvasRef, contours, blobs } = useBlobTracker(videoRef, {\n * threshold: 30,\n * onFrame: (ctx, { contours, blobs }, layout) => {\n * // draw your own overlays here\n * },\n * });\n * ```\n */\nexport function useBlobTracker(\n videoRef: React.RefObject<HTMLVideoElement | null>,\n options?: UseBlobTrackerOptions\n) {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const onFrameRef = useRef(options?.onFrame);\n onFrameRef.current = options?.onFrame;\n\n const [state, setState] = useState<BlobTrackerState>({\n contours: [],\n blobs: [],\n mask: null,\n });\n\n const { resolution = 120, threshold, minBlobArea } = options ?? {};\n\n useEffect(() => {\n const video = videoRef.current;\n const canvas = canvasRef.current;\n if (!video || !canvas) return;\n\n const ctx = canvas.getContext(\"2d\")!;\n\n const logicCanvas = document.createElement(\"canvas\");\n const logicCtx = logicCanvas.getContext(\"2d\")!;\n\n const syncCanvasSize = () => {\n const rect = canvas.getBoundingClientRect();\n const dpr = window.devicePixelRatio;\n canvas.width = rect.width * dpr;\n canvas.height = rect.height * dpr;\n };\n syncCanvasSize();\n window.addEventListener(\"resize\", syncCanvasSize);\n\n let prevFrame: ImageData | null = null;\n let animId: number;\n let started = false;\n\n const tryStart = () => {\n if (started) return;\n if (video.videoWidth === 0) return;\n started = true;\n\n const lh = resolution;\n const lw = Math.round(lh * (video.videoWidth / video.videoHeight)) || Math.round(lh * (16 / 9));\n logicCanvas.width = lw;\n logicCanvas.height = lh;\n\n const render = () => {\n if (video.paused || video.ended) {\n animId = requestAnimationFrame(render);\n return;\n }\n\n const dw = canvas.width;\n const dh = canvas.height;\n\n // Letterbox\n const videoAspect = video.videoWidth / video.videoHeight;\n const canvasAspect = dw / dh;\n let drawW: number, drawH: number, offsetX: number, offsetY: number;\n if (videoAspect > canvasAspect) {\n drawW = dw;\n drawH = dw / videoAspect;\n offsetX = 0;\n offsetY = (dh - drawH) / 2;\n } else {\n drawH = dh;\n drawW = dh * videoAspect;\n offsetX = (dw - drawW) / 2;\n offsetY = 0;\n }\n\n // Background: dimmed video\n ctx.fillStyle = \"black\";\n ctx.fillRect(0, 0, dw, dh);\n ctx.globalAlpha = 0.4;\n ctx.drawImage(video, offsetX, offsetY, drawW, drawH);\n ctx.globalAlpha = 1.0;\n\n // Capture low-res frame\n logicCtx.drawImage(video, 0, 0, lw, lh);\n const currentFrame = logicCtx.getImageData(0, 0, lw, lh);\n\n if (prevFrame) {\n const result = processFrame(currentFrame, prevFrame, { threshold, minBlobArea });\n setState(result);\n\n const layout: FrameLayout = {\n dw, dh, offsetX, offsetY,\n scaleX: drawW / lw,\n scaleY: drawH / lh,\n };\n onFrameRef.current?.(ctx, result, layout);\n }\n\n prevFrame = currentFrame;\n animId = requestAnimationFrame(render);\n };\n\n render();\n };\n\n if (video.videoWidth > 0) {\n tryStart();\n }\n video.addEventListener(\"loadedmetadata\", tryStart);\n\n return () => {\n cancelAnimationFrame(animId);\n window.removeEventListener(\"resize\", syncCanvasSize);\n video.removeEventListener(\"loadedmetadata\", tryStart);\n };\n }, [videoRef, resolution, threshold, minBlobArea]);\n\n return { canvasRef, ...state };\n}\n","export interface RGBA {\n r: number;\n g: number;\n b: number;\n a: number;\n}\n\n/** Convert RGBA to a CSS rgba() string for canvas usage. */\nexport function toRGBAString(c: RGBA): string {\n return `rgba(${c.r}, ${c.g}, ${c.b}, ${c.a})`;\n}\n\n/** Convert RGB to HSL. Returns h in [0,360], s and l in [0,1]. */\nexport function rgbToHsl(r: number, g: number, b: number): [number, number, number] {\n r /= 255; g /= 255; b /= 255;\n const max = Math.max(r, g, b), min = Math.min(r, g, b);\n const l = (max + min) / 2;\n if (max === min) return [0, 0, l];\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let h = 0;\n if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;\n else if (max === g) h = ((b - r) / d + 2) / 6;\n else h = ((r - g) / d + 4) / 6;\n return [h * 360, s, l];\n}\n\n/** Convert HSL to RGB. h in [0,360], s and l in [0,1]. Returns r,g,b in [0,255]. */\nexport function hslToRgb(h: number, s: number, l: number): [number, number, number] {\n h /= 360;\n if (s === 0) { const v = Math.round(l * 255); return [v, v, v]; }\n const hue2rgb = (p: number, q: number, t: number) => {\n if (t < 0) t += 1; if (t > 1) t -= 1;\n if (t < 1/6) return p + (q - p) * 6 * t;\n if (t < 1/2) return q;\n if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;\n return p;\n };\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n const p = 2 * l - q;\n return [\n Math.round(hue2rgb(p, q, h + 1/3) * 255),\n Math.round(hue2rgb(p, q, h) * 255),\n Math.round(hue2rgb(p, q, h - 1/3) * 255),\n ];\n}\n","import { useRef, useCallback } from \"react\";\nimport { useBlobTracker, type UseBlobTrackerOptions, type FrameLayout } from \"./useBlobTracker\";\nimport { toRGBAString, type RGBA } from \"./lib/cv/color\";\nimport type { FrameResult } from \"./lib/cv/processFrame\";\n\nexport interface BlobTrackerProps extends Omit<UseBlobTrackerOptions, \"onFrame\"> {\n /** Video source URL */\n src: string;\n /** Show contour edge lines. Default: true */\n showEdges?: boolean;\n /** Show blob bounding boxes with centroid labels. Default: true */\n showBlobs?: boolean;\n /** Show diagnostic motion mask overlay. Default: false */\n showDiagnostic?: boolean;\n /** Edge line color. Default: { r: 179, g: 162, b: 255, a: 1 } */\n edgeColor?: RGBA;\n /** Blob bounding box color. Default: { r: 0, g: 255, b: 255, a: 1 } */\n blobColor?: RGBA;\n /** CSS style for the container div */\n style?: React.CSSProperties;\n}\n\nconst DEFAULT_EDGE_COLOR: RGBA = { r: 179, g: 162, b: 255, a: 1 };\nconst DEFAULT_BLOB_COLOR: RGBA = { r: 0, g: 255, b: 255, a: 1 };\n\n/**\n * Drop-in blob tracker component. Renders a video with real-time\n * contour edges and blob bounding boxes overlaid.\n *\n * @example\n * ```tsx\n * <BlobTracker src=\"/video.mp4\" threshold={30} />\n * ```\n */\nexport function BlobTracker({\n src,\n showEdges = true,\n showBlobs = true,\n showDiagnostic = false,\n edgeColor = DEFAULT_EDGE_COLOR,\n blobColor = DEFAULT_BLOB_COLOR,\n style,\n resolution,\n threshold,\n minBlobArea,\n}: BlobTrackerProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n\n // Store visual options in refs so onFrame always sees latest values\n const showEdgesRef = useRef(showEdges);\n showEdgesRef.current = showEdges;\n const showBlobsRef = useRef(showBlobs);\n showBlobsRef.current = showBlobs;\n const showDiagRef = useRef(showDiagnostic);\n showDiagRef.current = showDiagnostic;\n const edgeColorRef = useRef(edgeColor);\n edgeColorRef.current = edgeColor;\n const blobColorRef = useRef(blobColor);\n blobColorRef.current = blobColor;\n\n // Reusable temp canvas for diagnostic overlay\n const tempCanvasRef = useRef<HTMLCanvasElement | null>(null);\n\n const onFrame = useCallback((ctx: CanvasRenderingContext2D, result: FrameResult, layout: FrameLayout) => {\n const { contours, blobs, mask } = result;\n const { offsetX, offsetY, scaleX, scaleY, dw } = layout;\n const lw = mask.width;\n const lh = mask.height;\n\n // Contour edges\n if (showEdgesRef.current) {\n const segments: { x: number; y: number }[][] = [];\n for (const contour of contours) {\n if (contour.points.length >= 2) segments.push(contour.points);\n }\n\n if (segments.length > 0) {\n const used = new Uint8Array(segments.length);\n const ordered: { x: number; y: number }[][] = [];\n used[0] = 1;\n ordered.push(segments[0]);\n\n for (let n = 1; n < segments.length; n++) {\n const last = ordered[ordered.length - 1];\n const end = last[last.length - 1];\n let bestDist = Infinity;\n let bestIdx = -1;\n let bestReverse = false;\n\n for (let j = 0; j < segments.length; j++) {\n if (used[j]) continue;\n const c = segments[j];\n const ds = (c[0].x - end.x) ** 2 + (c[0].y - end.y) ** 2;\n const de = (c[c.length - 1].x - end.x) ** 2 + (c[c.length - 1].y - end.y) ** 2;\n const d = Math.min(ds, de);\n if (d < bestDist) {\n bestDist = d;\n bestIdx = j;\n bestReverse = de < ds;\n }\n }\n\n if (bestIdx !== -1) {\n used[bestIdx] = 1;\n ordered.push(bestReverse ? [...segments[bestIdx]].reverse() : segments[bestIdx]);\n }\n }\n\n const allPts: { x: number; y: number }[] = [];\n for (const seg of ordered) {\n for (const p of seg) allPts.push(p);\n }\n\n ctx.save();\n const jitterSeed = allPts.length * 7.31;\n ctx.strokeStyle = toRGBAString(edgeColorRef.current);\n ctx.lineWidth = 1.5;\n ctx.beginPath();\n ctx.moveTo(offsetX + allPts[0].x * scaleX, offsetY + allPts[0].y * scaleY);\n for (let i = 1; i < allPts.length; i++) {\n const jx = Math.sin(i * 3.17 + jitterSeed) * 0.4;\n const jy = Math.cos(i * 2.73 + jitterSeed) * 0.4;\n ctx.lineTo(offsetX + allPts[i].x * scaleX + jx, offsetY + allPts[i].y * scaleY + jy);\n }\n ctx.stroke();\n ctx.restore();\n }\n }\n\n // Bounding boxes\n if (showBlobsRef.current) {\n ctx.save();\n const bc = toRGBAString(blobColorRef.current);\n ctx.strokeStyle = bc;\n ctx.lineWidth = 1.5;\n ctx.font = \"11px monospace\";\n ctx.fillStyle = bc;\n\n for (const blob of blobs) {\n const bx = offsetX + blob.boundingBox.x * scaleX;\n const by = offsetY + blob.boundingBox.y * scaleY;\n const bw = blob.boundingBox.w * scaleX;\n const bh = blob.boundingBox.h * scaleY;\n ctx.strokeRect(bx, by, bw, bh);\n\n const cx = (offsetX + blob.centroid.x * scaleX).toFixed(5);\n const cy = (offsetY + blob.centroid.y * scaleY).toFixed(4);\n ctx.fillText(`${cx}, ${cy}`, bx + bw / 2, by + bh / 2 + 4);\n }\n ctx.restore();\n }\n\n // Diagnostic overlay\n if (showDiagRef.current) {\n const diagW = dw * 0.18;\n const diagH = diagW * (lh / lw);\n const diagX = dw - diagW - 16;\n const diagY = 16;\n\n if (!tempCanvasRef.current) {\n tempCanvasRef.current = document.createElement(\"canvas\");\n }\n const tempCanvas = tempCanvasRef.current;\n tempCanvas.width = lw;\n tempCanvas.height = lh;\n const tempCtx = tempCanvas.getContext(\"2d\")!;\n\n const maskImg = tempCtx.createImageData(lw, lh);\n for (let i = 0; i < mask.data.length; i++) {\n const p = i * 4;\n const v = mask.data[i] * 255;\n maskImg.data[p] = v;\n maskImg.data[p + 1] = v;\n maskImg.data[p + 2] = v;\n maskImg.data[p + 3] = 255;\n }\n tempCtx.putImageData(maskImg, 0, 0);\n\n ctx.save();\n ctx.globalAlpha = 0.85;\n ctx.drawImage(tempCanvas, diagX, diagY, diagW, diagH);\n ctx.globalAlpha = 1.0;\n ctx.strokeStyle = \"#00ffff\";\n ctx.lineWidth = 1;\n ctx.strokeRect(diagX, diagY, diagW, diagH);\n ctx.font = \"10px monospace\";\n ctx.fillStyle = \"#00ffff\";\n ctx.fillText(\"MOTION MASK\", diagX, diagY - 4);\n ctx.restore();\n }\n }, []);\n\n const { canvasRef } = useBlobTracker(videoRef, {\n resolution,\n threshold,\n minBlobArea,\n onFrame,\n });\n\n return (\n <div\n style={{\n width: \"100%\",\n height: \"100%\",\n background: \"black\",\n overflow: \"hidden\",\n position: \"relative\",\n ...style,\n }}\n >\n <video\n ref={videoRef}\n src={src}\n loop\n muted\n playsInline\n autoPlay\n style={{ display: \"none\" }}\n />\n <canvas\n ref={canvasRef}\n style={{ width: \"100%\", height: \"100%\" }}\n />\n </div>\n );\n}\n"],"names":["createMotionMask","current","previous","threshold","w","mask","cd","pd","i","p","dr","dg","db","erodeMask","data","h","out","y","x","dilateMask","morphOpen","extractContours","contours","cw","ch","visitedEdges","px","cellCase","cx","cy","edgeMidpoint","edge","edgePairs","oppositeEdge","neighborCell","edgeKey","findExit","caseVal","entryEdge","pairs","pair","c","startEdge","key","points","curCx","curCy","curEntry","isClosed","safety","curCase","exitEdge","nx","ny","labelConnectedComponents","minArea","labels","nextLabel","blobs","dx","dy","idx","label","queue","minX","maxX","minY","maxY","sumX","sumY","area","head","ci","d","ni","processFrame","options","minBlobArea","useBlobTracker","videoRef","canvasRef","useRef","onFrameRef","state","setState","useState","resolution","useEffect","video","canvas","ctx","logicCanvas","logicCtx","syncCanvasSize","rect","dpr","prevFrame","animId","started","tryStart","lh","lw","render","dw","dh","videoAspect","canvasAspect","drawW","drawH","offsetX","offsetY","currentFrame","result","layout","toRGBAString","DEFAULT_EDGE_COLOR","DEFAULT_BLOB_COLOR","BlobTracker","src","showEdges","showBlobs","showDiagnostic","edgeColor","blobColor","style","showEdgesRef","showBlobsRef","showDiagRef","edgeColorRef","blobColorRef","tempCanvasRef","onFrame","useCallback","scaleX","scaleY","segments","contour","used","ordered","n","last","end","bestDist","bestIdx","bestReverse","j","ds","de","allPts","seg","jitterSeed","jx","jy","bc","blob","bx","by","bw","bh","diagW","diagH","diagX","diagY","tempCanvas","tempCtx","maskImg","v","jsxs","jsx"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ (function(P,q){typeof exports=="object"&&typeof module<"u"?q(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],q):(P=typeof globalThis<"u"?globalThis:P||self,q(P.BlobTracker={},P.jsxRuntime,P.React))})(this,(function(P,q,O){"use strict";function ee(c,a,s=30){const i=c.width,h=c.height,r=new Uint8Array(i*h),l=c.data,o=a.data;for(let p=0,u=0;p<l.length;p+=4,u++){const g=Math.abs(l[p]-o[p]),m=Math.abs(l[p+1]-o[p+1]),y=Math.abs(l[p+2]-o[p+2]);(g>m?g>y?g:y:m>y?m:y)>s&&(r[u]=1)}return{data:r,width:i,height:h}}function te(c){const{data:a,width:s,height:i}=c,h=new Uint8Array(s*i);for(let r=1;r<i-1;r++)for(let l=1;l<s-1;l++){const o=r*s+l;a[o]&&a[o-1]&&a[o+1]&&a[o-s]&&a[o+s]&&(h[o]=1)}return{data:h,width:s,height:i}}function ne(c){const{data:a,width:s,height:i}=c,h=new Uint8Array(s*i);for(let r=1;r<i-1;r++)for(let l=1;l<s-1;l++){const o=r*s+l;(a[o]||a[o-1]||a[o+1]||a[o-s]||a[o+s])&&(h[o]=1)}return{data:h,width:s,height:i}}function oe(c){return ne(te(c))}function se(c){const{data:a,width:s,height:i}=c,h=[],r=s-1,l=i-1,o=new Set,p=(t,n)=>t<0||t>=s||n<0||n>=i?0:a[n*s+t],u=(t,n)=>p(t,n)<<3|p(t+1,n)<<2|p(t+1,n+1)<<1|p(t,n+1),g=(t,n,b)=>{switch(b){case 0:return{x:t+.5,y:n};case 1:return{x:t+1,y:n+.5};case 2:return{x:t+.5,y:n+1};case 3:return{x:t,y:n+.5};default:return{x:t+.5,y:n+.5}}},m=[[],[[2,3]],[[1,2]],[[1,3]],[[0,1]],[[0,3],[1,2]],[[0,2]],[[0,3]],[[0,3]],[[0,2]],[[0,1],[2,3]],[[0,1]],[[1,3]],[[1,2]],[[2,3]],[]],y=[2,3,0,1],X=(t,n,b)=>{switch(b){case 0:return[t,n-1];case 1:return[t+1,n];case 2:return[t,n+1];case 3:return[t-1,n];default:return[t,n]}},M=(t,n,b)=>(n*r+t)*4+b,I=(t,n)=>{const b=m[t];for(const e of b){if(e[0]===n)return e[1];if(e[1]===n)return e[0]}return-1};for(let t=0;t<l;t++)for(let n=0;n<r;n++){const b=u(n,t);if(b===0||b===15)continue;const e=m[b];for(const S of e){const E=S[0],j=M(n,t,E);if(o.has(j))continue;const B=[];let f=n,w=t,v=E,k=!1;for(let R=0;R<5e4;R++){const A=u(f,w);if(A===0||A===15)break;const L=I(A,v);if(L===-1)break;o.add(M(f,w,v)),o.add(M(f,w,L)),B.push(g(f,w,L));const[Y,d]=X(f,w,L);if(Y===n&&d===t&&y[L]===E){k=!0;break}if(Y<0||Y>=r||d<0||d>=l)break;f=Y,w=d,v=y[L]}B.length>=3&&h.push({points:B,isClosed:k})}}return h}function re(c,a=15){const{data:s,width:i,height:h}=c,r=new Int32Array(i*h);let l=1;const o=[],p=[1,-1,0,0],u=[0,0,1,-1];for(let g=0;g<h;g++)for(let m=0;m<i;m++){const y=g*i+m;if(s[y]===0||r[y]!==0)continue;const X=l++,M=[y];r[y]=X;let I=m,t=m,n=g,b=g,e=0,S=0,E=0,j=0;for(;j<M.length;){const B=M[j++],f=B%i,w=(B-f)/i;E++,e+=f,S+=w,f<I&&(I=f),f>t&&(t=f),w<n&&(n=w),w>b&&(b=w);for(let v=0;v<4;v++){const k=f+p[v],R=w+u[v];if(k<0||k>=i||R<0||R>=h)continue;const A=R*i+k;s[A]===1&&r[A]===0&&(r[A]=X,M.push(A))}}E>=a&&o.push({id:o.length,boundingBox:{x:I,y:n,w:t-I+1,h:b-n+1},centroid:{x:e/E,y:S/E},area:E})}return o}function G(c,a,s){const i=s?.threshold??30,h=s?.minBlobArea??15;let r=ee(c,a,i);r=oe(r);const l=se(r),o=re(r,h);return{contours:l,blobs:o,mask:r}}function K(c,a){const s=O.useRef(null),i=O.useRef(a?.onFrame);i.current=a?.onFrame;const[h,r]=O.useState({contours:[],blobs:[],mask:null}),{resolution:l=120,threshold:o,minBlobArea:p}=a??{};return O.useEffect(()=>{const u=c.current,g=s.current;if(!u||!g)return;const m=g.getContext("2d"),y=document.createElement("canvas"),X=y.getContext("2d"),M=()=>{const e=g.getBoundingClientRect(),S=window.devicePixelRatio;g.width=e.width*S,g.height=e.height*S};M(),window.addEventListener("resize",M);let I=null,t,n=!1;const b=()=>{if(n||u.videoWidth===0)return;n=!0;const e=l,S=Math.round(e*(u.videoWidth/u.videoHeight))||Math.round(e*(16/9));y.width=S,y.height=e;const E=()=>{if(u.paused||u.ended){t=requestAnimationFrame(E);return}const j=g.width,B=g.height,f=u.videoWidth/u.videoHeight,w=j/B;let v,k,R,A;f>w?(v=j,k=j/f,R=0,A=(B-k)/2):(k=B,v=B*f,R=(j-v)/2,A=0),m.fillStyle="black",m.fillRect(0,0,j,B),m.globalAlpha=.4,m.drawImage(u,R,A,v,k),m.globalAlpha=1,X.drawImage(u,0,0,S,e);const L=X.getImageData(0,0,S,e);if(I){const Y=G(L,I,{threshold:o,minBlobArea:p});r(Y);const d={dw:j,dh:B,offsetX:R,offsetY:A,scaleX:v/S,scaleY:k/e};i.current?.(m,Y,d)}I=L,t=requestAnimationFrame(E)};E()};return u.videoWidth>0&&b(),u.addEventListener("loadedmetadata",b),()=>{cancelAnimationFrame(t),window.removeEventListener("resize",M),u.removeEventListener("loadedmetadata",b)}},[c,l,o,p]),{canvasRef:s,...h}}function N(c){return`rgba(${c.r}, ${c.g}, ${c.b}, ${c.a})`}const ie={r:179,g:162,b:255,a:1},ae={r:0,g:255,b:255,a:1};function ce({src:c,showEdges:a=!0,showBlobs:s=!0,showDiagnostic:i=!1,edgeColor:h=ie,blobColor:r=ae,style:l,resolution:o,threshold:p,minBlobArea:u}){const g=O.useRef(null),m=O.useRef(a);m.current=a;const y=O.useRef(s);y.current=s;const X=O.useRef(i);X.current=i;const M=O.useRef(h);M.current=h;const I=O.useRef(r);I.current=r;const t=O.useRef(null),n=O.useCallback((e,S,E)=>{const{contours:j,blobs:B,mask:f}=S,{offsetX:w,offsetY:v,scaleX:k,scaleY:R,dw:A}=E,L=f.width,Y=f.height;if(m.current){const d=[];for(const C of j)C.points.length>=2&&d.push(C.points);if(d.length>0){const C=new Uint8Array(d.length),W=[];C[0]=1,W.push(d[0]);for(let x=1;x<d.length;x++){const T=W[W.length-1],D=T[T.length-1];let H=1/0,$=-1,J=!1;for(let z=0;z<d.length;z++){if(C[z])continue;const _=d[z],Q=(_[0].x-D.x)**2+(_[0].y-D.y)**2,V=(_[_.length-1].x-D.x)**2+(_[_.length-1].y-D.y)**2,Z=Math.min(Q,V);Z<H&&(H=Z,$=z,J=V<Q)}$!==-1&&(C[$]=1,W.push(J?[...d[$]].reverse():d[$]))}const F=[];for(const x of W)for(const T of x)F.push(T);e.save();const U=F.length*7.31;e.strokeStyle=N(M.current),e.lineWidth=1.5,e.beginPath(),e.moveTo(w+F[0].x*k,v+F[0].y*R);for(let x=1;x<F.length;x++){const T=Math.sin(x*3.17+U)*.4,D=Math.cos(x*2.73+U)*.4;e.lineTo(w+F[x].x*k+T,v+F[x].y*R+D)}e.stroke(),e.restore()}}if(y.current){e.save();const d=N(I.current);e.strokeStyle=d,e.lineWidth=1.5,e.font="11px monospace",e.fillStyle=d;for(const C of B){const W=w+C.boundingBox.x*k,F=v+C.boundingBox.y*R,U=C.boundingBox.w*k,x=C.boundingBox.h*R;e.strokeRect(W,F,U,x);const T=(w+C.centroid.x*k).toFixed(5),D=(v+C.centroid.y*R).toFixed(4);e.fillText(`${T}, ${D}`,W+U/2,F+x/2+4)}e.restore()}if(X.current){const d=A*.18,C=d*(Y/L),W=A-d-16,F=16;t.current||(t.current=document.createElement("canvas"));const U=t.current;U.width=L,U.height=Y;const x=U.getContext("2d"),T=x.createImageData(L,Y);for(let D=0;D<f.data.length;D++){const H=D*4,$=f.data[D]*255;T.data[H]=$,T.data[H+1]=$,T.data[H+2]=$,T.data[H+3]=255}x.putImageData(T,0,0),e.save(),e.globalAlpha=.85,e.drawImage(U,W,F,d,C),e.globalAlpha=1,e.strokeStyle="#00ffff",e.lineWidth=1,e.strokeRect(W,F,d,C),e.font="10px monospace",e.fillStyle="#00ffff",e.fillText("MOTION MASK",W,F-4),e.restore()}},[]),{canvasRef:b}=K(g,{resolution:o,threshold:p,minBlobArea:u,onFrame:n});return q.jsxs("div",{style:{width:"100%",height:"100%",background:"black",overflow:"hidden",position:"relative",...l},children:[q.jsx("video",{ref:g,src:c,loop:!0,muted:!0,playsInline:!0,autoPlay:!0,style:{display:"none"}}),q.jsx("canvas",{ref:b,style:{width:"100%",height:"100%"}})]})}P.BlobTracker=ce,P.processFrame=G,P.useBlobTracker=K,Object.defineProperty(P,Symbol.toStringTag,{value:"Module"})}));
2
+ //# sourceMappingURL=blob-tracker.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blob-tracker.umd.js","sources":["../src/lib/cv/binaryMask.ts","../src/lib/cv/marchingSquares.ts","../src/lib/cv/connectedComponents.ts","../src/lib/cv/processFrame.ts","../src/useBlobTracker.ts","../src/lib/cv/color.ts","../src/BlobTracker.tsx"],"sourcesContent":["export interface BinaryMask {\n data: Uint8Array;\n width: number;\n height: number;\n}\n\n/**\n * Create a binary motion mask from two frames using max-channel difference.\n */\nexport function createMotionMask(\n current: ImageData,\n previous: ImageData,\n threshold: number = 30\n): BinaryMask {\n const w = current.width;\n const h = current.height;\n const mask = new Uint8Array(w * h);\n const cd = current.data;\n const pd = previous.data;\n\n for (let i = 0, p = 0; i < cd.length; i += 4, p++) {\n const dr = Math.abs(cd[i] - pd[i]);\n const dg = Math.abs(cd[i + 1] - pd[i + 1]);\n const db = Math.abs(cd[i + 2] - pd[i + 2]);\n const maxDiff = dr > dg ? (dr > db ? dr : db) : (dg > db ? dg : db);\n if (maxDiff > threshold) {\n mask[p] = 1;\n }\n }\n\n return { data: mask, width: w, height: h };\n}\n\n/**\n * Erode mask with a 3x3 structuring element.\n * A pixel survives only if all 4 cardinal neighbors are also 1.\n */\nexport function erodeMask(mask: BinaryMask): BinaryMask {\n const { data, width: w, height: h } = mask;\n const out = new Uint8Array(w * h);\n\n for (let y = 1; y < h - 1; y++) {\n for (let x = 1; x < w - 1; x++) {\n const i = y * w + x;\n if (\n data[i] &&\n data[i - 1] &&\n data[i + 1] &&\n data[i - w] &&\n data[i + w]\n ) {\n out[i] = 1;\n }\n }\n }\n\n return { data: out, width: w, height: h };\n}\n\n/**\n * Dilate mask with a 3x3 structuring element.\n * A pixel becomes 1 if any of its 4 cardinal neighbors is 1.\n */\nexport function dilateMask(mask: BinaryMask): BinaryMask {\n const { data, width: w, height: h } = mask;\n const out = new Uint8Array(w * h);\n\n for (let y = 1; y < h - 1; y++) {\n for (let x = 1; x < w - 1; x++) {\n const i = y * w + x;\n if (\n data[i] ||\n data[i - 1] ||\n data[i + 1] ||\n data[i - w] ||\n data[i + w]\n ) {\n out[i] = 1;\n }\n }\n }\n\n return { data: out, width: w, height: h };\n}\n\n/**\n * Morphological open: erode then dilate. Removes small noise specks.\n */\nexport function morphOpen(mask: BinaryMask): BinaryMask {\n return dilateMask(erodeMask(mask));\n}\n","import type { BinaryMask } from \"./binaryMask\";\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface Contour {\n points: Point[];\n isClosed: boolean;\n}\n\n/**\n * Marching squares contour extraction.\n * Walks cell-to-cell along boundaries in the binary mask.\n * Returns ordered polylines tracing the edges of foreground regions.\n */\nexport function extractContours(mask: BinaryMask): Contour[] {\n const { data, width: w, height: h } = mask;\n const contours: Contour[] = [];\n\n // Grid of cells is (w-1) x (h-1) — each cell looks at a 2x2 pixel block\n const cw = w - 1;\n const ch = h - 1;\n // Track visited edges: for each cell, track which edges have been traced\n // We use a Set of \"cellIndex:edgeDir\" strings\n const visitedEdges = new Set<number>();\n\n // Helper: get pixel value (0 or 1), with out-of-bounds = 0\n const px = (x: number, y: number): number => {\n if (x < 0 || x >= w || y < 0 || y >= h) return 0;\n return data[y * w + x];\n };\n\n // Compute the 4-bit case for the 2x2 cell at (cx, cy)\n // Top-left = (cx, cy), Top-right = (cx+1, cy)\n // Bottom-left = (cx, cy+1), Bottom-right = (cx+1, cy+1)\n const cellCase = (cx: number, cy: number): number => {\n return (\n (px(cx, cy) << 3) |\n (px(cx + 1, cy) << 2) |\n (px(cx + 1, cy + 1) << 1) |\n px(cx, cy + 1)\n );\n };\n\n // Edge midpoints for a cell at (cx, cy):\n // top: (cx+0.5, cy), right: (cx+1, cy+0.5), bottom: (cx+0.5, cy+1), left: (cx, cy+0.5)\n const edgeMidpoint = (cx: number, cy: number, edge: number): Point => {\n switch (edge) {\n case 0: return { x: cx + 0.5, y: cy }; // top\n case 1: return { x: cx + 1, y: cy + 0.5 }; // right\n case 2: return { x: cx + 0.5, y: cy + 1 }; // bottom\n case 3: return { x: cx, y: cy + 0.5 }; // left\n default: return { x: cx + 0.5, y: cy + 0.5 };\n }\n };\n\n // Lookup table: for each of the 16 cases, list pairs of edges that get connected\n // Each entry is [entryEdge, exitEdge] — edges: 0=top, 1=right, 2=bottom, 3=left\n // For saddle cases (5, 10), we use a default disambiguation\n const edgePairs: number[][][] = [\n [], // 0: no edges\n [[2, 3]], // 1: BL\n [[1, 2]], // 2: BR\n [[1, 3]], // 3: BL+BR\n [[0, 1]], // 4: TR\n [[0, 3], [1, 2]], // 5: TR+BL (saddle)\n [[0, 2]], // 6: TR+BR\n [[0, 3]], // 7: TR+BR+BL\n [[0, 3]], // 8: TL\n [[0, 2]], // 9: TL+BL\n [[0, 1], [2, 3]], // 10: TL+BR (saddle)\n [[0, 1]], // 11: TL+BL+BR\n [[1, 3]], // 12: TL+TR\n [[1, 2]], // 13: TL+TR+BL\n [[2, 3]], // 14: TL+TR+BR\n [], // 15: all filled, no boundary\n ];\n\n // Opposite edge: top<->bottom, right<->left\n const oppositeEdge = [2, 3, 0, 1];\n\n // Neighbor cell when exiting through an edge\n const neighborCell = (cx: number, cy: number, edge: number): [number, number] => {\n switch (edge) {\n case 0: return [cx, cy - 1]; // exit top → cell above\n case 1: return [cx + 1, cy]; // exit right → cell to right\n case 2: return [cx, cy + 1]; // exit bottom → cell below\n case 3: return [cx - 1, cy]; // exit left → cell to left\n default: return [cx, cy];\n }\n };\n\n // Encode cell+edge into a unique integer for visited tracking\n const edgeKey = (cx: number, cy: number, edge: number): number => {\n return (cy * cw + cx) * 4 + edge;\n };\n\n // Find the exit edge for a given cell case when entering from a specific edge\n const findExit = (caseVal: number, entryEdge: number): number => {\n const pairs = edgePairs[caseVal];\n // entryEdge is the edge we entered from (i.e., the opposite of the exit edge of the previous cell)\n for (const pair of pairs) {\n if (pair[0] === entryEdge) return pair[1];\n if (pair[1] === entryEdge) return pair[0];\n }\n return -1;\n };\n\n // Scan for contour starting points\n for (let cy = 0; cy < ch; cy++) {\n for (let cx = 0; cx < cw; cx++) {\n const c = cellCase(cx, cy);\n if (c === 0 || c === 15) continue;\n\n const pairs = edgePairs[c];\n for (const pair of pairs) {\n const startEdge = pair[0];\n const key = edgeKey(cx, cy, startEdge);\n if (visitedEdges.has(key)) continue;\n\n // Trace a contour starting from this cell/edge\n const points: Point[] = [];\n let curCx = cx;\n let curCy = cy;\n let curEntry = startEdge;\n let isClosed = false;\n\n for (let safety = 0; safety < 50000; safety++) {\n const curCase = cellCase(curCx, curCy);\n if (curCase === 0 || curCase === 15) break;\n\n const exitEdge = findExit(curCase, curEntry);\n if (exitEdge === -1) break;\n\n // Mark this edge pair as visited\n visitedEdges.add(edgeKey(curCx, curCy, curEntry));\n visitedEdges.add(edgeKey(curCx, curCy, exitEdge));\n\n // Add the midpoint of the exit edge\n points.push(edgeMidpoint(curCx, curCy, exitEdge));\n\n // Move to neighbor cell through the exit edge\n const [nx, ny] = neighborCell(curCx, curCy, exitEdge);\n\n // Check if we've looped back to start\n if (nx === cx && ny === cy) {\n const reentryEdge = oppositeEdge[exitEdge];\n if (reentryEdge === startEdge) {\n isClosed = true;\n break;\n }\n }\n\n // Check bounds\n if (nx < 0 || nx >= cw || ny < 0 || ny >= ch) break;\n\n curCx = nx;\n curCy = ny;\n curEntry = oppositeEdge[exitEdge]; // entry edge is opposite of exit\n }\n\n if (points.length >= 3) {\n contours.push({ points, isClosed });\n }\n }\n }\n }\n\n return contours;\n}\n","import type { BinaryMask } from \"./binaryMask\";\n\nexport interface BoundingBox {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\nexport interface Blob {\n id: number;\n boundingBox: BoundingBox;\n centroid: { x: number; y: number };\n area: number;\n}\n\n/**\n * Label connected components using flood-fill BFS.\n * Returns blobs with bounding boxes, centroids, and areas.\n * Filters out components smaller than minArea.\n */\nexport function labelConnectedComponents(\n mask: BinaryMask,\n minArea: number = 15\n): Blob[] {\n const { data, width: w, height: h } = mask;\n const labels = new Int32Array(w * h); // 0 = unlabeled\n let nextLabel = 1;\n const blobs: Blob[] = [];\n\n // 4-connected BFS neighbors\n const dx = [1, -1, 0, 0];\n const dy = [0, 0, 1, -1];\n\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const idx = y * w + x;\n if (data[idx] === 0 || labels[idx] !== 0) continue;\n\n // BFS flood fill\n const label = nextLabel++;\n const queue: number[] = [idx];\n labels[idx] = label;\n\n let minX = x, maxX = x, minY = y, maxY = y;\n let sumX = 0, sumY = 0, area = 0;\n\n let head = 0;\n while (head < queue.length) {\n const ci = queue[head++];\n const cx = ci % w;\n const cy = (ci - cx) / w;\n\n area++;\n sumX += cx;\n sumY += cy;\n if (cx < minX) minX = cx;\n if (cx > maxX) maxX = cx;\n if (cy < minY) minY = cy;\n if (cy > maxY) maxY = cy;\n\n for (let d = 0; d < 4; d++) {\n const nx = cx + dx[d];\n const ny = cy + dy[d];\n if (nx < 0 || nx >= w || ny < 0 || ny >= h) continue;\n const ni = ny * w + nx;\n if (data[ni] === 1 && labels[ni] === 0) {\n labels[ni] = label;\n queue.push(ni);\n }\n }\n }\n\n if (area >= minArea) {\n blobs.push({\n id: blobs.length,\n boundingBox: { x: minX, y: minY, w: maxX - minX + 1, h: maxY - minY + 1 },\n centroid: { x: sumX / area, y: sumY / area },\n area,\n });\n }\n }\n }\n\n return blobs;\n}\n","import { createMotionMask, morphOpen, type BinaryMask } from \"./binaryMask\";\nimport { extractContours, type Contour } from \"./marchingSquares\";\nimport { labelConnectedComponents, type Blob } from \"./connectedComponents\";\n\nexport interface ProcessFrameOptions {\n /** Pixel difference threshold for motion detection (0-255). Default: 30 */\n threshold?: number;\n /** Minimum blob area in pixels to keep. Default: 15 */\n minBlobArea?: number;\n}\n\nexport interface FrameResult {\n contours: Contour[];\n blobs: Blob[];\n mask: BinaryMask;\n}\n\n/**\n * Run the full blob-tracking pipeline on two consecutive frames.\n * Framework-agnostic — works with raw ImageData from any canvas.\n *\n * Pipeline: frame diff → binary mask → morphological open → contour extraction + blob labeling\n */\nexport function processFrame(\n current: ImageData,\n previous: ImageData,\n options?: ProcessFrameOptions\n): FrameResult {\n const threshold = options?.threshold ?? 30;\n const minBlobArea = options?.minBlobArea ?? 15;\n\n let mask = createMotionMask(current, previous, threshold);\n mask = morphOpen(mask);\n\n const contours = extractContours(mask);\n const blobs = labelConnectedComponents(mask, minBlobArea);\n\n return { contours, blobs, mask };\n}\n","import { useRef, useEffect, useState } from \"react\";\nimport { processFrame, type FrameResult, type ProcessFrameOptions } from \"./lib/cv/processFrame\";\nimport type { Contour } from \"./lib/cv/marchingSquares\";\nimport type { Blob } from \"./lib/cv/connectedComponents\";\nimport type { BinaryMask } from \"./lib/cv/binaryMask\";\n\nexport interface FrameLayout {\n /** Display canvas width */\n dw: number;\n /** Display canvas height */\n dh: number;\n /** Letterbox offset X */\n offsetX: number;\n /** Letterbox offset Y */\n offsetY: number;\n /** Scale factor: logic → display X */\n scaleX: number;\n /** Scale factor: logic → display Y */\n scaleY: number;\n}\n\nexport interface UseBlobTrackerOptions extends ProcessFrameOptions {\n /** Processing resolution height in pixels. Width computed from video aspect ratio. Default: 120 */\n resolution?: number;\n /** Called each frame after background is drawn. Draw your overlays here. */\n onFrame?: (ctx: CanvasRenderingContext2D, result: FrameResult, layout: FrameLayout) => void;\n}\n\nexport interface BlobTrackerState {\n contours: Contour[];\n blobs: Blob[];\n mask: BinaryMask | null;\n}\n\n/**\n * Hook that runs real-time blob tracking on a video element.\n * Returns a canvas ref for display + live contour/blob data each frame.\n *\n * @example\n * ```tsx\n * const videoRef = useRef<HTMLVideoElement>(null);\n * const { canvasRef, contours, blobs } = useBlobTracker(videoRef, {\n * threshold: 30,\n * onFrame: (ctx, { contours, blobs }, layout) => {\n * // draw your own overlays here\n * },\n * });\n * ```\n */\nexport function useBlobTracker(\n videoRef: React.RefObject<HTMLVideoElement | null>,\n options?: UseBlobTrackerOptions\n) {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const onFrameRef = useRef(options?.onFrame);\n onFrameRef.current = options?.onFrame;\n\n const [state, setState] = useState<BlobTrackerState>({\n contours: [],\n blobs: [],\n mask: null,\n });\n\n const { resolution = 120, threshold, minBlobArea } = options ?? {};\n\n useEffect(() => {\n const video = videoRef.current;\n const canvas = canvasRef.current;\n if (!video || !canvas) return;\n\n const ctx = canvas.getContext(\"2d\")!;\n\n const logicCanvas = document.createElement(\"canvas\");\n const logicCtx = logicCanvas.getContext(\"2d\")!;\n\n const syncCanvasSize = () => {\n const rect = canvas.getBoundingClientRect();\n const dpr = window.devicePixelRatio;\n canvas.width = rect.width * dpr;\n canvas.height = rect.height * dpr;\n };\n syncCanvasSize();\n window.addEventListener(\"resize\", syncCanvasSize);\n\n let prevFrame: ImageData | null = null;\n let animId: number;\n let started = false;\n\n const tryStart = () => {\n if (started) return;\n if (video.videoWidth === 0) return;\n started = true;\n\n const lh = resolution;\n const lw = Math.round(lh * (video.videoWidth / video.videoHeight)) || Math.round(lh * (16 / 9));\n logicCanvas.width = lw;\n logicCanvas.height = lh;\n\n const render = () => {\n if (video.paused || video.ended) {\n animId = requestAnimationFrame(render);\n return;\n }\n\n const dw = canvas.width;\n const dh = canvas.height;\n\n // Letterbox\n const videoAspect = video.videoWidth / video.videoHeight;\n const canvasAspect = dw / dh;\n let drawW: number, drawH: number, offsetX: number, offsetY: number;\n if (videoAspect > canvasAspect) {\n drawW = dw;\n drawH = dw / videoAspect;\n offsetX = 0;\n offsetY = (dh - drawH) / 2;\n } else {\n drawH = dh;\n drawW = dh * videoAspect;\n offsetX = (dw - drawW) / 2;\n offsetY = 0;\n }\n\n // Background: dimmed video\n ctx.fillStyle = \"black\";\n ctx.fillRect(0, 0, dw, dh);\n ctx.globalAlpha = 0.4;\n ctx.drawImage(video, offsetX, offsetY, drawW, drawH);\n ctx.globalAlpha = 1.0;\n\n // Capture low-res frame\n logicCtx.drawImage(video, 0, 0, lw, lh);\n const currentFrame = logicCtx.getImageData(0, 0, lw, lh);\n\n if (prevFrame) {\n const result = processFrame(currentFrame, prevFrame, { threshold, minBlobArea });\n setState(result);\n\n const layout: FrameLayout = {\n dw, dh, offsetX, offsetY,\n scaleX: drawW / lw,\n scaleY: drawH / lh,\n };\n onFrameRef.current?.(ctx, result, layout);\n }\n\n prevFrame = currentFrame;\n animId = requestAnimationFrame(render);\n };\n\n render();\n };\n\n if (video.videoWidth > 0) {\n tryStart();\n }\n video.addEventListener(\"loadedmetadata\", tryStart);\n\n return () => {\n cancelAnimationFrame(animId);\n window.removeEventListener(\"resize\", syncCanvasSize);\n video.removeEventListener(\"loadedmetadata\", tryStart);\n };\n }, [videoRef, resolution, threshold, minBlobArea]);\n\n return { canvasRef, ...state };\n}\n","export interface RGBA {\n r: number;\n g: number;\n b: number;\n a: number;\n}\n\n/** Convert RGBA to a CSS rgba() string for canvas usage. */\nexport function toRGBAString(c: RGBA): string {\n return `rgba(${c.r}, ${c.g}, ${c.b}, ${c.a})`;\n}\n\n/** Convert RGB to HSL. Returns h in [0,360], s and l in [0,1]. */\nexport function rgbToHsl(r: number, g: number, b: number): [number, number, number] {\n r /= 255; g /= 255; b /= 255;\n const max = Math.max(r, g, b), min = Math.min(r, g, b);\n const l = (max + min) / 2;\n if (max === min) return [0, 0, l];\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let h = 0;\n if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;\n else if (max === g) h = ((b - r) / d + 2) / 6;\n else h = ((r - g) / d + 4) / 6;\n return [h * 360, s, l];\n}\n\n/** Convert HSL to RGB. h in [0,360], s and l in [0,1]. Returns r,g,b in [0,255]. */\nexport function hslToRgb(h: number, s: number, l: number): [number, number, number] {\n h /= 360;\n if (s === 0) { const v = Math.round(l * 255); return [v, v, v]; }\n const hue2rgb = (p: number, q: number, t: number) => {\n if (t < 0) t += 1; if (t > 1) t -= 1;\n if (t < 1/6) return p + (q - p) * 6 * t;\n if (t < 1/2) return q;\n if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;\n return p;\n };\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n const p = 2 * l - q;\n return [\n Math.round(hue2rgb(p, q, h + 1/3) * 255),\n Math.round(hue2rgb(p, q, h) * 255),\n Math.round(hue2rgb(p, q, h - 1/3) * 255),\n ];\n}\n","import { useRef, useCallback } from \"react\";\nimport { useBlobTracker, type UseBlobTrackerOptions, type FrameLayout } from \"./useBlobTracker\";\nimport { toRGBAString, type RGBA } from \"./lib/cv/color\";\nimport type { FrameResult } from \"./lib/cv/processFrame\";\n\nexport interface BlobTrackerProps extends Omit<UseBlobTrackerOptions, \"onFrame\"> {\n /** Video source URL */\n src: string;\n /** Show contour edge lines. Default: true */\n showEdges?: boolean;\n /** Show blob bounding boxes with centroid labels. Default: true */\n showBlobs?: boolean;\n /** Show diagnostic motion mask overlay. Default: false */\n showDiagnostic?: boolean;\n /** Edge line color. Default: { r: 179, g: 162, b: 255, a: 1 } */\n edgeColor?: RGBA;\n /** Blob bounding box color. Default: { r: 0, g: 255, b: 255, a: 1 } */\n blobColor?: RGBA;\n /** CSS style for the container div */\n style?: React.CSSProperties;\n}\n\nconst DEFAULT_EDGE_COLOR: RGBA = { r: 179, g: 162, b: 255, a: 1 };\nconst DEFAULT_BLOB_COLOR: RGBA = { r: 0, g: 255, b: 255, a: 1 };\n\n/**\n * Drop-in blob tracker component. Renders a video with real-time\n * contour edges and blob bounding boxes overlaid.\n *\n * @example\n * ```tsx\n * <BlobTracker src=\"/video.mp4\" threshold={30} />\n * ```\n */\nexport function BlobTracker({\n src,\n showEdges = true,\n showBlobs = true,\n showDiagnostic = false,\n edgeColor = DEFAULT_EDGE_COLOR,\n blobColor = DEFAULT_BLOB_COLOR,\n style,\n resolution,\n threshold,\n minBlobArea,\n}: BlobTrackerProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n\n // Store visual options in refs so onFrame always sees latest values\n const showEdgesRef = useRef(showEdges);\n showEdgesRef.current = showEdges;\n const showBlobsRef = useRef(showBlobs);\n showBlobsRef.current = showBlobs;\n const showDiagRef = useRef(showDiagnostic);\n showDiagRef.current = showDiagnostic;\n const edgeColorRef = useRef(edgeColor);\n edgeColorRef.current = edgeColor;\n const blobColorRef = useRef(blobColor);\n blobColorRef.current = blobColor;\n\n // Reusable temp canvas for diagnostic overlay\n const tempCanvasRef = useRef<HTMLCanvasElement | null>(null);\n\n const onFrame = useCallback((ctx: CanvasRenderingContext2D, result: FrameResult, layout: FrameLayout) => {\n const { contours, blobs, mask } = result;\n const { offsetX, offsetY, scaleX, scaleY, dw } = layout;\n const lw = mask.width;\n const lh = mask.height;\n\n // Contour edges\n if (showEdgesRef.current) {\n const segments: { x: number; y: number }[][] = [];\n for (const contour of contours) {\n if (contour.points.length >= 2) segments.push(contour.points);\n }\n\n if (segments.length > 0) {\n const used = new Uint8Array(segments.length);\n const ordered: { x: number; y: number }[][] = [];\n used[0] = 1;\n ordered.push(segments[0]);\n\n for (let n = 1; n < segments.length; n++) {\n const last = ordered[ordered.length - 1];\n const end = last[last.length - 1];\n let bestDist = Infinity;\n let bestIdx = -1;\n let bestReverse = false;\n\n for (let j = 0; j < segments.length; j++) {\n if (used[j]) continue;\n const c = segments[j];\n const ds = (c[0].x - end.x) ** 2 + (c[0].y - end.y) ** 2;\n const de = (c[c.length - 1].x - end.x) ** 2 + (c[c.length - 1].y - end.y) ** 2;\n const d = Math.min(ds, de);\n if (d < bestDist) {\n bestDist = d;\n bestIdx = j;\n bestReverse = de < ds;\n }\n }\n\n if (bestIdx !== -1) {\n used[bestIdx] = 1;\n ordered.push(bestReverse ? [...segments[bestIdx]].reverse() : segments[bestIdx]);\n }\n }\n\n const allPts: { x: number; y: number }[] = [];\n for (const seg of ordered) {\n for (const p of seg) allPts.push(p);\n }\n\n ctx.save();\n const jitterSeed = allPts.length * 7.31;\n ctx.strokeStyle = toRGBAString(edgeColorRef.current);\n ctx.lineWidth = 1.5;\n ctx.beginPath();\n ctx.moveTo(offsetX + allPts[0].x * scaleX, offsetY + allPts[0].y * scaleY);\n for (let i = 1; i < allPts.length; i++) {\n const jx = Math.sin(i * 3.17 + jitterSeed) * 0.4;\n const jy = Math.cos(i * 2.73 + jitterSeed) * 0.4;\n ctx.lineTo(offsetX + allPts[i].x * scaleX + jx, offsetY + allPts[i].y * scaleY + jy);\n }\n ctx.stroke();\n ctx.restore();\n }\n }\n\n // Bounding boxes\n if (showBlobsRef.current) {\n ctx.save();\n const bc = toRGBAString(blobColorRef.current);\n ctx.strokeStyle = bc;\n ctx.lineWidth = 1.5;\n ctx.font = \"11px monospace\";\n ctx.fillStyle = bc;\n\n for (const blob of blobs) {\n const bx = offsetX + blob.boundingBox.x * scaleX;\n const by = offsetY + blob.boundingBox.y * scaleY;\n const bw = blob.boundingBox.w * scaleX;\n const bh = blob.boundingBox.h * scaleY;\n ctx.strokeRect(bx, by, bw, bh);\n\n const cx = (offsetX + blob.centroid.x * scaleX).toFixed(5);\n const cy = (offsetY + blob.centroid.y * scaleY).toFixed(4);\n ctx.fillText(`${cx}, ${cy}`, bx + bw / 2, by + bh / 2 + 4);\n }\n ctx.restore();\n }\n\n // Diagnostic overlay\n if (showDiagRef.current) {\n const diagW = dw * 0.18;\n const diagH = diagW * (lh / lw);\n const diagX = dw - diagW - 16;\n const diagY = 16;\n\n if (!tempCanvasRef.current) {\n tempCanvasRef.current = document.createElement(\"canvas\");\n }\n const tempCanvas = tempCanvasRef.current;\n tempCanvas.width = lw;\n tempCanvas.height = lh;\n const tempCtx = tempCanvas.getContext(\"2d\")!;\n\n const maskImg = tempCtx.createImageData(lw, lh);\n for (let i = 0; i < mask.data.length; i++) {\n const p = i * 4;\n const v = mask.data[i] * 255;\n maskImg.data[p] = v;\n maskImg.data[p + 1] = v;\n maskImg.data[p + 2] = v;\n maskImg.data[p + 3] = 255;\n }\n tempCtx.putImageData(maskImg, 0, 0);\n\n ctx.save();\n ctx.globalAlpha = 0.85;\n ctx.drawImage(tempCanvas, diagX, diagY, diagW, diagH);\n ctx.globalAlpha = 1.0;\n ctx.strokeStyle = \"#00ffff\";\n ctx.lineWidth = 1;\n ctx.strokeRect(diagX, diagY, diagW, diagH);\n ctx.font = \"10px monospace\";\n ctx.fillStyle = \"#00ffff\";\n ctx.fillText(\"MOTION MASK\", diagX, diagY - 4);\n ctx.restore();\n }\n }, []);\n\n const { canvasRef } = useBlobTracker(videoRef, {\n resolution,\n threshold,\n minBlobArea,\n onFrame,\n });\n\n return (\n <div\n style={{\n width: \"100%\",\n height: \"100%\",\n background: \"black\",\n overflow: \"hidden\",\n position: \"relative\",\n ...style,\n }}\n >\n <video\n ref={videoRef}\n src={src}\n loop\n muted\n playsInline\n autoPlay\n style={{ display: \"none\" }}\n />\n <canvas\n ref={canvasRef}\n style={{ width: \"100%\", height: \"100%\" }}\n />\n </div>\n );\n}\n"],"names":["createMotionMask","current","previous","threshold","w","mask","cd","pd","i","p","dr","dg","db","erodeMask","data","h","out","y","x","dilateMask","morphOpen","extractContours","contours","cw","ch","visitedEdges","px","cellCase","cx","cy","edgeMidpoint","edge","edgePairs","oppositeEdge","neighborCell","edgeKey","findExit","caseVal","entryEdge","pairs","pair","c","startEdge","key","points","curCx","curCy","curEntry","isClosed","safety","curCase","exitEdge","nx","ny","labelConnectedComponents","minArea","labels","nextLabel","blobs","dx","dy","idx","label","queue","minX","maxX","minY","maxY","sumX","sumY","area","head","ci","d","ni","processFrame","options","minBlobArea","useBlobTracker","videoRef","canvasRef","useRef","onFrameRef","state","setState","useState","resolution","useEffect","video","canvas","ctx","logicCanvas","logicCtx","syncCanvasSize","rect","dpr","prevFrame","animId","started","tryStart","lh","lw","render","dw","dh","videoAspect","canvasAspect","drawW","drawH","offsetX","offsetY","currentFrame","result","layout","toRGBAString","DEFAULT_EDGE_COLOR","DEFAULT_BLOB_COLOR","BlobTracker","src","showEdges","showBlobs","showDiagnostic","edgeColor","blobColor","style","showEdgesRef","showBlobsRef","showDiagRef","edgeColorRef","blobColorRef","tempCanvasRef","onFrame","useCallback","scaleX","scaleY","segments","contour","used","ordered","n","last","end","bestDist","bestIdx","bestReverse","j","ds","de","allPts","seg","jitterSeed","jx","jy","bc","blob","bx","by","bw","bh","diagW","diagH","diagX","diagY","tempCanvas","tempCtx","maskImg","v","jsxs","jsx"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "blob-tracker",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Trace and Blob Tracking Effect for React.",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "main": "./dist/blob-tracker.umd.js",
10
+ "module": "./dist/blob-tracker.es.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/blob-tracker.es.js",
16
+ "require": "./dist/blob-tracker.umd.js"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "dev": "vite",
21
+ "build": "tsc -b && vite build",
22
+ "lint": "eslint .",
23
+ "preview": "vite preview"
24
+ },
25
+ "peerDependencies": {
26
+ "react": ">=18",
27
+ "react-dom": ">=18"
28
+ },
29
+ "devDependencies": {
30
+ "@eslint/js": "^9.39.1",
31
+ "@types/node": "^24.10.1",
32
+ "@types/react": "^19.2.5",
33
+ "@types/react-dom": "^19.2.3",
34
+ "@vitejs/plugin-react": "^5.1.1",
35
+ "eslint": "^9.39.1",
36
+ "eslint-plugin-react-hooks": "^7.0.1",
37
+ "eslint-plugin-react-refresh": "^0.4.24",
38
+ "globals": "^16.5.0",
39
+ "react": "^19.2.0",
40
+ "react-dom": "^19.2.0",
41
+ "typescript": "~5.9.3",
42
+ "typescript-eslint": "^8.46.4",
43
+ "vite": "^7.2.4",
44
+ "vite-plugin-dts": "^4.5.4"
45
+ }
46
+ }