In p5.js how do I create a moving animation of an array group of lines? - html

Hi I created a sparkler like shape in P5.js using array() and randomGaussian(). Here is what it looks like with the codes in p5.js:
let distribution = new Array(360);
let b = false;
let x, y;
function setup() {
var cny = createCanvas(windowWidth, windowHeight);
cnv.parent("sketchholder");
for (let i = 0; i < distribution.length; i++) {
distribution[i] = floor(randomGaussian(60, 50));
}
colorMode(HSB, 255);
// hue, saturation, brightness
x = width / 4;
у = height / 3;
}
function draw() {
background(21, 30, 10);
translate(width / 2, height / 2);
strokeWeight(3);
stroke(255, 70);
line(0, 0, -x, y);
if (b) {
for (let i = 0; i < distribution.length; i++) {
rotate(TWO_PI / distribution.length);
var colorH = random(0, 255);
var colorS = random(100, 200);
var colorB = random(0, 255);
var YY = random(1, 4);
stroke(colorH, colorS, colorB);
strokeWeight(YY);
strokeCap(ROUND);
let dist = abs(distribution[i]);
line(0, 0, dist, 0);
}
}
}
And I would like it to move slowly along the stick and disappear after like 20s before reaching the end. Can I achieve that in p5.js? Or can I create the same effect using the p5.js layer as a div in html and animate it in CSS?
Thanks.

Yes, you can definitely do this with p5.js. I think it would not make much sense to try to do an animation like this with CSS and that wouldn't work well with p5.js regardless (you would only be able to animate DOM elements). However, you do need to basically implement your own animation system with different parameter values for different times in the animation and then interpolating between them yourself. Here is a basic example:
let distribution = new Array(360);
let originX, originY;
let keyframes = [];
let keyframeTimes = [];
function setup() {
createCanvas(windowWidth, windowHeight);
for (let i = 0; i < distribution.length; i++) {
distribution[i] = floor(randomGaussian(60, 50));
}
colorMode(HSB, 255);
// hue, saturation, brightness
// Origin relative to the center of the sketch
originX = -width / 4;
originY = height / 3;
const lenX = width / 4;
const lenY = - height / 3;
// Add the desired parameters and times to keyframes and keyframeTimes
keyframes.push({
x: lenX,
y: lenY,
mag: 1
});
keyframeTimes.push(0);
keyframes.push({
x: 0.2 * lenX,
y: 0.2 * lenY,
mag: 0
});
keyframeTimes.push(20);
}
function draw() {
background(21, 30, 10);
translate(width / 2, height / 2);
translate(originX, originY);
strokeWeight(3);
stroke(255, 70);
// Find the next keyframe that we are animating towards.
let nextKeyframeIx = keyframeTimes.findIndex(t => t > millis() / 1000);
// Find the current/previous keyframe that we are starting from.
// Cases to consider: the next keyframe is the first one in the list, we're in
// between two keyframes, or there is no next keyframe
let keyframeIx =
nextKeyframeIx === 0 ?
// There is no previous keyframe (we're at the beginning)
0 :
// If we're beyond the end of the list, just use the parameters from the
// last keyframe
(nextKeyframeIx > 0 ? nextKeyframeIx - 1 : keyframes.length - 1);
let x, y, mag;
if (keyframeIx < nextKeyframeIx) {
// lerp between the current and next keyframe
let kf1 = keyframes[keyframeIx];
let kf2 = keyframes[nextKeyframeIx];
let t1 = keyframeTimes[keyframeIx];
let t2 = keyframeTimes[nextKeyframeIx];
let amt = (millis() / 1000 - t1) / (t2 - t1);
x = lerp(kf1.x, kf2.x, amt);
y = lerp(kf1.y, kf2.y, amt);
mag = lerp(kf1.mag, kf2.mag, amt);
} else {
// just use the current/previous keyframe
let kf = keyframes[keyframeIx];
x = kf.x;
y = kf.y;
mag = kf.mag;
}
// Draw a line from the origin (bottom left of the sparkler) to the current
// x,y position of the end of the sparkler
line(0, 0, x, y);
// Translate to the current end of the sparkler for drawing the colored lines
translate(x, y);
if (mag > 0) {
for (let i = 0; i < distribution.length; i++) {
rotate(TWO_PI / distribution.length);
const colorH = random(0, 255);
const colorS = random(100, 200);
const colorB = random(0, 255);
const YY = random(1, 4);
stroke(colorH, colorS, colorB);
strokeWeight(YY);
strokeCap(ROUND);
let dist = abs(distribution[i]) * mag;
line(0, 0, dist, 0);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

Related

shadowBlur (html canvas) not working in js loop

I was trying to improve my stars in the sky animation which I created using JS. This is when i discovered, I could use the shadowBlur property to change the size of the shadow created around my stars, to make it look like they are flickering. The issue now is that the shadowBlur goes up but doesn't go back to black. Here is the code I have used. Any help with this will be greatly appreciated :).
Have a great day!
// ---- Vars for star animation
let randomStars = [];
let starCollection = [];
let numberofStars = 50;
let flickeringStars = 50;
class Star{
constructor(x,y,color,radius,shadowBlur){
this._canvas = document.querySelector('canvas');
this._canvas.width = window.innerWidth;
this._canvas.height = window.innerHeight;
this._c = this._canvas.getContext('2d');
this._radius = radius;
this._x = x;
this._y = y;
this._color = color;
this._shadowBlur = 10;
this._shadowColor = 'white';
}
//drawing individual stars
draw(){
this._c.beginPath();
this._c.arc(this._x,this._y,this._radius,0,Math.PI * 2,false);
this._c.fillStyle = this._color;
this._c.strokeStyle = 'black';
this._c.shadowColor = this._shadowColor;
this._c.shadowBlur = this._shadowBlur;
this._c.stroke();
this._c.fill();
this._c.closePath();
}
//Fade in and out for stars
flicker(){
setTimeout(()=>{this._shadowBlur=10;},200);
setTimeout(()=>{this._shadowBlur=8;},400);
setTimeout(()=>{this._shadowBlur=6;},600);
setTimeout(()=>{this._shadowBlur=4;},800);
setTimeout(()=>{this._shadowBlur=2;},1000);
setTimeout(()=>{this._shadowBlur=0;},1200);
setTimeout(()=>{this._shadowBlur=2;},1400);
setTimeout(()=>{this._shadowBlur=4;},1600);
setTimeout(()=>{this._shadowBlur=6;},1800);
setTimeout(()=>{this._shadowBlur=8;},2000);
setTimeout(()=>{this._shadowBlur=10;},2200);
setTimeout(()=>{this.draw();},200);
setTimeout(()=>{this.draw();},400);
setTimeout(()=>{this.draw();},600);
setTimeout(()=>{this.draw();},800);
setTimeout(()=>{this.draw();},1000);
setTimeout(()=>{this.draw();},1200);
setTimeout(()=>{this.draw();},1400);
setTimeout(()=>{this.draw();},1600);
setTimeout(()=>{this.draw();},1800);
setTimeout(()=>{this.draw();},2000);
setTimeout(()=>{this.draw();},2200);
}
//Clears the canvas
clearstars(){
this._c.clearRect(0,0,window.innerWidth, window.innerHeight);
}
}
// ---- Functions ----
//Typing animation
const typingAnimation = ()=>{
if(textProgress < text.length){
setTimeout(()=>{requestAnimationFrame(typingAnimation)}, speed);
if(text.charAt(textProgress) === '\n')document.getElementById('animation-text').innerHTML += '</br>';
document.getElementById('animation-text').innerHTML += text.charAt(textProgress);
textProgress ++;
}else{
let event = new CustomEvent('showStars');
dispatchEvent(event);
}
}
//Store stars
const storeStars = ()=>{
starCollection = [];
for(let i=0;i<numberofStars;i++){
let x = Math.floor(Math.random()*window.innerWidth);
let y = Math.floor(Math.random()*window.innerHeight);
starCollection.push(new Star(x,y,"white",(Math.random()+1)-0.7));
}
}
//Show stars to the screen
const showStars = ()=>{
for(let i=0;i<starCollection.length;i++){
starCollection[i].draw();
}
}
//Store random stars
const generateRandomStars = ()=>{
randomStars = [];
for(let i=0;i<flickeringStars;i++){
let x = Math.floor(Math.random()*window.innerWidth);
let y = Math.floor(Math.random()*window.innerHeight);
randomStars.push(new Star(x,y,"white",(Math.random()+1)-0.7));
}
}
//Show randoms stars after clearing previous set of flickering stars
const showRandomStars = ()=>{
let id = window.setTimeout(function () { }, 0);
while (id--) {
window.clearTimeout(id);
}
let starHandler = new Star(0,0,"white",0);
starHandler.clearstars();
showStars();
flickerStars();
}
//Flickers stars and changes set of stars randomly
const flickerStars = ()=>{
for(let i=0;i<flickeringStars;i++){
setInterval(()=>{
randomStars[i].flicker();
},2200);
setInterval(()=>{
console.log("changing stars pattern");
generateRandomStars();
showRandomStars();
},12200);
}
}
// ---- Event Listeners ----
//Typing animation on load
window.addEventListener("load", ()=>{
storeStars();
generateRandomStars();
showStars();
flickerStars();
});
//Handles star animation scaling on window resize
window.addEventListener("resize", ()=>{
let id = window.setTimeout(function () { }, 0);
while (id--) {
window.clearTimeout(id);
}
let starHandler = new Star(0,0,"white",0);
starHandler.clearstars();
generateRandomStars();
storeStars();
showStars();
flickerStars();
});
body{
background-color:black;
}
<html>
<body><canvas></canvas></body>
</html>
I can not work out what FX you are trying to get. Below is a simple flickering star animation that uses small rectangles that change size to simulate flicker.
The frame rate is reduced so that the flicker is more pronounced.
It is very efficient and does not require complex state changes, or slow render methods (like blur)
var W = 0, H = 0; // hold canvas size. Set to zero so first frame sizes canvas
var count = 500; // number of stars
var frame = 0; // current frame number
const frameRate = 5; // render stars ever 5 frames
const sizeRange = 1.5; // max size of star us minSize + sizeRange + flickerSize
const minSize = 1; // minimum size of star
const flickerSize = 1; // amount of change random change to make a star brighter
const flickerRate = 0.1; // odds per rendered frame that a star flickers
const stars = [];
// Create a random set of numbers for star positions.
// Values must be larger than largest canvas you are going to use.
// This will work up to 8K display
while (count--) { stars.push(Math.random() * 1024 * 8) }
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function mainLoop() {
var len = stars.length, i = 0, x = stars[i++], starSize;
// if the window has resized change the canvas to fit
if (W !== innerWidth || H !== innerHeight) {
W = canvas.width = innerWidth;
H = canvas.height = innerHeight;
ctx.fillStyle = "#FFF";
frame = 0;
}
if (frame++ % frameRate === 0) { // only render every frameRate frames
ctx.clearRect(0, 0, W, H);
ctx.beginPath(); // draw all stars with one path aas it is several orders
// of magnitude quicker than creating a path for each
while (i < len) {
// draws small stars to large
const starScale = (i / len); // set scale from 0 to 1
starSize = sizeRange; // set the range of sizes
if (Math.random() < flickerRate) { // on random odds of flicker
starSize += flickerSize * Math.random(); // add random flicker size
}
starSize *= starScale; // scale the star
starSize += minSize; // add min size of star
halfSize = starSize / 2; // offset to top left of star
const y = stars[i++]; // get next random number as star y pos
// add rect to path fitted to canvas width and height (W, H)
ctx.rect((x % W) - halfSize , (y % H) - halfSize , starSize , starSize );
x = y; // Use y for the next x star coordinate
}
ctx.fill(); // fill in all the stars
}
requestAnimationFrame(mainLoop);
}
canvas {
position: absolute;
top: 0px;
left: 0px;
background: #000;
}
<canvas id="canvas"></canvas>

Drawing in canvas with react doing weird things when rotating

I'm trying to draw in a canvas in a react component. The component draws a line and a number of squares depending on the length of an array passed to it as props inclining rotating all of them depending on another prop.
I have a loop that draws it perfectly until it reaches the 5th iteration, then something happens and it start to mess with the context restoration after the rotation. There is only one change of value in that loop ( initialX) Debugging the loop in the browser the rotate method is called the same times as the restore. I'm really confused by this behaviour, it is a very simple draw and I can't see where is my mistake.
This is what I'm getting
This is the same sketch without applying rotation
class Sketch extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
let canvas = document.getElementById("plano");
let detector = this.props.detector
let X, Y;
if (canvas && canvas.getContext && detector) {
inicializarCanvas(detector);
function inicializarCanvas(detector) {
let ctx = canvas.getContext("2d");
let s = getComputedStyle(canvas);
let w = s.width;
let h = s.height;
canvas.width = w.split("px")[0];
canvas.height = h.split("px")[0];
X = canvas.width / 2;
Y = canvas.height / 2;
//draw beam
ctx.moveTo( canvas.width / 3, canvas.height / 2);
ctx.lineTo(0, canvas.height / 2);
ctx.strokeStyle = "#f00";
ctx.stroke();
ctx.restore();
ctx.restore();
ctx.save();
drawBlades(ctx, canvas.width, canvas.height, detector)
}
function drawBlades(ctx, x, y, detector) {
let initialX = x / 3
let initialY = y / 4
let thick = 20
let margin = 5
let rotation = (90 - detector.angle) * Math.PI / 180
let i = 0
ctx.save();
let canvas = document.getElementById("plano");
let ctx2 = canvas.getContext("2d");
ctx2.save();
console.log("blade draw")
//This loop is messing up at the 5th iteration
for (; i < detector.blades.length; i++) {
ctx2.strokeStyle = "#000000";
ctx2.translate(initialX, initialY);
ctx2.rotate(rotation);
ctx2.strokeRect(0, 0, thick, y / 2);
ctx2.restore()
// this is the only variable in that changes of
// value in the loop
initialX = margin + thick + initialX
}
ctx2.save()
}
}
}
render() {
return (
<div className='sketch'>
<canvas width="400" height="150" id="plano">
Canvas not compatible with your browser
</canvas>
</div>
)
}
};
You're restoring your context in each iteration but you don't save it.
Try to add a ctx2.save() and it will work.
for (; i < detector.blades.length; i++) {
ctx2.save(); // save the context
ctx2.strokeStyle = "#000000";
ctx2.translate(initialX, initialY);
ctx2.rotate(rotation);
ctx2.strokeRect(0, 0, thick, y / 2);
ctx2.restore()
// this is the only variable in that changes of
// value in the loop
initialX = margin + thick + initialX
}

Check if image A exists in image B

I need to check if an image exists in another image using JavaScript, I need to know what are the best approaches (algorithm) and solutions (ex: librarie) to do this operations
I explained what I need to do in this image:
Using the GPU to help in image processing.
Using the 2D API and some simple tricks you can exploit the GPUs power to speed up Javascript.
Difference
To find an image you need to compare the pixels you are looking for (A) against the pixels in the image (B). If the difference between the Math.abs(A-B) === 0 then the pixels are the same.
A function to do this may look like the following
function findDif(imageDataSource, imageDataDest, xx,yy)
const ds = imageDataSource.data;
const dd = imageDataDest.data;
const w = imageDataSource.width;
const h = imageDataSource.height;
var x,y;
var dif = 0;
for(y = 0; y < h; y += 1){
for(x = 0; x < w; x += 1){
var indexS = (x + y * w) * 4;
var indexD = (x + xx + (y + yy) * imageDataDest.width) * 4;
dif += Math.abs(ds[indexS]-dd[indexD]);
dif += Math.abs(ds[indexS + 1]-dd[indexD + 1]);
dif += Math.abs(ds[indexS + 2]-dd[indexD + 2]);
}
}
return dif;
}
var source = sourceCanvas.getContext("2d").getImageData(0,0,sourceCanvas.width,sourceCanvas.height);
var dest = destinationCanvas.getContext("2d").getImageData(0,0,destinationCanvas.width,destinationCanvas.height);
if(findDif(source,dest,100,100)){ // is the image at 100,100?
// Yes image is very similar
}
Where the source is the image we are looking for and the dest is the image we want to find it in. We run the function for every location that the image may be and if the result is under a level then its a good chance we have found it.
But this is very very slow in JS. This is where the GPU can help.
Using the ctx.globalCompositeOperation = "difference"; operation we can speed up the process as it will do the difference calculation for us
When you render with the comp operation "difference" the resulting pixels are the difference between the pixels you are drawing and those that are already on the canvas. Thus if you draw on something that is the same the result is all pixels are black (no difference)
To find a similar image in the image you render the image you are testing for at each location on the canvas that you want to test for. Then you get the sum of all the pixels you just rendered on, if the result is under a threshold that you have set then the image under that area is very similar to the image you are testing for.
But we still need to count all the pixels one by one.
A GPU mean function
The comp op "difference" already does the pixel difference calculation for you, but to get the sum you can use the inbuilt image smoothing.
After you have rendered to find the difference you take that area and render it at a smaller scale with ctx.imageSmoothingEnabled = true the default setting. The GPU will do something similar to an average and can reduce the amount of work JS has to do by several orders of magnitude.
Now instead of 100s or 1000s of pixels you can reduce it down to as little at 4 or 16 depending on the accuracy you need.
An example.
Using these methods you can get a near realtime image in image search with just the basic numerical analysis.
Click to start a test. Results are shown plus the time it took. The image that is being searched for is in the top right.
//------------------------------------------------------------------------
// Some helper functions
var imageTools = (function () {
var tools = {
canvas(width, height) { // create a blank image (canvas)
var c = document.createElement("canvas");
c.width = width;
c.height = height;
return c;
},
createImage : function (width, height) {
var i = this.canvas(width, height);
i.ctx = i.getContext("2d");
return i;
},
image2Canvas(img) {
var i = this.canvas(img.width, img.height);
i.ctx = i.getContext("2d");
i.ctx.drawImage(img, 0, 0);
return i;
},
copyImage(img){ // just a named stub
return this.image2Canvas(img);
},
};
return tools;
})();
const U = undefined;
const doFor = (count, callback) => {var i = 0; while (i < count && callback(i ++) !== true ); };
const setOf = (count, callback) => {var a = [],i = 0; while (i < count) { a.push(callback(i ++)) } return a };
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
const randA = (array) => array[(Math.random() * array.length) | 0];
const randG = (min, max = min + (min = 0)) => Math.random() * Math.random() * Math.random() * Math.random() * (max - min) + min;
// end of helper functions
//------------------------------------------------------------------------
function doit(){
document.body.innerHTML = ""; // clear the page;
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
// a grid of 36 images
canvas.width = 6 * 64;
canvas.height = 6 * 64;
console.log("test");
// get a random character to look for
const digit = String.fromCharCode("A".charCodeAt(0) + randI(26));
// get some characters we dont want
const randomDigits = setOf(6,i=>{
return String.fromCharCode("A".charCodeAt(0) + randI(26));
})
randomDigits.push(digit); // add the image we are looking for
var w = canvas.width;
var h = canvas.height;
// create a canvas for the image we are looking for
const imageToFind = imageTools.createImage(64,64);
// and a larger one to cover pixels on the sides
const imageToFindExtend = imageTools.createImage(128,128);
// Draw the character onto the image with a white background and scaled to fit
imageToFindExtend.ctx.fillStyle = imageToFind.ctx.fillStyle = "White";
imageToFind.ctx.fillRect(0,0,64,64);
imageToFindExtend.ctx.fillRect(0,0,128,128);
ctx.font = imageToFind.ctx.font = "64px arial black";
ctx.textAlign = imageToFind.ctx.textAlign = "center";
ctx.textBaseline = imageToFind.ctx.textBaseline = "middle";
const digWidth = imageToFind.ctx.measureText(digit).width+8;
const scale = Math.min(1,64/digWidth);
imageToFind.ctx.fillStyle = "black";
imageToFind.ctx.setTransform(scale,0,0,scale,32,32);
imageToFind.ctx.fillText(digit,0,0);
imageToFind.ctx.setTransform(1,0,0,1,0,0);
imageToFindExtend.ctx.drawImage(imageToFind,32,32);
imageToFind.extendedImage = imageToFindExtend;
// Now fill the canvas with images of other characters
ctx.fillStyle = "white";
ctx.setTransform(1,0,0,1,0,0);
ctx.fillRect(0,0,w,h);
ctx.fillStyle = "black";
ctx.strokeStyle = "white";
ctx.lineJoin = "round";
ctx.lineWidth = 12;
// some characters will be rotated 90,180,-90 deg
const dirs = [
[1,0,0,1,0,0],
[0,1,-1,0,1,0],
[-1,0,0,-1,1,1],
[0,-1,1,0,0,1],
]
// draw random characters at random directions
doFor(h / 64, y => {
doFor(w / 64, x => {
const dir = randA(dirs)
ctx.setTransform(dir[0] * scale,dir[1] * scale,dir[2] * scale,dir[3] * scale,x * 64 + 32, y * 64 + 32);
const d = randA(randomDigits);
ctx.strokeText(d,0,0);
ctx.fillText(d,0,0);
});
});
ctx.setTransform(1,0,0,1,0,0);
// get a copy of the canvas
const saveCan = imageTools.copyImage(ctx.canvas);
// function that finds the images
// image is the image to find
// dir is the matrix direction to find
// smapleSize is the mean sampling size samller numbers are quicker
function checkFor(image,dir,sampleSize){
const can = imageTools.copyImage(saveCan);
const c = can.ctx;
const stepx = 64;
const stepy = 64;
// the image that will contain the reduced means of the differences
const results = imageTools.createImage(Math.ceil(w / stepx) * sampleSize,Math.ceil(h / stepy) * sampleSize);
const e = image.extendedImage;
// for each potencial image location
// set a clip area and draw the source image on it with
// comp mode "difference";
for(var y = 0 ; y < h; y += stepy ){
for(var x = 0 ; x < w; x += stepx ){
c.save();
c.beginPath();
c.rect(x,y,stepx,stepy);
c.clip();
c.globalCompositeOperation = "difference";
c.setTransform(dir[0],dir[1],dir[2],dir[3],x +32 ,y +32 );
c.drawImage(e,-64,-64);
c.restore();
}
}
// Apply the mean (reducing nnumber of pixels to check
results.ctx.drawImage(can,0,0,results.width,results.height);
// get the pixel data
var dat = new Uint32Array(results.ctx.getImageData(0,0,results.width,results.height).data.buffer);
// for each area get the sum of the difference
for(var y = 0; y < results.height; y += sampleSize){
for(var x = 0; x < results.width; x += sampleSize){
var val = 0;
for(var yy = 0; yy < sampleSize && y+yy < results.height; yy += 1){
var i = x + (y+yy)*results.width;
for(var xx = 0; xx < sampleSize && x + xx < results.width ; xx += 1){
val += dat[i++] & 0xFF;
}
}
// if the sum is under the threshold we have found an image
// and we mark it
if(val < sampleSize * sampleSize * 5){
ctx.strokeStyle = "red";
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.lineWidth = 2;
ctx.strokeRect(x * (64/sampleSize),y * (64/sampleSize),64,64);
ctx.fillRect(x * (64/sampleSize),y * (64/sampleSize),64,64);
foundCount += 1;
}
}
}
}
var foundCount = 0;
// find the images at different orientations
var now = performance.now();
checkFor(imageToFind,dirs[0],4);
checkFor(imageToFind,dirs[1],6); // rotated images need larger sample size
checkFor(imageToFind,dirs[2],6);
checkFor(imageToFind,dirs[3],6);
var time = performance.now() - now;
var result = document.createElement("div");
result.textContent = "Found "+foundCount +" matching images in "+time.toFixed(3)+"ms. Click to try again.";
document.body.appendChild(result);
// show the image we are looking for
imageToFind.style.left = (64*6 + 16) + "px";
imageToFind.id = "lookingFor";
document.body.appendChild(imageToFind);
}
document.addEventListener("click",doit);
canvas {
border : 2px solid black;
position : absolute;
top : 28px;
left : 2px;
}
#lookingFor {
border : 4px solid red;
}
div {
border : 2px solid black;
position : absolute;
top : 2px;
left : 2px;
}
Click to start test.
Not perfect
The example is not perfect and will sometimes make mistakes. There is a huge amount of room for improving both the accuracy and the speed. This is just something I threw together as an example to show how to use the GPU via the 2D API. Some further maths will be needed to find the statistically good results.
This method can also work for different scales, and rotations, you can even use some of the other comp modes to remove colour and normalize contrast. I have used a very similar approch to stabilize webcam by tracking points from one frame to the next, and a veriaty of other image tracking uses.

Getting pixel data on setInterval with canvas

I want to build an animated alphabet, made up of particles. Basically, the particles transform from one letter shape to another.
My idea is to fill the letters as text on canvas real quickly (like for a frame), get the pixel data and put the particles to the correct location on setInterval. I have this code for scanning the screen right now:
var ctx = canvas.getContext('2d'),
width = ctx.canvas.width,
height = ctx.canvas.height,
particles = [],
gridX = 8,
gridY = 8;
function Particle(x, y) {
this.x = x;
this.y = y;
}
// fill some text
ctx.font = 'bold 80px sans-serif';
ctx.fillStyle = '#ff0';
ctx.fillText("STACKOVERFLOW", 5, 120);
// now parse bitmap based on grid
var idata = ctx.getImageData(0, 0, width, height);
// use a 32-bit buffer as we are only checking if a pixel is set or not
var buffer32 = new Uint32Array(idata.data.buffer);
// using two loops here, single loop with index-to-x/y is also an option
for(var y = 0; y < height; y += gridY) {
for(var x = 0; x < width; x += gridX) {
//buffer32[] will have a value > 0 (true) if set, if not 0=false
if (buffer32[y * width + x]) {
particles.push(new Particle(x, y));
}
}
}
// render particles
ctx.clearRect(0, 0, width, height);
particles.forEach(function(p) {
ctx.fillRect(p.x - 2, p.y - 2, 4, 4); // just squares here
})
But this way I am only showing one word, without any changes throughout the time. Also, I want to set up initially like 200 particles and reorganise them based on the pixel data, not create them on each scan.. How would you rewrite the code, so on every 1500ms I can pass a different letter and render it with particles?
Hopefully the different parts of this code should be clear enough : There are particles, that can draw and update, fillParticle will spawn particles out of a text string, and spawnChars will get a new part of the text rendered on a regular basis.
It is working quite well, play with the parameters if you wish, they are all at the start of the fiddle.
You might want to make this code cleaner, by avoiding globals and creating classes.
http://jsbin.com/jecarupiri/1/edit?js,output
// --------------------
// parameters
var text = 'STACKOVERFLOW';
var fontHeight = 80;
var gridX = 4,
gridY = 4;
var partSize = 2;
var charDelay = 400; // time between two chars, in ms
var spead = 80; // max distance from start point to final point
var partSpeed = 0.012;
// --------------------
var canvas = document.getElementById('cv'),
ctx = canvas.getContext('2d'),
width = ctx.canvas.width,
height = ctx.canvas.height,
particles = [];
ctx.translate(0.5,0.5);
// --------------------
// Particle class
function Particle(startX, startY, finalX, finalY) {
this.speed = partSpeed*(1+Math.random()*0.5);
this.x = startX;
this.y = startY;
this.startX = startX;
this.startY = startY;
this.finalX =finalX;
this.finalY =finalY;
this.parameter = 0;
this.draw = function() {
ctx.fillRect(this.x - partSize*0.5, this.y - partSize*0.5, partSize, partSize);
};
this.update = function(p) {
if (this.parameter>=1) return;
this.parameter += partSpeed;
if (this.parameter>=1) this.parameter=1;
var par = this.parameter;
this.x = par*this.finalX + (1-par)*this.startX;
this.y = par*this.finalY + (1-par)*this.startY;
};
}
// --------------------
// Text spawner
function fillParticle(text, offx, offy, spread) {
// fill some text
tmpCtx.clearRect(0,0,tmpCtx.canvas.width, tmpCtx.canvas.height);
tmpCtx.font = 'bold ' + fontHeight +'px sans-serif';
tmpCtx.fillStyle = '#A40';
tmpCtx.textBaseline ='top';
tmpCtx.textAlign='left';
tmpCtx.fillText(text, 0, 0);
//
var txtWidth = Math.floor(tmpCtx.measureText(text).width);
// now parse bitmap based on grid
var idata = tmpCtx.getImageData(0, 0, txtWidth, fontHeight);
// use a 32-bit buffer as we are only checking if a pixel is set or not
var buffer32 = new Uint32Array(idata.data.buffer);
// using two loops here, single loop with index-to-x/y is also an option
for(var y = 0; y < fontHeight; y += gridY) {
for(var x = 0; x < txtWidth; x += gridX) {
//buffer32[] will have a value > 0 (true) if set, if not 0=false
if (buffer32[y * txtWidth + x]) {
particles.push(new Particle(offx + x+Math.random()*spread - 0.5*spread,
offy + y+Math.random()*spread - 0.5*spread, offx+x, offy+y));
}
}
}
return txtWidth;
}
var tmpCv = document.createElement('canvas');
// uncomment for debug
//document.body.appendChild(tmpCv);
var tmpCtx = tmpCv.getContext('2d');
// --------------------------------
// spawn the chars of the text one by one
var charIndex = 0;
var lastSpawnDate = -1;
var offX = 30;
var offY = 30;
function spawnChars() {
if (charIndex>= text.length) return;
if (Date.now()-lastSpawnDate < charDelay) return;
offX += fillParticle(text[charIndex], offX, offY, spead);
lastSpawnDate = Date.now();
charIndex++;
}
// --------------------------------
function render() {
// render particles
particles.forEach(function(p) { p.draw();
});
}
function update() {
particles.forEach(function(p) { p.update(); } );
}
// --------------------------------
// animation
function animate(){
requestAnimationFrame(animate);
ctx.clearRect(0, 0, width, height);
render();
update();
//
spawnChars();
}
// launch :
animate();

How to move canvas speedometer needle slowly?

I use following codes in order to move a picture in canvas for my speedometer.
var meter = new Image,
needle = new Image;
window.onload = function () {
var c = document.getElementsByTagName('canvas')[0];
var ctx = c.getContext('2d');
setInterval(function () {
ctx.save();
ctx.clearRect(0, 0, c.width, c.height);
ctx.translate(c.width / 2, c.height / 2);
ctx.drawImage(meter, -165, -160);
ctx.rotate((x * Math.PI / 180);
/ x degree
ctx.drawImage( needle, -13, -121.5 );
ctx.restore();
},50);
};
meter.src = 'meter.png';
needle.src = 'needle.png';
}
However I want to move the needle slowly to the degree which I entered such as speedtest webpages. Any idea?
Thanks.
Something like this should work:
var meter = new Image,
needle = new Image;
window.onload = function () {
var c = document.getElementsByTagName('canvas')[0],
ctx = c.getContext('2d'),
x, // Current angle
xTarget, // Target angle.
step = 1; // Angle change step size.
setInterval(function () {
if(Math.abs(xTarget - x) < step){
x = xTarget; // If x is closer to xTarget than the step size, set x to xTarget.
}else{
x += (xTarget > x) ? step : // Increment x to approach the target.
(xTarget < x) ? -step : // (Or substract 1)
0;
}
ctx.save();
ctx.clearRect(0, 0, c.width, c.height);
ctx.translate(c.width / 2, c.height / 2);
ctx.drawImage(meter, -165, -160);
ctx.rotate((x * Math.PI / 180); // x degree
ctx.drawImage( needle, -13, -121.5 );
ctx.restore();
},50);
};
dial.src = 'meter.png';
needle.src = 'needle.png';
}
I'm using a shorthand if / else here to determine whether to add 1 to x, substract 1, or do nothing. Functionally, this is the same as:
if(xTarget > x){
x += step;
}else if(xTarget < x){
x += -step;
}else{
x += 0;
}
But it's shorter, and in my opinion, just as easy to read, once you know what a shorthand if (ternary operator) looks like.
Please keep in mind that this code assumes x is a integer value (So, not a float, just a rounded int).