I currently have an array holding 5 different colours and I've got a for loop to loop through those colours and give them to a shape and display them.
var canvas = document.getElementById('canvas');
var gc = canvas.getContext('2d');
var xPos = 25;
var yPos = 25;
var colourArray = [];
colourArray[0] = 'red';
colourArray[1] = 'yellow';
colourArray[2] = 'blue';
colourArray[3] = 'magenta';
colourArray[4] = 'green';
for(var count = 0; count < 5; count++){
myColour = colourArray[count];
xPos = 20+70*count;
gc.beginPath();
gc.rect(xPos, yPos, 50, 50);
gc.fillStyle = myColour;
gc.fill();
}
For the life of me I'm trying to figure a way to reverse this so it displays green first and red last.
Just change what index you are accessing from the array based on the array's length. On your first iteration, you want to get the last item in the array (in this case, 4), and on the last iteration, you want to get the item at index 0. This code will work no matter how many colors you have in colourArray:
var length = colourArray.length;
for (var i = 0; i < length; i++) {
myColour = colourArray[length - (i + 1)];
xPos = 20+70*i;
gc.beginPath();
gc.rect(xPos, yPos, 50, 50);
gc.fillStyle = myColour;
gc.fill();
}
var colourArray = [];
colourArray[0] = 'red';
colourArray[1] = 'yellow';
colourArray[2] = 'blue';
colourArray[3] = 'magenta';
colourArray[4] = 'green';
colourArray =colourArray.reverse();
Related
I am new to JavaScript, and trying to create a function that will draw lines on the canvas. This is currently what I have and it's not working. I may be doing unnecessary things and I need some suggestions for this code.
function start(){
var COUNT = 5;
for(var i = 0; i < COUNT; i++){
var row = 0;
var rect = new Rectangle(100, 100);
rect.setPosition(0, row);
rect.setColor(Color.blue);
var rect1 = new Rectangle(100, 100);
rect1.setPosition(100, row);
rect1.setColor(Color.red);
var rect2 = new Rectangle(100, 100);
rect2.setPosition(200, row);
rect2.setColor(Color.blue);
var rect3 = new Rectangle(100, 100);
rect3.setPosition(300, row);
rect3.setColor(Color.red);
add(rect);
add(rect1);
add(rect2);
add(rect3);
row + 100;
}
}
Are you trying to do something like this :
var ctx = canvas.getContext('2d')
var colors = ['red','blue']
function start(){
var COUNT = 5;
for(var i = 0; i < COUNT; i++)
for(var j = 0 ; j<4;j++){
var rect = [10,10];
ctx.fillStyle = colors[(i+j)%2];
ctx.fillRect(j*10, i*10, rect[0],rect[1]);
}
}
start();
https://jsfiddle.net/dbo3htov/29/
I am trying to draw a tiled map using canvas. There are three types of tiles that need to be drawn to the canvas, so rather than call the function three times for each individual image, I'm passing an array of the images as a parameter, looping through the array, and telling canvas to draw each image. However, when I do this, I'm getting a blank canvas with no error messages being returned. When I don't pass in an array but make the function call manually for each image, the images are drawn to the canvas. Can anyone explain to me what I'm doing wrong?
window.onload = function(){
var basemap = document.getElementById("basemap");
var basemapCtx = basemap.getContext("2d");
initMap(Map.map, basemapCtx, basemap);
}
function initMap(map, ctx, canvas){
var deepWater = new Image();
var shallowWater = new Image();
var coastalWater = new Image();
deepWater.src = "deepWater.png";
shallowWater.src = "shallowWater.jpg";
coastalWater.src = "coastalWater.jpg";
//Does not draw the images
drawMap(map, [deepWater, shallowWater, coastalWater], ctx, canvas, [-2, -1, 0]);
//Does draw the images
//drawMap(map, deepWater, ctx, canvas, -2);
//drawMap(map, shallowWater, ctx, canvas, -1);
//drawMap(map, coastalWater, ctx, canvas, 0);
}
function drawMap(map, image, ctx, canvas, pos){
var screenWidth = canvas.width;
var screenHeight = canvas.height;
var tileWidth = 0;
var tileHeight = 0;
var xPos = 0;
var yPos = 0;
for(var i = 0; i < image.length; i++){
image[i].onload = function(){
for(var rows = 0; rows < map.length; rows++){
tileHeight = (screenHeight / map.length);
for(var cols = 0; cols < map[rows].length; cols++){
tileWidth = (screenWidth / map[rows].length);
if(map[rows][cols] == pos[i]){
ctx.drawImage(image[i], xPos, yPos, tileWidth, tileHeight);
}
xPos += tileWidth;
}
xPos = 0;
yPos += tileHeight;
}
yPos = 0;
tileWidth = 0;
tileHeight = 0;
}
}
}
The onload event will not fire until the current function has returned and Javascript is doing nothing. The for loop is using the variable i. When the onload events fire the value of i will be image.length one past the last image.
You need to make the variables for each onload event unique to that call. You can use closure to do that.
Change the code as follows
function drawMap(map, image, ctx, canvas, pos){
function setOnLoad(i){ // this function creates closure over the variable i so
// that it is unique each time this function and the onload
// function runs.
var screenWidth = canvas.width;
var screenHeight = canvas.height;
var tileWidth = 0;
var tileHeight = 0;
var xPos = 0;
var yPos = 0;
// the following function closes over all the variables in this function
image[i].onload = function(){
for(var rows = 0; rows < map.length; rows++){
tileHeight = (screenHeight / map.length);
for(var cols = 0; cols < map[rows].length; cols++){
tileWidth = (screenWidth / map[rows].length);
if(map[rows][cols] == pos[i]){
ctx.drawImage(image[i], xPos, yPos, tileWidth, tileHeight);
}
xPos += tileWidth;
}
xPos = 0;
yPos += tileHeight;
}
}
}
for(var i = 0; i < image.length; i++){
setOnLoad(i);
}
}
Thus you will have a unique set of variables each time the onload function is called.
I am making a map that renders position of game objects (Project Zomboid zombies):
As user zooms out, single dots are no longer useful. Instead, I'd like to render distribution of zombies on an area using red color gradient. I tried to loop over all zombies for every rendered pixel and color it reciprocally to the sum of squared distances to the zombies. The result:
That's way too blurry. Also the results are more influenced by the zombies that are AWAY from the points - I need to influence them more by the zombies that are CLOSE. So what this is is just math. Here's the code I used:
var h = canvas.height;
var w = canvas.width;
// To loop over more than 1 pixel (performance)
var tileSize = 10;
var halfRadius = Math.floor(tileSize/2);
var time = performance.now();
// "Squared" because we didnt unsquare it
function distanceSquared(A, B) {
return (A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y);
}
// Loop for every x,y pixel (or region of pixels)
for(var y=0; y<h; y+=tileSize) {
for(var x=0; x<w; x+=tileSize) {
// Time security - stop rendering after 1 second
if(performance.now()-time>1000) {
x=w;y=h;break;
}
// Convert relative canvas offset to absolute point on the map
var point = canvasPixeltoImagePixel(x, y);
// For every zombie add sqrt(distance from this point to zombie)
var distancesRoot = 0;
// Loop over the zombies
var zombieCoords;
for(var i=0; i<zombies_length; i++) {
// Get single zombie coordinates as {x:0, y:0}
if((coords=zombies[i].pixel)==null)
coords = zombies[i].pixel = tileToPixel(zombies[i].coordinates[0], zombies[i].coordinates[1], drawer);
// square root is a) slow and b) probably not what I want anyway
var dist = distanceSquared(coords, point);
distancesRoot+=dist;
}
// The higher the sum of distances is, the more intensive should the color be
var style = 'rgba(255,0,0,'+300000000/distancesRoot+')';
// Kill the console immediatelly
//console.log(style);
// Maybe we should sample and cache the transparency styles since there's limited ammount of colors?
ctx.fillStyle = style;
ctx.fillRect(x-halfRadius,y-halfRadius,tileSize,tileSize);
}
}
I'm pretty fine with theoretical explanation how to do it, though if you make simple canvas example with some points, what would be awesome.
This is an example of a heat map. It's basically gradient orbs over points and then ramping the opacity through a heat ramp. The more orbs cluster together the more solid the color which can be shown as an amplified region with the proper ramp.
update
I cleaned up the variables a bit and put the zeeks in an animation loop. There's an fps counter to see how it's performing. The gradient circles can be expensive. We could probably do bigger worlds if we downscale the heat map. It won't be as smooth looking but will compute a lot faster.
update 2
The heat map now has an adjustable scale and as predicted we get an increase in fps.
if (typeof app === "undefined") {
var app = {};
}
app.zeeks = 200;
app.w = 600;
app.h = 400;
app.circleSize = 50;
app.scale = 0.25;
init();
function init() {
app.can = document.getElementById('can');
app.ctx = can.getContext('2d');
app.can.height = app.h;
app.can.width = app.w;
app.radius = Math.floor(app.circleSize / 2);
app.z = genZ(app.zeeks, app.w, app.h);
app.flip = false;
// Make temporary layer once.
app.layer = document.createElement('canvas');
app.layerCtx = app.layer.getContext('2d');
app.layer.width = Math.floor(app.w * app.scale);
app.layer.height = Math.floor(app.h * app.scale);
// Make the gradient canvas once.
var sCircle = Math.floor(app.circleSize * app.scale);
app.radius = Math.floor(sCircle / 2);
app.gCan = genGradientCircle(sCircle);
app.ramp = genRamp();
// fps counter
app.frames = 0;
app.fps = "- fps";
app.fpsInterval = setInterval(calcFps, 1000);
// start animation
ani();
flicker();
}
function calcFps() {
app.fps = app.frames + " fps";
app.frames = 0;
}
// animation loop
function ani() {
app.frames++;
var ctx = app.ctx;
var w = app.w;
var h = app.h;
moveZ();
//ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "#006600";
ctx.fillRect(0, 0, w, h);
if (app.flip) {
drawZ2();
drawZ();
} else {
drawZ2();
}
ctx.fillStyle = "#FFFF00";
ctx.fillText(app.fps, 10, 10);
requestAnimationFrame(ani);
}
function flicker() {
app.flip = !app.flip;
if (app.flip) {
setTimeout(flicker, 500);
} else {
setTimeout(flicker, 5000);
}
}
function genGradientCircle(size) {
// gradient image
var gCan = document.createElement('canvas');
gCan.width = gCan.height = size;
var gCtx = gCan.getContext('2d');
var radius = Math.floor(size / 2);
var grad = gCtx.createRadialGradient(radius, radius, radius, radius, radius, 0);
grad.addColorStop(1, "rgba(255,255,255,.65)");
grad.addColorStop(0, "rgba(255,255,255,0)");
gCtx.fillStyle = grad;
gCtx.fillRect(0, 0, gCan.width, gCan.height);
return gCan;
}
function genRamp() {
// Create heat gradient
var heat = document.createElement('canvas');
var hCtx = heat.getContext('2d');
heat.width = 256;
heat.height = 5;
var linGrad = hCtx.createLinearGradient(0, 0, heat.width, heat.height);
linGrad.addColorStop(1, "rgba(255,0,0,.75)");
linGrad.addColorStop(0.5, "rgba(255,255,0,.03)");
linGrad.addColorStop(0, "rgba(255,255,0,0)");
hCtx.fillStyle = linGrad;
hCtx.fillRect(0, 0, heat.width, heat.height);
// create ramp from gradient
var ramp = [];
var imageData = hCtx.getImageData(0, 0, heat.width, 1);
var d = imageData.data;
for (var x = 0; x < heat.width; x++) {
var i = x * 4;
ramp[x] = [d[i], d[i + 1], d[i + 2], d[i + 3]];
}
return ramp;
}
function genZ(n, w, h) {
var a = [];
for (var i = 0; i < n; i++) {
a[i] = [
Math.floor(Math.random() * w),
Math.floor(Math.random() * h),
Math.floor(Math.random() * 3) - 1,
Math.floor(Math.random() * 3) - 1
];
}
return a;
}
function moveZ() {
var w = app.w
var h = app.h;
var z = app.z;
for (var i = 0; i < z.length; i++) {
var s = z[i];
s[0] += s[2];
s[1] += s[3];
if (s[0] > w || s[0] < 0) s[2] *= -1;
if (s[1] > w || s[1] < 0) s[3] *= -1;
}
}
function drawZ() {
var ctx = app.ctx;
var z = app.z;
ctx.fillStyle = "#FFFF00";
for (var i = 0; i < z.length; i++) {
ctx.fillRect(z[i][0] - 2, z[i][1] - 2, 4, 4);
}
}
function drawZ2() {
var ctx = app.ctx;
var layer = app.layer;
var layerCtx = app.layerCtx;
var gCan = app.gCan;
var z = app.z;
var radius = app.radius;
// render gradients at coords onto layer
for (var i = 0; i < z.length; i++) {
var x = Math.floor((z[i][0] * app.scale) - radius);
var y = Math.floor((z[i][1] * app.scale) - radius);
layerCtx.drawImage(gCan, x, y);
}
// adjust layer for heat ramp
var ramp = app.ramp;
// apply ramp to layer
var imageData = layerCtx.getImageData(0, 0, layer.width, layer.height);
d = imageData.data;
for (var i = 0; i < d.length; i += 4) {
if (d[i + 3] != 0) {
var c = ramp[d[i + 3]];
d[i] = c[0];
d[i + 1] = c[1];
d[i + 2] = c[2];
d[i + 3] = c[3];
}
}
layerCtx.putImageData(imageData, 0, 0);
// draw layer on world
ctx.drawImage(layer, 0, 0, layer.width, layer.height, 0, 0, app.w, app.h);
}
<canvas id="can" width="600" height="400"></canvas>
I want to filter an image with a predefined filter mask in JavaScript using the HTML5 Canvas Element.
I found a solution which works fine:
//define source canvas
var srccanv = document.getElementById("src_canvas");
var ctx = srccanv.getContext("2d");
var w = srccanv.width;
var h = srccanv.height;
//just draw something into the canvas
ctx.beginPath();
ctx.fillStyle="gray";
ctx.fillRect(0,0,w,h);
ctx.lineWidth = 15;
ctx.strokeStyle = "lightgray";
ctx.moveTo(0,0);
ctx.lineTo(300,150);
ctx.stroke();
//define destination canvas
var dstcanv = document.getElementById("dst_canvas");
var dctx = dstcanv.getContext("2d");
var dstImageData = dctx.getImageData(0,0,dstcanv.width,dstcanv.height);
var dst = dstImageData.data;
//filtermask
var filtermask = [-1,-1,-1,0,0,0,1,1,1];
var side = Math.round(Math.sqrt(filtermask.length));
var halfSide = Math.floor(side/2);
var srcImageData = ctx.getImageData(0,0,w,h);
var src = srcImageData.data;
var sw = w;
var sh = h;
console.time('convolution');
// go through the destination image pixels
for (var y=1; y<h-1; y++) {
for (var x=1; x<w-1; x++) {
var sy = y;
var sx = x;
var dstOff = (y*w+x)*4;
// calculate the weighed sum of the source image pixels that
// fall under the convolution matrix
var r=0, g=0, b=0, a=0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = sy + cy - halfSide;
var scx = sx + cx - halfSide;
if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
var srcOff = (scy*sw+scx)*4;
var wt = filtermask[cy*side+cx];
r += src[srcOff] * wt;
g += src[srcOff+1] * wt;
b += src[srcOff+2] * wt;
a += src[srcOff+3];
}
}
}
dst[dstOff] = r;
dst[dstOff+1] = g;
dst[dstOff+2] = b;
dst[dstOff+3] = 255;
}
}
console.timeEnd('convolution');
dctx.putImageData(dstImageData,0,0);
<canvas id="src_canvas"></canvas>
<canvas id="dst_canvas"></canvas>
or see this fiddle: https://jsfiddle.net/w0fuxt64/20/
But... I found out that the performance in IE11 is very bad. I tested my code with latest versions of firefox (38.5.2 ESR) and Chrome (47.0) and I got some results around 10-20ms for the filtering of an 300px by 150px canvas. (See time in developer console of your browser)
Testing with IE gave me results around 280ms! which is way too long to be useful for my purposes. Does anybody have any ideas how to dramatically improve this code for IE.
Thanks in advance
Beni
This is a matrix-like animation and it works perfectly when tested on Firefox, but on Chrome and IE only the buttons are displayed. It has something to do with the functions for changing color, but actually any extra function i include in the code will make the whole thing not work.
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var color = "blue";
// setting canvas dimension
c.height = 500;
c.width = 500;
//the numbers that will be printed out
var numbers = "0123456789";
//create an array of single numbers
numbers = numbers.split("");
var font_size = 12;
var columns = c.width/font_size; //number of columns for the rain
//create an array of drops - one per column
var matrix = [];
for(var x = 0; x < columns; x++)
matrix[x] = c.height/font_size+1;
//functions to change the colour of the matrix
function setBlue() color = "blue";
function setGreen() color = "green";
function setRed() color = "red";
//drawing the matrix
function draw() {
//black background with transparency so that the drops leave trail
ctx.fillStyle = "rgba(0, 0, 0, 0.05)";
ctx.fillRect(0, 0, c.width, c.height);
if (color == "blue"){
ctx.fillStyle = "#00ffd2";
}else if (color == "green"){
ctx.fillStyle = "#0f0";
}else if (color == "red"){
ctx.fillStyle = "#ff0008";
}
ctx.font = font_size + "px papyrus";
//loop the drawing
for(var i = 0; i < matrix.length; i++)
{
//print random character
var text = numbers[Math.floor(Math.random()*numbers.length)];
ctx.fillText(text, i*font_size, matrix[i]*font_size);
//add randomness and send the character to the top of the screen
if(matrix[i]*font_size > c.height && Math.random() > 0.95)
matrix[i] = 0;
matrix[i]++;
}
}
setInterval(draw, 30);
Oops! Your function definitions are malformed...
Try this:
//functions to change the colour of the matrix
function setBlue(){ color = "blue";}
function setGreen(){ color = "green";}
function setRed(){ color = "red";}
Good luck with your project!