@xprite/pixelate 0.0.0-stage → 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.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +12 -0
  3. package/README.md +238 -2
  4. package/README.zh-CN.md +209 -0
  5. package/dist/analyze.d.ts +8 -0
  6. package/dist/analyze.js +217 -0
  7. package/dist/base.d.ts +18 -0
  8. package/dist/base.js +42 -0
  9. package/dist/cell-color-picker.d.ts +14 -0
  10. package/dist/cell-color-picker.js +278 -0
  11. package/dist/color-lab.d.ts +3 -0
  12. package/dist/color-lab.js +31 -0
  13. package/dist/color-step.d.ts +33 -0
  14. package/dist/color-step.js +62 -0
  15. package/dist/contours.d.ts +4 -0
  16. package/dist/contours.js +66 -0
  17. package/dist/denoise.d.ts +3 -0
  18. package/dist/denoise.js +37 -0
  19. package/dist/deskew.d.ts +24 -0
  20. package/dist/deskew.js +202 -0
  21. package/dist/grid-ac-rl-consensus.d.ts +127 -0
  22. package/dist/grid-ac-rl-consensus.js +497 -0
  23. package/dist/grid-arbitrate.d.ts +52 -0
  24. package/dist/grid-arbitrate.js +366 -0
  25. package/dist/grid-cut-snap.d.ts +15 -0
  26. package/dist/grid-cut-snap.js +77 -0
  27. package/dist/grid-description-length.d.ts +47 -0
  28. package/dist/grid-description-length.js +697 -0
  29. package/dist/grid-detect.d.ts +8 -0
  30. package/dist/grid-detect.js +665 -0
  31. package/dist/grid-edge-coherence.d.ts +4 -0
  32. package/dist/grid-edge-coherence.js +158 -0
  33. package/dist/grid-edge-path.d.ts +9 -0
  34. package/dist/grid-edge-path.js +201 -0
  35. package/dist/grid-frame-lattice.d.ts +25 -0
  36. package/dist/grid-frame-lattice.js +420 -0
  37. package/dist/grid-frequency-spectrum.d.ts +11 -0
  38. package/dist/grid-frequency-spectrum.js +89 -0
  39. package/dist/grid-period-boundary.d.ts +13 -0
  40. package/dist/grid-period-boundary.js +255 -0
  41. package/dist/grid-period-comb.d.ts +4 -0
  42. package/dist/grid-period-comb.js +230 -0
  43. package/dist/grid-period-distance.d.ts +7 -0
  44. package/dist/grid-period-distance.js +233 -0
  45. package/dist/grid-phase-field.d.ts +11 -0
  46. package/dist/grid-phase-field.js +446 -0
  47. package/dist/grid-pitch-lattice.d.ts +4 -0
  48. package/dist/grid-pitch-lattice.js +255 -0
  49. package/dist/grid-pitch-search.d.ts +11 -0
  50. package/dist/grid-pitch-search.js +233 -0
  51. package/dist/grid-sample.d.ts +4 -0
  52. package/dist/grid-sample.js +164 -0
  53. package/dist/grid-sampling.d.ts +13 -0
  54. package/dist/grid-sampling.js +132 -0
  55. package/dist/grid-strip-shift.d.ts +44 -0
  56. package/dist/grid-strip-shift.js +223 -0
  57. package/dist/grid-test-images.d.ts +69 -0
  58. package/dist/grid-test-images.js +547 -0
  59. package/dist/grid-warp.d.ts +38 -0
  60. package/dist/grid-warp.js +252 -0
  61. package/dist/grid.d.ts +8 -0
  62. package/dist/grid.js +43 -0
  63. package/dist/index.d.ts +16 -0
  64. package/dist/index.js +14 -0
  65. package/dist/line-guided/cv-lab.d.ts +4 -0
  66. package/dist/line-guided/cv-lab.js +134 -0
  67. package/dist/line-guided/cv-ops.d.ts +29 -0
  68. package/dist/line-guided/cv-ops.js +759 -0
  69. package/dist/line-guided/pixeloe-entry.d.ts +15 -0
  70. package/dist/line-guided/pixeloe-entry.js +122 -0
  71. package/dist/line-guided/pixeloe-legacy.d.ts +31 -0
  72. package/dist/line-guided/pixeloe-legacy.js +301 -0
  73. package/dist/line-guided/xdog.d.ts +7 -0
  74. package/dist/line-guided/xdog.js +84 -0
  75. package/dist/most-frequent.d.ts +3 -0
  76. package/dist/most-frequent.js +56 -0
  77. package/dist/outline-prepass.d.ts +37 -0
  78. package/dist/outline-prepass.js +56 -0
  79. package/dist/palette-snap.d.ts +4 -0
  80. package/dist/palette-snap.js +371 -0
  81. package/dist/palette.d.ts +21 -0
  82. package/dist/palette.js +280 -0
  83. package/dist/pixelate.d.ts +15 -0
  84. package/dist/pixelate.js +214 -0
  85. package/dist/pixelation-algorithm-registry.d.ts +34 -0
  86. package/dist/pixelation-algorithm-registry.js +716 -0
  87. package/dist/pixeloe-prepass.d.ts +12 -0
  88. package/dist/pixeloe-prepass.js +527 -0
  89. package/dist/quantization.d.ts +6 -0
  90. package/dist/quantization.js +145 -0
  91. package/dist/remove-background.d.ts +40 -0
  92. package/dist/remove-background.js +229 -0
  93. package/dist/resize.d.ts +3 -0
  94. package/dist/resize.js +86 -0
  95. package/dist/sampling.d.ts +7 -0
  96. package/dist/sampling.js +301 -0
  97. package/dist/settings.d.ts +36 -0
  98. package/dist/settings.js +181 -0
  99. package/dist/types.d.ts +255 -0
  100. package/dist/types.js +87 -0
  101. package/licenses/PixelOE-Apache-2.0.txt +201 -0
  102. package/licenses/pixel-art-fixer-MIT.txt +21 -0
  103. package/licenses/proper-pixel-art-MIT.txt +21 -0
  104. package/licenses/tensorhead-dithering-MIT.txt +21 -0
  105. package/package.json +62 -4
  106. package/wasm/ac-rl-detect-browser-worker.js +45 -0
  107. package/wasm/ac-rl-detect-worker.mjs +51 -0
  108. package/wasm/pixelfixer_detect.d.ts +65 -0
  109. package/wasm/pixelfixer_detect.js +334 -0
  110. package/wasm/pixelfixer_detect_bg.wasm +0 -0
@@ -0,0 +1,497 @@
1
+ /**
2
+ * AC+RL consensus detector via Fixer Rust WASM.
3
+ *
4
+ * Returns grid dimensions when autocorrelation and run-lengths agree
5
+ * on pixel grid size (consensus="fast:ac+rl(S)").
6
+ *
7
+ * Overlap modes:
8
+ * - Node (pixelbench / Vite SSR): worker_threads — beginAcRlConsensusJob + sync
9
+ * finishAcRlConsensusJob via receiveMessageOnPort (existing Exact harness path).
10
+ * - Browser product (import-worker): nested Worker + Promise join via
11
+ * beginAcRlConsensusJobAsync / finishAcRlConsensusJobAsync — no COOP/COEP /
12
+ * SharedArrayBuffer required (sync Atomics.wait join is what needed isolation).
13
+ * - Fallback: on-thread detectAcRlConsensus (sync job).
14
+ *
15
+ * Set XPRITE_AC_RL_WORKER=0 to force sync detect on Node. Product registers a
16
+ * browser worker factory with setAcRlBrowserWorkerFactory from import-worker.
17
+ */
18
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
19
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
20
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
21
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
22
+ });
23
+ }
24
+ return path;
25
+ };
26
+ const NODE_FS = "node:fs";
27
+ const WASM_BINARY = "../wasm/pixelfixer_detect_bg.wasm";
28
+ const NODE_WORKER = "../wasm/ac-rl-detect-worker.mjs";
29
+ /**
30
+ * A file in the package's `wasm/` folder, next to the compiled sources; null when this module
31
+ * was loaded from a URL that has no such neighbour (a bundle, a data: URL).
32
+ */
33
+ function packageFile(relative) {
34
+ try {
35
+ const url = new URL(relative, import.meta.url);
36
+ return url.protocol === "file:" ? decodeURIComponent(url.pathname) : null;
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ function nodeProcess() {
43
+ return globalThis.process;
44
+ }
45
+ function browserMessageChannelCtor() {
46
+ return globalThis.MessageChannel;
47
+ }
48
+ let wasmLoaded = false;
49
+ let detectFastAcRl = null;
50
+ let wtModule;
51
+ let detectWorker = null;
52
+ let workerReady = false;
53
+ let workerFailed = false;
54
+ let browserWorkerFactory = null;
55
+ let browserDetectWorker = null;
56
+ let browserWorkerReady = false;
57
+ let browserWorkerFailed = false;
58
+ let browserWorkerInitializing = null;
59
+ const browserRequests = new Set();
60
+ function nodeWorkerEnabled() {
61
+ const process = nodeProcess();
62
+ if (!process?.versions?.node)
63
+ return false;
64
+ if (process.env.XPRITE_AC_RL_WORKER === "0")
65
+ return false;
66
+ return true;
67
+ }
68
+ /**
69
+ * Product import-worker: register nested Worker factory (Vite `new URL(..., import.meta.url)`).
70
+ * Param is `() => unknown` so DOM `Worker` from apps/editor assigns without sharing DOM libs.
71
+ */
72
+ export function setAcRlBrowserWorkerFactory(factory) {
73
+ browserWorkerFactory = factory ? () => factory() : null;
74
+ for (const cancel of browserRequests)
75
+ cancel();
76
+ if (browserDetectWorker) {
77
+ try {
78
+ browserDetectWorker.terminate();
79
+ }
80
+ catch {
81
+ /* ignore */
82
+ }
83
+ }
84
+ browserDetectWorker = null;
85
+ browserWorkerReady = false;
86
+ browserWorkerFailed = false;
87
+ browserWorkerInitializing = null;
88
+ }
89
+ /** Lazy-load worker_threads without a static import (browser-safe). */
90
+ function loadWorkerThreads() {
91
+ if (wtModule !== undefined)
92
+ return wtModule;
93
+ if (!nodeWorkerEnabled()) {
94
+ wtModule = null;
95
+ return null;
96
+ }
97
+ const injected = globalThis.__XPRITE_WORKER_THREADS__;
98
+ if (injected &&
99
+ typeof injected.Worker === "function" &&
100
+ typeof injected.MessageChannel === "function" &&
101
+ typeof injected.receiveMessageOnPort === "function") {
102
+ wtModule = injected;
103
+ return wtModule;
104
+ }
105
+ try {
106
+ // eslint-disable-next-line no-new-func
107
+ const nodeRequire = new Function("m", "return require(m)");
108
+ wtModule = nodeRequire("node:worker_threads");
109
+ return wtModule;
110
+ }
111
+ catch {
112
+ wtModule = null;
113
+ return null;
114
+ }
115
+ }
116
+ /** Called from preloadWasm (async) so Vite SSR can import worker_threads without sync require. */
117
+ async function loadWorkerThreadsAsync() {
118
+ const existing = loadWorkerThreads();
119
+ if (existing)
120
+ return existing;
121
+ if (!nodeWorkerEnabled())
122
+ return null;
123
+ try {
124
+ const spec = "node:worker_threads";
125
+ const wt = (await import(__rewriteRelativeImportExtension(/* @vite-ignore */ spec)));
126
+ wtModule = wt;
127
+ globalThis.__XPRITE_WORKER_THREADS__ = wt;
128
+ return wt;
129
+ }
130
+ catch {
131
+ wtModule = null;
132
+ return null;
133
+ }
134
+ }
135
+ function resolveWorkerPath() {
136
+ const process = nodeProcess();
137
+ if (process?.env.XPRITE_AC_RL_WORKER_PATH)
138
+ return process.env.XPRITE_AC_RL_WORKER_PATH;
139
+ return packageFile(NODE_WORKER);
140
+ }
141
+ function syncCall(wt, worker, payload, transfer = []) {
142
+ const { port1, port2 } = new wt.MessageChannel();
143
+ worker.postMessage({ ...payload, port: port2 }, [port2, ...transfer]);
144
+ for (;;) {
145
+ const msg = wt.receiveMessageOnPort(port1);
146
+ if (msg)
147
+ return msg.message;
148
+ }
149
+ }
150
+ function ensureDetectWorker() {
151
+ if (workerFailed)
152
+ return false;
153
+ if (detectWorker && workerReady)
154
+ return true;
155
+ const wt = loadWorkerThreads();
156
+ if (!wt)
157
+ return false;
158
+ const workerPath = resolveWorkerPath();
159
+ if (!workerPath) {
160
+ workerFailed = true;
161
+ return false;
162
+ }
163
+ try {
164
+ if (!detectWorker) {
165
+ detectWorker = new wt.Worker(workerPath);
166
+ // Do not keep the parent process alive (pixel-bench workers must exit)
167
+ const maybeUnref = detectWorker;
168
+ maybeUnref.unref?.();
169
+ detectWorker.on("error", () => {
170
+ workerFailed = true;
171
+ detectWorker = null;
172
+ workerReady = false;
173
+ });
174
+ }
175
+ const init = syncCall(wt, detectWorker, { type: "init" });
176
+ if (!init.ok) {
177
+ workerFailed = true;
178
+ detectWorker = null;
179
+ workerReady = false;
180
+ return false;
181
+ }
182
+ workerReady = true;
183
+ return true;
184
+ }
185
+ catch {
186
+ workerFailed = true;
187
+ detectWorker = null;
188
+ workerReady = false;
189
+ return false;
190
+ }
191
+ }
192
+ function portMessagePromise(port1, pending) {
193
+ return new Promise((resolve) => {
194
+ let settled = false;
195
+ const cancel = () => finish({ ok: false });
196
+ const finish = (body) => {
197
+ if (settled)
198
+ return;
199
+ settled = true;
200
+ pending?.delete(cancel);
201
+ port1.onmessage = null;
202
+ port1.close?.();
203
+ resolve(body);
204
+ };
205
+ pending?.add(cancel);
206
+ // Node worker_threads MessagePort is an EventEmitter (prefer over onmessage).
207
+ const process = nodeProcess();
208
+ if (typeof port1.on === "function" && process?.versions?.node) {
209
+ const onMsg = (msg) => {
210
+ port1.off?.("message", onMsg);
211
+ finish(msg);
212
+ };
213
+ port1.on("message", onMsg);
214
+ port1.start?.();
215
+ return;
216
+ }
217
+ // Browser MessagePort
218
+ port1.onmessage = (ev) => {
219
+ port1.onmessage = null;
220
+ finish(ev.data);
221
+ };
222
+ });
223
+ }
224
+ function failBrowserDetectWorker(worker) {
225
+ if (browserDetectWorker !== worker)
226
+ return;
227
+ browserWorkerFailed = true;
228
+ browserDetectWorker = null;
229
+ browserWorkerReady = false;
230
+ try {
231
+ worker.terminate();
232
+ }
233
+ catch {
234
+ /* Already stopped. */
235
+ }
236
+ for (const cancel of browserRequests)
237
+ cancel();
238
+ }
239
+ async function initializeBrowserDetectWorker() {
240
+ let worker = browserDetectWorker;
241
+ try {
242
+ if (!worker) {
243
+ worker = browserWorkerFactory();
244
+ browserDetectWorker = worker;
245
+ const ownedWorker = worker;
246
+ worker.onerror = () => failBrowserDetectWorker(ownedWorker);
247
+ }
248
+ const MessageChannel = browserMessageChannelCtor();
249
+ if (!MessageChannel)
250
+ throw new Error("MessageChannel unavailable");
251
+ const { port1, port2 } = new MessageChannel();
252
+ const initPromise = portMessagePromise(port1, browserRequests);
253
+ worker.postMessage({ type: "init", port: port2 }, [port2]);
254
+ const init = await initPromise;
255
+ if (browserDetectWorker !== worker)
256
+ return false;
257
+ if (!init.ok) {
258
+ failBrowserDetectWorker(worker);
259
+ return false;
260
+ }
261
+ browserWorkerReady = true;
262
+ return true;
263
+ }
264
+ catch {
265
+ if (worker)
266
+ failBrowserDetectWorker(worker);
267
+ else
268
+ browserWorkerFailed = true;
269
+ return false;
270
+ }
271
+ }
272
+ async function ensureBrowserDetectWorker() {
273
+ if (browserWorkerFailed || !browserWorkerFactory)
274
+ return false;
275
+ if (browserDetectWorker && browserWorkerReady)
276
+ return true;
277
+ if (browserWorkerInitializing)
278
+ return browserWorkerInitializing;
279
+ const initializing = initializeBrowserDetectWorker();
280
+ browserWorkerInitializing = initializing;
281
+ try {
282
+ return await initializing;
283
+ }
284
+ finally {
285
+ if (browserWorkerInitializing === initializing)
286
+ browserWorkerInitializing = null;
287
+ }
288
+ }
289
+ /**
290
+ * Preload WASM module asynchronously (call during app/worker initialization).
291
+ * After this completes, detectAcRlConsensus will work synchronously without load overhead.
292
+ *
293
+ * Best practice: Call this during app startup or worker ready event, NOT on first recover call.
294
+ * Reduces first-image latency by ~5-10ms and eliminates async overhead from hot path.
295
+ *
296
+ * Also warms the Node AC+RL detect worker when worker_threads overlap is available,
297
+ * and the browser nested Worker when a factory was registered.
298
+ *
299
+ * @example
300
+ * // In worker initialization:
301
+ * await preloadWasm();
302
+ *
303
+ * // Or in app init:
304
+ * preloadWasm().catch(console.error);
305
+ */
306
+ export async function preloadWasm() {
307
+ if (!wasmLoaded) {
308
+ const module = await import("../wasm/pixelfixer_detect.js");
309
+ try {
310
+ await module.default();
311
+ }
312
+ catch {
313
+ // Node: fetch() cannot read file:// URLs; read the binary next to the glue instead.
314
+ const process = nodeProcess();
315
+ if (process?.versions?.node) {
316
+ const binary = packageFile(WASM_BINARY);
317
+ if (!binary)
318
+ throw new Error("WASM preload failed");
319
+ const fs = (await import(__rewriteRelativeImportExtension(/* @vite-ignore */ NODE_FS)));
320
+ module.initSync({ module: fs.readFileSync(binary) });
321
+ }
322
+ else {
323
+ throw new Error("WASM preload failed");
324
+ }
325
+ }
326
+ detectFastAcRl = module.detectFastAcRl;
327
+ wasmLoaded = true;
328
+ }
329
+ try {
330
+ await loadWorkerThreadsAsync();
331
+ ensureDetectWorker();
332
+ }
333
+ catch {
334
+ /* sync fallback remains */
335
+ }
336
+ try {
337
+ await ensureBrowserDetectWorker();
338
+ }
339
+ catch {
340
+ /* sync / Node path remains */
341
+ }
342
+ }
343
+ /**
344
+ * Detect pixel grid via AC+RL consensus (Fixer Rust WASM).
345
+ * WASM must be preloaded via preloadWasm() first.
346
+ *
347
+ * @param imageData - RGBA image data (Uint8ClampedArray or plain object with data/width/height)
348
+ * @returns Grid dimensions when consensus fires, otherwise null
349
+ */
350
+ function detectAcRlConsensus(imageData) {
351
+ if (!wasmLoaded || !detectFastAcRl) {
352
+ // Silently skip if WASM not loaded - will use fallback grid detection
353
+ return null;
354
+ }
355
+ // Convert Uint8ClampedArray to Uint8Array if needed
356
+ const rgba = rgbaBytes(imageData);
357
+ const result = detectFastAcRl(rgba, imageData.width, imageData.height);
358
+ // Only return grid when consensus fires
359
+ if (result.consensus === "fast:ac+rl(S)") {
360
+ return {
361
+ cols: result.cols,
362
+ rows: result.rows,
363
+ };
364
+ }
365
+ return null;
366
+ }
367
+ /**
368
+ * Start AC+RL detect on a Node worker_threads worker (copy + postMessage).
369
+ * Run proposers on the calling thread, then finishAcRlConsensusJob.
370
+ * Falls back to deferred sync detect when workers are unavailable (browser / disable flag).
371
+ */
372
+ export function beginAcRlConsensusJob(imageData) {
373
+ const wt = loadWorkerThreads();
374
+ if (!wt || !ensureDetectWorker() || !detectWorker) {
375
+ return { mode: "sync", imageData };
376
+ }
377
+ try {
378
+ const { port1, port2 } = new wt.MessageChannel();
379
+ const copy = copyRgba(imageData);
380
+ detectWorker.postMessage({
381
+ type: "detect",
382
+ width: imageData.width,
383
+ height: imageData.height,
384
+ data: copy.buffer,
385
+ port: port2,
386
+ }, [copy.buffer, port2]);
387
+ return { mode: "worker", port1 };
388
+ }
389
+ catch {
390
+ return { mode: "sync", imageData };
391
+ }
392
+ }
393
+ /**
394
+ * Join a job started by beginAcRlConsensusJob. Same consensus semantics as detectAcRlConsensus.
395
+ */
396
+ export function finishAcRlConsensusJob(job) {
397
+ if (job.mode === "sync") {
398
+ return detectAcRlConsensus(job.imageData);
399
+ }
400
+ const wt = loadWorkerThreads();
401
+ if (!wt)
402
+ return null;
403
+ for (;;) {
404
+ const msg = wt.receiveMessageOnPort(job.port1);
405
+ if (!msg)
406
+ continue;
407
+ const body = msg.message;
408
+ if (!body.ok)
409
+ return null;
410
+ return body.consensus ?? null;
411
+ }
412
+ }
413
+ /** Reinterpret the exact typed-array slice without copying or including adjacent pixels. */
414
+ function rgbaBytes(imageData) {
415
+ const { data } = imageData;
416
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
417
+ }
418
+ function copyRgba(imageData) {
419
+ // One owned allocation is required because transfer must not detach the caller's buffer.
420
+ return new Uint8Array(rgbaBytes(imageData));
421
+ }
422
+ /**
423
+ * Start AC+RL detect for async Auto: Node worker_threads or browser nested Worker.
424
+ * Promise is attached before proposers run; finishAcRlConsensusJobAsync awaits it
425
+ * (no SAB / COOP/COEP). Falls back to deferred sync detect when no worker is available.
426
+ * Async so browser nested Worker can be warmed on first use.
427
+ */
428
+ export async function beginAcRlConsensusJobAsync(imageData) {
429
+ // Prefer Node worker_threads when available (harness / SSR).
430
+ const wt = loadWorkerThreads();
431
+ if (wt && ensureDetectWorker() && detectWorker) {
432
+ try {
433
+ const { port1, port2 } = new wt.MessageChannel();
434
+ const copy = copyRgba(imageData);
435
+ const promise = portMessagePromise(port1).then((body) => body.ok ? (body.consensus ?? null) : null);
436
+ detectWorker.postMessage({
437
+ type: "detect",
438
+ width: imageData.width,
439
+ height: imageData.height,
440
+ data: copy.buffer,
441
+ port: port2,
442
+ }, [copy.buffer, port2]);
443
+ return { mode: "async", promise };
444
+ }
445
+ catch {
446
+ /* fall through */
447
+ }
448
+ }
449
+ // Browser nested Worker (product import-worker) — async join, no COOP/COEP.
450
+ if (browserWorkerFactory && !browserWorkerFailed) {
451
+ await ensureBrowserDetectWorker();
452
+ }
453
+ if (browserDetectWorker && browserWorkerReady && !browserWorkerFailed) {
454
+ const worker = browserDetectWorker;
455
+ try {
456
+ const MessageChannel = browserMessageChannelCtor();
457
+ if (!MessageChannel)
458
+ throw new Error("MessageChannel unavailable");
459
+ const { port1, port2 } = new MessageChannel();
460
+ const copy = copyRgba(imageData);
461
+ const promise = portMessagePromise(port1, browserRequests).then((body) => body.ok ? (body.consensus ?? null) : null);
462
+ worker.postMessage({
463
+ type: "detect",
464
+ width: imageData.width,
465
+ height: imageData.height,
466
+ data: copy.buffer,
467
+ port: port2,
468
+ }, [copy.buffer, port2]);
469
+ return { mode: "async", promise };
470
+ }
471
+ catch {
472
+ failBrowserDetectWorker(worker);
473
+ }
474
+ }
475
+ return { mode: "sync", imageData };
476
+ }
477
+ /**
478
+ * Await a job from beginAcRlConsensusJobAsync. Same consensus semantics as detectAcRlConsensus.
479
+ */
480
+ export async function finishAcRlConsensusJobAsync(job) {
481
+ if (job.mode === "sync") {
482
+ return detectAcRlConsensus(job.imageData);
483
+ }
484
+ return job.promise;
485
+ }
486
+ /** Kick off browser nested Worker warm-up after setAcRlBrowserWorkerFactory (non-blocking OK). */
487
+ export async function warmAcRlBrowserWorker() {
488
+ return ensureBrowserDetectWorker();
489
+ }
490
+ /** Test/harness: whether Node worker overlap is active after preload. */
491
+ export function acRlWorkerOverlapActive() {
492
+ return !workerFailed && workerReady && nodeWorkerEnabled();
493
+ }
494
+ /** Test/harness: whether browser nested-Worker async overlap is ready. */
495
+ export function acRlBrowserWorkerOverlapActive() {
496
+ return !browserWorkerFailed && browserWorkerReady && browserWorkerFactory !== null;
497
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Grid arbitration via description-length (MDL).
3
+ *
4
+ * GridEvidenceReader shares the image description, line prefixes and distinct readings across
5
+ * arbitration, corroboration and consensus selection for one evaluation view.
6
+ */
7
+ import type { PixelBuffer } from "./base.ts";
8
+ import { type DescribedImage } from "./grid-description-length.ts";
9
+ import { type PixelGridAnalysis } from "./types.ts";
10
+ /** A grid reading: the hypothesis with its cuts, its length and its twin's, per sample. */
11
+ export interface GridEvidence {
12
+ grid: PixelGridAnalysis;
13
+ length: number;
14
+ twin: number;
15
+ /** Phase evidence, per sample, the reading's strip search finds by chance. */
16
+ chance: number;
17
+ /** The image the reading describes: the input, or its straightened strips. */
18
+ frame: () => DescribedImage;
19
+ }
20
+ export interface ArbitratedGrid {
21
+ grid: PixelGridAnalysis;
22
+ /** Description length per sample, in nats. */
23
+ length: number;
24
+ /** The decision's score, per sample; lower is better. */
25
+ score: number;
26
+ }
27
+ export declare const phaseScore: (reading: Pick<GridEvidence, "length" | "twin" | "chance">) => number;
28
+ /** The cuts moved half a cell: one cut at each cell middle, inside the same extent. */
29
+ export declare function twinCuts(cuts: readonly number[]): number[];
30
+ /** The cuts with every `period`th inner cut, from the `phase`th, moved to its cell's middle. */
31
+ export declare function classTwinCuts(cuts: readonly number[], period: number, phase: number): number[];
32
+ /** Phase evidence, per sample, the reading's cuts miss from their least evident class. */
33
+ export declare function octaveShortfall(frame: DescribedImage, cutsX: readonly number[], cutsY: readonly number[]): number;
34
+ /** Reuse one evaluation view's descriptions and distinct readings during Auto's decision. */
35
+ export declare class GridEvidenceReader {
36
+ private readonly image;
37
+ private readonly described;
38
+ private readonly strips;
39
+ private readonly pixels;
40
+ private readonly readings;
41
+ constructor(image: PixelBuffer);
42
+ read(grids: readonly PixelGridAnalysis[]): {
43
+ readings: GridEvidence[];
44
+ pixels: number;
45
+ };
46
+ }
47
+ /**
48
+ * The best-scoring grid among the proposals, or null when the passthrough scores lower. The
49
+ * winner's confidence is the larger of its proposer's and the share of its twin's length that
50
+ * its phase saves.
51
+ */
52
+ export declare function arbitrateGrids(image: PixelBuffer, grids: readonly PixelGridAnalysis[], evidence?: GridEvidenceReader): ArbitratedGrid | null;