Open in a browser like Chrome / Safari / Firefox. Turn your webcam on. Wave your hand, turn your head
A fork of Triangles Camera by A5
xxxxxxxxxx
//let's start this thing off with the hacky bits ; that way the 'normal' processing code is left at the end.
//we make a HTML5 video element.
var video = document.createElement("video");
//preparing the HTML5 video element:
function initVideo()
{
//make it invisible
video.setAttribute("style", "display:none;");
video.setAttribute("id", "videoOutput");
video.setAttribute("width", "640px");
video.setAttribute("height", "480px");
video.setAttribute("autoplay", "true");
//add it to the doc
if(document.body != null)
{
document.body.appendChild(video);
}
//since not all browsers (and their HTML5 implementations) are equal, we have this ugly polyfill to get a video stream from the webcam to the html5 video element we added earlier
var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia|| navigator.msGetUserMedia;
if (getUserMedia) {
getUserMedia.call(navigator, {video:true}, gotStream, noStream);
function gotStream(stream) {
try
{
//older webkit / chrome browsers
video.src = URL.createObjectURL(stream.srcObject);
}
catch(ex)
{
//since the old one didn't work, we are likely on a newer version
print("notice : old-webkit-way failed. attempting to load video stream according to new standards");
video.srcObject = stream;
}
video.onerror = function () {
stream.stop();
streamError();
};
}
function noStream() {
alert('No camera available.');
}
function streamError() {
alert('Camera error.');
}
}
}
//more hacky bits:
//ctx is assigned in 'context'
var ctx;
var currentImageData;
var lastImageData;
//if the average of U and V for a zone is lower than this value, it is not rendered.
float UVCutoff = 7;
ArrayList<Particle> allParticles = new ArrayList<Particle>();
float currentHue = 0;
var pX,pY;
int maxParticles = 80;
class Particle {
PVector pos = new PVector(0, 0);
PVector vel;
float h = currentHue;
Particle(float x, float y, float velX, float velY) {
this.pos.set(x, y);
PVector lastPos = new PVector(pX, pY);
x = pX; y = pY;
this.vel = new PVector(velX, velY);
this.vel.sub(lastPos);
this.vel.limit(12);
this.vel.mult(random(0.1, 0.6));
}
void move()
{
this.vel.mult(0.98);
this.pos.add(this.vel);
}
}
void setup()
{
size(640,480);
ctx = externals.context; //externals.context is the HTML5 canvas' "context", which we can use to read and write data from and to the canvas.
colorMode(HSB, 360);
initVideo();
}
void AdvanceBuffer()
{
//swap the camera image 'back buffer' so we can compare our new data with the previous shot later on
lastImageData = currentImageData;
//read the camera data off the canvas as a bitmap
currentImageData = ctx.getImageData(0,0,640,480).data;
}
void draw()
{
pushMatrix();
//here come the hacky bits :
//Processing.js uses HTML canvas to draw the processing sketch.
//We want to access the image data that is currently available from the webcam.
//in order to bring this in, and get it into a format we can work with we will simply draw the
//last camera image to the canvas element.
ctx.drawImage(video, 0, 0, width, height); //video is defined outside processing code
//then, we can fetch that image data simply by reading what is on the canvas.
AdvanceBuffer();
//now, with the previous and current image from the camera stored as bitmaps,
//we can compare them to calculate where movement occurred.
ArrayList<FlowZone> fZones = calculateFlow(lastImageData, currentImageData, 640,480);
//flowZone.draw will spawn new particles.
for(FlowZone fz : fZones)
fz.draw();
//'particle' management :
for (int i = allParticles.size()-1; i > -1; i--) {
Particle p = allParticles.get(i);
if (p.vel.mag() < 5)
{
allParticles.remove(p);
continue;
}
p.move();
stroke(p.h, 360, 360);
strokeWeight(p.vel.mag()*1.25);
point(p.pos.x, p.pos.y);
for (int j = 0; j < allParticles.size(); j++)
{
Particle p2 = allParticles.get(j);
if (p == p2) {
continue;
}
float d = dist(p.pos.x, p.pos.y, p2.pos.x, p2.pos.y);
if (d < 100) {
stroke(p.h, 360, 360, p.vel.mag()+20);
strokeWeight(1);
line(p.pos.x, p.pos.y, p2.pos.x, p2.pos.y);
}
}
}
fill(0);
noStroke();
}
ArrayList<FlowZone> calculateFlow (oldImage, newImage,width,height)
{
ArrayList<FlowZone> zones = new ArrayList();
if(oldImage == null)
return zones;
var step = 8;
var winStep = step * 2 + 1;
var A2, A1B2, B1, C1, C2;
var u, v, uu, vv;
uu = vv = 0;
var wMax = width - step - 1;
var hMax = height - step - 1;
var globalY, globalX, localY, localX;
for (globalY = step + 1; globalY < hMax; globalY += winStep)
{
for (globalX = step + 1; globalX < wMax; globalX += winStep)
{
A2 = A1B2 = B1 = C1 = C2 = 0;
for (localY = -step; localY <= step; localY++)
{
for (localX = -step; localX <= step; localX++)
{
var address = (globalY + localY) * width + globalX + localX;
var gradX = (newImage[(address - 1) * 4]) - (newImage[(address + 1) * 4]);
var gradY = (newImage[(address - width) * 4]) - (newImage[(address + width) * 4]);
var gradT = (oldImage[address * 4]) - (newImage[address * 4]);
A2 += gradX * gradX;
A1B2 += gradX * gradY;
B1 += gradY * gradY;
C2 += gradX * gradT;
C1 += gradY * gradT;
}
}
var delta = (A1B2 * A1B2 - A2 * B1);
if (delta !== 0) {
/* system is not singular - solving by Kramer method */
var Idelta = step / delta;
var deltaX = -(C1 * A1B2 - C2 * B1);
var deltaY = -(A1B2 * C2 - A2 * C1);
u = deltaX * Idelta;
v = deltaY * Idelta;
}
else
{
/* singular system - find optical flow in gradient direction */
var norm = (A1B2 + A2) * (A1B2 + A2) + (B1 + A1B2) * (B1 + A1B2);
if (norm !== 0)
{
var IGradNorm = step / norm;
var temp = -(C1 + C2) * IGradNorm;
u = (A1B2 + A2) * temp;
v = (B1 + A1B2) * temp;
}
else
{
u = v = 0;
}
}
if (-winStep < u && u < winStep &&
-winStep < v && v < winStep) {
uu += u;
vv += v;
zones.add(new FlowZone(globalX, globalY, u, v));
}
}
}
return zones;
}
class FlowZone
{
public float X, Y, U, V;
public FlowZone(float x, float y,float u,float v)
{
X = x;
Y = y;
U = u;
V = v;
}
public void xdraw()
{
if((abs(U) + abs(V)) / 2.0 < UVCutoff) return;
pushMatrix();
pushStyle();
stroke(255);
fill(0,0,0,80);
translate(X, Y);
line(0,0,U*3, V*3);
popStyle();
popMatrix();
}
public void draw()
{
if((abs(U) + abs(V)) / 2.0 < UVCutoff) return;
currentHue = random(360);
if(allParticles.size() < maxParticles)
{
allParticles.add(new Particle(X, Y, U,V));
}
}
}