Multiple images not load on canvas - javascript

I have created Circle using arc like following way.
var startAngle = 0;
var arc = Math.PI / 6;
var ctx;
var leftValue=275;
var topValue=300;
var wheelImg = ["http://i.stack.imgur.com/wwXlF.png","http://i.stack.imgur.com/wwXlF.png","cat.png","cock.png",
"croco.png","dog.png","eagle.png","elephant.png","lion.png",
"monkey.png","rabbit.png","sheep.png"];
function drawWheelImg()
{
var canvas = document.getElementById("canvas");
if(canvas.getContext)
{
var outsideRadius = 260;
var textRadius = 217;
var insideRadius = 202;
ctx = canvas.getContext("2d");
for(var i = 0; i < 12; i++)
{
var angle = startAngle + i * arc;
ctx.beginPath();
ctx.arc(leftValue, topValue, outsideRadius, angle, angle + arc, false);
ctx.arc(leftValue, topValue, insideRadius, angle + arc, angle, true);
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(leftValue, topValue, outsideRadius, angle, angle + arc, false);
ctx.shadowBlur=3;
ctx.shadowColor="#A47C15";
ctx.stroke();
ctx.closePath();
ctx.save();
ctx.translate(leftValue + Math.cos(angle + arc / 2) * textRadius,
topValue + Math.sin(angle + arc / 2) * textRadius);
ctx.rotate(angle + arc / 2 + Math.PI / 2);
var img = new Image();
img.src = wheelImg[i];
ctx.drawImage(img,-44, -25,50,40);
ctx.restore();
}
}
}
function spin()
{
drawWheelImg();
}
drawWheelImg();
<button class="btnSpin" onclick="spin();">Spin</button>
<canvas id="canvas" width="550" height="580"></canvas>
Problem is that it will not load when first page load. But when i click on spin button it will load all images.
Don't know what is the issue. Any help is greatly appreciated.
Note: Here in the question same issue is solve by img.onload function but that for only single image. If there is number of multiple images in array then it's not working.

You want to preload the images.
To do this you can start the loading at the bottom of the page just before the closing </dody> tag.
<script>
// an array of image URLs
var imageNames = ["image1.png", "image2.jpg", ...moreImageNames];
// an array of images
var images = [];
// for each image name create the image and put it into the images array
imageNames.forEach(function(name){
image = new Image(); // create image
image.src = name; // set the src
images.push(image); // push it onto the image array
});
</script>
In the code that uses the images you just need to check if they have loaded yet. To do this just check the image complete attribute.
// draw the first image in the array
if(images[0].complete){ // has it loaded
ctx.drawImage(images[0],0,0); // yes so draw it
}
Note that complete does not mean loaded. If you proved a bad URL or there is another error the complete flag will still be true. Complete means the browser has finished dealing with the image. You will have to add an onload event if there is a chance that images may fail.
Handling Errors
If you are unsure that an image will not load you will have to devise a strategy to deal with the missing content. You will need to answer questions like, Can my app function without the image? Are there alternative sources to get the image from? Is there a way to determine how often this is likely to happen? How do I stop this from happening?
On the simplest level you can flag an image as loaded by adding your own semaphore to the image object during the onload event
// an array of image URLs
var imageNames = ["image1.png", "image2.jpg", ...moreImageNames];
// an array of images
var images = [];
// function to flag image as loaded
function loaded(){
this.loaded = true;
}
// for each image name create the image and put it into the images array
imageNames.forEach(function(name){
image = new Image(); // create image
image.src = name; // set the src
image.onload = loaded; // add the image on load event
images.push(image); // push it onto the image array
});
Then in code you will check for the loaded semaphore before using the image.
// draw the first image in the array
if(images[0].loaded){ // has it loaded
ctx.drawImage(images[0],0,0); // yes so draw it
}
Critical content
If you have images that are required for you app to function then you should redirect to an error page if there is a problem loading the image. You may have several servers so you can also try the different sources before giving up.
If you need to stop the app or try an alternative URL you will have to intercept the onerror image event.
To simplify your use of the images (100% sure the images are loaded when that app runs) you should start the parts that use the images only when all the images are loaded. One way to do this is to count all the images that are being loaded, and count down as the images load. When the counter reaches zero you know all the images have loaded and you can then call you app.
The following will load images, if they fail it will try another server (source) until there are no more options at which point you should redirect to the appropriate error page to inform the client that a monkey has put a spanner in the works. It counts the loading images and when all the images have loaded then it will start the app running, sure in the knowledge that all the image content is safe to use.
// Server list in order of preferance
var servers = ["https://fakesiteABC.com/", "http://boogusplace.com/", "http://foobarpoo.com.au/"];
// an array of image URLs
var imageNames = ["image1.png", "image2.jpg", ...moreImageNames];
// an array of images
var images = [];
// loading count tracks the number of images that are being loaded
var loadingImageCount = 0;
// function to track loaded images
function loaded(){
loadingImageCount -= 1;
if(loadingImageCount === 0){
// call your application start all images have loaded
appStart();
}
}
// function to deal with error
function loadError(){ // the keyword "this" references the image that generated the event.
if(this.retry === undefined){ // is this the first error
this.retry = 1; // point to the second server
}else{
this.retry += 1; // on each error keep trying servers (locations)
}
// are the any other sources to try?
if(this.retry >= servers.length){ // no 11
// redirect to error page.
return;
}
// try a new server by setting the source and stripping the
// last server name from the previous src URL
this.src = servers[this.retry] + this.src.replace( servers[ this.retry - 1],"");
// now wait for load or error
}
// for each image name create the image and put it into the images array
imageNames.forEach(function(name){
image = new Image(); // create image
image.src = servers[0] + name; // set the src from the first server
image.onload = loaded; // add the image on load event
image.onerror = loadError; // add the image error handler
images.push(image); // push it onto the image array
loadingImageCount += 1; // count the images being loaded.
});
There are many other strategies for dealing with missing content. This just shows some of the mechanism used and does not define a perfect solution (there is none)

Related

Dom doesn't update right away when using convertToBlob on an Offscreen canvas

I have an application that needs to generate a couple thousand images. The way that I'm doing that is with a set of preloaded pngs (acting as transparent layers) and an offscreen canvas. I draw the images onto the canvas, convert it to a blob, and then write the image to a div using a custom class called Images.
I want to show a loading bar and clear old images, first, but there is a 3-5 second delay before the dom updates even though the "empty()" and "show()" code is at the beginning of the click request.
Is there something I'm doing wrong with regard to the asynchrony or promises that is causing the dom to not update immediately?
Here's some of the code:
// Generate Images
$("#generate_images").click(function(){
// Show loading bar
$("#progress_bar .progress-bar").css("width", "0%");
$("#progress_bar").show(0);
// Show loading spinner
$("#loading").show(0);
$("#images").empty();
console.log("Generating Images");
$.each(images, function(id, image){
// Sort traits (png layers)
var traits = Object.values(image.traits).sort((a, b) => {
return a.z_index - b.z_index;
});
images[id].layers = [];
$.each(traits, function(trait_idx, trait){
images[id].layers.push(preloaded_images[trait.variant_id])
});
});
// Create canvas
var canvas = new OffscreenCanvas(1200, 1200);
var context = canvas.getContext("2d");
console.log("Generating canvases.");
$.each(images, function(id, image){
// Clear the canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// Draw each image layer
$.each(images.layers, function(src, layer){
context.drawImage(layer, 0, 0);
});
// Add imageData to screen
canvas.convertToBlob().then(function(blob) {
// Do something with the blob like render to the screen
});
});
});
Any thoughts on making this more efficient would also be appreciated.
Generating a thousand images is not something a browser is necessarily meant to do, but you need to give the DOM a chance to update so add a timeout
Collect what you want to do in an array
Do not loop, but instead do
function generate() {
if (arrayCounter >= array.length) return
canvas.convertToBlob().then(function(blob) {
if (success) {
arrayCounter++
generate()
}
}
}
collect()
generate()

javascript canvas - check if images created with toDataURL() are loaded

I'm loading an image into my albImg array.
in my loop i then do this:
for(var j = 1; j < albImg.length; j++){
if(albImg[j].complete == true && albImg[j].width > 0){
loadedimages++;
}
}
to make sure all my images are loaded.
I then call my flipImg() function like this:
if(loadedimages == albImg.length-1){
flipImg();
}
I then flip an image and
ctx2.save();
ctx2.scale(-1, 1);
for (var i = RszSpriteCount; i < sprites.length; i++) {
ctx2.drawImage(albImg[sprites[i][0]], sprites[i][1], sprites[i][2], sprites[i][3], sprites[i][4], 0 - (sprites[i][1] + sprites[i][3]), sprites[i][2], sprites[i][3], sprites[i][4]);
}
ctx2.restore();
var flipSz = albImg.length;
albImg[flipSz] = new Image();
albImg[flipSz].src = cnv2.toDataURL();
Here's where my problem begins.
The new image I created - albImg[5] - can't be displayed until it is loaded.
But it is created as if it already is loaded.
That is to say that:
albImg[5].width is already set (to 750) before I can display it.
albImg[5].complete is set to true, before I can display it.
albImg[5].onload = ctx.drawImage(albImg[5], 0, 0); will try to draw the image before it is loaded.
How can I check if my flipped image really is loaded before I display it? in Javascript?
(due to circumstances I'm not using jQuery for this)
Please help.
Your main error is in how you do set the onload event handler :
albImg[5].onload = ctx.drawImage(albImg[5], 0, 0)
will set the return value of drawImage() (undefined) to the onload listener.
What you want is
albImg[5].onload = e => ctx.drawImage(albImg[5], 0, 0);
or
albImg[5].onload = function(){ ctx.drawImage(this, 0, 0) };
For the complete and width properties set to true, it's because while the loading of an Image is always async, in your case, the image is probably already HTTP cached.
Since the HTTP loading and the javascript execution are not executed on the same thread, it is possible that the Image actually loaded before the browser returns its properties.
But even then, the onload event will fire (best to set it before the src though).
var cacher = new Image();
cacher.onload = function(){
var img = new Image();
img.onload = function(){
console.log('"onload" fires asynchronously even when cached');
};
img.src = c.toDataURL();
console.log('cached : ', img.complete, img.naturalWidth);
}
cacher.src = c.toDataURL();
console.log('before cache :', cacher.complete, cacher.naturalWidth);
<canvas id="c"></canvas>
So when dealing with an new Image (not one in the html markup), always simply listen to its onload event.
Now, with the few information you gave in your question, it would seem that you don't even need these images, nor to deal with any of their loadings (except for the sprites of course), since you can directly and synchronously call ctx.drawImage(CanvasElement, x, y).
const ctx = c.getContext('2d');
ctx.moveTo(0, 0);
ctx.lineTo(300, 75);
ctx.lineTo(0, 150);
ctx.fillStyle = 'rgba(120,120,30, .35)';
ctx.fill();
const flipped = c.cloneNode(); // create an offscreen canvas
const f_ctx = flipped.getContext('2d');
f_ctx.setTransform(-1, 0,0,1, c.width, 0);// flip it
f_ctx.drawImage(c,0,0);// draw the original image
// now draw this flipped version on the original one just like an Image.
ctx.drawImage(flipped, 0,0);
// again in 3s
setTimeout(()=>ctx.drawImage(flipped, 150,0), 3000);
<canvas id="c"></canvas>

Why JavaScript code execute second part sometimes

I am draw background on canvas and also small images on that background. But sometimes, background draw on small images. Why ?
JavaScript code -
var canvasupdate = document.getElementById("myCanvas");
ctxupdate = canvasupdate.getContext("2d");
var background = new Image();
background.src = sitePath + "ATOM/chapter1/book/" + `bgimagename`;
background.onload = function() {
ctxupdate.drawImage(background, 0, 0); // set background image
};
var imageobj = new Array();
for (var d = 0; d < calloutImageArray.length; d++) // getting small images in array
{
imageobj[d] = new Image();
(function(d) {
imageobj[d].src = sitePath + "ATOM/chapter1/book/" + calloutImageArray[d];
imageobj[d].onload = function() {
ctxupdate.drawImage(imageobj[d], calloutImageArrayX[d], calloutImageArrayY[d], calloutImageArrayW[d], calloutImageArrayH[d]);
};
})(d);
}
In above code, background image code should be execute first then call out image(small image) code execute but some time execute small images code and then background image code why?
All the images are loaded asynchronously. So sometimes the small images are loaded (and drawn) before the background image. Please take a look into e.g. Network tab in Chrome in which order the resources load is done.
The simplest solution for this problem is to move the loading of small images into the callback function for load event of the background image.
The onLoad function runs asynchronously. That means JavaScript will continue to run the rest of your code and will run the callback function when the background image is loaded. That's why your second part of the code runs first. So instead try to add your code inside the onload function like this:
var canvasupdate = document.getElementById("myCanvas");
ctxupdate = canvasupdate.getContext("2d");
var background = new Image();
background.src = sitePath + "ATOM/chapter1/book/" + `bgimagename`;
background.onload = function() {
ctxupdate.drawImage(background, 0, 0); // set background image
var imageobj = new Array();
for (var d = 0; d < calloutImageArray.length; d++) // getting small images in array
{
imageobj[d] = new Image();
(function(d) {
imageobj[d].src = sitePath + "ATOM/chapter1/book/" + calloutImageArray[d];
imageobj[d].onload = function() {
ctxupdate.drawImage(imageobj[d], calloutImageArrayX[d], calloutImageArrayY[d], calloutImageArrayW[d], calloutImageArrayH[d]);
};
})(d);
}
};
that way you can be sure that the background will be set first and then your small images. Note that I didn't try your code to check if it does what you want, I just rearranged the code blocks to show you the logic and why does not run as you would expect.
Hope it helps

Canvas - image opacity loop (fade in)

guys. here is a bit of code that is supposed to be okay, though it doesn't work...heh...quite typical =)
function xxx() {
var txtCanvas = document.getElementById('text');
var textOne = txtCanvas.getContext('2d');
var alpha = 0.5;
textOne.globalAlpha = alpha;
// loading image
var img = new Image();
img.src = "http://tvangeste.com/gallery/selani/tv4_2.jpg"
img.onload = function () {
textOne.drawImage(img, 0, 0);
}
//end of image loader
if (alpha < 1)
{
alpha += 0.1;
}
}
requestAnimationFrame(xxx);
This is Fiddle to show how it doesn't work...
http://jsfiddle.net/gLs1owd6/
The script is supposed to do one simple thing - to fade in the image.
Any suggestions? Thanks!
You need a loop to be able to redraw the image at various opacity levels. For a loop you need something that doesn't block UI as well as refreshing with each monitor update, so, requestAnimationFrame to the rescue.
Here is one way to go about this:
var img = new Image();
img.onload = function () {
// when image has loaded we can use it.
// this is a self-invoking function used to fade in the image
(function loop() {
// we can update the property directly
textOne.globalAlpha += 0.01;
// as we have opacity involved we need to clear the canvas each time
textOne.clearRect(0, 0, txtCanvas.width, txtCanvas.height);
// redraw the image
textOne.drawImage(img, 0, 0);
// if not full opacity, loop (canvas will clamp the value for us)
if (textOne.globalAlpha < 1.0) {
requestAnimationFrame(loop);
}
else {
// when done, call the next step from here...
}
})();
}
// set source as last step for image loading
img.src = "http://tvangeste.com/gallery/selani/tv4_2.jpg"
Modified fiddle
Hope this helps!

jQuery and Canvas loading behaviour

After being a long time lurker, this is my first post here! I've been RTFMing and searching everywhere for an answer to this question to no avail. I will try to be as informative as I can, hope you could help me.
This code is for my personal webpage.
I am trying to implement some sort of a modern click-map using HTML5 and jQuery.
In the website you would see the main image and a hidden canvas with the same size at the same coordinates with this picture drawn into it.
When the mouse hovers the main picture, it read the mouse pixel data (array of r,g,b,alpha) from the image drawn onto the canvas. When it sees the pixel color is black (in my case I only check the RED value, which in a black pixel would be 0) it knows the activate the relevant button.
(Originally, I got the idea from this article)
The reason I chose this method, is for the page to be responsive and dynamically change to fit different monitors and mobile devices. To achieve this, I call the DrawCanvas function every time the screen is re-sized, to redraw the canvas with the new dimensions.
Generally, this works OK. The thing is ,there seems to be an inconsistent behavior in Chrome and IE(9). When I initially open the page, I sometimes get no pixel data (0,0,0,0), until i re-size the browser. At first I figured there's some loading issues that are making this happen so I tried to hack it with setTimeout, it still doesn't work. I also tried to trigger the re-size event and call the drawCanvas function at document.ready, still didn't work.
What's bothering me is most, are the inconsistencies. Sometimes it works, sometimes is doesn't. Generally, it is more stable in chrome than in IE(9).
Here is the deprecated code:
<script type="text/javascript">
$(document).ready(function(){setTimeout(function() {
// Get main image object
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
// Create a hidden canvas the same size as the main image and append it to main div
var canvas = document.createElement('canvas');
canvas.height = mapWrapper.clientHeight;
canvas.width = mapWrapper.clientWidth;
canvas.fillStyle = 'rgb(255,255,255)';
canvas.style.display = 'none';
canvas.id = 'hiddencvs';
$('#map_wrapper').append(canvas);
// Draw the buttons image into the canvas
drawCanvas(null);
$("#map_wrapper").mousemove(function(e){
var canvas = document.getElementById('hiddencvs');
var context = canvas.getContext('2d');
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
// Get pixel information array (red, green, blue, alpha)
var pixel = context.getImageData(x,y,1,1).data;
var red = pixel[0];
var main_img = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
if (red == 0)
{
...
}
else {
...
}
});
},3000);}); // End DOM Ready
function drawCanvas(e)
{
// Get context of hidden convas and set size according to main image
var cvs = document.getElementById('hiddencvs');
var ctx = cvs.getContext('2d');
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
cvs.width = mapWrapper.clientWidth;
cvs.height = mapWrapper.clientHeight;
// Create img element for buttons image
var img = document.createElement("img");
img.src = "img/main-page-buttons.png";
// Draw buttons image inside hidden canvas, strech it to canvas size
ctx.drawImage(img, 0, 0,cvs.width,cvs.height);
}
$(window).resize(function(e){
drawCanvas(e);
}
);
function findPos(obj)
{
...
}
</script>
I'd appreciate any help!
Thanks!
Ron.
You don't wait for the image to be loaded so, depending on the cache, you may draw an image or not in the canvas.
You should do this :
$(function(){
var img = document.createElement("img");
img.onload = function() {
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
...
// your whole code here !
...
}
img.src = "img/main-page-buttons.png";
});

Categories

Resources