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
Related
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.
I would like to create a strip of images and compose a new image, like image = [image0-image1-image2].
We'll use:
images = ['https://upload.wikimedia.org/wikipedia/commons/5/55/Al-Farabi.jpg',
'https://upload.wikimedia.org/wikipedia/commons/e/e1/FullMoon2010.jpg',
'https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/3D_coordinate_system.svg/10000px-3D_coordinate_system.svg.png']
I would like to take external above, and make a collage.
I would like to do it in background.
I learnt that is possible to use a canvas element off the dom; for the sake of watching what I am doing, I will use a canvas element here.
// create an off-screen canvas using document.createElement('canvas')
// here I use a canvas in DOM cause I cannot find a way to displayed the final collage
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d');
// set its dimension to target size
canvas.width = 1200;
canvas.height = 630;
and found three different behaviors for what I think should give same result. Could you explain me why?
If I manually copy and paste in console code for each image, one at a timeenter code here`
var image = new Image();
// i is the i-th element in images
image.src = images[i];
image.onload = function() {
context.save();
context.drawImage(image, canvas.width * 0.3 * i, 0, canvas.width*0.3, canvas.height);
}
I can see the elements are positioned one aside of the other, like I would like to have.
But If I copy all of three pieces of code at once, either in a loop, I can see only the last image placed in all of the three different positions:
for (var i = images.length; i <= 0; i++) {
var image = new Image();
image.src = images[i];
image.onload = function(){
context.save();
context.drawImage(image, canvas.width*0.3 * i, 0, canvas.width*0.3, canvas.height);
}
}
So I thought, maybe it's a matter of using a callback after image is loaded - I tried the following but nothing happens: canvas stays empty.
// my callback
function addImage(image, position){
image.onload = function(){
context.save();
context.drawImage(image, canvas.width*0.3 * position, 0, canvas.width*0.3, canvas.height);
}
}
function loadImages (images, callback) {
for (var i = images.length-1; i >= 0; i--) {
var image = new Image();
image.src = images[i];
callback(image, i);
}
}
// canvas will stay empty:
loadImages(images, addImage);
Can you help in clarifying the differences in the three parts, and figure out how to combine an array of images in a single one?
Possibly in background, I want to then save the image and post it via ajax.
In your loop example, all the onload functions are sharing the same i and image variables from the loop. But the onload functions are callback functions that get called after the loop completes. Thus, all the onload functions are using the same i and image values from after the loop completed. You need to create a local scope such that each onload function has its own i and image values. For example...
for (var i = 0; i < images.length; i++) {
var image = new Image();
image.src = images[i];
image.onload = function(image, i) {
return function(){
context.drawImage(image, canvas.width*0.3 * i, 0, canvas.width*0.3, canvas.height);
}
}(image, i);
}
I have combined two images and some text using html5 canvas, but I can't seem to position the images the same way I can with the text. With the text I just changed the x and y properties to position it on the canvas, but this does not seem to work with my two images. They are just set at 0, 0 (top left) regardless.
<canvas width="850" height="450" id="canvas"></canvas>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img1 = loadImage('<%= image_url(#entry.picture(:small)) %>', main);
var img2 = loadImage("<%= image_url('overlay.png') %>", main);
var imagesLoaded = 0;
function main() {
imagesLoaded += 1;
if(imagesLoaded == 2) {
// composite now
ctx.drawImage(img1, 0, 0);
ctx.globalAlpha = 1;
ctx.drawImage(img2, 0, 0);
}
}
function loadImage(src, onload) {
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
ctx.font="30px sans-serif";
ctx.fillText("<%= full_name(#entry.user) %>", 60, 435);
ctx.fillStyle="#333333";
};
img.src = src;
return img;
}
</script>
In my head, the line ctx.drawImage(img1, 0, 0,); should position the image when the values of 0 and 0 are changed, but it does not. My Javascript knowledge isn't great, so there is probably something obvious, but I just can't figure it out. Any help would be greatly appreciated.
Thanks.
There are many styles of code used to preload images to insure they are fully loaded before you try to draw them using drawImage:
Here's annotated code and a Demo:
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// put the paths to your images in imageURLs[]
var imageURLs=[];
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/face1.png");
imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/multple/face2.png");
// the loaded images will be placed in imgs[]
var imgs=[];
var imagesOK=0;
startLoadingAllImages(imagesAreNowLoaded);
// Create a new Image() for each item in imageURLs[]
// When all images are loaded, run the callback (==imagesAreNowLoaded)
function startLoadingAllImages(callback){
// iterate through the imageURLs array and create new images for each
for (var i=0; i<imageURLs.length; i++) {
// create a new image an push it into the imgs[] array
var img = new Image();
imgs.push(img);
// when this image loads, call this img.onload
img.onload = function(){
// this img loaded, increment the image counter
imagesOK++;
// if we've loaded all images, call the callback
if (imagesOK>=imageURLs.length ) {
callback();
}
};
// notify if there's an error
img.onerror=function(){alert("image load failed");}
// set img properties
img.src = imageURLs[i];
}
}
// All the images are now loaded
// Do drawImage & fillText
function imagesAreNowLoaded(){
// the imgs[] array now holds fully loaded images
// the imgs[] are in the same order as imageURLs[]
ctx.font="30px sans-serif";
ctx.fillStyle="#333333";
// drawImage the first image (face1.png) from imgs[0]
// and fillText its label below the image
ctx.drawImage(imgs[0],0,10);
ctx.fillText("face1.png", 0, 135);
// drawImage the first image (face2.png) from imgs[1]
// and fillText its label below the image
ctx.drawImage(imgs[1],200,10);
ctx.fillText("face2.png", 210, 135);
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=350 height=300></canvas>
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);
}
}
I have a number of images within #mycontainer like:
<div id="mycontainer">
<img src="http://localhost:8080/images/my-image.png" />
…
</div>
I need to convert those into B/W. Pretty common task but I didn't find any solution for this that would just work for me — there is some problem with the actions execution.
What I have now is the following:
function grayscale(src) {
var ctx = document.createElement('canvas').getContext('2d'),
imgObj = new Image(),
pixels, i, n, gs, url;
// wait until the image has been loaded
imgObj.onload = function () {
ctx.canvas.width = this.width;
ctx.canvas.height = this.height;
ctx.drawImage(this, 0, 0);
pixels = ctx.getImageData(0, 0, this.width, this.height);
for (i = 0, n = pixels.data.length; i < n; i += 4) {
gs = pixels.data[i] * 0.3 + pixels.data[i+1] * 0.59 + pixels.data[i+2] * 0.11;
pixels.data[i] = gs; // red
pixels.data[i+1] = gs; // green
pixels.data[i+2] = gs; // blue
}
ctx.putImageData(pixels, 0, 0);
};
imgObj.src = src;
return ctx.canvas.toDataURL('image/png');
}
In general the actions are:
I supply src of an image to process,
wait for the image to be fully loaded,
draw the image on the canvas, convert the pixels and put the converted pixels back on the canvas
then I want the data URL of the resulting image from the canvas to be returned.
Right now, when in Developer Tools I am tring something like:
c = $('#mycontainer').find('img')[0];
grayscale(c.src);
I get back data URL of a fully transparent default 300px x 150px canvas as if that imgObj.onload() doesn't exist in the script at all.
Can anybody point me to a mistake here please?
Quick answer: Since you're using jQuery, you might look at the jQuery desaturate plugin, which might do what you need.
Longer answer in reference to your code - imgObj.onload is an asynchronous callback function, so it won't have executed by the time you reach your return statement. You'll need to execute any code that requires the post-onload data URL from inside the onload callback. One way to do this would be to have grayscale take a callback argument:
function grayscale(src, callback) {
// ... snip ...
// wait until the image has been loaded
imgObj.onload = function () {
// ... snip ...
ctx.putImageData(pixels, 0, 0);
// now fire the callback
callback(ctx.canvas.toDataURL('image/png'));
};
imgObj.src = src;
}
c = $('#mycontainer').find('img')[0];
grayscale(c.src, function(dataUrl) {
// further stuff with grayscale dataUrl
});