mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-28 14:20:46 -05:00
85 lines
2.5 KiB
TypeScript
85 lines
2.5 KiB
TypeScript
/**
|
|
* OpenCV.js singleton loader for Node, browser main thread and workers (the
|
|
* UMD bundle embeds its WASM). core/ obtains the namespace through getCV();
|
|
* callers await loadOpenCV() once at startup. Gotcha of this build
|
|
* (5.0.0-release.1): `.data` and `.clone()` are broken on ROI views —
|
|
* `view.copyTo(freshMat)` before pixel access. Views are fine as cv inputs.
|
|
*/
|
|
import cvModule from "@techstark/opencv-js";
|
|
|
|
export type CV = typeof cvModule;
|
|
export type Mat = InstanceType<CV["Mat"]>;
|
|
|
|
let cvInstance: CV | null = null;
|
|
let loading: Promise<CV> | null = null;
|
|
|
|
export function loadOpenCV(): Promise<CV> {
|
|
if (cvInstance) return Promise.resolve(cvInstance);
|
|
if (loading) return loading;
|
|
const attempt = (async () => {
|
|
// The package is patched (patches/@techstark__opencv-js…) to export
|
|
// { cvReadyPromise } instead of the bare ready-promise: a thenable
|
|
// module.exports leaks `then` through vite-node's CJS namespace proxy and
|
|
// crashes every Node-side import. Depending on bundler interop we see the
|
|
// wrapper (possibly under `default`) or, via the UMD's non-CJS branch, the promise.
|
|
const raw = cvModule as {
|
|
cvReadyPromise?: unknown;
|
|
default?: { cvReadyPromise?: unknown };
|
|
} | null;
|
|
const mod: unknown =
|
|
raw?.cvReadyPromise ??
|
|
raw?.default?.cvReadyPromise ??
|
|
raw?.default ??
|
|
raw;
|
|
let cv: CV;
|
|
if (mod instanceof Promise) {
|
|
cv = await mod;
|
|
} else if ((mod as CV).Mat) {
|
|
cv = mod as CV;
|
|
} else {
|
|
await new Promise<void>((resolve) => {
|
|
(mod as { onRuntimeInitialized?: () => void }).onRuntimeInitialized =
|
|
resolve;
|
|
});
|
|
cv = mod as CV;
|
|
}
|
|
cvInstance = cv;
|
|
return cv;
|
|
})();
|
|
// a failed load must not poison the singleton — clear it so callers can retry
|
|
loading = attempt.catch((error) => {
|
|
loading = null;
|
|
throw error;
|
|
});
|
|
return loading;
|
|
}
|
|
|
|
export function getCV(): CV {
|
|
if (!cvInstance) {
|
|
throw new Error(
|
|
"OpenCV not loaded — await loadOpenCV() before using core/",
|
|
);
|
|
}
|
|
return cvInstance;
|
|
}
|
|
|
|
// The bundled type definitions mark the optional mask argument of these as
|
|
// required; thin wrappers restore the real (mask-less) signatures.
|
|
|
|
export interface MinMaxResult {
|
|
minVal: number;
|
|
maxVal: number;
|
|
minLoc: { x: number; y: number };
|
|
maxLoc: { x: number; y: number };
|
|
}
|
|
|
|
export function minMaxLoc(mat: Mat): MinMaxResult {
|
|
return (getCV() as unknown as { minMaxLoc(m: Mat): MinMaxResult }).minMaxLoc(
|
|
mat,
|
|
);
|
|
}
|
|
|
|
export function meanOf(mat: Mat): number[] {
|
|
return (getCV() as unknown as { mean(m: Mat): number[] }).mean(mat);
|
|
}
|