Need to create a loop using HTML5 canvas and JavaScript - javascript

I am using a jQuery carousel to display 38 different magnifications/positions of a large SVG image. I would ideally like to use some sort of loop to go through all the different sizes, draw to an individual canvas and place one in each of the li's in my carousel. Can anyone help me achieve this. Here's what I tried:
function drawSlides() {
for (var i = 1; i <= 38; i++) {
var currentCanvas = 'myCanvas_' + slideNumber;
// initialise canvas element
var canvas_i = document.getElementById('' + currentCanvas + '');
var context = canvas_i.getContext('2d');
// position of SVG – these measurements are subject to change!
var destX_i = -6940;
var destY_i = -29240;
var destWidth_i = 9373;
var destHeight_i = 30000;
context.drawImage('/path/image.svg',
destX_i, destY_i, destWidth_i, destHeight_i);
// white rectangle background – these are constant
var topLeftCornerX_i = 453;
var topLeftCornerY_i = -10;
var width_i = 370;
var height_i = 480;
context.beginPath();
context.rect(topLeftCornerX_i, topLeftCornerY_i, width_i, height_i);
context.fillStyle = "rgba(255, 255, 255, 1)";
context.fill();
// orange vertical line – these elements are constant
context.moveTo(453, 0);
context.lineTo(453, 460);
context.lineWidth = 2;
context.strokeStyle = "#f5d7cb";
context.stroke();
//orange ball – these are constant
var centerX_ball_i = 453;
var centerY_ball_i = 323;
var radius = 99;
context.beginPath();
context.arc(centerX_ball_i, centerY_ball_i, radius, 0, 2 * Math.PI, false);
var grd_ball_i = context.createLinearGradient(224, 354, 422, 552);
grd_ball_i.addColorStop(0, "#f5d7cb"); // light orange
grd_ball_i.addColorStop(1, "#ff4900"); // dark orange
context.fillStyle = grd_ball_i;
context.fill();
}
};
drawSlides();

This should get you moving:
var numCarouselItems = 38;
var myUL = document.getElementById('carousel');
var items = myUL.childNodes;
var img = new Image;
img.onload = function(){
for (var i=0;i<numCarouselItems;++i){
// Find the nth li, or create it
var li = items[i] || myUL.appendChild(document.createElement('li'));
// Find the nth canvas, or create it
var canvas = li.getElementsByTagName('canvas')[0] ||
li.appendChild(document.createElement('canvas'));
canvas.width = 1; // Erase the canvas, in case it existed
canvas.width = 320; // Set the width and height as desired
canvas.height = 240;
var ctx = canvas.getContext('2d');
// Use your actual calculations for the SVG size/position here
ctx.drawImage( img, 0, 0 );
}
}
// Be sure to set your image source after your load handler is in place
img.src = "foo.svg";

Related

How can I embed use a file selector in this code instead of specifying the image source?

I found these two pieces of code and I'm quite new to Java. Essentially I'd like to be able to select the image from a file selector (like the first piece of code) instead of specifying the url of an image. Also, is there a way that I could output a list of all the coordinates to the console pr ideally create a txt file with the coords in?
I've left links to jsfiddle for both
thank you!!
http://jsfiddle.net/edprattt/m2a1j8yf/
https://jsfiddle.net/edprattt/8xqo71Lk/
##1
function showImage(src, target) {
var fr = new FileReader();
fr.onload = function(){
target.src = fr.result;
}
fr.readAsDataURL(src.files[0]);
}
function putImage() {
var src = document.getElementById("select_image");
var target = document.getElementById("target");
showImage(src, target);
}
##2
var canvas = document.getElementById('Canvas');
var context = canvas.getContext("2d");
var mapSprite = new Image();
mapSprite.src = "http://www.retrogameguide.com/images/screenshots/snes-legend-of-zelda-linkto-the-past-8.jpg";
var Marker = function () {
this.Sprite = new Image();
this.Sprite.src = "http://www.clker.com/cliparts/w/O/e/P/x/i/map-marker-hi.png"
this.Width = 12;
this.Height = 20;
this.XPos = 0;
this.YPos = 0;
}
var Markers = new Array();
var mouseClicked = function (mouse) {
// Get corrent mouse coords
var rect = canvas.getBoundingClientRect();
var mouseXPos = (mouse.x - rect.left);
var mouseYPos = (mouse.y - rect.top);
console.log("Marker added");
var marker = new Marker();
marker.XPos = mouseXPos - (marker.Width / 2);
marker.YPos = mouseYPos - marker.Height;
Markers.push(marker);
}
canvas.addEventListener("mousedown", mouseClicked, false);
var firstLoad = function () {
context.font = "15px Georgia";
context.textAlign = "center";
}
firstLoad();
var main = function () {
draw();
};
var draw = function () {
// Clear Canvas
context.fillStyle = "#000";
context.fillRect(0, 0, canvas.width, canvas.height);
// Draw map
// Sprite, X location, Y location, Image width, Image height
// You can leave the image height and width off, if you do it will draw the image at default size
context.drawImage(mapSprite, 0, 0, 700, 700);
// Draw markers
for (var i = 0; i < Markers.length; i++) {
var tempMarker = Markers[i];
// Draw marker
context.drawImage(tempMarker.Sprite, tempMarker.XPos, tempMarker.YPos, tempMarker.Width, tempMarker.Height);
// Calculate postion text
var markerText = "Postion (X:" + tempMarker.XPos + ", Y:" + tempMarker.YPos;
// Draw a simple box so you can see the position
var textMeasurements = context.measureText(markerText);
context.fillStyle = "#666";
context.globalAlpha = 0.7;
context.fillRect(tempMarker.XPos - (textMeasurements.width / 2), tempMarker.YPos - 15, textMeasurements.width, 20);
context.globalAlpha = 1;
// Draw position above
context.fillStyle = "#000";
context.fillText(markerText, tempMarker.XPos, tempMarker.YPos);
}
};
setInterval(main, (1000 / 60)); // Refresh 60 times a second

Why is my animated character sliding left?

I'm using a simple clearInterval draw animation to animate a spritesheet. I have two animations and have set the canvas size the same for both. I have checked the width of the spritesheet to make sure it's correct. For some reason the animation executes but my character slides left and part of the next frame becomes visible before starting the animation over. I've checked over and over for bugs. Hopefully just missed something easy. Here is my code. Spritesheet with issue is posted in a link at the bottom of post. Thank you so much.
// QUIZGAME ANIMATION
var canvasWidth = 200;
var canvasHeight = 250;
var canvas2Height = 250;
var canvas2Width = 200;
var canvas1 = document.getElementById("canvas1");
var canvas2 = document.getElementById("canvas2");
document.addEventListener("DOMContentLoaded", function(event) {
canvas1.width = canvasWidth;
canvas1.height = canvasHeight;
canvas2.height = canvas2Height;
canvas2.width = canvas2Width;
});
var spriteWidth = 605;
var spriteHeight = 200;
var sprite2Width = 1632;
var sprite2Height = 200;
var columns = 3;
var rows = 1;
var columns2 = 11;
var rows2 = 1;
var width = spriteWidth / columns;
var height = spriteHeight / rows;
var height2 = sprite2Height / rows2;
var width2 = sprite2Width / columns2;
var curFrame = 0;
var curFrame2 = 0;
var frameCount = 3;
var frameCount2 = 11;
var x = 0;
var y = 0;
var x2 = 0;
var y2 = 0;
var frameX = 0;
var frameY = 0;
var frameX2 = 0;
var frameY2 = 0;
var ctx = canvas1.getContext("2d");
var ctx2 = canvas2.getContext("2d");
var character = new Image();
var character2 = new Image();
character.src = "img/testanimation.png";
character2.src = "https://i.stack.imgur.com/7uRCO.png";
function changeFrameIndex() {
curFrame = ++curFrame % frameCount;
frameX = curFrame * width;
ctx.clearRect(x, y, width, height);
}
function changeFrame2Index() {
curFrame2 = ++curFrame2 % frameCount2;
frameX2 = curFrame2 * width2;
ctx2.clearRect(x2, y2, width2, height2);
}
function draw() {
//Updating the frame
changeFrameIndex();
//Drawing the image
ctx.drawImage(character, frameX, frameY, width, height, x, y, width, height);
}
function draw2() {
changeFrame2Index();
ctx2.drawImage(character2, frameX2, frameY2, width2, height2, x2, y2, width2, height2);
}
//setInterval(draw, 140); // disabled by editor because source is unavailable
setInterval(draw2, 100);
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>
spritesheet with issue
I have partially resolved the issue but I'm still a little confused. I cropped some empty space off the end of the spritesheet (making sprite2Width = 1604 instead of the original 1632) which helped, reducing the motion to next to none, but for some reason I still have to clarify a width that is 4-6 px shorter than the actual png. The animation is working now after cropping spritesheet width at 1604px and setting the sprite2Width = 1598; (as opposed to 1604). Anyone have any idea why there is a 6 px difference here? CSS related maybe? Atleast it is working.
The first thing you should do when troubleshooting something like that is simplify your code...
Take it to the bare basic, then start playing with the variables that build each frame, yes like you find out there is a 6px padding on each sprite, you can compensate that on code see the calculation of the width.
var character = new Image();
character.src = "https://i.stack.imgur.com/7uRCO.png";
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var spriteWidth = 1632;
var columns = 11;
var width = (spriteWidth / columns) - 3;
var curFrame = 0;
function draw() {
ctx.clearRect(0, 0, 900, 900);
curFrame = ++curFrame % columns;
ctx.drawImage(character, curFrame * width, 0, width, 200, 0, 0, width, 200);
}
setInterval(draw, 100);
<canvas id="canvas" height=175></canvas>
...and for the record that sprite is not the best looking one

Drawing multiple offscreen canvases into onscreen canvas

I am having an issue when I'm trying to render multiple offscreen canvases into onscreen canvas. I do get one offscreen canvas rendered but the problem is that there should be two other rendered before. In other words, only last canvas is rendered. The expected result would be three overlapping rectangles (or squares :) in red, green and blue. Here's the code:
function rectangle(color) {
var offScreenCanvas = document.createElement('canvas');
var offScreenCtx = offScreenCanvas.getContext('2d');
var width = offScreenCanvas.width = 150;
var height = offScreenCanvas.height = 150;
switch(color) {
case 1:
offScreenCtx.fillStyle='rgb(255,0,0)';
break;
case 2:
offScreenCtx.fillStyle='rgb(0,255,0)';
break;
case 3:
offScreenCtx.fillStyle='rgb(0,0,255)';
break;
}
offScreenCtx.fillRect(0,0,width,height);
return offScreenCanvas;
}
function draw(offScreenCanvas, x , y) {
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d');
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
ctx.drawImage(offScreenCanvas, x, y);
}
var images = [];
var color = 1;
for (var i=0; i<3; i++) {
var img = new rectangle(color);
images.push(img);
color++;
}
var x = 0;
var y = 0;
for (var i = 0; i < images.length; i++) {
draw(images[i], x, y);
x += 100;
y += 100;
}
I did some searching and it seems that I'm not the first with this issue, but I could not get this working properly.
Setting canvas height or width clears the canvas.
The problem with your code is that you are causing the onscreen canvas to be cleared when you set it size in the function draw
Setting the canvas size, even if that size is the same, will cause the canvas context to reset and clear the canvas. All the other canvases are rendered, but erased when you set the onscreen canvas size.
Your draw function
function draw(offScreenCanvas, x , y) {
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d');
// The cause of the problem ===================================
// Either one of the following lines will clear the canvas
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
//=============================================================
ctx.drawImage(offScreenCanvas, x, y);
}
To avoid this just set the canvas size once. If you need to resize the canvas and keep its content you first need to create a copy of the canvas, then resize it, then render the copy back to the original.
Demo shows 5 offscreen canvases being rendered onto one onscreen canvas.
const colours = ['#f00', '#ff0', '#0f0', '#0ff', '#00f'];
const ctx = can.getContext('2d');
can.width = innerWidth - 4; // sub 4 px for border
can.height = innerHeight - 4;
function createCanvas(color, i) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 150;
canvas.height = 150;
ctx.font = "24px arial";
ctx.fillStyle = color;
ctx.fillRect(0, i * 30, canvas.width, 30);
ctx.fillStyle = "black";
ctx.fillText("Canvas "+i,10,(i + 0.75) * 30);
return canvas;
}
colours.forEach((c, i) => {
ctx.drawImage(createCanvas(c, i), 0, 0);
})
canvas {
border: 2px solid black;
position : absolute;
top : 0px;
left : 0px;
}
<canvas id="can"></canvas>

Use Clip Function to with Gradient Effect with JavaScript on Canvas

I'm trying to use the clip() function in canvas to create this effect, as pictured: there is a background image, and when your mouse hover on it, part of the image is shown. I got it to work as a circle, but I want this gradient effect you see the picture. How do I achieve that?
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="./assets/stylesheet/normalize.css">
<link rel="stylesheet" type="text/css" href="./assets/stylesheet/style.css">
</head>
<body>
<canvas id="canvas" width="2000" height="1200"></canvas>
<script>
var can = document.getElementById('canvas');
var ctx = can.getContext('2d');
can.addEventListener('mousemove', function(e) {
var mouse = getMouse(e, can);
redraw(mouse);
}, false);
function redraw(mouse) {
console.log('a');
can.width = can.width;
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
ctx.drawImage(img, 0, 0);
ctx.beginPath();
ctx.rect(0,0,2000,1200);
ctx.arc(mouse.x, mouse.y, 200, 0, Math.PI*2, true)
ctx.clip();
ctx.fillRect(0,0,2000,1200);
}
var img = new Image();
img.onload = function() {
redraw({x: 0, y: 0})
}
img.src = 'http://placekitten.com/2000/1000';
function getMouse(e, canvas) {
var element = canvas,
offsetX = 0,
offsetY = 0,
mx, my;
// Compute the total offset. It's possible to cache this if you want
if (element.offsetParent !== undefined) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
mx = e.pageX - offsetX;
my = e.pageY - offsetY;
return {
x: mx,
y: my
};
}
</script>
USING a RADIAL gradient
There are many ways to do that but the simplest is a gradient with an alpha.
First you need to define the size of the circle you wish to show.
var cirRadius = 300;
Then the location (canvas coordinates) where this circle will be centered
var posX = 100;
var posY = 100;
Now define the rgb colour
var RGB = [0,0,0] ; // black
Then an array of alpha values to define what is transparent
var alphas = [0,0,0.2,0.5,1]; // zero is transparent;
Now all you do is render the background image
// assume ctx is context and image is loaded
ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height); // fill the canvas
Then create the gradient with it centered at the position you want and the second circle at the radius you want. The first 3 numbers define the center and radius of the start of the gradient, the last 3 define the center and radius of the end
var grad = ctx.createRadialGradient(posX,posY,0,posX,posY,cirRadius);
Now add the colour stops using the CSS color string rgba(255,255,255,1) where the last is the alpha value from 0 to 1.
var len = alphas.length-1;
alphas.forEach((a,i) => {
grad.addColorStop(i/len,`rgba(${RGB[0]},${RGB[1]},${RGB[2]},${a})`);
});
or for legacy browsers that do not support arrow functions or template strings
var i,len = alphas.length;
for(i = 0; i < len; i++){
grad.addColorStop(i / (len - 1), "rgba(" + RGB[0] + "," + RGB[1] + "," + RGB[2] + "," + alphas[i] + ")");
}
Then set the fill style to the gradient
ctx.fillStyle = grad;
then just fill a rectangle covering the image
ctx.fillRect(0,0,ctx.canvas.width,ctx.canvas.height);
And you are done.
By setting the position with via a mouse event and then doing the above steps 60times a second using window.requestAnimationFrame you can get the effect you are looking for in real time.
Here is an example
// create a full screen canvas
var canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 10;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);
// var to hold context
var ctx;
// load an image
var image = new Image();
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
// add resize event
var resize = function(){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
}
// add mouse event. Because it is full screen no need to bother with offsets
var mouse = function(event){
posX = event.clientX;
posY = event.clientY;
}
// incase the canvas size is changed
window.addEventListener("resize",resize);
// listen to the mouse move
canvas.addEventListener("mousemove",mouse)
// Call resize as that gets our context
resize();
// define the gradient
var cirRadius = 300;
var posX = 100; // this will be set by the mouse
var posY = 100;
var RGB = [0,0,0] ; // black any values from 0 to 255
var alphas = [0,0,0.2,0.5,0.9,0.95,1]; // zero is transparent one is not
// the update function
var update = function(){
if(ctx){ // make sure all is in order..
if(image.complete){ // draw the image when it is ready
ctx.drawImage(image,0,0,canvas.width,canvas.height)
}else{ // while waiting for image clear the canvas
ctx.clearRect(0,0,canvas.width,canvas.height);
}
// create gradient
var grad = ctx.createRadialGradient(posX,posY,0,posX,posY,cirRadius);
// add colour stops
var len = alphas.length-1;
alphas.forEach((a,i) => {
grad.addColorStop(i/len,`rgba(${RGB[0]},${RGB[1]},${RGB[2]},${a})`);
});
// set fill style to gradient
ctx.fillStyle = grad;
// render that gradient
ctx.fillRect(0,0,canvas.width,canvas.height);
}
requestAnimationFrame(update); // keep doing it till cows come home.
}
// start it all happening;
requestAnimationFrame(update);

HTML5 Canvas - Fastest way to display an array of pixel colors on the screen

Given a 40x30 array of colors what would be the fastest way to display them on the screen in a 400x300 canvas? That is, each color would require 100 pixels in the canvas element.
Using the canvas element in HTML5 I created 4 possible ways to do it. http://jsperf.com/pixel-plotting
Random pixel data is loaded into the 40x30 array. It is then displayed in a canvas of size 400x300 such that each logical pixel is represented by 100 pixels on the screen.
The fastest way (using Chrome 38.0) was to create the image data in a hidden canvas and copy it using toDataURL into the canvas that is displayed on the screen. The property imageSmoothingEnabled is set to false to stop the pixels blurring when they are scaled upwards.
Is there any other method of doing this? I tried using small canvas dimensions and just scaling it up in CSS but this led to the pixels being blurred.
I'd like to know how the test stands in other web browsers; especially mobile phone ones.
Drawing the pixel data onto the screen is quite important as it needs to happen at least 30 times a second so even a small tweak could improve efficiency a lot.
Here is the code for the four ways I tested.
Method 1
var png = document.createElement("img");
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");
var c1 = document.createElement("canvas");
c1.width = 40;
c1.height = 30;
var ctx1 = c1.getContext("2d");
var imgData = ctx1.createImageData(40, 30);
for (var i=0; i<imgData.data.length; i+=4) {
var x = (i/4)%40;
var y = Math.floor(i/160);
imgData.data[i] = pixelData1[x][y].r;
imgData.data[i+1] = pixelData1[x][y].g;
imgData.data[i+2] = pixelData1[x][y].b;
imgData.data[i+3] = 255;
}
ctx1.putImageData(imgData, 0, 0);
png.src = c1.toDataURL("image/png");
c2.width = 400;
c2.height = 300;
ctx2.imageSmoothingEnabled = false;
ctx2.drawImage(png, 0, 0, 400, 300);
Method 2
var c = document.getElementById("myCanvas");
c.width = 400;
c.height = 300;
var ctx = c.getContext("2d");
var imgData = ctx.createImageData(400, 300);
for (var i=0; i<imgData.data.length; i+=4) {
var x = Math.floor(((i/4)%400)/10);
var y = Math.floor(i/16000);
imgData.data[i] = pixelData1[x][y].r;
imgData.data[i+1] = pixelData1[x][y].g;
imgData.data[i+2] = pixelData1[x][y].b;
imgData.data[i+3] = 255;
}
ctx.putImageData(imgData, 0, 0);
Method 3
var png = document.createElement("img");
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");
var c1 = document.createElement("canvas");
c1.width = 40;
c1.height = 30;
var ctx1 = c1.getContext("2d");
for (var x = 0; x < 40; x++) {
for (var y = 0; y < 30; y++) {
ctx1.fillStyle = pixelData2[x][y];
ctx1.fillRect(x, y, 1, 1);
}
}
png.src = c1.toDataURL("image/png");
c2.width = 400;
c2.height = 300;
ctx2.imageSmoothingEnabled = false;
ctx2.drawImage(png, 0, 0, 400, 300);
Method 4
var c = document.getElementById("myCanvas");
c.width = 400;
c.height = 300;
var ctx = c.getContext("2d");
for (var x=0; x<40; x++) {
for (var y=0; y<30; y++) {
ctx.fillStyle = pixelData2[x][y];
ctx.fillRect(x*10, y*10, 10, 10);
}
}
Thanks for the response in the comments.
From Ken Frystenberg's comment I updated the tests on jsperf: http://jsperf.com/pixel-plotting/5
It seems that the fastest way is to edit the image data of a canvas not visible on the screen. Then, on the second canvas's context, set the property imageSmoothingEnabled to false. Finally call drawImage passing the first canvas as the source of the image.
To support this method in most browsers the appropriate prefixes were added to the imageSmoothingEnabled property of the canvas's context.
Here is the final code:
var c2 = document.getElementById("myCanvas");
var ctx2 = c2.getContext("2d");
var c1 = document.createElement("canvas");
c1.width = 40;
c1.height = 30;
var ctx1 = c1.getContext("2d");
var imgData = ctx1.createImageData(40, 30);
for (var i=0; i<imgData.data.length; i+=4) {
var x = (i/4)%40;
var y = Math.floor(i/160);
imgData.data[i] = pixelData1[x][y].r;
imgData.data[i+1] = pixelData1[x][y].g;
imgData.data[i+2] = pixelData1[x][y].b;
imgData.data[i+3] = 255;
}
ctx1.putImageData(imgData, 0, 0);
c2.width = 400;
c2.height = 300;
ctx2.mozImageSmoothingEnabled = false;
ctx2.webkitImageSmoothingEnabled = false;
ctx2.msImageSmoothingEnabled = false;
ctx2.imageSmoothingEnabled = false;
ctx2.drawImage(c1, 0, 0, 400, 300);

Categories