Related
<canvas id="canvas" width="300" height="300" style="background:green">
<img id="shape" onload="run()" src="shape.png">
<script>
function run()
{
const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");
const im = document.getElementById('shape');
context.drawImage(im, 0, 0);
}
</script>
how to get feathered edges around the white part of the image for my custom shape.
The green color in the image is transparent.
I tried with shadowblur but that is not what I want
I want to blur edges as if it is fading into background.
I want to do this using html5 canvas or using fabricjs.
Note : The shape is not circle or oval. It is a custom hand drawn shape.
You can draw your shape with a CSS blur() filter and then use that as a mask.
If you need to support Safari, which still doesn't support ctx.filter (and maybe will never...) you can actually do the same with shadows, the trick is to render the mask outside of the visible viewport and add a huge offset on the shadow.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const mask = new Image();
// using #obscure's image
mask.src = "https://i.stack.imgur.com/tyKHb.png";
const img = new Image();
img.src = "https://picsum.photos/200/200";
Promise.all([
mask.decode(),
img.decode()
]).then(draw);
function draw() {
canvas.width = img.width;
canvas.height = img.height;
if (ctx.filter === "none") {
ctx.filter = "blur(5px)";
ctx.drawImage(mask, 0, 0);
ctx.filter = "none";
}
else { // thanks Safari
ctx.shadowOffsetX = 200;
ctx.shadowBlur = 5;
ctx.shadowColor = "black";
ctx.drawImage(mask, -200, 0);
}
ctx.globalCompositeOperation = "source-in";
ctx.drawImage(img, 0, 0);
}
<canvas></canvas>
There are quite a few steps involved to accomplish this task.
Say we have a hand-drawn shape like this:
First we need to get the outline of the actual shape. As it's a solid single coloured shape we can simply look for non-transparent pixels, or pixels that are above a certain threshold. Based on this data we need to get the contours that form the shape.
Luckily we don't need to re-invent the wheel as this is just what the d3 contours module does and we end up with something like this:
In the next step we need to determine the center point of the shape. This is done by looking through the points data returned in the last step and find the min/max horizontal and vertical values.
let rect = {
x1: Number.MAX_VALUE,
y1: Number.MAX_VALUE,
x2: Number.MIN_VALUE,
y2: Number.MIN_VALUE
};
for (let a = 0; a < points.length; a++) {
rect.x1 = points[a][0] < rect.x1 ? points[a][0] : rect.x1;
rect.y1 = points[a][1] < rect.y1 ? points[a][1] : rect.y1;
rect.x2 = points[a][0] > rect.x2 ? points[a][0] : rect.x2;
rect.y2 = points[a][1] > rect.y2 ? points[a][1] : rect.y2;
}
let center = {
x: (rect.x1 + rect.x2) / 2,
y: (rect.y1 + rect.y2) / 2
};
Finally we fill the inside of the shape and redraw the outlines, say 20 times. With each iteration we enlarge the shape by 1 pixel and reduce the alpha value of the stroke until hitting zero, which will give us:
Now it's just a matter of combining the mask with an image by setting the globalCompositeOperation to 'source-atop'.
Here's the complete example:
let image = new Image();
let mask = new Image();
let canvas = document.getElementById('canvas');
let context = canvas.getContext('2d');
let imagesLoaded = 0;
context.fillStyle = 'rgb(255,0,0)';
image.crossOrigin = 'anonymous';
mask.crossOrigin = 'anonymous';
image.onload = loaded;
mask.onload = loaded;
image.src = 'https://api.codetabs.com/v1/proxy?quest=https://picsum.photos/id/237/200/200';
mask.src = 'https://api.codetabs.com/v1/proxy?quest=https://i.stack.imgur.com/tyKHb.png';
function loaded() {
imagesLoaded++;
if (imagesLoaded == 2) {
process();
}
}
function process() {
context.drawImage(mask, 0, 0, image.width, image.height);
let imageData = context.getImageData(0, 0, canvas.width, canvas.height).data;
let points = d3.geom.contour((x, y) => {
let a = imageData[(y * canvas.width + x) * 4 + 3];
return (a > 0);
});
context.clearRect(0, 0, canvas.width, canvas.height);
let rect = {
x1: Number.MAX_VALUE,
y1: Number.MAX_VALUE,
x2: Number.MIN_VALUE,
y2: Number.MIN_VALUE
};
for (let a = 0; a < points.length; a++) {
rect.x1 = points[a][0] < rect.x1 ? points[a][0] : rect.x1;
rect.y1 = points[a][1] < rect.y1 ? points[a][1] : rect.y1;
rect.x2 = points[a][0] > rect.x2 ? points[a][0] : rect.x2;
rect.y2 = points[a][1] > rect.y2 ? points[a][1] : rect.y2;
}
let center = {
x: (rect.x1 + rect.x2) / 2,
y: (rect.y1 + rect.y2) / 2
};
let point = {};
for (let a = 0; a < 20; a++) {
context.strokeStyle = 'rgba(255,0,0,' + (1 - a / 20) + ')';
point.x = points[0][0];
point.y = points[0][1];
point.x = point.x < center.x ? point.x -= a : point.x += a;
point.y = point.y < center.y ? point.y -= a : point.y += a;
context.moveTo(point.x, point.y);
for (let b = 1; b < points.length; b++) {
point.x = points[b][0];
point.y = points[b][1];
point.x = point.x < center.x ? point.x -= a : point.x += a;
point.y = point.y < center.y ? point.y -= a : point.y += a;
context.lineTo(point.x, point.y);
}
context.closePath();
if (a == 0) {
context.fill();
}
context.stroke();
}
context.globalCompositeOperation = 'source-atop';
context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
<script type="text/javascript">
let d3 = {
geom: {}
};
</script>
<script src="https://api.codetabs.com/v1/proxy?quest=https://raw.githubusercontent.com/d3/d3-plugins/master/geom/contour/contour.js"></script>
<canvas id="canvas" width="200" height="200"></canvas>
I have a massive image of a map that is much larger than the viewport and centered in the viewport, which can be explored by the user by dragging the screen. In order to create a parallax effect, I used a massive image of clouds in the foreground. As the user explores the map via dragging, both the background and foreground move in a parallax fashion. So far, so good.
However, what I really want to do is give the image of clouds a "default" movement that would be randomly generated on each page load, so that the clouds would always be moving, even if the user is not dragging. I know this can be done by animating the foreground along a path, but I am not exactly sure how to go about this.
How can I randomly generate irregularly curved or wavy paths on each page load?
Does anybody know of any algorithms that can do this?
I also use a copy of the previous answers to realize a simplified version of what I hinted at in the comments.
Use a random walk on the unit circle, that is on the angle, to determine a velocity vector that slowly but randomly changes and move forward using cubic Bezier patches.
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 600;
var ch = c.height = 400;
var cx = cw / 4, cy = ch / 2;
var angVel = v.value;
var tension = t.value;
ctx.lineWidth = 4;
var npts = 60;
var dw = Array();
var xs = Array();
var ys = Array();
var vxs = Array();
var vys = Array();
function Randomize() {
for (var i = 0; i < npts; i++) {
dw[i] = (2*Math.random()-1);
}
}
function ComputePath() {
xs[0]=cx; ys[0]=cy;
var angle = 0;
for (var i = 0; i < npts; i++) {
vxs[i]=10*Math.cos(2*Math.PI*angle);
vys[i]=10*Math.sin(2*Math.PI*angle);
angle = angle + dw[i]*angVel;
}
for (var i = 1; i < npts; i++) {
xs[i] = xs[i-1]+3*(vxs[i-1]+vxs[i])/2;
ys[i] = ys[i-1]+3*(vys[i-1]+vys[i])/2;
}
}
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
ctx.moveTo(xs[0],ys[0]);
for (var i = 1; i < npts; i++) {
var cp1x = xs[i-1]+tension*vxs[i-1];
var cp1y = ys[i-1]+tension*vys[i-1];
var cp2x = xs[i]-tension*vxs[i];
var cp2y = ys[i]-tension*vys[i]
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, xs[i], ys[i]);
}
ctx.stroke();
}
Randomize();
ComputePath();
Draw();
r.addEventListener("click",()=>{
Randomize();
ComputePath();
Draw();
})
v.addEventListener("input",()=>{
angVel = v.value;
vlabel.innerHTML = ""+angVel;
ComputePath();
Draw();
})
t.addEventListener("input",()=>{
tension = t.value;
tlabel.innerHTML = ""+tension;
Draw();
})
canvas{border:1px solid}
<canvas id = 'c'></canvas>
<table>
<tr><td>angular velocity:</td><td> <input type="range" id="v" min ="0" max = "0.5" step = "0.01" value="0.2" /></td><td id="vlabel"></td></tr>
<tr><td>tension</td><td> <input type="range" id="t" min ="0" max = "1" step = "0.1" value="0.8" /></td><td id="tlabel"></td></tr>
<tr><td>remix</td><td> <button id="r"> + </button></td><td></td></tr>
</table>
If your question is: How can I randomly generate curved or wavy paths? this is how I would do it: I'm using inputs type range to change the value for amplitude and frequency, but you can set those values randomly on load.
I hope it helps.
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 800;
var ch = c.height = 150;
var cx = cw / 2,
cy = ch / 2;
var amplitude = a.value;
var frequency = f.value;
ctx.lineWidth = 4;
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
for (var x = 0; x < cw; x++) {
y = Math.sin(x * frequency) * amplitude;
ctx.lineTo(x, y+cy);
}
ctx.stroke();
}
Draw();
a.addEventListener("input",()=>{
amplitude = a.value;
Draw();
})
f.addEventListener("input",()=>{
frequency = f.value;
Draw();
})
canvas{border:1px solid}
<canvas id = 'c'></canvas>
<p>frequency: <input type="range" id="f" min ="0.01" max = "0.1" step = "0.001" value=".05" /></p>
<p>amplitude: <input type="range" id="a" min ="1" max = "100" value="50" /></p>
I was impressed by the functionality to be able to draw canvases in the SO answers, so I "stole" enxaneta code snippet and played a bit with it (hope that is ok).
The idea is to generate several random points (xs, ys) and for each x from the path to interpolate the y as y = sum{ys_i*w_i}/sum{w_i}, where w_i is some interpolation weight as a function of x. For example w_i(x) = (xs_i - x)^(-2). Hope this makes sense - if this is of any interested I'll try to provide more details.
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var cw = c.width = 600;
var ch = c.height = 150;
var cx = cw / 2,
cy = ch / 2;
var amplitude = a.value;
var frequency = f.value;
ctx.lineWidth = 4;
var npts = 20;
var xs = Array();
var ys = Array();
for (var i = 0; i < npts; i++) {
xs[i] = (cw/npts)*i;
ys[i] = 2.0*(Math.random()-0.5)*amplitude;
}
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
for (var x = 0; x < cw; x++) {
y = 0.0;
wsum = 0.0;
for (var i = -5; i <= 5; i++) {
xx = x;
ii = Math.round(x/xs[1]) + i;
if (ii < 0) { xx += cw; ii += npts; }
if (ii >= npts) { xx -= cw; ii -= npts; }
w = Math.abs(xs[ii] - xx);
w = Math.pow(w, frequency);
y += w*ys[ii];
wsum += w;
}
y /= wsum;
//y = Math.sin(x * frequency) * amplitude;
ctx.lineTo(x, y+cy);
}
ctx.stroke();
}
Draw();
a.addEventListener("input",()=>{
amplitude = a.value;
for (var i = 0; i < npts; i++) {
xs[i] = (cw/npts)*i;
ys[i] = 2.0*(Math.random()-0.5)*amplitude;
}
Draw();
})
f.addEventListener("input",()=>{
frequency = f.value;
Draw();
})
canvas{border:1px solid}
<canvas id = 'c'></canvas>
<p>amplitude: <input type="range" id="a" min ="1" max = "100" value="50" /></p>
<p>frequency: <input type="range" id="f" min ="-10" max = "1" step = "0.1" value="-2" hidden/></p>
Deterministic random paths
Storing paths for random movements is not needed. Also random is another way of being very complex, and for humans it does not take much complexity to look randoms.
Thus with a little randomness to add to complexity you can make the appearance of the infinite non repeating sequence that and be rewound, stopped, slowed down speed up, and be fully deterministic and requiring only a single value to store.
Complex cycles.
To move a point in a circle around a center you can use sin and cos.
For example a point x,y and you want to move in a ball around that point at a distance of dist and a rate once a second. Example in snippet.
var px = 100; // point of rotation.
var py = 100;
const RPS = 1; // Rotations Per Second
const dist = 50; // distance from point
const radius = 25; // circle radius
function moveObj(time) { // Find rotated point and draw
time = (time / 1000) * PI2 * RPS; // convert the time to rotations per secon
const xx = Math.cos(time) * dist;
const yy = Math.sin(time) * dist;
drawCircle(xx, yy)
}
// Helpers
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function drawCircle(x,y,r = radius) {
ctx.setTransform(1,0,0,1,px,py);
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.arc(x,y,r,0,PI2);
ctx.fill();
}
function mainLoop(time) {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
moveObj(time);
requestAnimationFrame(mainLoop);
}
const PI = Math.PI;
const PI2 = PI * 2;
canvas {
background : #8AF;
border : 1px solid black;
}
<canvas id="canvas" width="200" height="200"></canvas>
Next let's move the point around which we rotate, using the method above.
Then for the ball we can change the phase of the rotation in x from the rotation in y. This means that the ball rotating around the now rotating point, and the balls rotating axis are out of phase.
The result is a more complex movements.
var px = 100; // point of rotation.
var py = 100;
const RPS_P = 0.1; // point Rotations Per Second 0.1 every 10 seconds
const RPS_X = 1; // Rotations Per Second in x axis of circle
const RPS_Y = 0.8; // Rotations Per Second in y axis of circle
const dist_P = 30; // distance from center point is
const dist = 50; // distance from point
const radius = 25; // circle radius
function moveObj(time) { // Find rotated point and draw
var phaseX = (time / 1000) * PI2 * RPS_X;
var phaseY = (time / 1000) * PI2 * RPS_Y;
const xx = Math.cos(phaseX) * dist;
const yy = Math.sin(phaseY) * dist;
drawCircle(xx, yy)
}
function movePoint(time) { // move point around center
time = (time / 1000) * PI2 * RPS_P;
px = 100 + Math.cos(time) * dist_P;
py = 100 + Math.sin(time) * dist_P;
}
// Helpers
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function drawCircle(x,y,r = radius) {
ctx.setTransform(1,0,0,1,px,py);
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.arc(x,y,r,0,PI2);
ctx.fill();
}
function mainLoop(time) {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
movePoint(time);
moveObj(time);
requestAnimationFrame(mainLoop);
}
const PI = Math.PI;
const PI2 = PI * 2;
canvas {
background : #8AF;
border : 1px solid black;
}
<canvas id="canvas" width="200" height="200"></canvas>
We can continue to add out of phase rotations. In the next example we now rotate the rotation point around the center, add out of phase rotation to that point and finally draw the ball with its out of phase rotation.
var px = 100; // point of rotation.
var py = 100;
const RPS_C_X = 0.43; // Rotation speed X of rotating rotation point
const RPS_C_Y = 0.47; // Rotation speed X of rotating rotation point
const RPS_P_X = 0.093; // point Rotations speed X
const RPS_P_Y = 0.097; // point Rotations speed Y
const RPS_X = 1; // Rotations Per Second in x axis of circle
const RPS_Y = 0.8; // Rotations Per Second in y axis of circle
const dist_C = 20; // distance from center point is
const dist_P = 30; // distance from center point is
const dist = 30; // distance from point
const radius = 25; // circle radius
function moveObj(time) { // Find rotated point and draw
var phaseX = (time / 1000) * PI2 * RPS_X;
var phaseY = (time / 1000) * PI2 * RPS_Y;
const xx = Math.cos(phaseX) * dist;
const yy = Math.sin(phaseY) * dist;
drawCircle(xx, yy)
}
function movePoints(time) { // Move the rotating pointe and rotate the rotation point
// around that point
var phaseX = (time / 1000) * PI2 * RPS_C_X;
var phaseY = (time / 1000) * PI2 * RPS_C_Y;
px = 100 + Math.cos(phaseX) * dist_C;
py = 100 + Math.sin(phaseY) * dist_C;
phaseX = (time / 1000) * PI2 * RPS_P_X;
phaseY = (time / 1000) * PI2 * RPS_P_Y;
px = px + Math.cos(phaseX) * dist_P;
py = py + Math.sin(phaseY) * dist_P;
}
// Helpers
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
function drawCircle(x,y,r = radius) {
ctx.setTransform(1,0,0,1,px,py);
ctx.fillStyle = "#fff";
ctx.beginPath();
ctx.arc(x,y,r,0,PI2);
ctx.fill();
}
function mainLoop(time) {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
movePoints(time);
moveObj(time);
requestAnimationFrame(mainLoop);
}
const PI = Math.PI;
const PI2 = PI * 2;
canvas {
background : #8AF;
border : 1px solid black;
}
<canvas id="canvas" width="200" height="200"></canvas>
So now we have a very complex rotation. However as it is set to the time, you can repeat the movement by just setting the time back to the start. You don't need to store a long complex path.
Add a little random
You may see some repeating movement but if you make the phases of each axis a prime then the repeat time is the product of all the primes.
If you want many objects each with a different movement you can randomise the rotation rates and many more properties.
Javascript does not have a seeded random generator. However you can create one. With a seeded random generator you can us the seed to generate a random object. But if you use that seed again you get the same object. In the example below I us a seed from 0 to 10000000 to create a cloud. That means there are 10000000 unique clouds, but all repeatable.
Example of deterministic random clouds
Restart and it will repeat exactly the same. To change it to non deterministic random just add randSeed(Math.random() * 100000 | 0)
const seededRandom = (() => {
var seed = 1;
return { max : 2576436549074795, reseed (s) { seed = s }, random () { return seed = ((8765432352450986 * seed) + 8507698654323524) % this.max }}
})();
const randSeed = (seed) => seededRandom.reseed(seed|0);
const randSI = (min = 2, max = min + (min = 0)) => (seededRandom.random() % (max - min)) + min;
const randS = (min = 1, max = min + (min = 0)) => (seededRandom.random() / seededRandom.max) * (max - min) + min;
const randSPow = (min, max = min + (min = 0), p = 2) => (max + min) / 2 + (Math.pow(seededRandom.random() / seededRandom.max, p) * (max - min) * 0.5) * (randSI(2) < 1 ? 1 : -1);
const ctx = canvas.getContext("2d");
const W = ctx.canvas.width;
const H = ctx.canvas.height;
const DIAG = (W * W + H * H) ** 0.5;
const colors = {
dark : {
minRGB : [100 * 0.6,200 * 0.6,240 * 0.6],
maxRGB : [255 * 0.6,255 * 0.6,255 * 0.6],
},
light : {
minRGB : [100,200,240],
maxRGB : [255,255,255],
},
}
const getCol = (pos, range) => "rgba(" +
((range.maxRGB[0] - range.minRGB[0]) * pos + range.minRGB[0] | 0) + "," +
((range.maxRGB[1] - range.minRGB[1]) * pos + range.minRGB[1] | 0) + "," +
((range.maxRGB[2] - range.minRGB[2]) * pos + range.minRGB[2] | 0) + "," +(pos * 0.2 + 0.8) + ")";
const Cloud = {
x : 0,
y : 0,
dir : 0, // in radians
wobble : 0,
wobble1 : 0,
wSpeed : 0,
wSpeed1 : 0,
mx : 0, // Move offsets
my : 0,
seed : 0,
size : 2,
detail : null,
reset : true, // when true could resets
init() {
this.seed = randSI(10000000);
this.reset = false;
var x,y,r,dir,dist,f;
if (this.detail === null) { this.detail = [] }
else { this.detail.length = 0 }
randSeed(this.seed);
this.size = randSPow(2, 8); // The pow add bias to smaller values
var col = (this.size -2) / 6;
this.col1 = getCol(col,colors.dark)
this.col2 = getCol(col,colors.light)
var flufCount = randSI(5,15);
while (flufCount--) {
x = randSI(-this.size * 8, this.size * 8);
r = randS(this.size * 2, this.size * 8);
dir = randS(Math.PI * 2);
dist = randSPow(1) * r ;
this.detail.push(f = {x,r,y : 0,mx:0,my:0, move : randS(0.001,0.01), phase : randS(Math.PI * 2)});
f.x+= Math.cos(dir) * dist;
f.y+= Math.sin(dir) * dist;
}
this.xMax = this.size * 12 + this.size * 10 + this.size * 4;
this.yMax = this.size * 10 + this.size * 4;
this.wobble = randS(Math.PI * 2);
this.wSpeed = randS(0.01,0.02);
this.wSpeed1 = randS(0.01,0.02);
const aOff = randS(1) * Math.PI * 0.5 - Math.PI *0.25;
this.x = W / 2 - Math.cos(this.dir+aOff) * DIAG * 0.7;
this.y = H / 2 - Math.sin(this.dir+aOff) * DIAG * 0.7;
clouds.sortMe = true; // flag that coulds need resort
},
move() {
var dx,dy;
this.dir = gTime / 10000;
if(this.reset) { this.init() }
this.wobble += this.wSpeed;
this.wobble1 += this.wSpeed1;
this.mx = Math.cos(this.wobble) * this.size * 4;
this.my = Math.sin(this.wobble1) * this.size * 4;
this.x += dx = Math.cos(this.dir) * this.size / 5;
this.y += dy = Math.sin(this.dir) * this.size / 5;
if (dx > 0 && this.x > W + this.xMax ) { this.reset = true }
else if (dx < 0 && this.x < - this.xMax ) { this.reset = true }
if (dy > 0 && this.y > H + this.yMax) { this.reset = true }
else if (dy < 0 && this.y < - this.yMax) { this.reset = true }
},
draw(){
const s = this.size;
const s8 = this.size * 8;
ctx.fillStyle = this.col1;
ctx.setTransform(1,0,0,1,this.x+ this.mx,this.y +this.my);
ctx.beginPath();
for (const fluf of this.detail) {
fluf.phase += fluf.move + Math.sin(this.wobble * this.wSpeed1) * 0.02 * Math.cos(fluf.phase);
fluf.mx = Math.cos(fluf.phase) * fluf.r / 2;
fluf.my = Math.sin(fluf.phase) * fluf.r / 2;
const x = fluf.x + fluf.mx;
const y = fluf.y + fluf.my;
ctx.moveTo(x + fluf.r + s, y);
ctx.arc(x,y,fluf.r+ s,0,Math.PI * 2);
}
ctx.fill();
ctx.fillStyle = this.col2;
ctx.globalAlpha = 0.5;
ctx.beginPath();
for (const fluf of this.detail) {
const x = fluf.x + fluf.mx - s;
const y = fluf.y + fluf.my - s;
ctx.moveTo(x + fluf.r, y);
ctx.arc(x,y,fluf.r,0,Math.PI * 2);
}
ctx.fill();
ctx.globalAlpha = 0.6;
ctx.beginPath();
for (const fluf of this.detail) {
const x = fluf.x + fluf.mx - s * 1.4;
const y = fluf.y + fluf.my - s * 1.4;
ctx.moveTo(x + fluf.r * 0.8, y);
ctx.arc(x,y,fluf.r* 0.8,0,Math.PI * 2);
}
ctx.fill();
ctx.globalAlpha = 1;
}
}
function createCloud(size){ return {...Cloud} }
const clouds = Object.assign([],{
move() { for(const cloud of this){ cloud.move() } },
draw() { for(const cloud of this){ cloud.draw() } },
sortMe : true, // if true then needs to resort
resort() {
this.sortMe = false;
this.sort((a,b)=>a.size - b.size);
}
});
for(let i = 0; i < 15; i ++) { clouds.push(createCloud(40)) }
requestAnimationFrame(mainLoop)
var gTime = 0;
function mainLoop() {
gTime += 16;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
if(clouds.sortMe) { clouds.resort() }
clouds.move();
clouds.draw();
requestAnimationFrame(mainLoop);
}
body { padding : 0px; margin : 0px;}
canvas {
background : rgb(60,120,148);
border : 1px solid black;
}
<canvas id="canvas" width="600" height="200"></canvas>
I want to practice pixel manipulation with matrix for extract an image from another.
This is what I have done with css transformation matrix :
https://www.noelshack.com/2017-18-1493893008-capture-2.png
With the Left image 'L' I have place 4 points around the image and in the right image 'R' I find the content of the transformation.
For that i use the property transform of the css but i want to do the manipulation manually.
CSS version :
matrix3d(1.5456325781948308,1.6561987730956724,0,0.0012239101773909712,-0.4663849104791486,2.218793881308064,0,0.0009095626603861196,0,0,1,0,12.247969030166722,-17.754955132517754,0,0.9951722722714726)
Matrix 'M':
[[1.5456325781948308, 1.6561987730956724, 0, 0.0012239101773909712],
[-0.4663849104791486, 2.218793881308064, 0, 0.0009095626603861196],
[0, 0, 1, 0],
[12.247969030166722, -17.754955132517754, 0, 0.9951722722714726]]
I want to know for each pixel in the image R what are their pixel related position in the image L.
For example (0,0) in R is (52,203) in R.
For that i do this calculation.
M * P = P'
P is the pixel position in R image
P' is the pixel position in L image
P matrix is define like that:
[[x],
[y],
[0],
[1]]
So for the 0,0 position, I do this :
[[1.5456325781948308, 1.6561987730956724, 0, 0.0012239101773909712],
[-0.4663849104791486, 2.218793881308064, 0, 0.0009095626603861196],
[0, 0, 1, 0],
[12.247969030166722, -17.754955132517754, 0, 0.9951722722714726]]
X
[[0],
[0],
[0],
[1]]
=
[[0.0012239101773909712],
[0.0009095626603861196],
[0],
[0.9951722722714726]]
This is the result, but the 2 first component :
(0.0012239101773909712, 0.0009095626603861196)
is too smaller than expected. can you help me to find the problem.
scincerly,
MatrixCuriosity.
These are homogeneous coordinates. So given some [x1, y1, z1, 1] as input you obtain some [x2, y2, z2, w2] but the actual position they describe is [x2/w2, y2/w2, z2/w2], i.e. you have to divide by the last coordinate.
But this doesn't lead to the result you expected. Nor does replacing the matrix with its adjunct (or equivalently inverse), nor its transpose. Both of these are conventions that are easy to get wrong, so without spending too much thought about which version you actually have and should have, trying all four alternatives (with and without adjunct, with and without transpose) solves a huge number of trivial problems.
But not yours. So my next best bet would be that the coordinates you expect are measured from some corner of the image, while the CSS property transform-origin is at it's initial value of 50% 50% 0 so the origin of the coordinate system is in fact in the center of the object.
Actually sharing the HTML and CSS for this might have allowed me to verify this assumption. Now you have to check whether this applies to you. I remember that when I last created a projective image transformation demo to answer a question about finding the transform, I deliberately set transform-origin: 0 0; (and the various vendor-prefixed versions of this) to avoid such problems.
Thanks a lot MvG.
I follow your link and I find what I want [https://math.stackexchange.com/a/339033]
Just one thing, I have to invert the C matrix to find the pixel related L<-R
I share my code for give an idea of what you have to do
You can find my implementation in the function computeMat()
<style>
body {
touch-action: none;
overflow-y: hidden;
}
#canvas_toeic
{
position:absolute;
top:0;
left:0;
}
</style>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/mathjs/3.12.2/math.min.js"></script>
</head>
<body>
<canvas id="canvas_toeic" width="600" height="400">
</canvas>
<script type="text/javascript">
var image = new Image();
image.src = 'image.jpg';
image.onload = function() {
var c = document.getElementById("canvas_toeic");
var ratio = image.width / image.height;
var canvasWidth = document.body.clientWidth;
var canvasHeight = canvasWidth / ratio;
if(document.body.clientHeight < canvasHeight)
{
canvasHeight = document.body.clientHeight;
canvasWidth = canvasHeight * ratio;
}
var canvasLargeur = canvasWidth;
var canvasLongueur = canvasHeight;
if(canvasLargeur < canvasHeight) {
canvasLargeur = canvasHeight;
canvasLongueur = canvasWidth;
}
var canvasPixelRatio = canvasLargeur / image.width;
c.setAttribute("width", canvasWidth);
c.setAttribute("height", canvasHeight);
var ctx = c.getContext("2d");
var idPoint = -1;
var points = [];
for(var i = 0; i < 4; i++)
points[i] = {x:0, y:0};
var marginImage = Math.round(40 * canvasPixelRatio);
points[0].x = marginImage;
points[0].y = marginImage;
points[1].x = marginImage;
points[1].y = canvasHeight - marginImage;
points[2].x = canvasWidth - marginImage;
points[2].y = canvasHeight - marginImage;
points[3].x = canvasWidth - marginImage;
points[3].y = marginImage;
function draw(points) {
console.log("draw");
// Fond
ctx.fillStyle = "#222";
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.drawImage(image, marginImage, marginImage, canvasWidth - marginImage * 2, canvasHeight - marginImage * 2); // this fait référence à l'objet courant (=image)
if(idPoint == -1)
ctx.lineWidth = 3 * canvasPixelRatio;
else
ctx.lineWidth = 5 * canvasPixelRatio;
ctx.beginPath(); // Début du chemin
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.strokeStyle = "rgba(64, 128, 255, 0.5)";
ctx.moveTo(points[0].x, points[0].y); // Le tracé part du point 50,50
for(var i = 0; i < 4; i++)
ctx.lineTo(points[i].x, points[i].y); // Un segment est ajouté vers 200,200
ctx.closePath(); // Fermeture du chemin (facultative)
ctx.stroke();
for(var i = 0; i < 4; i++)
{
var radius = 30 * canvasPixelRatio;
if(idPoint == i)
radius = 60 * canvasPixelRatio;
ctx.beginPath();
ctx.arc(points[i].x, points[i].y, radius, 0, Math.PI*2, true);
ctx.strokeStyle = "#FF8800";
ctx.fillStyle = "rgba(255, 128, 0, 0.5)";
ctx.fill();
ctx.stroke();
}
if(idPoint != -1)
{
var zoomWidth = canvasWidth / 3;
var zoomHeight = canvasHeight / 3;
var zoomMargin = 5;
var zoomAroundWidth = 50;
var zoomAroundHeight = zoomAroundWidth / ratio;
var positionMouse = points[idPoint];
var imagePositionX = (positionMouse.x - marginImage) / (canvasWidth - marginImage * 2) * image.width;
var imagePositionY = (positionMouse.y - marginImage) / (canvasHeight - marginImage * 2) * image.height;
var zoomX = 0;
var zoomY = 0;
if(imagePositionX < image.width / 2)
zoomX = canvasWidth - zoomWidth;
if(imagePositionY < image.height / 2)
zoomY = canvasHeight - zoomHeight;
ctx.fillStyle = "#F08";
ctx.fillRect(zoomX, zoomY, zoomWidth, zoomHeight);
ctx.drawImage(image, imagePositionX - zoomAroundWidth, imagePositionY - zoomAroundHeight, zoomAroundWidth * 2, zoomAroundHeight * 2, zoomX + zoomMargin, zoomY + zoomMargin, zoomWidth - zoomMargin * 2, zoomHeight - zoomMargin * 2);
ctx.lineWidth = 3 * canvasPixelRatio;
ctx.beginPath();
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.strokeStyle = "rgba(255, 0, 0, 0.5)";
ctx.moveTo(zoomX, zoomY + zoomHeight / 2);
ctx.lineTo(zoomX + zoomWidth, zoomY + zoomHeight / 2);
ctx.moveTo(zoomX + zoomWidth / 2, zoomY);
ctx.lineTo(zoomX + zoomWidth / 2, zoomY + zoomHeight);
ctx.closePath();
ctx.stroke();
}
}
function nearPoint(points, x, y)
{
var radiusDetection = 60 * canvasPixelRatio;
var distances = [];
for(i = 0; i < 4; i++) {
var mx = x - points[i].x;
var my = y - points[i].y;
distances[i] = Math.sqrt(mx * mx + my * my);
}
minI = 0;
minD = distances[0];
for(i = 1; i < 4; i++)
{
if(minD > distances[i])
{
minD = distances[i];
minI = i;
}
}
if(minD <= radiusDetection)
return minI;
return -1;
}
function getTouchPosition(e)
{
var target = null;
var mouse = null;
if(e.changedTouches != undefined)
{
var touches = e.changedTouches;
mouse = touches[0];
target = touches[0].target;
}
else if(e.originalTarget != undefined)
{
mouse = e;
target = e.originalTarget;
}
var coordX = 0;
var coordY = 0;
if(mouse.layerX != undefined)
{
coordX = mouse.layerX;
coordY = mouse.layerY;
}
else
{
coordX = mouse.pageX;
coordY = mouse.pageY;
}
var x = coordX - target.offsetLeft;
var y = coordY - target.offsetTop;
if(x < 0) x = 0;
if(y < 0) y = 0;
if(x >= canvasWidth) x = canvasWidth - 1;
if(y >= canvasHeight) y = canvasHeight - 1;
return {'x':x, 'y':y};
}
function mouseDown(e)
{
var position = getTouchPosition(e);
idPoint = nearPoint(points, position.x, position.y);
if(idPoint == -1)
{
if(position.x < marginImage * 3 && position.y < marginImage * 3)
{
computeMat();
}
}
}
function mouseUp(e)
{
if(idPoint != -1)
{
idPoint = -1;
draw(points);
}
}
function mouseMove(e)
{
if(idPoint != -1)
{
var position = getTouchPosition(e);
points[idPoint].x = position.x;
points[idPoint].y = position.y;
draw(points);
}
}
function cancelDefault(e)
{
e.preventDefault();
}
function matStep12(pts)
{
var matP = [
[pts[0].x, pts[1].x, pts[2].x],
[pts[0].y, pts[1].y, pts[2].y],
[1, 1, 1]
];
var vecP = [[pts[3].x], [pts[3].y], [1]];
var matPi = math.inv(matP);
var vecPi = math.multiply(matPi, vecP);
var result = [
[pts[0].x * vecPi[0][0], pts[1].x * vecPi[1][0], pts[2].x * vecPi[2][0]],
[pts[0].y * vecPi[0][0], pts[1].y * vecPi[1][0], pts[2].y * vecPi[2][0]],
[vecPi[0][0], vecPi[1][0], vecPi[2][0]]
];
return result;
}
function distance(a, b)
{
var mx = b.x - a.x;
var my = b.y - a.y;
return Math.sqrt(mx * mx + my * my);
}
function computeMat()
{
var pts = getPointRelativePosition();
var widthT = distance(pts[0], pts[3]);
var widthB = distance(pts[1], pts[2]);
var heightL = distance(pts[0], pts[1]);
var heightR = distance(pts[2], pts[3]);
var maxWidth = (widthT > widthB) ? widthT : widthB;
var maxHeight = (heightL > heightR) ? heightL : heightR;
var imgWidth = Math.round(maxWidth);
var imgHeight = Math.round(maxHeight);
var matA = matStep12(pts);
var matB = matStep12([{x:0,y:0}, {x:0,y:maxHeight}, {x:maxWidth,y:maxHeight}, {x:maxWidth,y:0}]);
var matC = math.multiply(matB, math.inv(matA));
var matCi = math.inv(matC);
console.log('width:' + imgWidth + ', height:' + imgHeight);
printMat(matC);
// construct image with transformation matrice
imageData = ctx.createImageData(imgWidth, imgHeight);
var tempCanvas = document.createElement('canvas');
var tempCtx = tempCanvas.getContext('2d');
tempCanvas.width = image.width;
tempCanvas.height = image.height;
tempCtx.drawImage(image, 0, 0, image.width, image.height);
var imageDataSrc = tempCtx.getImageData(0, 0, image.width, image.height);
var mz = [matCi[0][2], matCi[1][2], matCi[2][2]];
for(var y = 0; y < imgHeight; y++)
{
var my = [matCi[0][1] * y, matCi[1][1] * y, matCi[2][1] * y];
var offsetY = y * imgWidth;
for(var x = 0; x < imgWidth; x++)
{
var mx = [matCi[0][0] * x, matCi[1][0] * x, matCi[2][0] * x];
var cx = mx[0] + my[0] + mz[0];
var cy = mx[1] + my[1] + mz[1];
var cz = mx[2] + my[2] + mz[2];
var px = Math.round(cx / cz);
var py = Math.round(cy / cz);
if(px < 0.0 || py < 0.0 || px >= image.width || py >= image.height)
{
imageData.data[pixelIndex] = 0;
imageData.data[pixelIndex + 1] = 255;
imageData.data[pixelIndex + 2] = 0;
imageData.data[pixelIndex + 3] = 255;
}
else
{
var pixelIndex = (offsetY + x) * 4;
var pixelIndexSrc = (py * image.width + px) * 4;
imageData.data[pixelIndex] = imageDataSrc.data[pixelIndexSrc];
imageData.data[pixelIndex + 1] = imageDataSrc.data[pixelIndexSrc + 1];
imageData.data[pixelIndex + 2] = imageDataSrc.data[pixelIndexSrc + 2];
imageData.data[pixelIndex + 3] = 255;
}
}
}
// here to do, image analysis
}
function getPointRelativePosition()
{
var pointOrigin = [];
for(i = 0; i < 4; i++)
{
pointOrigin[i] = {x:(points[i].x - marginImage) * image.width / (canvasWidth - marginImage * 2), y:(points[i].y - marginImage) * image.height / (canvasHeight - marginImage * 2)};
}
return pointOrigin;
}
function getPointPosition()
{
var pointOrigin = [];
for(i = 0; i < 4; i++)
{
pointOrigin[i] = {x:(points[i].x - marginImage) / (canvasWidth - marginImage * 2), y:(points[i].y - marginImage) / (canvasHeight - marginImage * 2)};
}
return pointOrigin;
}
function printPoint(pts)
{
var result = '';
for(var i = 0; i < 4; i++)
{
result += "{x:" + pts[i].x + ", y:" + pts[i].y + "},\n";
}
console.log(result);
}
function printMat(mat)
{
var result = '';
for(var i = 0; i < mat.length; i++)
{
result += "[";
for(var j = 0; j < mat[i].length; j++)
{
result += mat[i][j] + ", ";
}
result += "],\n";
}
console.log(result);
}
function canvasResize()
{
if(canvasWidth != document.body.clientWidth && canvasHeight != document.body.clientHeight)
{
var transformPoint = getPointPosition();
ratio = image.width / image.height;
canvasWidth = document.body.clientWidth;
canvasHeight = canvasWidth / ratio;
if(document.body.clientHeight < canvasHeight)
{
canvasHeight = document.body.clientHeight;
canvasWidth = canvasHeight * ratio;
}
canvasLargeur = canvasWidth;
canvasLongueur = canvasHeight;
if(canvasLargeur < canvasHeight) {
canvasLargeur = canvasHeight;
canvasLongueur = canvasWidth;
}
canvasPixelRatio = canvasLargeur / image.width;
c.setAttribute("width", canvasWidth);
c.setAttribute("height", canvasHeight);
marginImage = Math.round(40 * canvasPixelRatio);
for(i = 0; i < 4; i++)
{
points[i].x = transformPoint[i].x * (canvasWidth - marginImage * 2) + marginImage;
points[i].y = transformPoint[i].y * (canvasHeight - marginImage * 2) + marginImage;
}
draw(points);
}
}
c.addEventListener("mousedown", mouseDown, false);
c.addEventListener("mouseup", mouseUp, false);
c.addEventListener("mousemove", mouseMove, false);
c.addEventListener("touchstart", mouseDown, false);
c.addEventListener("touchend", mouseUp, false);
c.addEventListener("touchmove", mouseMove, false);
document.addEventListener("touchstart", cancelDefault, true);
document.addEventListener("touchend", cancelDefault, true);
document.addEventListener("touchmove", cancelDefault, true);
setInterval(canvasResize, 30);
draw(points);
};
</script>
I need to set background image for this canvas animation without affecting the animation style.
This CodePen is shown below.
var c = document.getElementById('canv');
var $ = c.getContext('2d');
var w = c.width = window.innerWidth;
var h = c.height = window.innerHeight;
var grav = 0.00095;
var s = [20, 15, 10, 5];
var gravX = w / 2;
var gravY = h / 2;
var nodes;
var num = 55;
var minDist = 155;
var spr = 0.0000009;
part();
run();
//random size function
function S() {
var curr = s.length;
var cur_ = Math.floor(Math.random() * curr);
return s[cur_];
}
function part() {
nodes = [];
for (var i = 0; i < num; i++) {
var node = {
hue: Math.random()*360,
rad: S(),
x: Math.random() * w,
y: Math.random() * h,
vx: Math.random() * 8 - 4,
vy: Math.random() * 8 - 4,
upd: function() {
this.x += this.vx;
this.y += this.vy;
if (this.x > w) this.x = 0;
else if (this.x < 0) this.x = w;
if (this.y > h) this.y = 0;
else if (this.y < 0) this.y = h;
},
draw: function() {
//outer ring
var g = $.createRadialGradient(this.x, this.y, this.rad * 2, this.x, this.y, this.rad);
g.addColorStop(0,'hsla(242, 55%, 15%,.7)');
g.addColorStop(.5, 'hsla(242, 50%, 10%,.5)');
g.addColorStop(1,'hsla(242, 30%, 5%,.5)');
$.fillStyle = g;
$.beginPath();
$.arc(this.x, this.y, this.rad * 2, 0, Math.PI * 2, true);
$.fill();
$.closePath();
//inner particle
var g2 = $.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.rad);
g2.addColorStop(0, 'hsla('+this.hue+', 85%, 40%, 1)');
g2.addColorStop(.5, 'hsla('+this.hue+',95%, 50%,1)');
g2.addColorStop(1,'hsla(0,0%,0%,0)');
$.fillStyle = g2;
$.beginPath();
$.arc(this.x, this.y, this.rad, 0, Math.PI * 2, true);
$.fill();
$.closePath();
}
};
nodes.push(node);
}
}
function run() {
$.globalCompositeOperation = 'source-over';
$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.fillRect(0, 0, w, h);
$.globalCompositeOperation = 'lighter';
for (i = 0; i < num; i++) {
nodes[i].upd();
nodes[i].draw();
}
for (i = 0; i < num - 1; i++) {
var n1 = nodes[i];
for (var j = i + 1; j < num; j++) {
var n2 = nodes[j];
Spr(n1, n2);
}
Grav(n1);
}
window.requestAnimationFrame(run);
}
function Spr(na, nb) {
var dx = nb.x - na.x;
var dy = nb.y - na.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
$.lineWidth = 1;
$.beginPath();
$.strokeStyle = "hsla(217, 95%, 55%, .15)";
$.moveTo(na.x, na.y);
$.lineTo(nb.x, nb.y);
$.stroke();
$.closePath();
var ax = dx * spr;
var ay = dy * spr;
na.vx += ax;
na.vy += ay;
nb.vx -= ax;
nb.vy -= ay;
}
}
function Grav(n) {
n.vx += (gravX - n.x) * grav;
n.vy += (gravY - n.y) * grav;
};
window.addEventListener('resize', function() {
c.width = w = window.innerWidth;
c.height = h = window.innerHeight;
});
body{
width:100%;
margin:0;
overflow:hidden;
}
<canvas id='canv' ></canvas>
CSS
Just replace the beginning of the run() code to:
function run() {
...
//$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.clearRect(0, 0, w, h);
...
Then move the color settings to CSS together with an image reference:
#canv {
background: hsla(242, 40%, 5%, .85) url(path/to/image.jpg);
}
Add background-size to the CSS rule if needed. Note that since you're using different blending modes such as lighter which depends on existing content, you may not get desired result as it will blend with an empty canvas and not a solid - the approach below should solve that in this case.
CodePen
JavaScript
As before, replace the first lines in run() but after you made sure the image you want to use has loaded, simply draw it in:
function run() {
...
//$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.drawImage(img, 0, 0, w, h); // img must be loaded (use onload)
...
If your image contains transparency you also need to clear the canvas first:
function run() {
...
//$.fillStyle = 'hsla(242, 40%, 5%,.85)';
$.clearRect(0, 0, w, h);
$.drawImage(img, 0, 0, w, h); // img must be loaded (use onload)
...
CodePen
I'm trying to change the shape of the particles in this script so that every time you click, the shapes change to a random shape. The shapes I want to do are circles (which they already are), squares, triangles, pentagons, and a four-leaf clover shape without the stem. I want to use the addEventListener method, but i have no idea where to even start with that. Thanks in advance, and here's the code I have so far:
http://jsfiddle.net/eampkcrr/
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = window.innerWidth;
var height = window.innerHeight;
var xCirc;
var yCirc;
var rCirc;
var animate = true;
canvas.width = width;
canvas.height = height;
makeParticles();
makeShapes();
function makeParticles() {
xCenter = canvas.width/2;
yCenter = canvas.height/2;
particles = [];
for (var i = 0; i < 3000; i++){
particles.push(new Particle());
}
}
function makeShapes() {
xCenter = canvas.width/2;
yCenter = canvas.height/2;
shapes = [];
shapes.push(new Circle());
}
function Circle() {
var r1 = 150;
var r2 = 1000;
var gradient1 = context.createRadialGradient(width/2, height/2, r1, width/2, height/2, r2);
gradient1.addColorStop(0.2, "yellow");
gradient1.addColorStop(0.8, "purple");
context.fillStyle = gradient1;
context.fillRect(0, 0, canvas.width, canvas.height);
var gradient2 = context.createRadialGradient(width/2, height/2, 120, width/2, height/2, 150);
gradient2.addColorStop(0, "black");
gradient2.addColorStop(.75, "black");
gradient2.addColorStop(1, "orange");
context.beginPath();
context.arc(width/2, height/2, 150, 0, 2 * Math.PI, true);
context.fillStyle = gradient2;
context.fill();
}
function start() {
if(animate){
window.requestAnimationFrame(start);
}
draw();
moveParticles();
}
function Particle() {
this.x = Math.floor((Math.random() * canvas.width) + 1);
this.y = Math.floor((Math.random() * canvas.height) + 1);
this.z = Math.floor((Math.random() * canvas.width));
var grad = context.createRadialGradient(this.x, this.y, Math.floor((Math.random() * 10) + 1), this.x, this.y, Math.floor((Math.random() * 10) + 1));
var colors = ["red", "green", "blue", "orange", "purple", "yellow", "white"];
grad.addColorStop(0, colors[Math.floor(Math.random()*colors.length)]);
grad.addColorStop(1, colors[Math.floor(Math.random()*colors.length)]);
this.color = grad;
this.radius = 1;
}
function draw() {
Circle();
for (var i = 0; i < particles.length; i++){
var p = particles[i];
xP = (xCenter - p.x) * (canvas.width/p.z);
xP += xCenter;
yP = (yCenter - p.y) * (canvas.width/p.z);
yP += yCenter;
rP = (canvas.width/p.z);
context.beginPath();
context.arc(xP, yP, rP, 0, 2 * Math.PI, true);
context.fillStyle = p.color;
context.fill();
xCirc -= p.x;
yCirc -= p.y;
}
}
function moveParticles() {
for (var j = 0; j < particles.length; j++){
var p = particles[j];
p.z -= 2;
if (p.z <= 0){
p.z = canvas.width;
}
}
}
start();
You can listen for click events on the canvas like this:
canvas.onclick=function(){ ... }
To change the particle shape, you can add a draw method to Particle that draws the appropriate shape based on a particle's this.particleType.
function Particle(particleType) {
this.particleType=particleType;
this.x = Math.floor((Math.random() * canvas.width) + 1);
this.y = Math.floor((Math.random() * canvas.height) + 1);
this.z = Math.floor((Math.random() * canvas.width));
var grad = context.createRadialGradient(this.x, this.y, Math.floor((Math.random() * 10) + 1), this.x, this.y, Math.floor((Math.random() * 10) + 1));
var colors = ["red", "green", "blue", "orange", "purple", "yellow", "white"];
grad.addColorStop(0, colors[Math.floor(Math.random()*colors.length)]);
grad.addColorStop(1, colors[Math.floor(Math.random()*colors.length)]);
this.color = grad;
this.radius = 1;
this.draw=function(){
// update position
var xP = (xCenter - this.x) * (canvas.width/this.z);
xP += xCenter;
var yP = (yCenter - this.y) * (canvas.width/this.z);
yP += yCenter;
var rP = (canvas.width/this.z);
// set fillStyle
context.fillStyle = this.color;
// draw on context based on the particle's current shape
switch (this.particleType){
case 'circle':
context.beginPath();
context.arc(xP, yP, rP, 0, 2 * Math.PI, true);
context.fill();
break;
case 'square':
context.fillRect(xP-rP, yP-rP, rP*2, rP*2);
break;
}
// update
xCirc -= this.x;
yCirc -= this.y;
}
}
Then your external function draw simply requests each particle to draw itself:
function draw() {
Circle();
for (var i = 0; i < particles.length; i++){
// request each particle to draw itself
var p = particles[i].draw();
}
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = window.innerWidth;
var height = window.innerHeight;
var xCirc;
var yCirc;
var rCirc;
var xCenter,yCenter;
var animate = true;
var pTypes=['circle','square'];
var pTypeIndex=0;
canvas.width = width;
canvas.height = height;
makeParticles();
makeShapes();
canvas.onclick=function(){
pTypeIndex++;
if(pTypeIndex>pTypes.length-1){pTypeIndex=0;}
var pType=pTypes[pTypeIndex];
for(var i=0;i<particles.length;i++){
particles[i].particleType=pType;
}
};
start();
function makeParticles() {
xCenter = canvas.width/2;
yCenter = canvas.height/2;
particles = [];
for (var i = 0; i < 500; i++){
particles.push(new Particle(pTypes[pTypeIndex]));
}
}
function makeShapes() {
xCenter = canvas.width/2;
yCenter = canvas.height/2;
shapes = [];
shapes.push(new Circle());
}
function Circle() {
var r1 = 150;
var r2 = 1000;
var gradient1 = context.createRadialGradient(width/2, height/2, r1, width/2, height/2, r2);
gradient1.addColorStop(0.2, "yellow");
gradient1.addColorStop(0.8, "purple");
context.fillStyle = gradient1;
context.fillRect(0, 0, canvas.width, canvas.height);
var gradient2 = context.createRadialGradient(width/2, height/2, 120, width/2, height/2, 150);
gradient2.addColorStop(0, "black");
gradient2.addColorStop(.75, "black");
gradient2.addColorStop(1, "orange");
context.beginPath();
context.arc(width/2, height/2, 150, 0, 2 * Math.PI, true);
context.fillStyle = gradient2;
context.fill();
}
function start() {
if(animate){
window.requestAnimationFrame(start);
}
draw();
moveParticles();
}
function Particle(particleType) {
this.particleType=particleType;
this.x = Math.floor((Math.random() * canvas.width) + 1);
this.y = Math.floor((Math.random() * canvas.height) + 1);
this.z = Math.floor((Math.random() * canvas.width));
var grad = context.createRadialGradient(this.x, this.y, Math.floor((Math.random() * 10) + 1), this.x, this.y, Math.floor((Math.random() * 10) + 1));
var colors = ["red", "green", "blue", "orange", "purple", "yellow", "white"];
grad.addColorStop(0, colors[Math.floor(Math.random()*colors.length)]);
grad.addColorStop(1, colors[Math.floor(Math.random()*colors.length)]);
this.color = grad;
this.radius = 1;
this.draw=function(){
// update position
var xP = (xCenter - this.x) * (canvas.width/this.z);
xP += xCenter;
var yP = (yCenter - this.y) * (canvas.width/this.z);
yP += yCenter;
var rP = (canvas.width/this.z);
// set fillStyle
context.fillStyle = this.color;
// draw on context
switch (this.particleType){
case 'circle':
context.beginPath();
context.arc(xP, yP, rP, 0, 2 * Math.PI, true);
context.fill();
break;
case 'square':
context.fillRect(xP-rP, yP-rP, rP*2, rP*2);
break;
}
// update
xCirc -= this.x;
yCirc -= this.y;
}
}
function draw() {
Circle();
for (var i = 0; i < particles.length; i++){
var p = particles[i].draw();
}
}
function moveParticles() {
for (var j = 0; j < particles.length; j++){
var p = particles[j];
p.z -= 2;
if (p.z <= 0){
p.z = canvas.width;
}
}
}
<h4>Click to change shapes.</h4>
<canvas id="canvas" width=300 height=300></canvas>
[ Fixed "yip" in circle <--> square conversion -- Thanks #Kaiido ]