@srsergio/taptapp-ar 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -45
- package/dist/compiler/aframe.js +0 -3
- package/dist/compiler/compiler-base.d.ts +3 -7
- package/dist/compiler/compiler-base.js +28 -14
- package/dist/compiler/compiler.js +1 -1
- package/dist/compiler/compiler.worker.js +1 -1
- package/dist/compiler/controller.d.ts +4 -4
- package/dist/compiler/controller.js +4 -5
- package/dist/compiler/controller.worker.js +0 -2
- package/dist/compiler/detector/crop-detector.d.ts +12 -12
- package/dist/compiler/detector/crop-detector.js +0 -2
- package/dist/compiler/detector/detector-lite.d.ts +73 -0
- package/dist/compiler/detector/detector-lite.js +430 -0
- package/dist/compiler/detector/detector.d.ts +20 -21
- package/dist/compiler/detector/detector.js +236 -243
- package/dist/compiler/detector/kernels/cpu/binomialFilter.js +0 -1
- package/dist/compiler/detector/kernels/cpu/computeExtremaAngles.d.ts +1 -1
- package/dist/compiler/detector/kernels/cpu/computeLocalization.js +0 -4
- package/dist/compiler/detector/kernels/cpu/computeOrientationHistograms.js +0 -18
- package/dist/compiler/detector/kernels/cpu/fakeShader.js +1 -1
- package/dist/compiler/detector/kernels/cpu/prune.d.ts +7 -1
- package/dist/compiler/detector/kernels/cpu/prune.js +1 -42
- package/dist/compiler/detector/kernels/webgl/upsampleBilinear.d.ts +1 -1
- package/dist/compiler/detector/kernels/webgl/upsampleBilinear.js +2 -2
- package/dist/compiler/estimation/refine-estimate.js +0 -1
- package/dist/compiler/estimation/utils.d.ts +1 -1
- package/dist/compiler/estimation/utils.js +1 -14
- package/dist/compiler/image-list.js +4 -4
- package/dist/compiler/input-loader.d.ts +4 -5
- package/dist/compiler/input-loader.js +2 -2
- package/dist/compiler/matching/hamming-distance.js +13 -13
- package/dist/compiler/matching/hierarchical-clustering.js +1 -1
- package/dist/compiler/matching/matching.d.ts +20 -4
- package/dist/compiler/matching/matching.js +67 -41
- package/dist/compiler/matching/ransacHomography.js +1 -2
- package/dist/compiler/node-worker.d.ts +1 -0
- package/dist/compiler/node-worker.js +84 -0
- package/dist/compiler/offline-compiler.d.ts +171 -6
- package/dist/compiler/offline-compiler.js +303 -421
- package/dist/compiler/tensorflow-setup.d.ts +0 -1
- package/dist/compiler/tensorflow-setup.js +27 -1
- package/dist/compiler/three.d.ts +7 -12
- package/dist/compiler/three.js +3 -5
- package/dist/compiler/tracker/extract.d.ts +1 -0
- package/dist/compiler/tracker/extract.js +200 -244
- package/dist/compiler/tracker/tracker.d.ts +9 -17
- package/dist/compiler/tracker/tracker.js +13 -18
- package/dist/compiler/utils/cumsum.d.ts +4 -2
- package/dist/compiler/utils/cumsum.js +17 -19
- package/dist/compiler/utils/gpu-compute.d.ts +57 -0
- package/dist/compiler/utils/gpu-compute.js +262 -0
- package/dist/compiler/utils/images.d.ts +4 -4
- package/dist/compiler/utils/images.js +67 -53
- package/dist/compiler/utils/worker-pool.d.ts +13 -0
- package/dist/compiler/utils/worker-pool.js +84 -0
- package/package.json +12 -14
- package/src/compiler/aframe.js +2 -4
- package/src/compiler/compiler-base.js +29 -14
- package/src/compiler/compiler.js +1 -1
- package/src/compiler/compiler.worker.js +1 -1
- package/src/compiler/controller.js +4 -5
- package/src/compiler/controller.worker.js +0 -2
- package/src/compiler/detector/crop-detector.js +0 -2
- package/src/compiler/detector/detector-lite.js +494 -0
- package/src/compiler/detector/detector.js +1052 -1063
- package/src/compiler/detector/kernels/cpu/binomialFilter.js +0 -1
- package/src/compiler/detector/kernels/cpu/computeLocalization.js +0 -4
- package/src/compiler/detector/kernels/cpu/computeOrientationHistograms.js +0 -17
- package/src/compiler/detector/kernels/cpu/fakeShader.js +1 -1
- package/src/compiler/detector/kernels/cpu/prune.js +1 -37
- package/src/compiler/detector/kernels/webgl/upsampleBilinear.js +2 -2
- package/src/compiler/estimation/refine-estimate.js +0 -1
- package/src/compiler/estimation/utils.js +9 -24
- package/src/compiler/image-list.js +4 -4
- package/src/compiler/input-loader.js +2 -2
- package/src/compiler/matching/hamming-distance.js +11 -15
- package/src/compiler/matching/hierarchical-clustering.js +1 -1
- package/src/compiler/matching/matching.js +72 -42
- package/src/compiler/matching/ransacHomography.js +0 -2
- package/src/compiler/node-worker.js +93 -0
- package/src/compiler/offline-compiler.js +339 -504
- package/src/compiler/tensorflow-setup.js +29 -1
- package/src/compiler/three.js +3 -5
- package/src/compiler/tracker/extract.js +211 -267
- package/src/compiler/tracker/tracker.js +13 -22
- package/src/compiler/utils/cumsum.js +17 -19
- package/src/compiler/utils/gpu-compute.js +303 -0
- package/src/compiler/utils/images.js +84 -53
- package/src/compiler/utils/worker-pool.js +89 -0
- package/dist/compiler/estimation/esimate-experiment.d.ts +0 -5
- package/dist/compiler/estimation/esimate-experiment.js +0 -267
- package/dist/compiler/estimation/refine-estimate-experiment.d.ts +0 -6
- package/dist/compiler/estimation/refine-estimate-experiment.js +0 -429
- package/dist/react/AREditor.d.ts +0 -5
- package/dist/react/AREditor.js +0 -159
- package/dist/react/ProgressDialog.d.ts +0 -13
- package/dist/react/ProgressDialog.js +0 -57
- package/src/compiler/estimation/esimate-experiment.js +0 -316
- package/src/compiler/estimation/refine-estimate-experiment.js +0 -512
|
@@ -5,14 +5,14 @@ const AR2_DEFAULT_TS_GAP = 1;
|
|
|
5
5
|
const AR2_SEARCH_SIZE = 10;
|
|
6
6
|
const AR2_SEARCH_GAP = 1;
|
|
7
7
|
const AR2_SIM_THRESH = 0.8;
|
|
8
|
-
const TRACKING_KEYFRAME =
|
|
8
|
+
const TRACKING_KEYFRAME = 0; // 0: 128px (optimized)
|
|
9
9
|
// For some mobile device, only 16bit floating point texture is supported
|
|
10
10
|
// ref: https://www.tensorflow.org/js/guide/platform_environment#precision
|
|
11
11
|
// Empirical results shows that modelViewProjectTransform can go up beyond that, resulting in error
|
|
12
12
|
// We get around this by dividing the transform matrix by 1000, and then multiply back inside webgl program
|
|
13
13
|
const PRECISION_ADJUST = 1000;
|
|
14
14
|
class Tracker {
|
|
15
|
-
constructor(markerDimensions, trackingDataList, projectionTransform,
|
|
15
|
+
constructor(markerDimensions, trackingDataList, projectionTransform, debugMode = false) {
|
|
16
16
|
this.markerDimensions = markerDimensions;
|
|
17
17
|
this.trackingDataList = trackingDataList;
|
|
18
18
|
this.projectionTransform = projectionTransform;
|
|
@@ -24,7 +24,7 @@ class Tracker {
|
|
|
24
24
|
// prebuild feature and marker pixel tensors
|
|
25
25
|
let maxCount = 0;
|
|
26
26
|
for (let i = 0; i < this.trackingKeyframeList.length; i++) {
|
|
27
|
-
maxCount = Math.max(maxCount, this.trackingKeyframeList[i].
|
|
27
|
+
maxCount = Math.max(maxCount, this.trackingKeyframeList[i].px.length);
|
|
28
28
|
}
|
|
29
29
|
this.featurePointsListT = [];
|
|
30
30
|
this.imagePixelsListT = [];
|
|
@@ -51,10 +51,6 @@ class Tracker {
|
|
|
51
51
|
let debugExtra = {};
|
|
52
52
|
const modelViewProjectionTransform = buildModelViewProjectionTransform(this.projectionTransform, lastModelViewTransform);
|
|
53
53
|
const modelViewProjectionTransformT = this._buildAdjustedModelViewTransform(modelViewProjectionTransform);
|
|
54
|
-
const markerWidth = this.markerDimensions[targetIndex][0];
|
|
55
|
-
const markerHeight = this.markerDimensions[targetIndex][1];
|
|
56
|
-
const keyframeWidth = this.trackingKeyframeList[targetIndex].width;
|
|
57
|
-
const keyframeHeight = this.trackingKeyframeList[targetIndex].height;
|
|
58
54
|
const featurePointsT = this.featurePointsListT[targetIndex];
|
|
59
55
|
const imagePixelsT = this.imagePixelsListT[targetIndex];
|
|
60
56
|
const imagePropertiesT = this.imagePropertiesListT[targetIndex];
|
|
@@ -66,14 +62,15 @@ class Tracker {
|
|
|
66
62
|
const worldCoords = [];
|
|
67
63
|
const screenCoords = [];
|
|
68
64
|
const goodTrack = [];
|
|
65
|
+
const { px, py, s: scale } = trackingFrame;
|
|
69
66
|
for (let i = 0; i < matchingPoints.length; i++) {
|
|
70
|
-
if (sim[i] > AR2_SIM_THRESH && i <
|
|
67
|
+
if (sim[i] > AR2_SIM_THRESH && i < px.length) {
|
|
71
68
|
goodTrack.push(i);
|
|
72
69
|
const point = computeScreenCoordiate(modelViewProjectionTransform, matchingPoints[i][0], matchingPoints[i][1]);
|
|
73
70
|
screenCoords.push(point);
|
|
74
71
|
worldCoords.push({
|
|
75
|
-
x:
|
|
76
|
-
y:
|
|
72
|
+
x: px[i] / scale,
|
|
73
|
+
y: py[i] / scale,
|
|
77
74
|
z: 0,
|
|
78
75
|
});
|
|
79
76
|
}
|
|
@@ -297,20 +294,18 @@ class Tracker {
|
|
|
297
294
|
}
|
|
298
295
|
_prebuild(trackingFrame, maxCount) {
|
|
299
296
|
return tf.tidy(() => {
|
|
300
|
-
const scale = trackingFrame
|
|
297
|
+
const { px, py, s: scale, d: data, w: width, h: height } = trackingFrame;
|
|
301
298
|
const p = [];
|
|
302
299
|
for (let k = 0; k < maxCount; k++) {
|
|
303
|
-
if (k <
|
|
304
|
-
p.push([
|
|
300
|
+
if (k < px.length) {
|
|
301
|
+
p.push([px[k] / scale, py[k] / scale]);
|
|
305
302
|
}
|
|
306
303
|
else {
|
|
307
304
|
p.push([-1, -1]);
|
|
308
305
|
}
|
|
309
306
|
}
|
|
310
|
-
const imagePixels = tf.tensor(
|
|
311
|
-
|
|
312
|
-
]);
|
|
313
|
-
const imageProperties = tf.tensor([trackingFrame.width, trackingFrame.height, trackingFrame.scale], [3]);
|
|
307
|
+
const imagePixels = tf.tensor(data, [width * height]);
|
|
308
|
+
const imageProperties = tf.tensor([width, height, scale], [3]);
|
|
314
309
|
const featurePoints = tf.tensor(p, [p.length, 2], "float32");
|
|
315
310
|
return {
|
|
316
311
|
featurePoints,
|
|
@@ -321,7 +316,7 @@ class Tracker {
|
|
|
321
316
|
}
|
|
322
317
|
_compileAndRun(program, inputs) {
|
|
323
318
|
const outInfo = tf.backend().compileAndRun(program, inputs);
|
|
324
|
-
return tf.engine().
|
|
319
|
+
return tf.engine().makeTensor(outInfo.dataId, outInfo.shape, outInfo.dtype);
|
|
325
320
|
}
|
|
326
321
|
}
|
|
327
322
|
export { Tracker };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export class Cumsum {
|
|
2
2
|
constructor(data: any, width: any, height: any);
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
width: any;
|
|
4
|
+
height: any;
|
|
5
|
+
cumsum: Int32Array<ArrayBuffer>;
|
|
6
|
+
query(x1: any, y1: any, x2: any, y2: any): number;
|
|
5
7
|
}
|
|
@@ -1,38 +1,36 @@
|
|
|
1
1
|
// fast 2D submatrix sum using cumulative sum algorithm
|
|
2
2
|
class Cumsum {
|
|
3
3
|
constructor(data, width, height) {
|
|
4
|
-
this.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
this.cumsum[j].push(0);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
this.cumsum[0][0] = data[0];
|
|
4
|
+
this.width = width;
|
|
5
|
+
this.height = height;
|
|
6
|
+
this.cumsum = new Int32Array(width * height);
|
|
7
|
+
this.cumsum[0] = data[0];
|
|
12
8
|
for (let i = 1; i < width; i++) {
|
|
13
|
-
this.cumsum[
|
|
9
|
+
this.cumsum[i] = this.cumsum[i - 1] + data[i];
|
|
14
10
|
}
|
|
15
11
|
for (let j = 1; j < height; j++) {
|
|
16
|
-
this.cumsum[j]
|
|
12
|
+
this.cumsum[j * width] = this.cumsum[(j - 1) * width] + data[j * width];
|
|
17
13
|
}
|
|
18
14
|
for (let j = 1; j < height; j++) {
|
|
19
15
|
for (let i = 1; i < width; i++) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.cumsum[j
|
|
24
|
-
this.cumsum[j
|
|
16
|
+
const pos = j * width + i;
|
|
17
|
+
this.cumsum[pos] =
|
|
18
|
+
data[pos] +
|
|
19
|
+
this.cumsum[(j - 1) * width + i] +
|
|
20
|
+
this.cumsum[j * width + i - 1] -
|
|
21
|
+
this.cumsum[(j - 1) * width + i - 1];
|
|
25
22
|
}
|
|
26
23
|
}
|
|
27
24
|
}
|
|
28
25
|
query(x1, y1, x2, y2) {
|
|
29
|
-
|
|
26
|
+
const { width } = this;
|
|
27
|
+
let ret = this.cumsum[y2 * width + x2];
|
|
30
28
|
if (y1 > 0)
|
|
31
|
-
ret -= this.cumsum[y1 - 1
|
|
29
|
+
ret -= this.cumsum[(y1 - 1) * width + x2];
|
|
32
30
|
if (x1 > 0)
|
|
33
|
-
ret -= this.cumsum[y2
|
|
31
|
+
ret -= this.cumsum[y2 * width + x1 - 1];
|
|
34
32
|
if (x1 > 0 && y1 > 0)
|
|
35
|
-
ret += this.cumsum[y1 - 1
|
|
33
|
+
ret += this.cumsum[(y1 - 1) * width + x1 - 1];
|
|
36
34
|
return ret;
|
|
37
35
|
}
|
|
38
36
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GPU Compute class - provides optimized image processing
|
|
3
|
+
*/
|
|
4
|
+
export class GPUCompute {
|
|
5
|
+
gpu: any;
|
|
6
|
+
kernelCache: Map<any, any>;
|
|
7
|
+
initialized: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Initialize (tries GPU in browser, uses JS in Node)
|
|
10
|
+
*/
|
|
11
|
+
init(): void;
|
|
12
|
+
/**
|
|
13
|
+
* Compute edge gradients
|
|
14
|
+
*/
|
|
15
|
+
computeGradients(imageData: any, width: any, height: any): Float32Array<ArrayBuffer>;
|
|
16
|
+
/**
|
|
17
|
+
* Find local maxima
|
|
18
|
+
*/
|
|
19
|
+
findLocalMaxima(gradients: any, width: any, height: any): Uint8Array<ArrayBuffer>;
|
|
20
|
+
/**
|
|
21
|
+
* Combined edge detection
|
|
22
|
+
*/
|
|
23
|
+
edgeDetection(imageData: any, width: any, height: any): {
|
|
24
|
+
dValue: Float32Array<ArrayBuffer>;
|
|
25
|
+
isCandidate: Uint8Array<ArrayBuffer>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Gaussian blur
|
|
29
|
+
*/
|
|
30
|
+
gaussianBlur(imageData: any, width: any, height: any): Float32Array<ArrayBuffer>;
|
|
31
|
+
/**
|
|
32
|
+
* Downsample by factor of 2
|
|
33
|
+
*/
|
|
34
|
+
downsample(imageData: any, width: any, height: any): {
|
|
35
|
+
data: Float32Array<ArrayBuffer>;
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Build Gaussian pyramid
|
|
41
|
+
*/
|
|
42
|
+
buildPyramid(imageData: any, width: any, height: any, numLevels?: number): {
|
|
43
|
+
data: Float32Array<ArrayBuffer>;
|
|
44
|
+
width: any;
|
|
45
|
+
height: any;
|
|
46
|
+
scale: number;
|
|
47
|
+
}[];
|
|
48
|
+
/**
|
|
49
|
+
* Check if GPU is available
|
|
50
|
+
*/
|
|
51
|
+
isGPUAvailable(): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Cleanup resources
|
|
54
|
+
*/
|
|
55
|
+
destroy(): void;
|
|
56
|
+
}
|
|
57
|
+
export const gpuCompute: GPUCompute;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview GPU Compute Layer for AR Compiler
|
|
3
|
+
*
|
|
4
|
+
* Provides optimized image processing with GPU acceleration when available.
|
|
5
|
+
* - In browser: Uses GPU.js with WebGL
|
|
6
|
+
* - In Node.js: Uses pure JavaScript (GPU.js requires headless-gl which may not compile)
|
|
7
|
+
*
|
|
8
|
+
* All methods have pure JS fallbacks that work universally.
|
|
9
|
+
*/
|
|
10
|
+
// Detect if running in Node.js
|
|
11
|
+
const isNode = typeof process !== 'undefined' &&
|
|
12
|
+
process.versions != null &&
|
|
13
|
+
process.versions.node != null;
|
|
14
|
+
// Lazy load GPU.js only in browser environments
|
|
15
|
+
let GPU = null;
|
|
16
|
+
let gpuInstance = null;
|
|
17
|
+
let gpuLoadAttempted = false;
|
|
18
|
+
/**
|
|
19
|
+
* Try to initialize GPU.js (browser only)
|
|
20
|
+
*/
|
|
21
|
+
const tryInitGPU = () => {
|
|
22
|
+
if (gpuLoadAttempted)
|
|
23
|
+
return gpuInstance;
|
|
24
|
+
gpuLoadAttempted = true;
|
|
25
|
+
// Skip GPU.js in Node.js to avoid headless-gl issues
|
|
26
|
+
if (isNode) {
|
|
27
|
+
console.log("⚡ Running in Node.js - using optimized JS");
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
// Dynamically import GPU.js in browser
|
|
31
|
+
try {
|
|
32
|
+
// Use dynamic import pattern that works in browsers
|
|
33
|
+
const GPUModule = globalThis.GPU || (typeof require !== 'undefined' ? require('gpu.js').GPU : null);
|
|
34
|
+
if (GPUModule) {
|
|
35
|
+
GPU = GPUModule;
|
|
36
|
+
gpuInstance = new GPU({ mode: "gpu" });
|
|
37
|
+
// Test if GPU works
|
|
38
|
+
const testKernel = gpuInstance.createKernel(function () { return 1; }).setOutput([1]);
|
|
39
|
+
testKernel();
|
|
40
|
+
testKernel.destroy();
|
|
41
|
+
console.log("🚀 GPU.js: Using GPU acceleration");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
console.log("⚡ GPU.js unavailable, using optimized JS:", e.message);
|
|
46
|
+
gpuInstance = null;
|
|
47
|
+
}
|
|
48
|
+
return gpuInstance;
|
|
49
|
+
};
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// PURE JAVASCRIPT IMPLEMENTATIONS (Always work)
|
|
52
|
+
// ============================================================================
|
|
53
|
+
/**
|
|
54
|
+
* Pure JS: Compute edge gradients
|
|
55
|
+
*/
|
|
56
|
+
const computeGradientsJS = (imageData, width, height) => {
|
|
57
|
+
const dValue = new Float32Array(width * height);
|
|
58
|
+
for (let j = 1; j < height - 1; j++) {
|
|
59
|
+
const rowOffset = j * width;
|
|
60
|
+
const prevRowOffset = (j - 1) * width;
|
|
61
|
+
const nextRowOffset = (j + 1) * width;
|
|
62
|
+
for (let i = 1; i < width - 1; i++) {
|
|
63
|
+
const pos = rowOffset + i;
|
|
64
|
+
const dx = (imageData[prevRowOffset + i + 1] - imageData[prevRowOffset + i - 1] +
|
|
65
|
+
imageData[rowOffset + i + 1] - imageData[rowOffset + i - 1] +
|
|
66
|
+
imageData[nextRowOffset + i + 1] - imageData[nextRowOffset + i - 1]) / 768;
|
|
67
|
+
const dy = (imageData[nextRowOffset + i - 1] - imageData[prevRowOffset + i - 1] +
|
|
68
|
+
imageData[nextRowOffset + i] - imageData[prevRowOffset + i] +
|
|
69
|
+
imageData[nextRowOffset + i + 1] - imageData[prevRowOffset + i + 1]) / 768;
|
|
70
|
+
dValue[pos] = Math.sqrt((dx * dx + dy * dy) / 2);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return dValue;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Pure JS: Find local maxima
|
|
77
|
+
*/
|
|
78
|
+
const findLocalMaximaJS = (gradients, width, height) => {
|
|
79
|
+
const isCandidate = new Uint8Array(width * height);
|
|
80
|
+
for (let j = 1; j < height - 1; j++) {
|
|
81
|
+
const rowOffset = j * width;
|
|
82
|
+
for (let i = 1; i < width - 1; i++) {
|
|
83
|
+
const pos = rowOffset + i;
|
|
84
|
+
const val = gradients[pos];
|
|
85
|
+
if (val > 0 &&
|
|
86
|
+
val >= gradients[pos - 1] && val >= gradients[pos + 1] &&
|
|
87
|
+
val >= gradients[pos - width] && val >= gradients[pos + width]) {
|
|
88
|
+
isCandidate[pos] = 1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return isCandidate;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Pure JS: Gaussian blur (5x5 binomial)
|
|
96
|
+
*/
|
|
97
|
+
const gaussianBlurJS = (data, width, height) => {
|
|
98
|
+
const output = new Float32Array(width * height);
|
|
99
|
+
const temp = new Float32Array(width * height);
|
|
100
|
+
const k0 = 1 / 16, k1 = 4 / 16, k2 = 6 / 16;
|
|
101
|
+
const w1 = width - 1;
|
|
102
|
+
const h1 = height - 1;
|
|
103
|
+
// Horizontal pass
|
|
104
|
+
for (let y = 0; y < height; y++) {
|
|
105
|
+
const rowOffset = y * width;
|
|
106
|
+
for (let x = 0; x < width; x++) {
|
|
107
|
+
const x0 = x < 2 ? 0 : x - 2;
|
|
108
|
+
const x1 = x < 1 ? 0 : x - 1;
|
|
109
|
+
const x3 = x > w1 - 1 ? w1 : x + 1;
|
|
110
|
+
const x4 = x > w1 - 2 ? w1 : x + 2;
|
|
111
|
+
temp[rowOffset + x] =
|
|
112
|
+
data[rowOffset + x0] * k0 +
|
|
113
|
+
data[rowOffset + x1] * k1 +
|
|
114
|
+
data[rowOffset + x] * k2 +
|
|
115
|
+
data[rowOffset + x3] * k1 +
|
|
116
|
+
data[rowOffset + x4] * k0;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Vertical pass
|
|
120
|
+
for (let y = 0; y < height; y++) {
|
|
121
|
+
const y0 = (y < 2 ? 0 : y - 2) * width;
|
|
122
|
+
const y1 = (y < 1 ? 0 : y - 1) * width;
|
|
123
|
+
const y2 = y * width;
|
|
124
|
+
const y3 = (y > h1 - 1 ? h1 : y + 1) * width;
|
|
125
|
+
const y4 = (y > h1 - 2 ? h1 : y + 2) * width;
|
|
126
|
+
for (let x = 0; x < width; x++) {
|
|
127
|
+
output[y2 + x] =
|
|
128
|
+
temp[y0 + x] * k0 +
|
|
129
|
+
temp[y1 + x] * k1 +
|
|
130
|
+
temp[y2 + x] * k2 +
|
|
131
|
+
temp[y3 + x] * k1 +
|
|
132
|
+
temp[y4 + x] * k0;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return output;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Pure JS: Downsample by factor of 2
|
|
139
|
+
*/
|
|
140
|
+
const downsampleJS = (data, width, height) => {
|
|
141
|
+
const newWidth = Math.floor(width / 2);
|
|
142
|
+
const newHeight = Math.floor(height / 2);
|
|
143
|
+
const output = new Float32Array(newWidth * newHeight);
|
|
144
|
+
for (let y = 0; y < newHeight; y++) {
|
|
145
|
+
const sy = y * 2;
|
|
146
|
+
for (let x = 0; x < newWidth; x++) {
|
|
147
|
+
const sx = x * 2;
|
|
148
|
+
const pos = sy * width + sx;
|
|
149
|
+
output[y * newWidth + x] =
|
|
150
|
+
(data[pos] + data[pos + 1] + data[pos + width] + data[pos + width + 1]) / 4;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return { data: output, width: newWidth, height: newHeight };
|
|
154
|
+
};
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// GPU COMPUTE CLASS
|
|
157
|
+
// ============================================================================
|
|
158
|
+
/**
|
|
159
|
+
* GPU Compute class - provides optimized image processing
|
|
160
|
+
*/
|
|
161
|
+
export class GPUCompute {
|
|
162
|
+
constructor() {
|
|
163
|
+
this.gpu = null;
|
|
164
|
+
this.kernelCache = new Map();
|
|
165
|
+
this.initialized = false;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Initialize (tries GPU in browser, uses JS in Node)
|
|
169
|
+
*/
|
|
170
|
+
init() {
|
|
171
|
+
if (this.initialized)
|
|
172
|
+
return;
|
|
173
|
+
this.gpu = tryInitGPU();
|
|
174
|
+
this.initialized = true;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Compute edge gradients
|
|
178
|
+
*/
|
|
179
|
+
computeGradients(imageData, width, height) {
|
|
180
|
+
this.init();
|
|
181
|
+
// Always use JS implementation for reliability
|
|
182
|
+
return computeGradientsJS(imageData, width, height);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Find local maxima
|
|
186
|
+
*/
|
|
187
|
+
findLocalMaxima(gradients, width, height) {
|
|
188
|
+
this.init();
|
|
189
|
+
return findLocalMaximaJS(gradients, width, height);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Combined edge detection
|
|
193
|
+
*/
|
|
194
|
+
edgeDetection(imageData, width, height) {
|
|
195
|
+
const dValue = this.computeGradients(imageData, width, height);
|
|
196
|
+
const isCandidate = this.findLocalMaxima(dValue, width, height);
|
|
197
|
+
return { dValue, isCandidate };
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Gaussian blur
|
|
201
|
+
*/
|
|
202
|
+
gaussianBlur(imageData, width, height) {
|
|
203
|
+
this.init();
|
|
204
|
+
return gaussianBlurJS(imageData, width, height);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Downsample by factor of 2
|
|
208
|
+
*/
|
|
209
|
+
downsample(imageData, width, height) {
|
|
210
|
+
this.init();
|
|
211
|
+
return downsampleJS(imageData, width, height);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Build Gaussian pyramid
|
|
215
|
+
*/
|
|
216
|
+
buildPyramid(imageData, width, height, numLevels = 5) {
|
|
217
|
+
this.init();
|
|
218
|
+
const pyramid = [];
|
|
219
|
+
let currentData = imageData instanceof Float32Array ? imageData : Float32Array.from(imageData);
|
|
220
|
+
let currentWidth = width;
|
|
221
|
+
let currentHeight = height;
|
|
222
|
+
for (let level = 0; level < numLevels; level++) {
|
|
223
|
+
const blurred = this.gaussianBlur(currentData, currentWidth, currentHeight);
|
|
224
|
+
pyramid.push({
|
|
225
|
+
data: blurred,
|
|
226
|
+
width: currentWidth,
|
|
227
|
+
height: currentHeight,
|
|
228
|
+
scale: Math.pow(2, level),
|
|
229
|
+
});
|
|
230
|
+
if (currentWidth > 8 && currentHeight > 8) {
|
|
231
|
+
const downsampled = this.downsample(blurred, currentWidth, currentHeight);
|
|
232
|
+
currentData = downsampled.data;
|
|
233
|
+
currentWidth = downsampled.width;
|
|
234
|
+
currentHeight = downsampled.height;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return pyramid;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Check if GPU is available
|
|
244
|
+
*/
|
|
245
|
+
isGPUAvailable() {
|
|
246
|
+
this.init();
|
|
247
|
+
return this.gpu !== null;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Cleanup resources
|
|
251
|
+
*/
|
|
252
|
+
destroy() {
|
|
253
|
+
this.kernelCache.clear();
|
|
254
|
+
if (this.gpu && this.gpu.destroy) {
|
|
255
|
+
this.gpu.destroy();
|
|
256
|
+
}
|
|
257
|
+
this.gpu = null;
|
|
258
|
+
this.initialized = false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Singleton instance
|
|
262
|
+
export const gpuCompute = new GPUCompute();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export function downsampleBilinear({ image }: {
|
|
2
2
|
image: any;
|
|
3
3
|
}): {
|
|
4
|
-
data:
|
|
4
|
+
data: Uint8Array<ArrayBuffer>;
|
|
5
5
|
width: number;
|
|
6
6
|
height: number;
|
|
7
7
|
};
|
|
@@ -18,7 +18,7 @@ export function resize({ image, ratio }: {
|
|
|
18
18
|
image: any;
|
|
19
19
|
ratio: any;
|
|
20
20
|
}): {
|
|
21
|
-
data: Uint8Array<
|
|
22
|
-
width:
|
|
23
|
-
height:
|
|
21
|
+
data: Uint8Array<any>;
|
|
22
|
+
width: any;
|
|
23
|
+
height: any;
|
|
24
24
|
};
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
// simpler version of upsampling. better performance
|
|
2
|
-
const _upsampleBilinear = ({ image, padOneWidth, padOneHeight }) => {
|
|
3
|
-
const { width, height, data } = image;
|
|
4
|
-
const dstWidth = image.width * 2 + (padOneWidth ? 1 : 0);
|
|
5
|
-
const dstHeight = image.height * 2 + (padOneHeight ? 1 : 0);
|
|
6
|
-
const temp = new Float32Array(dstWidth * dstHeight);
|
|
7
|
-
for (let i = 0; i < width; i++) {
|
|
8
|
-
for (let j = 0; j < height; j++) {
|
|
9
|
-
const v = 0.25 * data[j * width + i];
|
|
10
|
-
const ii = Math.floor(i / 2);
|
|
11
|
-
const jj = Math.floor(j / 2);
|
|
12
|
-
const pos = Math.floor(j / 2) * dstWidth + Math.floor(i / 2);
|
|
13
|
-
temp[pos] += v;
|
|
14
|
-
temp[pos + 1] += v;
|
|
15
|
-
temp[pos + dstWidth] += v;
|
|
16
|
-
temp[pos + dstWidth + 1] += v;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return { data: temp, width: dstWidth, height: dstHeight };
|
|
20
|
-
};
|
|
21
1
|
// artoolkit version. slower. is it necessary?
|
|
22
2
|
const upsampleBilinear = ({ image, padOneWidth, padOneHeight }) => {
|
|
23
3
|
const { width, height, data } = image;
|
|
@@ -51,47 +31,81 @@ const upsampleBilinear = ({ image, padOneWidth, padOneHeight }) => {
|
|
|
51
31
|
};
|
|
52
32
|
const downsampleBilinear = ({ image }) => {
|
|
53
33
|
const { data, width, height } = image;
|
|
54
|
-
const dstWidth =
|
|
55
|
-
const dstHeight =
|
|
56
|
-
const temp = new
|
|
57
|
-
|
|
34
|
+
const dstWidth = width >>> 1; // Floor division by 2
|
|
35
|
+
const dstHeight = height >>> 1;
|
|
36
|
+
const temp = new Uint8Array(dstWidth * dstHeight);
|
|
37
|
+
// Cache width for fast indexing
|
|
38
|
+
const srcWidth = width | 0;
|
|
39
|
+
const srcRowStep = (srcWidth * 2) | 0;
|
|
40
|
+
let srcRowOffset = 0;
|
|
41
|
+
let dstIndex = 0;
|
|
58
42
|
for (let j = 0; j < dstHeight; j++) {
|
|
43
|
+
let srcPos = srcRowOffset;
|
|
59
44
|
for (let i = 0; i < dstWidth; i++) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
temp[
|
|
45
|
+
// Unrolled loop for performance
|
|
46
|
+
// (0,0), (1,0), (0,1), (1,1)
|
|
47
|
+
const value = (data[srcPos] +
|
|
48
|
+
data[srcPos + 1] +
|
|
49
|
+
data[srcPos + srcWidth] +
|
|
50
|
+
data[srcPos + srcWidth + 1]) * 0.25;
|
|
51
|
+
temp[dstIndex++] = value | 0; // Fast floor
|
|
52
|
+
srcPos += 2;
|
|
67
53
|
}
|
|
54
|
+
srcRowOffset += srcRowStep;
|
|
68
55
|
}
|
|
69
56
|
return { data: temp, width: dstWidth, height: dstHeight };
|
|
70
57
|
};
|
|
71
58
|
const resize = ({ image, ratio }) => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
59
|
+
// Fast path for identity
|
|
60
|
+
if (ratio === 1) {
|
|
61
|
+
return {
|
|
62
|
+
data: new Uint8Array(image.data), // Copy to be safe/consistent
|
|
63
|
+
width: image.width,
|
|
64
|
+
height: image.height
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// Recursive downsampling for better quality on large reductions
|
|
68
|
+
if (ratio <= 0.5) {
|
|
69
|
+
// 1024 -> 512 -> ...
|
|
70
|
+
return resize({
|
|
71
|
+
image: downsampleBilinear({ image }),
|
|
72
|
+
ratio: ratio * 2
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const width = Math.round(image.width * ratio) | 0;
|
|
76
|
+
const height = Math.round(image.height * ratio) | 0;
|
|
75
77
|
const imageData = new Uint8Array(width * height);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
78
|
+
const srcData = image.data;
|
|
79
|
+
const srcW = image.width | 0;
|
|
80
|
+
const srcH = image.height | 0;
|
|
81
|
+
// Pre-calculate limits to avoid Math.min inside loop
|
|
82
|
+
const srcW_1 = (srcW - 1) | 0;
|
|
83
|
+
const srcH_1 = (srcH - 1) | 0;
|
|
84
|
+
let dstIndex = 0;
|
|
85
|
+
for (let j = 0; j < height; j++) {
|
|
86
|
+
// Y coords
|
|
87
|
+
const srcY = j / ratio;
|
|
88
|
+
const y0 = srcY | 0; // Math.floor
|
|
89
|
+
const y1 = (y0 < srcH_1 ? y0 + 1 : srcH_1) | 0;
|
|
90
|
+
const fy = srcY - y0;
|
|
91
|
+
const ify = 1 - fy;
|
|
92
|
+
// Row offsets
|
|
93
|
+
const row0 = (y0 * srcW) | 0;
|
|
94
|
+
const row1 = (y1 * srcW) | 0;
|
|
95
|
+
for (let i = 0; i < width; i++) {
|
|
96
|
+
// X coords
|
|
97
|
+
const srcX = i / ratio;
|
|
98
|
+
const x0 = srcX | 0; // Math.floor
|
|
99
|
+
const x1 = (x0 < srcW_1 ? x0 + 1 : srcW_1) | 0;
|
|
100
|
+
const fx = srcX - x0;
|
|
101
|
+
const ifx = 1 - fx;
|
|
102
|
+
// Bilinear interpolation optimized
|
|
103
|
+
// v = (1-fx)(1-fy)v00 + fx(1-fy)v10 + (1-fx)fy*v01 + fx*fy*v11
|
|
104
|
+
// Factored: (1-fy) * ((1-fx)v00 + fx*v10) + fy * ((1-fx)v01 + fx*v11)
|
|
105
|
+
const val0 = srcData[row0 + x0] * ifx + srcData[row0 + x1] * fx;
|
|
106
|
+
const val1 = srcData[row1 + x0] * ifx + srcData[row1 + x1] * fx;
|
|
107
|
+
const value = val0 * ify + val1 * fy;
|
|
108
|
+
imageData[dstIndex++] = value | 0;
|
|
95
109
|
}
|
|
96
110
|
}
|
|
97
111
|
return { data: imageData, width: width, height: height };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class WorkerPool {
|
|
2
|
+
constructor(workerPath: any, poolSize?: any);
|
|
3
|
+
workerPath: any;
|
|
4
|
+
poolSize: any;
|
|
5
|
+
workers: any[];
|
|
6
|
+
queue: any[];
|
|
7
|
+
activeWorkers: number;
|
|
8
|
+
runTask(taskData: any): Promise<any>;
|
|
9
|
+
_createWorker(): any;
|
|
10
|
+
_executeTask(worker: any, task: any): void;
|
|
11
|
+
_finishTask(worker: any, callback: any, result: any): void;
|
|
12
|
+
destroy(): Promise<void>;
|
|
13
|
+
}
|