Published
Edited
Oct 9, 2020
3 forks
6 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
webcamBlitter = {
const context = blitterContext2D;
const imageData = context.getImageData(0, 0, canvasWidth, canvasHeight);
const webcamWorker = new Worker(workerScript);
const messaged = ({data: {rgba}}) => {
imageData.data.set(rgba);
context.putImageData(imageData, 0, 0);
};
invalidation.then(() => webcamWorker.terminate());
webcamWorker.onmessage = messaged;
yield webcamWorker;
}
Insert cell
workerScript = {
const blob = new Blob([`
// Just a dumb fade + hue shift effect, to demonstrate processing this in a worker
// Using a Uint16Array to reduce rounding errors (shown as colors that never fade out)
let fadeArr = new Uint16Array(0);

onmessage = event => {
// rgba has been transferred
const {data: {rgba}} = event;

// dynamically adjust fadeArr to webcam dimensions
if (fadeArr.length !== rgba.length) {
// Initiate fadeArr with first frame
fadeArr = Uint16Array.from(rgba);
// we start with white to also ensure that opacity is 100%
fadeArr.fill(0xFFFF);
}

// update fade (which is not transferred back)
for(let i = 0; i < rgba.length;) {

// Trick to convert 8-bit value to 16-bit square value.
// value between 1-256, so in range notation: [0x01,0x100]
let r = (rgba[i] + 1);
// convert to fit precisely in 16 bits:
// [0x01 * 0x01, 0x100 * 0x100] = [0x1, 0x1000]
// [0x1, 0x1000] - 1 = [0x00, 0xFFFF]
r = r*r - 1;
// Take a slow average of the frame
const shift = 6;
let fade = fadeArr[i];
fade = (fade << shift) - fade; // faster than multiplication
fadeArr[i++] = (fade + r) >>> shift;

// repeat for green and blue channels
let g = (rgba[i] + 1);
g = g*g - 1;
fade = fadeArr[i];
fade = (fade << shift) - fade; // faster than multiplication
fadeArr[i++] = (fade + g) >>> shift;
let b = (rgba[i] + 1);
b = b*b - 1;
fade = fadeArr[i];
fade = (fade << shift) - fade; // faster than multiplication
fadeArr[i++] = (fade + b) >>> shift;

i++; // skip opacity, which is 100% anyway
}

for(let i = 0; i < rgba.length;) {
let r = fadeArr[i];
let g = fadeArr[i+1];
let b = fadeArr[i+2];
// Hue-shift, then reverse the whole squaring thing from before
rgba[i++] = Math.sqrt((g+b+2)>>>1) & 0xFF;
rgba[i++] = Math.sqrt((r+b+2)>>>1) & 0xFF;
rgba[i++] = Math.sqrt((g+r+2)>>>1) & 0xFF;
i++; // skipping opacity again
}

// Transfer the rgba array back, avoiding another allocation
postMessage({rgba}, [rgba.buffer]);
};
`], {type: "text/javascript"});
const script = URL.createObjectURL(blob);
invalidation.then(() => URL.revokeObjectURL(script));
return script;
}
Insert cell
rgba = {
while (startRendering && webcamStream) {
const context = webcamContext2D;
context.drawImage(webcamStream, 0, 0, canvasWidth, canvasHeight);
const {data: rgba} = context.getImageData(0, 0, canvasWidth, canvasHeight);
webcamBlitter.postMessage({rgba}, [rgba.buffer]);
// This should be an empty array, because it was transferred
// Also, yield is purely to ensure we're not in a tight loop
yield rgba;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more