I am drawing on the canvas each time a user presses a button, however sometimes the image is not getting drawn on the canvas. I think this could be that the image isn't loaded in time before the context.drawimage function runs, as some of the smaller files sometimes get drawn. I've used the console and checked resources and so this is the only problem I can think of.
How do I avoid this problem?
This is my Javascript code.
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var questionbg = new Image();
var answerbg = new Image();
//this code is inside a function that is called each time a user presses a button
if(questiontype == "text"){
questionbg.src = "./resources/textquestionbg.png";
context.drawImage(questionbg, 0, 0);
}
//if image question
else if(questiontype == "image"){
questionbg.src = "./resources/imageaudiovideoquestionbg.png";
context.drawImage(questionbg, 0, 0);
}
//if audio question
else if(questiontype == "audio"){
questionbg.src = "./resources/imageaudiovideoquestionbg.png";
context.drawImage(questionbg, 0, 0);
}
//else it is a video question
else{
questionbg.src = "./resources/imageaudiovideoquestionbg.png";
context.drawImage(questionbg, 0, 0);
}
You should check if the image is loaded. If not then listen to the load event.
questionbg.src = "./resources/imageaudiovideoquestionbg.png";
if (questionbg.complete) {
context.drawImage(questionbg, 0, 0);
} else {
questionbg.onload = function () {
context.drawImage(questionbg, 0, 0);
};
}
MDN (Mozilla Doc, great source btw) suggests:
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image();
img.onload = function(){
ctx.drawImage(img,0,0);
ctx.beginPath();
ctx.moveTo(30,96);
ctx.lineTo(70,66);
ctx.lineTo(103,76);
ctx.lineTo(170,15);
ctx.stroke();
};
img.src = '/files/4531/backdrop.png';
}
Obviously, you are not wanting to apply the stroke or fill. However, the idea is the same.
Related
The code I'm using to load two images (note that one image is 7MB (I know - I will fix it later)).
var loaded = 0;
var img1 = new Image();
var img2 = new Image();
img1.onload = function(){ both() };
img2.onload = function(){ both() };
img1.src = 'map.png';
img2.src = 'ovl.png';
function both() {
loaded++
console.log(loaded);
if (loaded == 2) {
console.log("LOADED");
resizeCanvas();
console.log("RESIZED");
}
}
function resizeCanvas() {
//resize code
drawStuff();
}
function drawStuff() {
console.log("DRAWSTART");
ctx.save();
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillStyle="#868f9c";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
ctx.drawImage(img1, 0,0);
ctx.drawImage(img2, 0,0);
console.log("DRAWED");
}
As expected the console output is;
(index):28 1
(index):28 2
(index):30 LOADED
(index):81 DRAWSTART
(index):90 DRAWEND
(index):64 RESIZED
But the images are nowhere to be found, most of the times. Sometimes they appear after ~30 seconds but then no other functions work (I have some panning/zooming stuff aswell).
Note that if I comment out one of the ctx.drawImage(img[1,2], 0,0); it works perfectly fine. It seems like drawing two images just isn't working...
You should test first with small images in order to check everything is ok. Then for managing big images, you can use a preload library like http://www.createjs.com/preloadjs
So I'm trying to create a print map function for an OpenLayers 3 application I'm building. I'm aware of their example but whenever I attempt to use it I run into the dreaded tainted canvas issue. I've read the whole internet and come across folks saying first to set CORS correctly (done and done) but also to do:
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.src = url;
The above is described here.
My question is, I've never really used toDataURL() before and I'm not really sure how I make sure the image being created has the crossOrigin attribute correctly set before it slams into the:
Error: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
Any thoughts?
I have seen this. My question is how they incorporate that into a function that works. Something like:
var printMap = function(){
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.src = url;
img.onload = function() {
var canvas = document.getElementsByTagName('canvas');
var dataURL = canvas.toDataURL("image/png");
console.log(dataURL);
};
};
If the crossOrigin property/attribute is supported by the browser (it is now in FF, Chrome, latest Safari and Edge ), but the server doesn't answer with the proper headers (Access-Control-Allow-Origin: *), then the img's onerror event fires.
So we can just handle this event and remove the attribute if we want to draw the image anyway.
For browsers that don't handle this attribute, the only way o test if the canvas is tainted is to call the toDataURL into a try catch block.
Here is an example :
var urls =
["http://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png",
"http://lorempixel.com/200/200"];
var tainted = false;
var img = new Image();
img.crossOrigin = 'anonymous';
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
var load_handler = function() {
canvas.width = 200;
canvas.height = 200;
ctx.fillStyle = 'white';
ctx.font = '15px sans-serif';
ctx.drawImage(this, 0, 0, 200, 200*(this.height/this.width));
// for browsers supporting the crossOrigin attribute
if (tainted) {
ctx.strokeText('canvas tainted', 20, 100);
ctx.fillText('canvas tainted', 20, 100);
} else {
// for others
try {
canvas.toDataURL();
} catch (e) {
tainted = true;
ctx.strokeText('canvas tainted after try catch', 20, 100);
ctx.fillText('canvas tainted after try catch', 20, 100);
}
}
};
var error_handler = function() {
// remove this onerror listener to avoid an infinite loop
this.onerror = function() {
return false
};
// certainly that the canvas was tainted
tainted = true;
// we need to removeAttribute() since chrome doesn't like the property=undefined way...
this.removeAttribute('crossorigin');
this.src = this.src;
};
img.onload = load_handler;
img.onerror = error_handler;
img.src = urls[0];
btn.onclick = function() {
// reset the flag
tainted = false;
// we need to create a new canvas, or it will keep its marked as tainted flag
// try to comment the 3 next lines and switch multiple times the src to see what I mean
ctx = canvas.cloneNode(true).getContext('2d');
canvas.parentNode.replaceChild(ctx.canvas, canvas);
canvas = ctx.canvas;
// reset the attributes and error handler
img.crossOrigin = 'anonymous';
img.onerror = error_handler;
img.src = urls[+!urls.indexOf(img.src)];
};
<button id="btn"> change image src </button><br>
But since toDataURL can be a really heavy call for just a check and that code in try catch is deoptimized, a better alternative for older browsers is to create a 1px*1px tester canvas, draw the images on it first and call its toDataURL in the try-catch block :
var urls = ["http://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png", "http://lorempixel.com/200/200"];
var img = new Image();
img.crossOrigin = 'anonymous';
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
//create a canvas only for testing if our images will taint our canvas or not;
var taintTester = document.createElement('canvas').getContext('2d');
taintTester.width = 1;
taintTester.height = 1;
var load_handler = function() {
// our image flag
var willTaint = false;
// first draw on the tester
taintTester.drawImage(this, 0, 0);
// since it's only one pixel wide, toDataURL is way faster
try {
taintTester.canvas.toDataURL();
} catch (e) {
// update our flag
willTaint = true;
}
// it will taint the canvas
if (willTaint) {
// reset our tester
taintTester = taintTester.canvas.cloneNode(1).getContext('2d');
// do something
ctx.fillStyle = 'rgba(0,0,0,.7)';
ctx.fillRect(0, 75, ctx.measureText('we won\'t diplay ' + this.src).width + 40, 60);
ctx.fillStyle = 'white';
ctx.font = '15px sans-serif';
ctx.fillText('we won\'t diplay ' + this.src, 20, 100);
ctx.fillText('canvas would have been tainted', 20, 120);
} else {
// all clear
canvas.width = this.width;
canvas.height = this.height;
ctx.fillStyle = 'white';
ctx.font = '15px sans-serif';
ctx.drawImage(this, 0, 0);
}
};
var error_handler = function() {
// remove this onerror listener to avoid an infinite loop
this.onerror = function() {
return false
};
// we need to removeAttribute() since chrome doesn't like the property=undefined way...
this.removeAttribute('crossorigin');
this.src = this.src;
};
img.onload = load_handler;
img.onerror = error_handler;
img.src = urls[0];
btn.onclick = function() {
// reset the attributes and error handler
img.crossOrigin = 'anonymous';
img.onerror = error_handler;
img.src = urls[+!urls.indexOf(img.src)];
};
<button id="btn">change image src</button>
Note
Cross-origin requests are not the only way to taint a canvas :
In IE < Edge, drawing an svg on the canvas will taint the canvas for security issues, in the same way, latest Safari does taint the canvas if a <foreignObject> is present in an svg drawn onto the canvas and last, any UA will taint the canvas if an other tainted canvas is painted to it.
So the only solution in those cases to check if the canvas is tainted is to try-catch, and the best is to do so on a 1px by 1px test canvas.
So Pointy and Kaiido both had valid ways of making this work but they both missed that this was an OpenLayers issue (and in the case of Pointy, not a duplicate question).
The answer was to do this:
source = new ol.source.TileWMS({
crossOrigin: 'anonymous'
});
Basically you had to tell the map AND the layers that you wanted crossOrigin: anonymous. Otherwise your canvas would still be tainted. The more you know!
I'm very new to Html5 canvas and Javascript. I'm trying this :
function animate() {
var image1 = new Image();
image.src = /path
var image2 = new Image();
image2.src = /path
for(;;)
{
//change value of x and y so that it looks like moving
context.beginPath();
context.drawImage(<image>, x, y );
context.closePath();
context.fill();
}
}
EDIT:
And I call the animate function each 33ms :
if (playAnimation) {
// Run the animation loop again in 33 milliseconds
setTimeout(animate, 33);
};
If I follow the answer given here, I get the image struck and its not moving any further.
Update: Based on new information in the question, your problem (restated) is that you want to either
wait for all images to load first, and then start animating with them, or
start animating and only use an image if it is available.
Both are described below.
1. Loading many images and proceeding only when they are finished
With this technique we load all images immediately and when the last has loaded we run a custom callback.
Demo: http://jsfiddle.net/3MPrT/1/
// Load images and run the whenLoaded callback when all have loaded;
// The callback is passed an array of loaded Image objects.
function loadImages(paths,whenLoaded){
var imgs=[];
paths.forEach(function(path){
var img = new Image;
img.onload = function(){
imgs.push(img);
if (imgs.length==paths.length) whenLoaded(imgs);
}
img.src = path;
});
}
var imagePaths = [...]; // array of strings
loadImages(imagePaths,function(loadedImages){
setInterval(function(){ animateInCircle(loadedImages) }, 30);
});
2. Keeping track of all images loaded so far
With this technique we start animating immediately, but only draw images once they are loaded. Our circle dynamically changes dimension based on how many images are loaded so far.
Demo: http://jsfiddle.net/3MPrT/2/
var imagePaths = [...]; // array of strings
var loadedImages = []; // array of Image objects loaded so far
imagePaths.forEach(function(path){
// When an image has loaded, add it to the array of loaded images
var img = new Image;
img.onload = function(){ loadedImages.push(img); }
img.src = path;
});
setInterval(function(){
// Only animate the images loaded so far
animateInCircle(loadedImages);
}, 100);
And, if you wanted the images to rotate in a circle instead of just move in a circle:
Rotating images: http://jsfiddle.net/3MPrT/7/
ctx.save();
ctx.translate(cx,cy); // Center of circle
ctx.rotate( (angleOffset+(new Date)/3000) % Math.TAU );
ctx.translate(radius-img.width/2,-img.height/2);
ctx.drawImage(img,0,0);
ctx.restore();
Original answer follows.
In general, you must wait for each image loading to complete:
function animate(){
var img1 = new Image;
img1.onload = function(){
context.drawImage(img1,x1,y1);
};
img1.src = "/path";
var img2 = new Image;
img2.onload = function(){
context.drawImage(img2,x2,y2);
};
img2.src = "/path";
}
You may want to make this code more DRY by using an object:
var imgLocs = {
"/path1" : { x:17, y:42 },
"/path2" : { x:99, y:131 },
// as many as you want
};
function animate(){
for (var path in imgLocs){
(function(imgPath){
var xy = imgLocs[imgPath];
var img = new Image;
img.onload = function(){
context.drawImage( img, xy.x, xy.y );
}
img.src = imgPath;
})(path);
}
}
This is the button as is, it currently will get the canvas image and display that image on another canvas.
var formElement2 = document.getElementById("recImage");
formElement2.addEventListener('click', recImagePressed, false);
function recImagePressed(e){
var outputCanvas = document.getElementById("outputCanvas");
var recr = canvas2.toDataURL();
outputCtx = outputCanvas.getContext('2d');
outputCtx.drawImage(canvas2, 0, 0);
//context2.clearRect(0, 0, canvas2.width, canvas2.height);<----***
}
//***I need the image to clear when the user clicks, the above is wrong
The function that I need to react upon onclick is: (this function has been tested and works if I manually place the png into the function)
function init () { <---------this will be done away with and replaced w/
onClick ?? <-----------------****
canvas = document.getElementById('canVas');
ctx = canvas.getContext('2d');
draw ();
}
function draw() { <------This is the function that I need to react to mouse event
img = new Image();
img.src = myPng.png ***---->This is where I need the canvas image<---------------***
fr1 = makeFrame(ctx, makeVect(400,0), makeVect(400, 0), makeVect(0, 400));
img.onload = function(){
ctx.save();
newPainter = cornerSplit(imagePainter,5);
newPainter(fr1);
ctx.restore();
ctx.save();
newPainter(flipHorizLeft(fr1));
ctx.restore();
ctx.save();
newPainter(flipVertDown(fr1));
ctx.restore();
ctx.save();
newPainter(flipVertDown(flipHorizLeft(fr1)));
}
}
formElement2.onclick = function(args, ...) {
}
I want to use canvas to make an image to grayscale. There are a number of examples. But it has problem with my latest Chrome and Firefox. Surprisingly, IE9 is good. Is it the problem of my code?
And here is my code:
function draw() {
canvas = document.getElementById('canvas')
ctx = canvas.getContext('2d');
image = new Image();
image.src = 'ichiro.jpg';
ctx.drawImage(image, 0, 0);
imgd = ctx.getImageData(0, 0, 480, 400);
for (i=0; i<imgd.data.length; i+=4) {
grays = imgd.data[i]*.3 + imgd.data[i+1]*.6 + imgd.data[i+2]*.1;
imgd.data[i ] = grays; // red
imgd.data[i+1] = grays; // green
imgd.data[i+2] = grays; // blue
}
ctx.putImageData(imgd, 0, 0);
imggray = new Image();
imggray.src = canvas.toDataURL();
imggray.onload = function() {
ctx.drawImage(imggray, 0, 0);
}
}
I am new to HTML5 and javascript. So any help will be appreciated.
EDIT:
Sorry, I misread your question. It is almost certainly because of a security error. You are not allowed to use getImageData if you have drawn an image to the canvas that is from a different "origin" (a different domain or from your local file system). In Chrome locally you can get around it if you do:
C:\Users\root\AppData\Local\Google\Chrome\Application\chrome.exe --allow-file-access-from-files
There's something called the origin-clean flag and it is removed once you drawImage from a different origin. All files are of a different origin for (good) security reasons.
Original answer:
You need to wait for image to load:
working example: http://jsfiddle.net/SYLW2/1107/
...
// this now happens only after the image is loaded:
image.onload = function() {
ctx.drawImage(image, 0, 0);
imgd = ctx.getImageData(0, 0, 480, 400);
for (i=0; i<imgd.data.length; i+=4) {
grays = imgd.data[i]*.3 + imgd.data[i+1]*.6 + imgd.data[i+2]*.1;
imgd.data[i ] = grays; // red
imgd.data[i+1] = grays; // green
imgd.data[i+2] = grays; // blue
}
ctx.putImageData(imgd, 0, 0);
imggray = new Image();
imggray.src = canvas.toDataURL();
imggray.onload = function() {
ctx.drawImage(imggray, 0, 0);
}
}
image.src = 'http://placekitten.com/400/400';