@srsergio/taptapp-ar 1.0.2 → 1.0.3

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 (83) hide show
  1. package/README.md +47 -45
  2. package/dist/compiler/aframe.js +0 -3
  3. package/dist/compiler/compiler-base.d.ts +3 -7
  4. package/dist/compiler/compiler-base.js +28 -14
  5. package/dist/compiler/compiler.js +1 -1
  6. package/dist/compiler/compiler.worker.js +1 -1
  7. package/dist/compiler/controller.js +4 -5
  8. package/dist/compiler/controller.worker.js +0 -2
  9. package/dist/compiler/detector/crop-detector.js +0 -2
  10. package/dist/compiler/detector/detector-lite.d.ts +73 -0
  11. package/dist/compiler/detector/detector-lite.js +430 -0
  12. package/dist/compiler/detector/detector.js +236 -243
  13. package/dist/compiler/detector/kernels/cpu/binomialFilter.js +0 -1
  14. package/dist/compiler/detector/kernels/cpu/computeLocalization.js +0 -4
  15. package/dist/compiler/detector/kernels/cpu/computeOrientationHistograms.js +0 -18
  16. package/dist/compiler/detector/kernels/cpu/fakeShader.js +1 -1
  17. package/dist/compiler/detector/kernels/cpu/prune.d.ts +7 -1
  18. package/dist/compiler/detector/kernels/cpu/prune.js +1 -42
  19. package/dist/compiler/detector/kernels/webgl/upsampleBilinear.js +2 -2
  20. package/dist/compiler/estimation/refine-estimate.js +0 -1
  21. package/dist/compiler/estimation/utils.d.ts +1 -1
  22. package/dist/compiler/estimation/utils.js +1 -14
  23. package/dist/compiler/image-list.js +4 -4
  24. package/dist/compiler/input-loader.js +2 -2
  25. package/dist/compiler/matching/hamming-distance.js +13 -13
  26. package/dist/compiler/matching/hierarchical-clustering.js +1 -1
  27. package/dist/compiler/matching/matching.d.ts +20 -4
  28. package/dist/compiler/matching/matching.js +67 -41
  29. package/dist/compiler/matching/ransacHomography.js +1 -2
  30. package/dist/compiler/node-worker.d.ts +1 -0
  31. package/dist/compiler/node-worker.js +84 -0
  32. package/dist/compiler/offline-compiler.d.ts +171 -6
  33. package/dist/compiler/offline-compiler.js +303 -421
  34. package/dist/compiler/tensorflow-setup.js +27 -1
  35. package/dist/compiler/three.js +3 -5
  36. package/dist/compiler/tracker/extract.d.ts +1 -0
  37. package/dist/compiler/tracker/extract.js +200 -244
  38. package/dist/compiler/tracker/tracker.d.ts +1 -1
  39. package/dist/compiler/tracker/tracker.js +13 -18
  40. package/dist/compiler/utils/cumsum.d.ts +4 -2
  41. package/dist/compiler/utils/cumsum.js +17 -19
  42. package/dist/compiler/utils/gpu-compute.d.ts +57 -0
  43. package/dist/compiler/utils/gpu-compute.js +262 -0
  44. package/dist/compiler/utils/images.d.ts +4 -4
  45. package/dist/compiler/utils/images.js +67 -53
  46. package/dist/compiler/utils/worker-pool.d.ts +14 -0
  47. package/dist/compiler/utils/worker-pool.js +84 -0
  48. package/package.json +11 -13
  49. package/src/compiler/aframe.js +2 -4
  50. package/src/compiler/compiler-base.js +29 -14
  51. package/src/compiler/compiler.js +1 -1
  52. package/src/compiler/compiler.worker.js +1 -1
  53. package/src/compiler/controller.js +4 -5
  54. package/src/compiler/controller.worker.js +0 -2
  55. package/src/compiler/detector/crop-detector.js +0 -2
  56. package/src/compiler/detector/detector-lite.js +494 -0
  57. package/src/compiler/detector/detector.js +1052 -1063
  58. package/src/compiler/detector/kernels/cpu/binomialFilter.js +0 -1
  59. package/src/compiler/detector/kernels/cpu/computeLocalization.js +0 -4
  60. package/src/compiler/detector/kernels/cpu/computeOrientationHistograms.js +0 -17
  61. package/src/compiler/detector/kernels/cpu/fakeShader.js +1 -1
  62. package/src/compiler/detector/kernels/cpu/prune.js +1 -37
  63. package/src/compiler/detector/kernels/webgl/upsampleBilinear.js +2 -2
  64. package/src/compiler/estimation/refine-estimate.js +0 -1
  65. package/src/compiler/estimation/utils.js +9 -24
  66. package/src/compiler/image-list.js +4 -4
  67. package/src/compiler/input-loader.js +2 -2
  68. package/src/compiler/matching/hamming-distance.js +11 -15
  69. package/src/compiler/matching/hierarchical-clustering.js +1 -1
  70. package/src/compiler/matching/matching.js +72 -42
  71. package/src/compiler/matching/ransacHomography.js +0 -2
  72. package/src/compiler/node-worker.js +93 -0
  73. package/src/compiler/offline-compiler.js +339 -504
  74. package/src/compiler/tensorflow-setup.js +29 -1
  75. package/src/compiler/three.js +3 -5
  76. package/src/compiler/tracker/extract.js +211 -267
  77. package/src/compiler/tracker/tracker.js +13 -22
  78. package/src/compiler/utils/cumsum.js +17 -19
  79. package/src/compiler/utils/gpu-compute.js +303 -0
  80. package/src/compiler/utils/images.js +84 -53
  81. package/src/compiler/utils/worker-pool.js +89 -0
  82. package/src/compiler/estimation/esimate-experiment.js +0 -316
  83. package/src/compiler/estimation/refine-estimate-experiment.js +0 -512
@@ -7,7 +7,7 @@ const AR2_SEARCH_SIZE = 10;
7
7
  const AR2_SEARCH_GAP = 1;
8
8
  const AR2_SIM_THRESH = 0.8;
9
9
 
10
- const TRACKING_KEYFRAME = 1; // 0: 256px, 1: 128px
10
+ const TRACKING_KEYFRAME = 0; // 0: 128px (optimized)
11
11
 
12
12
  // For some mobile device, only 16bit floating point texture is supported
13
13
  // ref: https://www.tensorflow.org/js/guide/platform_environment#precision
@@ -20,8 +20,6 @@ class Tracker {
20
20
  markerDimensions,
21
21
  trackingDataList,
22
22
  projectionTransform,
23
- inputWidth,
24
- inputHeight,
25
23
  debugMode = false,
26
24
  ) {
27
25
  this.markerDimensions = markerDimensions;
@@ -37,7 +35,7 @@ class Tracker {
37
35
  // prebuild feature and marker pixel tensors
38
36
  let maxCount = 0;
39
37
  for (let i = 0; i < this.trackingKeyframeList.length; i++) {
40
- maxCount = Math.max(maxCount, this.trackingKeyframeList[i].points.length);
38
+ maxCount = Math.max(maxCount, this.trackingKeyframeList[i].px.length);
41
39
  }
42
40
  this.featurePointsListT = [];
43
41
  this.imagePixelsListT = [];
@@ -78,10 +76,6 @@ class Tracker {
78
76
  modelViewProjectionTransform,
79
77
  );
80
78
 
81
- const markerWidth = this.markerDimensions[targetIndex][0];
82
- const markerHeight = this.markerDimensions[targetIndex][1];
83
- const keyframeWidth = this.trackingKeyframeList[targetIndex].width;
84
- const keyframeHeight = this.trackingKeyframeList[targetIndex].height;
85
79
 
86
80
  const featurePointsT = this.featurePointsListT[targetIndex];
87
81
  const imagePixelsT = this.imagePixelsListT[targetIndex];
@@ -108,8 +102,10 @@ class Tracker {
108
102
  const screenCoords = [];
109
103
  const goodTrack = [];
110
104
 
105
+ const { px, py, s: scale } = trackingFrame;
106
+
111
107
  for (let i = 0; i < matchingPoints.length; i++) {
112
- if (sim[i] > AR2_SIM_THRESH && i < trackingFrame.points.length) {
108
+ if (sim[i] > AR2_SIM_THRESH && i < px.length) {
113
109
  goodTrack.push(i);
114
110
  const point = computeScreenCoordiate(
115
111
  modelViewProjectionTransform,
@@ -118,8 +114,8 @@ class Tracker {
118
114
  );
119
115
  screenCoords.push(point);
120
116
  worldCoords.push({
121
- x: trackingFrame.points[i].x / trackingFrame.scale,
122
- y: trackingFrame.points[i].y / trackingFrame.scale,
117
+ x: px[i] / scale,
118
+ y: py[i] / scale,
123
119
  z: 0,
124
120
  });
125
121
  }
@@ -361,23 +357,18 @@ class Tracker {
361
357
 
362
358
  _prebuild(trackingFrame, maxCount) {
363
359
  return tf.tidy(() => {
364
- const scale = trackingFrame.scale;
360
+ const { px, py, s: scale, d: data, w: width, h: height } = trackingFrame;
365
361
 
366
362
  const p = [];
367
363
  for (let k = 0; k < maxCount; k++) {
368
- if (k < trackingFrame.points.length) {
369
- p.push([trackingFrame.points[k].x / scale, trackingFrame.points[k].y / scale]);
364
+ if (k < px.length) {
365
+ p.push([px[k] / scale, py[k] / scale]);
370
366
  } else {
371
367
  p.push([-1, -1]);
372
368
  }
373
369
  }
374
- const imagePixels = tf.tensor(trackingFrame.data, [
375
- trackingFrame.width * trackingFrame.height,
376
- ]);
377
- const imageProperties = tf.tensor(
378
- [trackingFrame.width, trackingFrame.height, trackingFrame.scale],
379
- [3],
380
- );
370
+ const imagePixels = tf.tensor(data, [width * height]);
371
+ const imageProperties = tf.tensor([width, height, scale], [3]);
381
372
  const featurePoints = tf.tensor(p, [p.length, 2], "float32");
382
373
 
383
374
  return {
@@ -390,7 +381,7 @@ class Tracker {
390
381
 
391
382
  _compileAndRun(program, inputs) {
392
383
  const outInfo = tf.backend().compileAndRun(program, inputs);
393
- return tf.engine().makeTensorFromDataId(outInfo.dataId, outInfo.shape, outInfo.dtype);
384
+ return tf.engine().makeTensor(outInfo.dataId, outInfo.shape, outInfo.dtype);
394
385
  }
395
386
  }
396
387
 
@@ -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.cumsum = [];
5
- for (let j = 0; j < height; j++) {
6
- this.cumsum.push([]);
7
- for (let i = 0; i < width; i++) {
8
- this.cumsum[j].push(0);
9
- }
10
- }
4
+ this.width = width;
5
+ this.height = height;
6
+ this.cumsum = new Int32Array(width * height);
11
7
 
12
- this.cumsum[0][0] = data[0];
8
+ this.cumsum[0] = data[0];
13
9
  for (let i = 1; i < width; i++) {
14
- this.cumsum[0][i] = this.cumsum[0][i - 1] + data[i];
10
+ this.cumsum[i] = this.cumsum[i - 1] + data[i];
15
11
  }
16
12
  for (let j = 1; j < height; j++) {
17
- this.cumsum[j][0] = this.cumsum[j - 1][0] + data[j * width];
13
+ this.cumsum[j * width] = this.cumsum[(j - 1) * width] + data[j * width];
18
14
  }
19
15
 
20
16
  for (let j = 1; j < height; j++) {
21
17
  for (let i = 1; i < width; i++) {
22
- this.cumsum[j][i] =
23
- data[j * width + i] +
24
- this.cumsum[j - 1][i] +
25
- this.cumsum[j][i - 1] -
26
- this.cumsum[j - 1][i - 1];
18
+ const pos = j * width + i;
19
+ this.cumsum[pos] =
20
+ data[pos] +
21
+ this.cumsum[(j - 1) * width + i] +
22
+ this.cumsum[j * width + i - 1] -
23
+ this.cumsum[(j - 1) * width + i - 1];
27
24
  }
28
25
  }
29
26
  }
30
27
 
31
28
  query(x1, y1, x2, y2) {
32
- let ret = this.cumsum[y2][x2];
33
- if (y1 > 0) ret -= this.cumsum[y1 - 1][x2];
34
- if (x1 > 0) ret -= this.cumsum[y2][x1 - 1];
35
- if (x1 > 0 && y1 > 0) ret += this.cumsum[y1 - 1][x1 - 1];
29
+ const { width } = this;
30
+ let ret = this.cumsum[y2 * width + x2];
31
+ if (y1 > 0) ret -= this.cumsum[(y1 - 1) * width + x2];
32
+ if (x1 > 0) ret -= this.cumsum[y2 * width + x1 - 1];
33
+ if (x1 > 0 && y1 > 0) ret += this.cumsum[(y1 - 1) * width + x1 - 1];
36
34
  return ret;
37
35
  }
38
36
  }
@@ -0,0 +1,303 @@
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
+
11
+ // Detect if running in Node.js
12
+ const isNode = typeof process !== 'undefined' &&
13
+ process.versions != null &&
14
+ process.versions.node != null;
15
+
16
+ // Lazy load GPU.js only in browser environments
17
+ let GPU = null;
18
+ let gpuInstance = null;
19
+ let gpuLoadAttempted = false;
20
+
21
+ /**
22
+ * Try to initialize GPU.js (browser only)
23
+ */
24
+ const tryInitGPU = () => {
25
+ if (gpuLoadAttempted) return gpuInstance;
26
+ gpuLoadAttempted = true;
27
+
28
+ // Skip GPU.js in Node.js to avoid headless-gl issues
29
+ if (isNode) {
30
+ console.log("⚡ Running in Node.js - using optimized JS");
31
+ return null;
32
+ }
33
+
34
+ // Dynamically import GPU.js in browser
35
+ try {
36
+ // Use dynamic import pattern that works in browsers
37
+ const GPUModule = globalThis.GPU || (typeof require !== 'undefined' ? require('gpu.js').GPU : null);
38
+ if (GPUModule) {
39
+ GPU = GPUModule;
40
+ gpuInstance = new GPU({ mode: "gpu" });
41
+
42
+ // Test if GPU works
43
+ const testKernel = gpuInstance.createKernel(function () { return 1; }).setOutput([1]);
44
+ testKernel();
45
+ testKernel.destroy();
46
+
47
+ console.log("🚀 GPU.js: Using GPU acceleration");
48
+ }
49
+ } catch (e) {
50
+ console.log("⚡ GPU.js unavailable, using optimized JS:", e.message);
51
+ gpuInstance = null;
52
+ }
53
+
54
+ return gpuInstance;
55
+ };
56
+
57
+ // ============================================================================
58
+ // PURE JAVASCRIPT IMPLEMENTATIONS (Always work)
59
+ // ============================================================================
60
+
61
+ /**
62
+ * Pure JS: Compute edge gradients
63
+ */
64
+ const computeGradientsJS = (imageData, width, height) => {
65
+ const dValue = new Float32Array(width * height);
66
+
67
+ for (let j = 1; j < height - 1; j++) {
68
+ const rowOffset = j * width;
69
+ const prevRowOffset = (j - 1) * width;
70
+ const nextRowOffset = (j + 1) * width;
71
+
72
+ for (let i = 1; i < width - 1; i++) {
73
+ const pos = rowOffset + i;
74
+
75
+ const dx = (imageData[prevRowOffset + i + 1] - imageData[prevRowOffset + i - 1] +
76
+ imageData[rowOffset + i + 1] - imageData[rowOffset + i - 1] +
77
+ imageData[nextRowOffset + i + 1] - imageData[nextRowOffset + i - 1]) / 768;
78
+
79
+ const dy = (imageData[nextRowOffset + i - 1] - imageData[prevRowOffset + i - 1] +
80
+ imageData[nextRowOffset + i] - imageData[prevRowOffset + i] +
81
+ imageData[nextRowOffset + i + 1] - imageData[prevRowOffset + i + 1]) / 768;
82
+
83
+ dValue[pos] = Math.sqrt((dx * dx + dy * dy) / 2);
84
+ }
85
+ }
86
+
87
+ return dValue;
88
+ };
89
+
90
+ /**
91
+ * Pure JS: Find local maxima
92
+ */
93
+ const findLocalMaximaJS = (gradients, width, height) => {
94
+ const isCandidate = new Uint8Array(width * height);
95
+
96
+ for (let j = 1; j < height - 1; j++) {
97
+ const rowOffset = j * width;
98
+ for (let i = 1; i < width - 1; i++) {
99
+ const pos = rowOffset + i;
100
+ const val = gradients[pos];
101
+ if (val > 0 &&
102
+ val >= gradients[pos - 1] && val >= gradients[pos + 1] &&
103
+ val >= gradients[pos - width] && val >= gradients[pos + width]) {
104
+ isCandidate[pos] = 1;
105
+ }
106
+ }
107
+ }
108
+
109
+ return isCandidate;
110
+ };
111
+
112
+ /**
113
+ * Pure JS: Gaussian blur (5x5 binomial)
114
+ */
115
+ const gaussianBlurJS = (data, width, height) => {
116
+ const output = new Float32Array(width * height);
117
+ const temp = new Float32Array(width * height);
118
+ const k0 = 1 / 16, k1 = 4 / 16, k2 = 6 / 16;
119
+ const w1 = width - 1;
120
+ const h1 = height - 1;
121
+
122
+ // Horizontal pass
123
+ for (let y = 0; y < height; y++) {
124
+ const rowOffset = y * width;
125
+ for (let x = 0; x < width; x++) {
126
+ const x0 = x < 2 ? 0 : x - 2;
127
+ const x1 = x < 1 ? 0 : x - 1;
128
+ const x3 = x > w1 - 1 ? w1 : x + 1;
129
+ const x4 = x > w1 - 2 ? w1 : x + 2;
130
+
131
+ temp[rowOffset + x] =
132
+ data[rowOffset + x0] * k0 +
133
+ data[rowOffset + x1] * k1 +
134
+ data[rowOffset + x] * k2 +
135
+ data[rowOffset + x3] * k1 +
136
+ data[rowOffset + x4] * k0;
137
+ }
138
+ }
139
+
140
+ // Vertical pass
141
+ for (let y = 0; y < height; y++) {
142
+ const y0 = (y < 2 ? 0 : y - 2) * width;
143
+ const y1 = (y < 1 ? 0 : y - 1) * width;
144
+ const y2 = y * width;
145
+ const y3 = (y > h1 - 1 ? h1 : y + 1) * width;
146
+ const y4 = (y > h1 - 2 ? h1 : y + 2) * width;
147
+
148
+ for (let x = 0; x < width; x++) {
149
+ output[y2 + x] =
150
+ temp[y0 + x] * k0 +
151
+ temp[y1 + x] * k1 +
152
+ temp[y2 + x] * k2 +
153
+ temp[y3 + x] * k1 +
154
+ temp[y4 + x] * k0;
155
+ }
156
+ }
157
+
158
+ return output;
159
+ };
160
+
161
+ /**
162
+ * Pure JS: Downsample by factor of 2
163
+ */
164
+ const downsampleJS = (data, width, height) => {
165
+ const newWidth = Math.floor(width / 2);
166
+ const newHeight = Math.floor(height / 2);
167
+ const output = new Float32Array(newWidth * newHeight);
168
+
169
+ for (let y = 0; y < newHeight; y++) {
170
+ const sy = y * 2;
171
+ for (let x = 0; x < newWidth; x++) {
172
+ const sx = x * 2;
173
+ const pos = sy * width + sx;
174
+ output[y * newWidth + x] =
175
+ (data[pos] + data[pos + 1] + data[pos + width] + data[pos + width + 1]) / 4;
176
+ }
177
+ }
178
+
179
+ return { data: output, width: newWidth, height: newHeight };
180
+ };
181
+
182
+ // ============================================================================
183
+ // GPU COMPUTE CLASS
184
+ // ============================================================================
185
+
186
+ /**
187
+ * GPU Compute class - provides optimized image processing
188
+ */
189
+ export class GPUCompute {
190
+ constructor() {
191
+ this.gpu = null;
192
+ this.kernelCache = new Map();
193
+ this.initialized = false;
194
+ }
195
+
196
+ /**
197
+ * Initialize (tries GPU in browser, uses JS in Node)
198
+ */
199
+ init() {
200
+ if (this.initialized) return;
201
+ this.gpu = tryInitGPU();
202
+ this.initialized = true;
203
+ }
204
+
205
+ /**
206
+ * Compute edge gradients
207
+ */
208
+ computeGradients(imageData, width, height) {
209
+ this.init();
210
+ // Always use JS implementation for reliability
211
+ return computeGradientsJS(imageData, width, height);
212
+ }
213
+
214
+ /**
215
+ * Find local maxima
216
+ */
217
+ findLocalMaxima(gradients, width, height) {
218
+ this.init();
219
+ return findLocalMaximaJS(gradients, width, height);
220
+ }
221
+
222
+ /**
223
+ * Combined edge detection
224
+ */
225
+ edgeDetection(imageData, width, height) {
226
+ const dValue = this.computeGradients(imageData, width, height);
227
+ const isCandidate = this.findLocalMaxima(dValue, width, height);
228
+ return { dValue, isCandidate };
229
+ }
230
+
231
+ /**
232
+ * Gaussian blur
233
+ */
234
+ gaussianBlur(imageData, width, height) {
235
+ this.init();
236
+ return gaussianBlurJS(imageData, width, height);
237
+ }
238
+
239
+ /**
240
+ * Downsample by factor of 2
241
+ */
242
+ downsample(imageData, width, height) {
243
+ this.init();
244
+ return downsampleJS(imageData, width, height);
245
+ }
246
+
247
+ /**
248
+ * Build Gaussian pyramid
249
+ */
250
+ buildPyramid(imageData, width, height, numLevels = 5) {
251
+ this.init();
252
+
253
+ const pyramid = [];
254
+ let currentData = imageData instanceof Float32Array ? imageData : Float32Array.from(imageData);
255
+ let currentWidth = width;
256
+ let currentHeight = height;
257
+
258
+ for (let level = 0; level < numLevels; level++) {
259
+ const blurred = this.gaussianBlur(currentData, currentWidth, currentHeight);
260
+
261
+ pyramid.push({
262
+ data: blurred,
263
+ width: currentWidth,
264
+ height: currentHeight,
265
+ scale: Math.pow(2, level),
266
+ });
267
+
268
+ if (currentWidth > 8 && currentHeight > 8) {
269
+ const downsampled = this.downsample(blurred, currentWidth, currentHeight);
270
+ currentData = downsampled.data;
271
+ currentWidth = downsampled.width;
272
+ currentHeight = downsampled.height;
273
+ } else {
274
+ break;
275
+ }
276
+ }
277
+
278
+ return pyramid;
279
+ }
280
+
281
+ /**
282
+ * Check if GPU is available
283
+ */
284
+ isGPUAvailable() {
285
+ this.init();
286
+ return this.gpu !== null;
287
+ }
288
+
289
+ /**
290
+ * Cleanup resources
291
+ */
292
+ destroy() {
293
+ this.kernelCache.clear();
294
+ if (this.gpu && this.gpu.destroy) {
295
+ this.gpu.destroy();
296
+ }
297
+ this.gpu = null;
298
+ this.initialized = false;
299
+ }
300
+ }
301
+
302
+ // Singleton instance
303
+ export const gpuCompute = new GPUCompute();
@@ -1,24 +1,4 @@
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
1
 
8
- for (let i = 0; i < width; i++) {
9
- for (let j = 0; j < height; j++) {
10
- const v = 0.25 * data[j * width + i];
11
- const ii = Math.floor(i / 2);
12
- const jj = Math.floor(j / 2);
13
- const pos = Math.floor(j / 2) * dstWidth + Math.floor(i / 2);
14
- temp[pos] += v;
15
- temp[pos + 1] += v;
16
- temp[pos + dstWidth] += v;
17
- temp[pos + dstWidth + 1] += v;
18
- }
19
- }
20
- return { data: temp, width: dstWidth, height: dstHeight };
21
- };
22
2
 
23
3
  // artoolkit version. slower. is it necessary?
24
4
  const upsampleBilinear = ({ image, padOneWidth, padOneHeight }) => {
@@ -57,54 +37,105 @@ const upsampleBilinear = ({ image, padOneWidth, padOneHeight }) => {
57
37
 
58
38
  const downsampleBilinear = ({ image }) => {
59
39
  const { data, width, height } = image;
40
+ const dstWidth = width >>> 1; // Floor division by 2
41
+ const dstHeight = height >>> 1;
60
42
 
61
- const dstWidth = Math.floor(width / 2);
62
- const dstHeight = Math.floor(height / 2);
43
+ const temp = new Uint8Array(dstWidth * dstHeight);
63
44
 
64
- const temp = new Float32Array(dstWidth * dstHeight);
65
- const offsets = [0, 1, width, width + 1];
45
+ // Cache width for fast indexing
46
+ const srcWidth = width | 0;
47
+ const srcRowStep = (srcWidth * 2) | 0;
48
+
49
+ let srcRowOffset = 0;
50
+ let dstIndex = 0;
66
51
 
67
52
  for (let j = 0; j < dstHeight; j++) {
53
+ let srcPos = srcRowOffset;
54
+
68
55
  for (let i = 0; i < dstWidth; i++) {
69
- let srcPos = j * 2 * width + i * 2;
70
- let value = 0.0;
71
- for (let d = 0; d < offsets.length; d++) {
72
- value += data[srcPos + offsets[d]];
73
- }
74
- value *= 0.25;
75
- temp[j * dstWidth + i] = value;
56
+ // Unrolled loop for performance
57
+ // (0,0), (1,0), (0,1), (1,1)
58
+ const value = (
59
+ data[srcPos] +
60
+ data[srcPos + 1] +
61
+ data[srcPos + srcWidth] +
62
+ data[srcPos + srcWidth + 1]
63
+ ) * 0.25;
64
+
65
+ temp[dstIndex++] = value | 0; // Fast floor
66
+ srcPos += 2;
76
67
  }
68
+ srcRowOffset += srcRowStep;
77
69
  }
70
+
78
71
  return { data: temp, width: dstWidth, height: dstHeight };
79
72
  };
80
73
 
81
74
  const resize = ({ image, ratio }) => {
82
- const width = Math.round(image.width * ratio);
83
- const height = Math.round(image.height * ratio);
75
+ // Fast path for identity
76
+ if (ratio === 1) {
77
+ return {
78
+ data: new Uint8Array(image.data), // Copy to be safe/consistent
79
+ width: image.width,
80
+ height: image.height
81
+ };
82
+ }
84
83
 
85
- //const imageData = new Float32Array(width * height);
84
+ // Recursive downsampling for better quality on large reductions
85
+ if (ratio <= 0.5) {
86
+ // 1024 -> 512 -> ...
87
+ return resize({
88
+ image: downsampleBilinear({ image }),
89
+ ratio: ratio * 2
90
+ });
91
+ }
92
+
93
+ const width = Math.round(image.width * ratio) | 0;
94
+ const height = Math.round(image.height * ratio) | 0;
86
95
  const imageData = new Uint8Array(width * height);
87
- for (let i = 0; i < width; i++) {
88
- let si1 = Math.round((1.0 * i) / ratio);
89
- let si2 = Math.round((1.0 * (i + 1)) / ratio) - 1;
90
- if (si2 >= image.width) si2 = image.width - 1;
91
-
92
- for (let j = 0; j < height; j++) {
93
- let sj1 = Math.round((1.0 * j) / ratio);
94
- let sj2 = Math.round((1.0 * (j + 1)) / ratio) - 1;
95
- if (sj2 >= image.height) sj2 = image.height - 1;
96
-
97
- let sum = 0;
98
- let count = 0;
99
- for (let ii = si1; ii <= si2; ii++) {
100
- for (let jj = sj1; jj <= sj2; jj++) {
101
- sum += 1.0 * image.data[jj * image.width + ii];
102
- count += 1;
103
- }
104
- }
105
- imageData[j * width + i] = Math.floor(sum / count);
96
+
97
+ const srcData = image.data;
98
+ const srcW = image.width | 0;
99
+ const srcH = image.height | 0;
100
+ // Pre-calculate limits to avoid Math.min inside loop
101
+ const srcW_1 = (srcW - 1) | 0;
102
+ const srcH_1 = (srcH - 1) | 0;
103
+
104
+ let dstIndex = 0;
105
+
106
+ for (let j = 0; j < height; j++) {
107
+ // Y coords
108
+ const srcY = j / ratio;
109
+ const y0 = srcY | 0; // Math.floor
110
+ const y1 = (y0 < srcH_1 ? y0 + 1 : srcH_1) | 0;
111
+ const fy = srcY - y0;
112
+ const ify = 1 - fy;
113
+
114
+ // Row offsets
115
+ const row0 = (y0 * srcW) | 0;
116
+ const row1 = (y1 * srcW) | 0;
117
+
118
+ for (let i = 0; i < width; i++) {
119
+ // X coords
120
+ const srcX = i / ratio;
121
+ const x0 = srcX | 0; // Math.floor
122
+ const x1 = (x0 < srcW_1 ? x0 + 1 : srcW_1) | 0;
123
+ const fx = srcX - x0;
124
+ const ifx = 1 - fx;
125
+
126
+ // Bilinear interpolation optimized
127
+ // v = (1-fx)(1-fy)v00 + fx(1-fy)v10 + (1-fx)fy*v01 + fx*fy*v11
128
+ // Factored: (1-fy) * ((1-fx)v00 + fx*v10) + fy * ((1-fx)v01 + fx*v11)
129
+
130
+ const val0 = srcData[row0 + x0] * ifx + srcData[row0 + x1] * fx;
131
+ const val1 = srcData[row1 + x0] * ifx + srcData[row1 + x1] * fx;
132
+
133
+ const value = val0 * ify + val1 * fy;
134
+
135
+ imageData[dstIndex++] = value | 0;
106
136
  }
107
137
  }
138
+
108
139
  return { data: imageData, width: width, height: height };
109
140
  };
110
141