Multiple images in html5 canvas not stacking in the right sequence - html

I'm adding multiple png transparent images to an html5 canvas one above the other but they don't stack in the right sequence (defined by the array containing the url of these images)
I read in other discussions that it may be a closure problem but no solution as far.
This is my code:
function drawCanvas() {
var canvas;
var context;
var imgArray = [
'images/img1.png',
'images/img2.png',
'images/img3.png',
'images/img4.png',
'images/img5.png',
'images/img6.png'
];
canvas = document.getElementById('myCanvas');
context = canvas.getContext("2d");
for (var i = 0; i < imgArray.length; i++) {
var img = new Image();
img.src = imgArray[i];
var drawTee = function(i, context, canvas) {
return function() {
console.log(i, 'loaded')
context.drawImage(this, 0, 0);
}
};
img.onload = drawTee(i, context, canvas);
}
};
Can anyone help me?

The problem is that the order of images on the canvas is determined by the order in which the images finish loading.
Solution: Wait for all the images to finish loading and then add them to the canvas
function drawCanvas() {
var canvas;
var context;
var imgArray = [
'images/img1.png',
'images/img2.png',
'images/img3.png',
'images/img4.png',
'images/img5.png',
'images/img6.png'
];
var images = [];
canvas = document.getElementById('myCanvas');
context = canvas.getContext("2d");
var loadCount = 0;
for (var i = 0; i < imgArray.length; i++) {
var img = new Image();
img.src = imgArray[i];
images.push(img);
img.onload = function() {
if(++loadCount == imgArray.length) {
for(var i=0; i < imgArray.length; i++) {
context.drawImage(images[i], 0, 0);
}
}
};
}
};

Related

Canvas.toDataURL() not showing background image of canvas

I have added background image to canvas using css.
But when converting it into png using canvas.toDataURL() , its not showing background image.
It seems like its not parsing css background image.
var svgString = new XMLSerializer().serializeToString(document.querySelector('svg'));
var canvas = document.getElementById("canvas");
d3.select('canvas')
.attr('style', 'background-image:url("chart.png");background-position:50%;background-repeat:no-repeat');
var ctx = canvas.getContext("2d");
var DOMURL = self.URL || self.webkitURL || self;
var img = new Image();
//var svg = new Blob([svgString], {type: "image/svg+xml;charset=utf-8"});
var svg = new Blob([svgString], {type: "image/svg+xml"});
var url = DOMURL.createObjectURL(svg);
var png = '';
img.onload = function() {
ctx.drawImage(img, 0, 0);
png = canvas.toDataURL("image/png");
document.querySelector('#chart').innerHTML = '<img src="'+png+'"/>';
DOMURL.revokeObjectURL(png);
img.src = "";
};
img.crossOrigin = "anonymous";
img.src = url;
As said in comments, background-image is not part of the canvas context and thus, can't be exported by any canvas' export methods.
If you want to get this image drawn on the canvas, simply draw it before you draw the other image.* The main difficulty being to reproduce the different option CSS background has. But in your case, it's quite simple.
var svgString = new XMLSerializer().serializeToString(document.querySelector('svg'));
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = self.URL || self.webkitURL || self;
var svg = new Blob([svgString], {
type: "image/svg+xml"
});
var url = DOMURL.createObjectURL(svg);
var png = '';
var toLoad = 2;
var loaded = 0;
var background = new Image();
var svgImg = new Image();
// attach the event to both images
background.onload = svgImg.onload = function(){
// only when both has loaded
if(++loaded === toLoad){
// set the canvas size to the svg image's one
canvas.width = svgImg.width;
canvas.height = svgImg.height;
// draw the background image first
ctx.drawImage(background, (canvas.width/2)-(background.width/2), (canvas.height/2)-(background.height/2));
// then the svg image
ctx.drawImage(svgImg, 0, 0);
png = canvas.toDataURL("image/png");
document.querySelector('#chart').innerHTML = '<img src="' + png + '"/>';
// you want to revoke the svgImg ObjectURL, not the canvas dataURI
DOMURL.revokeObjectURL(svgImg.src);
}
}
svgImg.src = url;
// set it only if you're doing a cross origin request
// Object URL are not
background.crossOrigin = 'anonymous';
background.src = 'https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png'
svg{border: 1px solid red;}
canvas{border: 1px solid blue;}
img{border: 1px solid green;}
<svg width="120" height="120" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" width="55" height="55" fill-opacity=".5"/>
</svg>
<canvas id="canvas"></canvas>
<p id="chart"></p>
*As said by markE, if you can't draw the background before, you can also set the globalCompositeOperation of your context to destination-over in order to draw behind the actual content.
Now, this wasn't clearly part of your question, but if you tried to set the background-image to your svg node, you should have been able to draw it on your canvas image, as part of the svg.
But since HTMLImage (<img>) element can't load any external resources form its loaded media, you would have to first convert the image to a dataURI version, and append the styles either as the svg's style attribute, either in a <style> element appended inside the svg element.
Here is a ugly example that will convert an svg element with a CSS defined background-image, and draw it on the canvas.
Don't use it, this just here for the demo but is likely to break. Also, this has only been quickly tested against FF, Chrome and Safari.
function convertSVGWithBG(svg, callback) {
// this function is not be bullet proof, better if you know the urls in advance
// in case of multi-images
var imgsToLoad = getComputedStyle(svg).backgroundImage.split(',');
appendDocumentStylesTo(svg);
var toLoad = imgsToLoad.length,
loaded = 0,
// we need to keep it sorted
rules = [],
canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
imgsToLoad.forEach(function(s, i) {
var bgURL = parseURL(s),
bgImg = new Image();
bgImg.crossOrigin = 'anonymous';
var errored = false;
bgImg.onload = function() {
canvas.width = this.width;
canvas.height = this.height;
ctx.drawImage(this, 0, 0);
rules[i] = canvas.toDataURL();
if (++loaded === toLoad) {
onend();
}
bgImg.onerror = function() {
if (!errored) {
errored = true;
this.crossOrigin = null;
this.removeAttribute('crossorigin');
this.src = this.src;
} else {
console.warn('failed to load img at src ', this.src);
if (--toLoad === loaded) {
onend();
}
}
};
};
bgImg.src = bgURL;
function onend() {
var toLoad = rules.filter(function(r) {
return r;
}).length,
loaded = 0;
// wait for the dataURI version has loaded too
rules.forEach(function(url) {
var img = new Image();
img.onload = function() {
if (++loaded === toLoad) {
callback(svg);
}
};
img.src = url
});
// it has to be inline style, or appended in a <style> element directly in our svg element
var fullRule = 'url(' + rules.join('), url(') + ')';
svg.style.backgroundImage = fullRule;
}
});
function parseURL(str) {
// this is ugly and there should be a better way to do so (maybe regex)
var url = str.split('"')[1];
if (!url) {
url = str.split("'")[1];
}
if (!url) {
url = str.split('(')[1].split(')')[0];
}
return url;
}
}
function svgToCanvas(svg) {
var svgString = new XMLSerializer().serializeToString(svg);
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = self.URL || self.webkitURL || self;
var svg = new Blob([svgString], {
type: "image/svg+xml"
});
var url = DOMURL.createObjectURL(svg);
var svgImg = new Image();
svgImg.onload = function() {
canvas.width = svgImg.width;
canvas.height = svgImg.height;
ctx.drawImage(svgImg, 0, 0);
png = canvas.toDataURL("image/png");
document.querySelector('#chart').innerHTML = '<img src="' + png + '"/>';
DOMURL.revokeObjectURL(svgImg.src);
}
svgImg.src = url;
}
var svg = document.querySelector('svg');
convertSVGWithBG(svg, svgToCanvas);
// this is completely unstable and should never be used...
function appendDocumentStylesTo(element) {
var docStyleSheets = document.styleSheets;
var i = 0,
j, rules, r, key;
for (i; i < docStyleSheets.length; i++) {
rules = docStyleSheets[i].cssRules;
for (j = 0; j < rules.length; j++) {
r = rules[j];
if (element.matches(r.selectorText)) {
if (r.style) {
for (k = 0; k < r.style.length; k++) {
key = r.style[k];
// ugly hack for Safari
if (key.indexOf('repeat') > -1) {
key = 'background-repeat';
}
element.style[key] = r.style[key];
}
}
}
}
}
}
canvas {
border: 1px solid blue;
}
img {
border: 1px solid green;
}
svg {
background-image: url(https://dl.dropboxusercontent.com/s/rumlhyme6s5f8pt/ABC.png), url(https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png);
background-position: 50%;
background-repeat: no-repeat;
}
<svg width="120" height="120" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="10" width="55" height="55" fill-opacity=".5" />
</svg>
<canvas id="canvas"></canvas>
<p id="chart"></p>
This solution is working
var svgString = new XMLSerializer().serializeToString(document.querySelector('svg'));
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = self.URL || self.webkitURL || self;
var img = new Image();
//var svg = new Blob([svgString], {type: "image/svg+xml;charset=utf-8"});
var svg = new Blob([svgString], {type: "image/svg+xml"});
var url = DOMURL.createObjectURL(svg);
var png = '';
var emailConfirmationMessage = '';
var img1 = new Image();
img.onload = function() {
ctx.drawImage(img1, 136, 136);
ctx.drawImage(img, 0, 0);
sendEmail(canvas,DOMURL);
};
img1.onload = function(){
img.src = url;
};
img1.src = 'chart1.png';

HTML5 Canvas game, timer

Problem
Hello, I am creating a game and am stuck on how to get a timer on the canvas. I am aware that there are plenty of questions on Stackoverflow already about this topic.
Question
Could someone please tell me why my code does not work.
Code
var game = create_game();
game.init();
function create_game() {
debugger;
var level = 1;
var projectiles_per_level = 1;
var min_speed_per_level = 1;
var max_speed_per_level = 2;
var last_projectile_time = 0;
var next_projectile_time = 0;
var width = 600;
var height = 500;
var delay = 1000;
var item_width = 30;
var item_height = 30;
var total_projectiles = 0;
var projectile_img = new Image();
var projectile_w = 30;
var projectile_h = 30;
var player_img = new Image();
var background_img = new Image();
var c, ctx;
var projectiles = [];
var player = {
x: 200,
y: 400,
score: 0
};
function init() {
background_img.src = "background.png";
projectile_img.src = "projectile.png";
player_img.src = "player.png";
level = 1;
total_projectiles = 0;
projectiles = [];
c = document.getElementById("c");
ctx = c.getContext("2d");
ctx.fillStyle = "#410b11";
ctx.fillRect(0, 0, 500, 600);
c.addEventListener("mousemove", function (e) {
//moving over the canvas.
var bounding_box = c.getBoundingClientRect();
player.x = (e.clientX - bounding_box.left) * (c.width / bounding_box.width) - player_img.width / 2;
}, false);
setupProjectiles();
requestAnimationFrame(tick);
}
function tick() {
Most of game logic is here, I deleted as it I dont believe it helped keeping it in.
var i;
var projectile;
var dateNow = Date.now();
c.width = c.width;
ctx.drawImage(background_img, 0, 0);
for (i = 0; i < projectiles.length; i++) {
projectile = projectiles[i];
if (dateNow > projectile.delay) {
projectile.y += projectile.v;
if (collision(projectile)) {
initProjectile(i);
player.score++;
} else if (projectile.y > height) {
initProjectile(i);
} else {
ctx.drawImage(projectile_img, projectile.x, projectile.y);
}
}
}
ctx.font = "bold 24px sans-serif";
ctx.fillStyle = "#d1c09c";
ctx.fillText(player.score, c.width - 50, 50);
ctx.fillText("Level: " + level, 20, 50);
ctx.drawImage(player_img, player.x, player.y);
maybeIncreaseDifficulty();
requestAnimationFrame(tick);
This is where i was going to put my code for the timer:
var startTime;
function drawElapsedTime(){
var elapsed=parseInt((new Date() - startTime)/1000);
ctx.beginPath();
ctx.fillStyle="red";
ctx.font="bold 24px sans-serif";
g.fillText(elapsed+" secs", 75,25);
g.restore();
}
}
return {
init: init
};
}
I hope this makes sense, sorry for the overload of code.
From what I see, you might have forgotten to initialize your startTime variable.
ParseInt() of something undefined returns NaN.
Make sure to assign a value to it and that this value is accessible in the scope of your drawElapsedTime() function.

html5 drawing multiple canvas images

i've got a problem with canvas in html. I want to draw multiple canvas images.
I have created a loop which creates multiple canvas and then i want to draw in each canvas an image.
for (var i = 1; i <= playerStuff.MovingCards.length; i++){
$("#playersField").append("<canvas id='movingCardsField"+i+"' height='"+movingCardSizeY+"'\n\
width='"+movingCardSizeX+"'\n\
data='"+playerStuff.MovingCards[i-1].movePoints+"'></canvas>");
var imgObj = new Image();
var drawField = document.getElementById('movingCardsField'+i).getContext("2d");
imgObj.onload = function(){
drawField.drawImage(imgObj, 0,0);
};
imgObj.src = "./img/moveCards/"+playerStuff.MovingCards[i-1].name;
}
The result is that only the last canvas has a drawn image. What am I missing here?
Please dont use JQuery for this. In generally i'm not a friend of JQuery but thats my personally opinion,but here you should really dont useit, use the DOM its much more faster.
var playersField=document.getElementById("playersField");
var canvasElements=new Array ();
var canvasElement, drawField,imgObj;
var drawFields=new Array ();
for (var i = 0; i <= playerStuff.MovingCards.length; i++)
{
canvasElement=document.createElement ("canvas");
canvasElement.height=movingCardSizeY;
canvasElement.width=movingCardSizeX;
canvasElement.data=playerStuff.MovingCards[i-1].movePoints;
playersField.appendChild(canvasElement);
drawField=canvasElement.getContext("2d");
drawFields.push (drawField);
imgObj = new Image();
imgObj.src = "./img/moveCards/"+playerStuff.MovingCards[i-1].name;
imgObj.index=i;
imgObj.onload = function (){
drawFields[this.index].drawImage(this, 0,0);
};
that is becouse onload y asinchronous, when al the iterations of the for has ended, the onload is probably that has not been called. then drawField variable is the last element. to solve this in a fast way (but not the cleanest solution)
the scope of a varaible is not as it could be in c++ or other languages.
for (var i = 1; i <= playerStuff.MovingCards.length; i++){
(function(element) {
$("#playersField").append("<canvas id='movingCardsField"+i+"' height='"+movingCardSizeY+"'\n\
width='"+movingCardSizeX+"'\n\
data='"+element.movePoints+"'></canvas>");
var imgObj = new Image();
var drawField = document.getElementById('movingCardsField'+i).getContext("2d");
imgObj.onload = function(){
drawField.drawImage(imgObj, 0,0);
};
imgObj.src = "./img/moveCards/"+element.name;
})(playerStuff.MovingCards[i - 1]);
}
this may do the trick, more or less, I didn't check twice the code
btw is not very efficient also
this should be something better
var domElement = $("#playersField");
var canvasStart = "<canvas id='movingCardsField";
var canvasEnd = "' height='"+movingCardSizeY + "'width='" + movingCardSizeX + "' data='" + element.movePoints + "'></canvas>";
function createCanvas(element) {
var canvas = canvasStart + (i + 1) + canvasEnd;
domElement.append(canvas);
var imgObj = new Image();
var drawField = document.getElementById('movingCardsField' + (i + 1)).getContext("2d");
imgObj.onload = function() {
drawField.drawImage(imgObj, 0,0);
};
imgObj.src = "./img/moveCards/" + element.name;
}
for (var i = 0; i < playerStuff.MovingCards.length; i++) {
createCanvas(playerStuff.MovingCards[i]);
}

how to set the offset on touch events

i am not getting the exact position of x and y co-ordinates in the ipad. Can some explain what am i doing wrong? this s a painting app for children where they can color on an image using canvas. its coloring but error being that its on starting on the point where the user touches the screen, its slightly below the point of contact. you can test this on your mobile or tablet..thie link is http://talentedash.co.uk/color/paint.html . Dont try this on desktop as it wont work
<script type="text/javascript">
var canvas = null; //canvas object
var context = null; //canvas's context object
var clearBtn = null; //clear button object
var defaultColor="#3577BB";
var defaultShape="round";
var defaultWidth=50;
/*boolean var to check if the touchstart event
is caused and then record the initial co-ordinate*/
var buttonDown = false;
//onLoad event register
window.addEventListener('load', initApp, false);
function initApp() {
setTimeout(function() { window.scrollTo(0, 1); }, 10); //hide the address bar of the browser.
canvas = document.getElementById('paintBox');
clearBtn = document.getElementById('clearBtn');
initializeEvents();
context = canvas.getContext('2d'); //get the 2D drawing context of the canvas
context.strokeStyle = defaultColor;
context.lineJoin = defaultShape;
context.lineWidth = defaultWidth;
/*Main image*/
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
var sources = {
main: 'transparent-charac1.png',
};
loadImages(sources, function(images) {
context.drawImage(images.main, 0, 0, 400, 800);
context.globalCompositeOperation = "destination-atop";
});
}
function initializeEvents() {
canvas.addEventListener('touchstart', startPaint, false);
canvas.addEventListener('touchmove', continuePaint, false);
canvas.addEventListener('touchend', stopPaint, false);
clearBtn.addEventListener('touchend', clearCanvas,false);
}
function clearCanvas() {
context.clearRect(0,0,canvas.width,canvas.height);
}
function startPaint(evt) {
if(!buttonDown)
{
context.beginPath();
context.moveTo(evt.touches[0].pageX, evt.touches[0].pageY);
buttonDown = true;
}
evt.preventDefault();
}
function continuePaint(evt) {
if(buttonDown)
{
context.lineTo(evt.touches[0].pageX ,evt.touches[0].pageY);
context.stroke();
}
}
function setColor(col)
{
context.strokeStyle = col;
}
function setSize(px)
{
context.lineWidth=px;
}
function stopPaint() {
buttonDown = false;
}
</script>

HTML5 Canvas background image

I'm trying to place a background image on the back of this canvas script I found. I know it's something to do with the context.fillstyle but not sure how to go about it. I'd like that line to read something like this:
context.fillStyle = "url('http://www.samskirrow.com/background.png')";
Here is my current code:
var waveform = (function() {
var req = new XMLHttpRequest();
req.open("GET", "js/jquery-1.6.4.min.js", false);
req.send();
eval(req.responseText);
req.open("GET", "js/soundmanager2.js", false);
req.send();
eval(req.responseText);
req.open("GET", "js/soundcloudplayer.js", false);
req.send();
eval(req.responseText);
req.open("GET", "js/raf.js", false);
req.send();
eval(req.responseText);
// soundcloud player setup
soundManager.usePolicyFile = true;
soundManager.url = 'http://www.samskirrow.com/client-kyra/js/';
soundManager.flashVersion = 9;
soundManager.useFlashBlock = false;
soundManager.debugFlash = false;
soundManager.debugMode = false;
soundManager.useHighPerformance = true;
soundManager.wmode = 'transparent';
soundManager.useFastPolling = true;
soundManager.usePeakData = true;
soundManager.useWaveformData = true;
soundManager.useEqData = true;
var clientID = "345ae40b30261fe4d9e6719f6e838dac";
var playlistUrl = "https://soundcloud.com/kyraofficial/sets/kyra-ft-cashtastic-good-love";
var waveLeft = [];
var waveRight = [];
// canvas animation setup
var canvas;
var context;
function init(c) {
canvas = document.getElementById(c);
context = canvas.getContext("2d");
soundManager.onready(function() {
initSound(clientID, playlistUrl);
});
aniloop();
}
function aniloop() {
requestAnimFrame(aniloop);
drawWave();
}
function drawWave() {
var step = 10;
var scale = 60;
// clear
context.fillStyle = "#ff19a7";
context.fillRect(0, 0, canvas.width, canvas.height);
// left wave
context.beginPath();
for ( var i = 0; i < 256; i++) {
var l = (i/(256-step)) * 1000;
var t = (scale + waveLeft[i] * -scale);
if (i == 0) {
context.moveTo(l,t);
} else {
context.lineTo(l,t); //change '128' to vary height of wave, change '256' to move wave up or down.
}
}
context.stroke();
// right wave
context.beginPath();
context.moveTo(0, 256);
for ( var i = 0; i < 256; i++) {
context.lineTo(4 * i, 255 + waveRight[i] * 128.);
}
context.lineWidth = 0.5;
context.strokeStyle = "#000";
context.stroke();
}
function updateWave(sound) {
waveLeft = sound.waveformData.left;
}
return {
init : init
};
})();
Revised code - currently just showing black as the background, not an image:
// canvas animation setup
var backgroundImage = new Image();
backgroundImage.src = 'http://www.samskirrow.com/images/main-bg.jpg';
var canvas;
var context;
function init(c) {
canvas = document.getElementById(c);
context = canvas.getContext("2d");
soundManager.onready(function() {
initSound(clientID, playlistUrl);
});
aniloop();
}
function aniloop() {
requestAnimFrame(aniloop);
drawWave();
}
function drawWave() {
var step = 10;
var scale = 60;
// clear
context.drawImage(backgroundImage, 0, 0);
context.fillRect(0, 0, canvas.width, canvas.height);
// left wave
context.beginPath();
for ( var i = 0; i < 256; i++) {
var l = (i/(256-step)) * 1000;
var t = (scale + waveLeft[i] * -scale);
if (i == 0) {
context.moveTo(l,t);
} else {
context.lineTo(l,t); //change '128' to vary height of wave, change '256' to move wave up or down.
}
}
context.stroke();
// right wave
context.beginPath();
context.moveTo(0, 256);
for ( var i = 0; i < 256; i++) {
context.lineTo(4 * i, 255 + waveRight[i] * 128.);
}
context.lineWidth = 0.5;
context.strokeStyle = "#ff19a7";
context.stroke();
}
function updateWave(sound) {
waveLeft = sound.waveformData.left;
}
return {
init : init
};
})();
Theres a few ways you can do this. You can either add a background to the canvas you are currently working on, which if the canvas isn't going to be redrawn every loop is fine. Otherwise you can make a second canvas underneath your main canvas and draw the background to it. The final way is to just use a standard <img> element placed under the canvas. To draw a background onto the canvas element you can do something like the following:
Live Demo
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
canvas.width = 903;
canvas.height = 657;
var background = new Image();
background.src = "http://www.samskirrow.com/background.png";
// Make sure the image is loaded first otherwise nothing will draw.
background.onload = function(){
ctx.drawImage(background,0,0);
}
// Draw whatever else over top of it on the canvas.
Why don't you style it out:
<canvas id="canvas" width="800" height="600" style="background: url('./images/image.jpg')">
Your browser does not support the canvas element.
</canvas>
Make sure that in case your image is not in the dom, and you get it from local directory or server, you should wait for the image to load and just after that to draw it on the canvas.
something like that:
function drawBgImg() {
let bgImg = new Image();
bgImg.src = '/images/1.jpg';
bgImg.onload = () => {
gCtx.drawImage(bgImg, 0, 0, gElCanvas.width, gElCanvas.height);
}
}
Canvas does not using .png file as background image. changing to other file extensions like gif or jpg works fine.