Add a static image example with Teblid tracker
Description
Add a new example that uses a static image (pinball-demo.jpg) as input
instead of a webcam video stream. The example uses the Teblid feature
detector and follows the same structure as the existing threejs_ES6_example.html.
Motivation
- Enable testing without webcam access or camera permissions
- Provide a reproducible test case for CI/CD pipelines
- Simplify development and debugging of the Teblid tracker
Files to Add
examples/threejs_teblid_static_image_ES6_example.html
examples/threejs_static_image_worker_ES6.js
examples/data/pinball-demo.jpg (test image, to be provided)
worker_threejs.js and data/pinball.jpg are reused without any changes.
Implementation Details
threejs_teblid_static_image_ES6_example.html follows the structure of
threejs_ES6_example.html with two differences:
- the
<video> element is replaced by a hidden <img id="static-image">
pointing to data/pinball-demo.jpg (the test image to process)
index.js (initCamera) is not needed; the load event on the <img>
element triggers start() directly
threejs_static_image_worker_ES6.js mirrors threejs_worker_ES6.js exactly,
with one change in the process() function: drawImage reads from the
HTMLImageElement instead of the HTMLVideoElement.
- context_process.drawImage(video, 0, 0, vw, vh, ox, oy, w, h);
+ context_process.drawImage(image, 0, 0, vw, vh, ox, oy, w, h);
Notes
data/pinball.jpg (1637x2048) is the marker reference, already in the repo
data/pinball-demo.jpg is the test image containing the marker, to be added
setTrackerType() returns 'teblid'
- The
GrayScale class is not used
Code snippets
threejs_teblid_static_image_ES6_example.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebARKit Teblid - Static Image ES6 example</title>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=0.5, maximum-scale=1">
<link rel="stylesheet" href="css/nft-style.css">
</head>
<body>
<div id="loading">
<img src="data/aframe-k.png"/>
<span class="loading-text">Loading, please wait</span>
</div>
<div id="stats" class="ui stats">
<div id="stats1" class="stats-item">
<p class="stats-item-title">Main</p>
</div>
<div id="stats2" class="stats-item">
<p class="stats-item-title">Worker</p>
</div>
</div>
<div id="app">
<!--
Niente <video>: l'input è un'immagine statica.
L'elemento img è nascosto; viene usato solo come sorgente
per context_process.drawImage() in threejs_static_image_worker_ES6.js
-->
<img id="static-image" src="data/pinball-demo.jpg" style="display:none;">
<canvas id="canvas"></canvas>
</div>
<script src="js/stats.min.js"></script>
<script src="js/three.min.js"></script>
<script>
function setTrackerType() {
return 'teblid';
}
</script>
<script src="threejs_static_image_worker_ES6.js"></script>
<script>
var statsMain = new Stats();
statsMain.showPanel(0);
document.getElementById('stats1').appendChild(statsMain.dom);
var statsWorker = new Stats();
statsWorker.showPanel(0);
document.getElementById('stats2').appendChild(statsWorker.dom);
window.addEventListener('load', () => {
console.log('init WebARKit Teblid Static Image...');
const image = document.getElementById('static-image');
// Aspetta che l'immagine sia caricata prima di avviare il tracker
const ready = () => {
initTargetCanvas(image.naturalWidth, image.naturalHeight);
start(
'./data/pinball-demo.jpg',
image,
image.naturalWidth,
image.naturalHeight,
function () { statsMain.update(); },
function () { statsWorker.update(); }
);
};
if (image.complete) {
ready();
} else {
image.addEventListener('load', ready);
}
});
function initTargetCanvas(width, height) {
const canvas = document.querySelector('#canvas');
canvas.width = width;
canvas.height = height;
}
</script>
</body>
</html>
threejs_static_image_worker_ES6.js
function isMobile() {
return /Android|mobile|iPad|iPhone/i.test(navigator.userAgent);
}
const setMatrix = function (matrix, value) {
const array = [];
for (const key in value) {
array[key] = value[key];
}
if (typeof matrix.elements.set === 'function') {
matrix.elements.set(array);
} else {
matrix.elements = [].slice.call(array);
}
};
// Stessa firma di threejs_worker_ES6.js, ma `video` è un HTMLImageElement
function start(markerUrl, image, input_width, input_height, render_update, track_update) {
let vw, vh;
let sw, sh;
let pscale, sscale;
let w, h;
let pw, ph;
let ox, oy;
let worker;
const canvas_process = document.createElement('canvas');
const context_process = canvas_process.getContext('2d', { willReadFrequently: true });
const targetCanvas = document.querySelector('#canvas');
const renderer = new THREE.WebGLRenderer({ canvas: targetCanvas, alpha: true, antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
const scene = new THREE.Scene();
const fov = (0.8 * 180) / Math.PI;
const ratio = input_width / input_height;
const cameraConfig = {
fov: fov,
aspect: ratio,
near: 0.01,
far: 1000,
};
const camera = new THREE.PerspectiveCamera(cameraConfig);
camera.matrixAutoUpdate = false;
scene.add(camera);
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 8, 8),
new THREE.MeshNormalMaterial()
);
const root = new THREE.Object3D();
scene.add(root);
sphere.material.flatShading;
sphere.scale.set(0.5, 0.5, 0.5);
root.matrixAutoUpdate = false;
root.add(sphere);
const load = function () {
vw = input_width;
vh = input_height;
pscale = 320 / Math.max(vw, vh / 3 * 4);
sscale = isMobile() ? window.outerWidth / input_width : 1;
sw = vw * sscale;
sh = vh * sscale;
w = vw * pscale;
h = vh * pscale;
pw = Math.max(w, h / 3 * 4);
ph = Math.max(h, w / 4 * 3);
ox = (pw - w) / 2;
oy = (ph - h) / 2;
canvas_process.style.clientWidth = pw + 'px';
canvas_process.style.clientHeight = ph + 'px';
canvas_process.width = pw;
canvas_process.height = ph;
renderer.setSize(sw, sh);
worker = new Worker('./worker_threejs.js');
const type = setTrackerType();
const loadImage = (URL) => {
fetch(URL)
.then(response => response.arrayBuffer())
.then(buff => {
let buffer = new Uint8Array(buff);
worker.postMessage({
type: 'initTracker',
trackerType: type,
imageData: buffer,
imgWidth: 1637,
imgHeight: 2048,
videoWidth: vw,
videoHeight: vh,
});
return buffer;
});
};
loadImage(markerUrl);
worker.onmessage = function (ev) {
const msg = ev.data;
switch (msg.type) {
case 'loadedTracker': {
const proj = JSON.parse(msg.cameraProjMat);
const ratioW = pw / w;
const ratioH = ph / h;
proj[0] *= ratioW;
proj[4] *= ratioW;
proj[8] *= ratioW;
proj[12] *= ratioW;
proj[1] *= ratioH;
proj[5] *= ratioH;
proj[9] *= ratioH;
proj[13] *= ratioH;
setMatrix(camera.projectionMatrix, proj);
process();
break;
}
case 'endLoading': {
if (msg.end === true) {
const loader = document.getElementById('loading');
if (loader) {
loader.querySelector('.loading-text').innerText = 'Start the tracking!';
setTimeout(function () {
loader.parentElement.removeChild(loader);
}, 2000);
}
}
break;
}
case 'found': {
found(msg);
break;
}
case 'not found': {
found(null);
break;
}
}
track_update();
};
};
let world;
const found = function (msg) {
if (!msg) {
world = null;
} else {
world = JSON.parse(msg.pose);
}
};
var lasttime = Date.now();
var time = 0;
const draw = function () {
render_update();
var now = Date.now();
var dt = now - lasttime;
time += dt;
lasttime = now;
if (!world) {
sphere.visible = false;
} else {
sphere.visible = true;
setMatrix(root.matrix, world);
}
renderer.render(scene, camera);
};
const process = function () {
context_process.fillStyle = 'black';
context_process.fillRect(0, 0, pw, ph);
// Unica differenza rispetto a threejs_worker_ES6.js:
// `image` (HTMLImageElement statico) al posto di `video` (HTMLVideoElement)
context_process.drawImage(image, 0, 0, vw, vh, ox, oy, w, h);
const imageData = context_process.getImageData(0, 0, pw, ph);
worker.postMessage({ type: 'process', imagedata: imageData }, [imageData.data.buffer]);
};
const tick = function () {
draw();
process();
requestAnimationFrame(tick);
};
load();
tick();
}
Add a static image example with Teblid tracker
Description
Add a new example that uses a static image (pinball-demo.jpg) as input
instead of a webcam video stream. The example uses the Teblid feature
detector and follows the same structure as the existing
threejs_ES6_example.html.Motivation
Files to Add
examples/threejs_teblid_static_image_ES6_example.htmlexamples/threejs_static_image_worker_ES6.jsexamples/data/pinball-demo.jpg(test image, to be provided)worker_threejs.jsanddata/pinball.jpgare reused without any changes.Implementation Details
threejs_teblid_static_image_ES6_example.htmlfollows the structure ofthreejs_ES6_example.htmlwith two differences:<video>element is replaced by a hidden<img id="static-image">pointing to
data/pinball-demo.jpg(the test image to process)index.js(initCamera) is not needed; theloadevent on the<img>element triggers
start()directlythreejs_static_image_worker_ES6.jsmirrorsthreejs_worker_ES6.jsexactly,with one change in the
process()function:drawImagereads from theHTMLImageElementinstead of theHTMLVideoElement.Notes
data/pinball.jpg(1637x2048) is the marker reference, already in the repodata/pinball-demo.jpgis the test image containing the marker, to be addedsetTrackerType()returns'teblid'GrayScaleclass is not usedCode snippets
threejs_teblid_static_image_ES6_example.html
threejs_static_image_worker_ES6.js