Public
Edited
Sep 24, 2024
10 stars
Insert cell
Insert cell
Insert cell
Insert cell
{

const context = DOM.context2d(width, height);
const image = DOM.context2d(width, height);

// pixelRatio so it works on retna displays which double up all the pixel sizes
const colWidth = Math.round((width * pixelRatio) / 50);
const rowHeight = Math.round((height * pixelRatio) / 50);

let nextFrameTime = +new Date();
const frameTime = 1000 / framesPerSecond;
while(stream){
if (+new Date() > nextFrameTime) {
nextFrameTime = +new Date() + frameTime;
image.drawImage(stream, 0, 0, width, height);

// we loop each row and then each column inside to get the value of each cell of the video frame
// we can then sort them darkest to lightest and keep track of the coordinates
const newOrder = new Array(50).fill(1).flatMap( (_, row) =>
new Array(50).fill(1).map( (_, col) => {
const data = image.getImageData(col * colWidth, row * rowHeight, colWidth, rowHeight);
const { average } = getCorners(data);
return {
left: col * colWidth,
top: row * rowHeight,
average,
}
})
).sort((a,b) => b.average - a.average)

// we now render them not in order on screen but order of how dark they are
// this way we can just take the next one from the presorted array of Star Wars frames.
newOrder.forEach(({ top, left }, idx) => {
context.putImageData(imgData[idx].data, left, top);
})
}
yield context.canvas;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
imgData = {
const image = DOM.context2d(width, height);
image.drawImage(original, 0, 0, original.naturalWidth, original.naturalHeight, 0, 0, width, height);
const colWidth = (width * pixelRatio) / 50;
const rowHeight = Math.round((height * pixelRatio) / 50);

// we get the data for each Star Wars frame and use the function below to get the darkness value
// then we sort them by darkness so we can use them in darkness order in the funciton at the top
return new Array(50).fill(1).flatMap( (_, row) =>
new Array(50).fill(1).map( (_, col) => {

const data = image.getImageData(col * colWidth, row * rowHeight, colWidth, rowHeight);
const corners = getCorners(data);
return {
...corners,
data
}
})
).sort((a,b) => b.average - a.average)
}
Insert cell
Insert cell
getCorners = ({ width, height, data }) => {
const tl = {
red: data[0] / 255,
green: data[1] / 255,
blue: data[2] / 255,
darkness: (data[0] + data[1] + data[2]) / 755
};

const w = width * 4;
const tr = {
red: data[w] / 255,
green: data[w+1] / 255,
blue: data[w+2] / 255,
darkness: (data[w] + data[w + 1] + data[w + 2]) / 755
}

const h = (height-1) * w;
const bl = {
red: data[h] / 255,
green: data[h+1] / 255,
blue: data[h+2] / 255,
darkness: (data[h] + data[h+1] + data[h+2]) / 755
};
const len = data.length;
const br = {
red: data[len - 3] / 255,
green: data[len - 2] / 255,
blue: data[len - 1] / 255,
darkness: (data[len - 3] + data[len - 2] + data[len - 1]) / 755
}

const average = (tr.darkness + tl.darkness + bl.darkness + br.darkness) / 4
return { tl, tr, bl, br, average }
}
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