I'm trying to create a background that is inspired by this art work http://www.artinthepicture.com/paintings/Paul_Klee/Flora-on-the-Sand/
This is my code so far https://github.com/jessicacgu/jessicacgu.github.io/blob/faces/index.html
I'm using javascript instead of CSS because I wanted each square to have a facial expression that blinks (changes between happy and excited) (if there's something cooler to do, please let me know!)
Right now I have all the squares as the same size and equally spaced between each other. I want it to be more like the art piece by having different shaped squares.
My problem is that if I draw a square at 0,0 to be 15x15, how do I figure out that the other squares cannot overlap the first square? Is this possible?
If not, I can get rid of the expressions and just use CSS (I mainly want to imitiate that artwork :( ).
^Also, what's the right way to showcase large pieces of code on stackoverflow? Should I copy and paste or post link to github or put in codepen?
EDIT: The code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="application/javascript">
// Draws faces onto all of the canvas. Puts 10px distance between each
// face
function draw_faces() {
var canvas = document.getElementById("faces_bkgd");
canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
// Setting a background color
ctx.fillStyle = "rgb(244,191,175)";
ctx.fillRect(0,0,canvas.width,canvas.height);
// Creating the shape of the face
var shape = new Path2D();
shape.rect(0,0,30,30);
// Making the Happy Expression
var happy_expr = new Path2D();
// Making the left eye (as a carrot shape)
happy_expr.moveTo(4,13);
happy_expr.lineTo(8,5);
happy_expr.lineTo(12,13);
// Making the right eye (as a carrot shape)
happy_expr.moveTo(18,13);
happy_expr.lineTo(22,5);
happy_expr.lineTo(26,13);
// Making the mouth (a thin line)
happy_expr.moveTo(8,22);
happy_expr.lineTo(22,22);
// Making the Excited Expression
var excited_expr = new Path2D();
// Making the left eye (90 deg clockwise carrot)
excited_expr.moveTo(3,5);
excited_expr.lineTo(11,9);
excited_expr.lineTo(3,13);
// Making the right eye (90 deg counter clockwise carrot)
excited_expr.moveTo(26,5);
excited_expr.lineTo(18, 9);
excited_expr.lineTo(26, 13);
// Making the mouth (a W shape)
excited_expr.moveTo(7,17);
excited_expr.lineTo(11,25);
excited_expr.lineTo(15,17);
excited_expr.lineTo(19,25);
excited_expr.lineTo(23,17);
// Colors for the faces. Along with the background creates a palette
// that was taken from
// http://www.colourlovers.com/palette/1959912/L_i_n_d_a
var colors = ["rgb(211,210,164)", "rgb(198,223,194)",
"rgb(217,194,152)"];
// Expressions for the faces
var exprs = [happy_expr, excited_expr];
// Keeps track of the colors for each face drawn onto the canvas
var faces_color = [];
// Keeps track of the expressions for each face drawn onto the
// canvas
var faces_expr = [];
// Scaling the faces by 2x. Adjusting for new number of faces on
// canvas
ctx.scale(2, 2);
var num_width = canvas.width/70;
var num_height = canvas.height/70;
// Does actual drawing of face onto the canvas
function draw_face(x, y, color, shape, expr) {
ctx.save();
ctx.translate(x, y);
// Drawing the shape of the face
ctx.fillStyle = color
ctx.fill(shape);
// Giving the face a border
ctx.lineWidth = 3;
ctx.strokeStyle = "rgb(176,144,132)";
ctx.strokeRect(0,0,30,30);
// Drawing the expression
ctx.lineWidth = 1;
ctx.stroke(expr);
ctx.restore();
}
// Setting up the colors and initial expressions for each face
for (var i = 0; i < num_width ; i += 1) {
for (var j = 0; j < num_height ; j += 1) {
// Math.floor(Math.random()*100) chooses a random number from an
// even distribution of the integers[0,100)
var c = Math.floor(Math.random()*100)%colors.length;
var e = Math.floor(Math.random()*100)%exprs.length;
// Draw the initial face on the canvas
draw_face(i*40,j*40, colors[c], shape, exprs[e]);
// Keeps track of each faces's color and expression
faces_color.push(c);
faces_expr.push(e);
}
}
// Changes the expressions of a few random faces at a time
function change_facial_expr() {
var face_num = 0;
// Chooses a random face to change
var blink = Math.floor(Math.random() * faces_color.length);
for (var i = 0; i < num_width; i += 1) {
for (var j = 0; j < num_height ; j += 1) {
// Determines if the face is to be changed
if (face_num == blink) {
faces_expr[face_num] = (faces_expr[face_num]+1)%exprs.length
}
draw_face(i*40, j*40, colors[faces_color[face_num]], shape,
exprs[faces_expr[face_num]]);
face_num += 1;
}
}
}
window.setInterval(change_facial_expr, 1000);
}
}
</script>
</head>
<body onload="draw_faces();">
<canvas id="faces_bkgd"></canvas>
</body>
</html>
Related
There are a few similar questions but none of the answers fix my issue. I am simulating a solar system using canvas. The animation function calls a function to update the positions and then these positions are shown on screen in the form of circles. I have tried not calling the function animate and simply drawing the bodies using the initial conditions and this works fine however when trying to draw them via the animate function nothing is drawn - no even the sun - even though the functions have been passed through.
Why are they not drawing on the canvas?
here is the code (i have removed the for loop which would draw all the planets to only draw the earth just for development purposes, i have also not copied in all the global variables at the top as they take up a lot of space):
var massList = [massMecury, massVenus, massEarth, massMars, massJupiter, massSaturn, massUranus, massNeptune];
var xPosList = [initialMecuryXPos, initialVenusXPos, initialEarthXPos, initialMarsXPos, initialJupiterXPos, initialSaturnXPos, initialUranusXPos, initialNeptuneXPos];
var yPosList = [initialMecuryYPos, initialVenusYPos, initialEarthYPos, initialMarsYPos, initialJupiterYPos, initialSaturnYPos, initialUranusYPos, initialNeptuneYPos];
var xVelList = [initialMecuryXVel, initialVenusXVel, initialEarthXVel, initialMarsXVel, initialJupiterXVel, initialSaturnXVel, initialUranusXVel, initialNeptuneXVel];
var yVelList = [initialMecuryYVel, initialVenusYVel, initialEarthYVel, initialMarsYVel, initialJupiterYVel, initialSaturnYVel, initialUranusYVel, initialNeptuneYVel];
//position and velocity scales so they fit on the screen
var posScale = 1.7E10;
//var velScale = 3E9;
var pauseButtonPressed = false;
function axis (){
var canvas = document.getElementById("solarsys");
c=canvas.getContext('2d');
//moves the origin to the centre of the page
c.translate(400, 275);
//makes the y axis grow up and shrink down
c.scale(1,-1);
//c.fillRect(-innerWidth/2,-innerHeight/2,innerWidth,innerHeight); if want a black background
}
function calAcc(i) {
//calculates distance between the earth and the sun
var r = Math.sqrt((xPosList[i]*xPosList[i]) + (yPosList[i]*yPosList[i]));
//calculates the angle of displacement between the earth and sun
var theta = Math.atan(yPosList[i]/xPosList[i]);
//calculate the force on the earth using F = Gm1m2/r^2
//force is towards the centre of the sun
var F = (G*massSun*massList[i])/(r*r);
//correct the angle based on which quadrant it is in
theta=Math.abs(theta);
if (xPosList[i] < 0 && yPosList[i] < 0){
theta = theta;
} else if (xPosList[i] > 0 && yPosList[i] < 0){
theta = Math.PI-theta;
} else if (xPosList[i] > 0 && yPosList[i] > 0){
theta = theta-Math.PI;
} else{
theta = (2*Math.PI)-theta;
}
var fX = Math.cos(theta)*F;
var fY = Math.sin(theta)*F;
//calculate earths acceleration using Newton 2nd a = F / m
var aX = (fX/massList[i]);
var aY = (fY/massList[i]);
return [aX, aY];
}
function leapfrog(i) {
var dt = 5000;
var a = calAcc(i);
xVelList[i] = xVelList[i] + (a[0]*dt);
yVelList[i] = yVelList[i] + (a[1]*dt);
xPosList[i] = xPosList[i] + (xVelList[i]*dt);
yPosList[i] = yPosList[i] + (yVelList[i]*dt);
}
function drawBody(i) {
c.beginPath();
c.arc(xPosList[i]/posScale, yPosList[i]/posScale, 1, 0, twoPi, false);
c.stroke();
c.closePath();
console.log('body drawn');
}
function drawSun(){
//draw a yellow circle - the sun
c.beginPath();
c.arc(0, 0, 2, 0, twoPi, false);
c.fillStyle = '#ffcc00';
c.fill();
c.stroke();
c.closePath();
}
function animate() {
var i = 2;
//for (var i=0; i< xPosList.length; i++){
leapfrog(i);
drawBody(i);
drawSun();
console.log(xPosList);
//clears canvas each new loop
c.clearRect(-innerWidth/2,-innerHeight/2,innerWidth,innerHeight);
}
window.onload=function() {
axis();
var looper=setInterval(animate,1);}
You have several problems to fix:
You have a setInterval which is executed with pauses of 1 milliseconds. This seems to be too quick and I absolutely do not see any guarantee that your browser will be able to draw the things to be drawn.
In your animate function you draw things, but instantly remove them. You need to clear the canvas first and only then draw things on the canvas.
Your code is very difficult to read, consider refactoring it
For learning purpose, I am trying to display an image pixel by pixel in a canvas within a few seconds, below is the code I write
var timeStamps = [];
var intervals = [];
var c = document.getElementById('wsk');
var ctx = c.getContext("2d"),
img = new Image(),
i;
img.onload = init;
img.src = "http://placehold.it/100x100/000000";
var points = [];
function init(){
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
for (i=0; i<img.width*img.height; i++) {
points.push(i);
}
window.m = points.length;
var sec = 10; //animation duration
function animate(t) {
timeStamps.push(t);
var pointsPerFrame = Math.floor(img.width*img.height/sec/60)+1;
var start = Date.now();
for (j=0; j<pointsPerFrame; j++) {
var i = Math.floor(Math.random()*m--); //Pick a point
temp = points[i];
points[i] = points[m];
points[m] = temp; //swap the point with the last element of the points array
var point = new Point(i%img.width,Math.floor(i/img.width)); //get(x,y)
ctx.fillStyle = "rgba(255,255,255,1)";
ctx.globalCompositeOperation = "source-over";
ctx.fillRect(point.x,point.y,1,1); //DRAW DOZENS OF POINTS WITHIN ONE FRAME
}
ctx.globalCompositeOperation = "source-in";//Only display the overlapping part of the new content and old cont
ctx.drawImage(img,0,0); //image could be with transparent areas itself, so only draw the image on those points that are already on screen, exluding points that don't overlap with the image.
var time = Date.now()-start;
intervals.push(time);
if( m > 0 ) requestAnimationFrame(animate);
}
animate();
}
function Point(x,y) {
this.x = x;
this.y = y;
}
Live test: www.weiwei-tv.com/test.php.
I was expecting the dots would appear total randomly and eventually fill out the whole 100*100 canvas. What real happens is every time only the upper half of the picture gets displayed but many dots in the lower half are missed. I guess the problem is with the technique I use to randomly pick up dots, I get it from this page, but I can't find anything wrong in it.
Another thing I notice is that the intervals are mostly 1ms or 0ms, which means javascript takes very little time draw the 100*100/10/60 dots and draw image upon it within every frame. However, the differences between timeStamps are mostly 30~50ms, which should be about 16ms(1000/60). I am not sure if this also plays a part in the failure of my code.
The problem is that you are using the index of the points array to compute the point coordinates. You need to use the value of the chosen point (which is moved to the m-th position).
So, change
var point = new Point(i%img.width,Math.floor(i/img.width));
To
var point = new Point(points[m]%img.width,Math.floor(points[m]/img.width));
I am trying to design a traveling sine wave in JavaScript, but the design appears quite slow. The main bottleneck is the clearRect() for canvas clearing.
How can I solve this?
Also I am drawing the pixel by ctx.fillRect(x, y,1,1), but when I clear using clearRect(x, y,1,1), it leaves some footprints. Instead I have to do clearRect(x, y,5,5) to get proper clearing. What can be the work around?
/******************************/
var x = 0;
var sineval = [];
var offset = 0;
var animFlag;
function init() {
for(var i=0; i<=1000; ++i){
sineval[i] = Math.sin(i*Math.PI/180);
}
// Call the sineWave() function repeatedly every 1 microseconds
animFlag = setInterval(sineWave, 1);
//sineWave();
}
function sineWave()
{ //console.log('Drawing Sine');
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
}
for(x=0 ; x<1000 ;++x){
// Find the sine of the angle
//var i = x % 361;
var y = sineval[x+offset];
// If the sine value is positive, map it above y = 100 and change the colour to blue
if(y >= 0)
{
y = 100 - (y-0) * 70;
ctx.fillStyle = "green";
}
// If the sine value is negative, map it below y = 100 and change the colour to red
if( y < 0 )
{
y = 100 + (0-y) * 70;
ctx.fillStyle = "green";
}
// We will use the fillRect method to draw the actual wave. The length and breath of the
if(x == 0) ctx.clearRect(0,y-1,5,5);
else ctx.clearRect(x,y,5,5);
ctx.fillRect(x, y,1,1 /*Math.sin(x * Math.PI/180) * 5, Math.sin(x * Math.PI/180 * 5)*/);
}
offset = (offset > 360) ? 0 : ++offset ;
}
You need to refactor the code a bit:
Move all global variables such as canvas and context outside of the loop function
Inside the loop, clear full canvas at beginning, redraw sine
Use requestAnimationFrame instead of setInterval
Replace fillRect() with rect() and do a single fill() outside the inner for-loop
Using a timeout value of 1 ms will potentially result in blocking the browser, or at least slow it down noticeably. Considering that a monitor update only happens every 16.7ms this will of course be wasted cycles. If you want to reduce/increase the speed of the sine you can reduce/increase the incremental step instead.
In essence:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var sineval = [];
var offset = 0;
init();
function init() {
for (var i = 0; i <= 1000; ++i) {
sineval.push(Math.sin(i * Math.PI / 180));
}
// Call the sineWave() function
sineWave();
}
function sineWave() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
ctx.fillStyle = "green";
// draw positive part of sine wave here
for (var x = 0; x < 1000; x++) {
var y = sineval[x + offset];
if (y >= 0) {
y = 100 - (y - 0) * 70;
ctx.rect(x, y, 2, 2);
}
}
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "red";
// draw negative part of sine wave here
for (var x = 0; x < 1000; x++) {
var y = sineval[x + offset];
if (y < 0) {
y = 100 - (y - 0) * 70;
ctx.rect(x, y, 2, 2);
}
}
ctx.fill();
offset = (offset > 360) ? 0 : ++offset;
requestAnimationFrame(sineWave);
}
<canvas id="canvas" width=800 height=500></canvas>
And of course, if you load the script in <head> you need to wrap it in a window.onload block so canvas element is available. Or simply place the script at the bottom of the page if you haven't already.
A few speedups and odd ends:
In init, set up the sine wave pixel values one time.
Use typed arrays for these since sticking with integers is faster than using floats if possible.
We will manipulate the pixel data directly instead of using fill and clear. To start this, in init we call ctx.getImageData one time. We also just one time max the alpha value of all the pixels since the default 0 value is transparent and we want full opacity at 255.
Use setInterval like before. We want to update the pixels at a steady rate.
Use 'adj' as knob to adjust how fast the sine wave moves on the screen. The actual value (a decimal) will depend on the drawing frame rate. We use Date.now() calls to keep track of milliseconds consumed across frames. So the adjustment on the millisecond is mod 360 to set the 'offset' variable. Thus offset value is not inc by 1 every frame but instead is decided based on the consumption of time. The adj value could later be connected to gui if want.
At end of work (in sineWave function), we call requestAnimationFrame simply to do the ctx.putImageData to the canvas,screen in sync to avoid tearing. Notice 'paintit' function is fast and simple. Notice also that we still require setInterval to keep steady pace.
In between setting the offset and calling requestAnimationFrame, we do two loops. The first efficiently blackens out the exact pixels we drew from the prior frame (sets to 0). The second loop draws the new sine wave. Top half of wave is green (set the G in pixel rgba to 255). Bottom half is red (set the R pixel rgba to 255).
Use the .data array to paint a pixel, and index it to the pixel using 4x + 4y*canvas.width. Add 1 more if want the green value instead of the red one. No need to touch the blue value (byte offset 2) nor the already set alpha (byte offset 3).
The >>>0 used in some places turns the affected value into an unsigned integer if it wasn't already. It can also be used instead of Math.ceil. .data is typed Array already I think.
This answer is rather late but it addresses some issues brought up in comments or otherwise not yet addressed. The question showed up during googling.
Code hasn't been profiled. It's possible some of the speedups didn't speed anything up; however, the cpu consumption of firefox was pretty light by the end of the adjustments. It's set to run at 40 fps. Make 'delay' smaller to speed it up and tax cpu more.
var sineval;
var offset = 0;
var animFlag;
var canvas;
var ctx;
var obj;
var milli;
var delay=25;
var adj=1/delay; // .04 or so for 25 delay
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
obj=ctx.getImageData(0,0,canvas.width,canvas.height);
for (let i=0; i<obj.data.length; i+=4) {
obj.data[i+3]=255; //set all alpha to full one time only needed.
}
sineval=new Uint8Array(1400); //set up byte based table of final pixel sine values.. 1400 degrees total
for (let i=0; i<=1400; ++i) { //1400
sineval[i] = (100-70*Math.sin(i*Math.PI/180))>>>0;
}
animFlag = setInterval(sineWave, delay); //do processing once every 25 milli
milli=Date.now()>>>0; //start time in milli
}
function sineWave() {
let m=((Date.now()-milli)*adj)>>>0;
let oldoff = offset;
offset=(m % 360)>>>0; //offset,frequency tuned with adj param.
for(x=0 ; x<1000 ;++x) { //draw sine wave across canvas length of 1000
let y=sineval[x+oldoff];
obj.data [0+x*4+y*4*canvas.width]=0; //black the reds
obj.data [1+x*4+y*4*canvas.width]=0; //black the greens
}
for(x=0 ; x<1000 ;++x) { //draw sine wave across canvas length of 1000
let y=sineval[x+offset];
if (y<100) {
obj.data [1+x*4+y*4*canvas.width]=255; //rGba //green for top half
} else {
obj.data [0+x*4+y*4*canvas.width]=255; //Rgba //red for bottom half
}
}
requestAnimationFrame(paintit); //at end of processing try to paint next frame boundary
}
function paintit() {
ctx.putImageData(obj,0,0);
}
init();
<canvas id="canvas" height=300 width=1000></canvas>
I'm trying to implement ColorPicker using Canvas just for fun. But i seem lost. as my browser is freezing for a while when it loads due to all these for loops.
I'm adding the screenshot of the result of this script:
window.onload = function(){
colorPicker();
}
function colorPicker(){
var canvas = document.getElementById("colDisp"),
frame = canvas.getContext("2d");
var r=0,
g=0,
b= 0;
function drawColor(){
for(r=0;r<255;r++){
for(g=0;g<255;g++){
for(b=0;b<255;b++){
frame.fillStyle="rgb("+r+","+g+","+b+")";
frame.fillRect(r,g,1,1);
}
}
}
}
drawColor();
Currently , i only want a solution about the freezing problem with better algorithm and it's not displaying the BLACK and GREY colors.
Please someone help me.
Instead of calling fillRect for every single pixel, it might be a lot more efficient to work with a raw RGBA buffer. You can obtain one using context.getImageData, fill it with the color values, and then put it back in one go using context.putImageData.
Note that your current code overwrites each single pixel 255 times, once for each possible blue-value. The final pass on each pixel is 255 blue, so you see no grey and black in the output.
Finding a good way to map all possible RGB values to a two-dimensional image isn't trivial, because RGB is a three-dimensional color-space. There are a lot of strategies for doing so, but none is really optimal for any possible use-case. You can find some creative solutions for this problem on AllRGB.com. A few of them might be suitable for a color-picker for some use-cases.
If you want to fetch the rgba of the pixel under the mouse, you must use context.getImageData.
getImageData returns an array of pixels.
var pixeldata=context.getImageData(0,0,canvas.width,canvas.height);
Each pixel is defined by 4 sequential array elements.
So if you have gotten a pixel array with getImageData:
// first pixel defined by the first 4 pixel array elements
pixeldata[0] = red component of pixel#1
pixeldata[1] = green component of pixel#1
pixeldata[2] = blue component of pixel#1
pixeldata[4] = alpha (opacity) component of pixel#1
// second pixel defined by the next 4 pixel array elements
pixeldata[5] = red component of pixel#2
pixeldata[6] = green component of pixel#2
pixeldata[7] = blue component of pixel#2
pixeldata[8] = alpha (opacity) component of pixel#2
So if you have a mouseX and mouseY then you can get the r,g,b,a values under the mouse like this:
// get the offset in the array where mouseX,mouseY begin
var offset=(imageWidth*mouseY+mouseX)*4;
// read the red,blue,green and alpha values of that pixel
var red = pixeldata[offset];
var green = pixeldata[offset+1];
var blue = pixeldata[offset+2];
var alpha = pixeldata[offset+3];
Here's a demo that draws a colorwheel on the canvas and displays the RGBA under the mouse:
http://jsfiddle.net/m1erickson/94BAQ/
A way to go, using .createImageData():
window.onload = function() {
var canvas = document.getElementById("colDisp");
var frame = canvas.getContext("2d");
var width = canvas.width;
var height = canvas.height;
var imagedata = frame.createImageData(width, height);
var index, x, y;
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
index = (x * width + y) * 4;
imagedata.data[index + 0] = x;
imagedata.data[index + 1] = y;
imagedata.data[index + 2] = x + y - 255;
imagedata.data[index + 3] = 255;
}
}
frame.putImageData(imagedata, 0, 0);
};
http://codepen.io/anon/pen/vGcaF
Our company website features a "random shard generator", built in Flash, which creates a number of overlapping coloured shard graphics at random just below the site header.
http://www.clarendonmarketing.com
I am trying to replicate this effect using HTML5, and whilst I can generate the random shards easily enough, the blended overlapping (multiply in Adobe terms) is proving a challenge.
I have a solution which basically creates an array of all the canvas's pixel data before each shard is drawn, then another array with the canvas's pixel data after each shard is drawn. It then compares the two and where it finds a non transparent pixel in the first array whose corresponding pixel in the second array matches the currently selected fill colour, it redraws it with a new colour value determined by a 'multiply' function (topValue * bottomValue / 255).
Generally this works fine and achieves the desired effect, EXCEPT around the edges of the overlapping shards, where a jagged effect is produced.
I believe this has something to do with the browser's anti-aliasing. I have tried replicating the original pixel's alpha channel value for the computed pixel, but that doesn't seem to help.
Javascript:
// Random Shard Generator v2 (HTML5)
var theCanvas;
var ctx;
var maxShards = 6;
var minShards = 3;
var fillArray = new Array(
[180,181,171,255],
[162,202,28,255],
[192,15,44,255],
[222,23,112,255],
[63,185,127,255],
[152,103,158,255],
[251,216,45,255],
[249,147,0,255],
[0,151,204,255]
);
var selectedFill;
window.onload = function() {
theCanvas = document.getElementById('shards');
ctx = theCanvas.getContext('2d');
//ctx.translate(-0.5, -0.5)
var totalShards = getRandom(maxShards, minShards);
for(i=0; i<=totalShards; i++) {
//get snapshot of current canvas
imgData = ctx.getImageData(0,0,theCanvas.width,theCanvas.height);
currentPix = imgData.data
//draw a shard
drawRandomShard();
//get snapshot of new canvas
imgData = ctx.getImageData(0,0,theCanvas.width,theCanvas.height);
pix = imgData.data;
//console.log(selectedFill[0]+','+selectedFill[1]+','+selectedFill[2]);
//alert('break')
//CALCULATE THE MULTIPLIED RGB VALUES FOR OVERLAPPING PIXELS
for (var j = 0, n = currentPix.length; j < n; j += 4) {
if (
//the current pixel is not blank (alpha 0)
(currentPix[j+3]>0)
&& //and the new pixel matches the currently selected fill colour
(pix[j]==selectedFill[0] && pix[j+1]==selectedFill[1] && pix[j+2]==selectedFill[2])
) { //multiply the current pixel by the selected fill colour
//console.log('old: '+currentPix[j]+','+currentPix[j+1]+','+currentPix[j+2]+','+currentPix[j+3]+'\n'+'new: '+pix[j]+','+pix[j+1]+','+pix[j+2]+','+pix[j+3]);
pix[j] = multiply(selectedFill[0], currentPix[j]); // red
pix[j+1] = multiply(selectedFill[1], currentPix[j+1]); // green
pix[j+2] = multiply(selectedFill[2], currentPix[j+2]); // blue
}
}
//update the canvas
ctx.putImageData(imgData, 0, 0);
}
};
function drawRandomShard() {
var maxShardWidth = 200;
var minShardWidth = 30;
var maxShardHeight = 16;
var minShardHeight = 10;
var minIndent = 4;
var maxRight = theCanvas.width-maxShardWidth;
//generate a random start point
var randomLeftAnchor = getRandom(maxRight, 0);
//generate a random right anchor point
var randomRightAnchor = getRandom((randomLeftAnchor+maxShardWidth),(randomLeftAnchor+minShardWidth));
//generate a random number between the min and max limits for the lower point
var randomLowerAnchorX = getRandom((randomRightAnchor - minIndent),(randomLeftAnchor + minIndent));
//generate a random height for the shard
var randomLowerAnchorY = getRandom(maxShardHeight, minShardHeight);
//select a fill colour from an array
var fillSelector = getRandom(fillArray.length-1,0);
//console.log(fillSelector);
selectedFill = fillArray[fillSelector];
drawShard(randomLeftAnchor, randomLowerAnchorX, randomLowerAnchorY, randomRightAnchor, selectedFill);
}
function drawShard(leftAnchor, lowerAnchorX, lowerAnchorY, rightAnchor, selectedFill) {
ctx.beginPath();
ctx.moveTo(leftAnchor,0);
ctx.lineTo(lowerAnchorX,lowerAnchorY);
ctx.lineTo(rightAnchor,0);
ctx.closePath();
fillColour = 'rgb('+selectedFill[0]+','+selectedFill[1]+','+selectedFill[2]+')';
ctx.fillStyle=fillColour;
ctx.fill();
};
function getRandom(high, low) {
return Math.floor(Math.random() * (high-low)+1) + low;
}
function multiply(topValue, bottomValue){
return topValue * bottomValue / 255;
};
Working demo:
http://www.clarendonmarketing.com/html5shards.html
Do you really need multiplication? Why not just use lower opacity blending?
Demo http://jsfiddle.net/wk3eE/
ctx.globalAlpha = 0.6;
for(var i=totalShards;i--;) drawRandomShard();
Edit: If you really need multiplication, then leave it to the professionals, since multiply mode with alpha values is a little tricky:
Demo 2: http://jsfiddle.net/wk3eE/2/
<script type="text/javascript" src="context_blender.js"></script>
<script type="text/javascript">
var ctx = document.querySelector('canvas').getContext('2d');
// Create an off-screen canvas to draw shards to first
var off = ctx.canvas.cloneNode(true).getContext('2d');
var w = ctx.canvas.width, h = ctx.canvas.height;
for(var i=totalShards;i--;){
off.clearRect(0,0,w,h); // clear the offscreen context first
drawRandomShard(off); // modify to draw to the offscreen context
off.blendOnto(ctx,'multiply'); // multiply onto the main context
}
</script>