Javascript canvas how to drawImage a lot of images - javascript

How i can draw a lot of image in canvas?
I have a lot of images url array and need output it. How to do with good perfomance.
me example code (jsfiddle: http://jsfiddle.net/6sunguw4/):
$(document).ready(function () {
var bgCanvas = document.getElementById("bgCanvas");
var bgCtx = bgCanvas.getContext("2d");
bgCanvas.width = window.innerWidth;
bgCanvas.height = window.innerHeight + 200;
var array = new Array();
array[1] = 'https://developer.chrome.com/webstore/images/calendar.png';
array[2] = 'http://www.w3schools.com/html/html5.gif';
array[3] = 'http://www.linosartele.lt/wp-content/uploads/2014/08/images-9.jpg';
img0 = new Image();
img0.onload = function() {
bgCtx.drawImage(img0, 0,0, 100, 100);
}
img0.src = array[1];
img2 = new Image();
img2.onload = function() {
bgCtx.drawImage(img2, 100,0, 100, 100);
}
img2.src = array[2];
img3 = new Image();
img3.onload = function() {
bgCtx.drawImage(img3, 200,0,100,100);
}
img3.src = array[3];
});

Here's code to load all images from the URLs you put into your array without having to hand code 2000 times new Image/.onload/.drawImage (I call the array imageURLs in the example code below):
// image loader
// put the paths to your images in imageURLs[]
var imageURLs=[];
// push all your image urls!
imageURLs.push('https://developer.chrome.com/webstore/images/calendar.png');
imageURLs.push('http://www.w3schools.com/html/html5.gif');
imageURLs.push('http://www.linosartele.lt/wp-content/uploads/2014/08/images-9.jpg');
// the loaded images will be placed in imgs[]
var imgs=[];
var imagesOK=0;
loadAllImages(start);
function loadAllImages(callback){
for (var i=0; i<imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK>=imageURLs.length ) {
callback();
}
};
img.onerror=function(){alert("image load failed");}
img.crossOrigin="anonymous";
img.src = imageURLs[i];
}
}
function start(){
// the imgs[] array now holds fully loaded images
// the imgs[] are in the same order as imageURLs[]
bgCtx.drawImage(imgs[0], 000,0, 100, 100);
bgCtx.drawImage(imgs[1], 100,0, 100, 100);
bgCtx.drawImage(imgs[2], 200,0, 100, 100);
}
For better loading performance:
Browsers typically download only 6-8 files at a time from one domain. So your 2000 images would require 2000/8 == 250 separate sequential calls to your domain to load.
You can combine your 2000 into one or more spritesheets (preferably 6 or less spritesheets). That way the browser can download the 1-6 spritesheets containing your 2000 images in one trip.
You can use the extended version of context.drawImage to pull any desired image from the spritesheet. For example, assume an image you need is located at [200,200] on your spritesheet and is 100x100 in size. You can draw that image on your canvas like this:
bgCtx.drawImage(spritesheet,
200,200,100,100 // clip the image from the spritesheet at [200,200] with size 100x100
125,250,100,100 // draw the clipped image on the canvas at [125,250]
);

There's not much you can do with the code itself. drawImage seems pretty optimized, and it's the raw amount of images what could slow things down.
One thing you can maybe do, depending on your goal, is to prepare composite images. For example, those 3 images could be easily converted into a single PNG image, and then it would require only one drawImage call. However, if you plan to shift their places or some effects, I'm afraid you're stuck with what you have.

Related

Reduce File Size Of An Image In A File Upload Preview, Without Affecting The Size Of The File To Be Uploaded - JavaScript

I have some image preview functionality as part of a file uploader that shows the images prior to upload. Within this process the image previews use a htmlImageElement.decode() method that returns a promise so various frontend validations etc can happen on the images. This decode() method is run within a function that is called in a forEach() loop in relation to the files from a file <input> element.
The context
Even though I am limiting the number of files per upload to 10, because large files are allowed, if a user attaches 10 (large) files the image previewer is laggy, both in terms of image render, and also when any images are deleted from the previewer.
The Question
Is there anyway to reduce the file size of the image preview, without affecting the file size of the image to be uploaded?
You can add width and height parameters to the new Image() constructor, i.e. new Image(300,300), but these only affect the display size, not the file size, and if you alter the naturalHeight and naturalWidth properties, this changes the size of the file itself that is being uploaded, whereas what I want is just the preview file size to be smaller?
// this function is invoked in a forEach loop as part of a wider code block related to the individual files from a file <input> element
function showFiles(file) {
let previewImage = new Image();
// Set <img> src attribute
previewImage.src = URL.createObjectURL(file);
// get the original width and height values of the thumbnail using the decode() method
previewImage.decode().then((response) => {
// get image dimensions for validations
let w = previewImage.naturalWidth;
let h = previewImage.naturalHeight;
let imgs = document.querySelectorAll('img') // redeclare under new var name inside promise
}).catch((encodingError) => {
// Do something with the error.
});
} // end of showfiles(file)
A canvas can be used to create a resized clone of the original image, then use the canvas blob as source for preview:
// this function is invoked in a forEach loop as part of a wider code block related to the individual files from a file <input> element
function showFiles(file) {
let previewImage = new Image();
// Set <img> src attribute
previewImage.src = URL.createObjectURL(file);
// get the original width and height values of the thumbnail using the decode() method
previewImage.decode().then(() => {
// get image dimensions for validations
let w = previewImage.naturalWidth;
let h = previewImage.naturalHeight;
const W = w * 0.3, H = h * 0.3;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = W, canvas.height = H;
ctx.drawImage(previewImage, 0, 0, W, H);
canvas.toBlob((blob) => {
previewImage.src = URL.createObjectURL(blob);
document.body.append(previewImage);
});
}).catch((encodingError) => {
// Do something with the error.
});
} // end of showfiles(file)
images.addEventListener('change', (e) => {
[...e.target.files].forEach(showFiles);
});
<input id="images" type="file" multiple>

accessing texture after initial loading in pixi.js

I'm using the pixi.js render engine for my current project in javascript. I'm loading a spritesheet defined in json, using the assetloader. Problem is I need to create sprites or movieclips well after the onComplete event that the assetloader uses, finishes. However the texture cache seems to not be accessible after that point. Here is some of the code below demonstrating the problem I'm coming across.
var spriteSheet = [ "test.json" ];
loader = new PIXI.AssetLoader(spriteSheet); // only using the flappy bird sprite sheet as a test
loader.onComplete = OnAssetsLoaded;
loader.load();
function OnAssetsLoaded() {
var sprite = PIXI.Sprite.fromFrame("someFrame.png"); //this works
}
var sprite2 = PIXI.Sprite.fromFrame("someFrame2.png"); //This does not work, tells me "someFrame2" is not in the texture cache
The sprite sheet must be fully loaded before the images get stored in the cache. Once the Sprite sheet has loaded, those assets will exist in the cache until you delete them.
The reason your code above fails is because the line var sprite2... executes before the sprite sheet has finished loading.
This example will continuously add a new Sprite to the stage every second.
//build stage
var stage = new PIXI.Stage(0x66FF99);
var renderer = PIXI.autoDetectRenderer(400, 300);
document.body.appendChild(renderer.view);
//update renderer
requestAnimFrame( animate );
function animate() {
requestAnimFrame( animate );
renderer.render(stage);
}
//Flag will prevent Sprites from being created until the Sprite sheet is loaded.
var assetsReadyFlag = false;
//load sprite sheet
var loader = new PIXI.AssetLoader([ "test.json" ]);
loader.onComplete = function(){
assetsReadyFlag = true;
};
loader.load();
//Add a new bird every second
setInterval( addBird, 1000);
function addBird()
{
//assets must be loaded before creating sprites
if(!assetsReadyFlag) return;
//create Sprite
var bird = PIXI.Sprite.fromFrame("bird.png");
bird.anchor.x = bird.anchor.y = 0.5;
bird.x = Math.random() * stage.width;
bird.y = Math.random() * height;
stage.addChild(bird);
};

Drawing an image in javascript won't work when vars are global

Hey I want to draw multiple images, one a canvas background, two a player that can move on top of that canvas. Here is my question: When I put the var canvas and canvas properties globally it doesn't work. But when I put it back in the img.onload function it works!!! Why is this and what options do I have?
var canvas = document.getElementById("Canvas");
canvas.width = 500;
canvas.height = 500;
var g = canvas.getContext("2d");
var img = new Image();
img.src = "Images/Grass Texture.png";
var player1 = new Image();
player1.src = "Images/Players/Mignolet.png";
var player2 = new Image();
player2.src = "Images/Players/De Gea.png";
var football = new Image();
football.src = "Images/Football/Football.png";
img.onload = function()
{
/* The rest of the code draws onto the graphics context */
g.drawImage(img, 0, 0);
g.drawImage(img,1200, 1200, 1200, 1200);
};
It's important to understand that image loading is asynchronous. That means while your image is loading the code will continue despite.
If the browser is not able to load and decode the images in time there won't be any images to draw.
Even when you use the onload handler (which is necessary) you need to think about the asynchronous nature in the rest of the code - perhaps the code continues to other parts which will fail due to dependency on the images.
You can collect image loading like this:
...
/// if you have many images an array could be considered
var img = new Image();
var player1 = new Image();
var player2 = new Image();
var football = new Image();
/// number of images to load
var count = 4;
/// now share a load handler
img.onload = loadHandler;
player1.onload = loadHandler;
player2.onload = loadHandler;
football.onload = loadHandler;
/// set sources to start loading
img.src = "Images/Grass Texture.png";
player1.src = "Images/Players/Mignolet.png";
player2.src = "Images/Players/De Gea.png";
football.src = "Images/Football/Football.png";
function loadHandler() {
/// decrement the counter
count--;
/// when 0 all images has loaded
if (count === 0) startGame();
}
When all images has loaded the script will continue to startGame() which is also where you need to continue your game from:
function startGame() {
...
g.drawImage(img, 0, 0);
g.drawImage(img,1200, 1200, 1200, 1200);
/// Important! continue your code from here
nextStep();
}
So after invoking image loading by setting the sources your script does not call any other steps until startGame() is called.
Now, the loading can fail for various reasons. If you don't handle errors your game might just stop and you won't know why. During development you should of course always check the console for errors (see console, network etc. if any errors occur) but for a user this won't help much.
You should always add an error handler mechanism to the images as well. You can do this by using the onerror and onabort handlers - after setting the onload handler:
var hasErrors = false;
img.onerror = errorHandler;
player1.onerror = errorHandler;
player2.onerror = errorHandler;
football.onerror = errorHandler;
img.onabort = errorHandler; /// or use a separate abort handler
player1.onabort = errorHandler;
player2.onabort = errorHandler;
football.onabort = errorHandler;
In the error handler you can notify the user, or use a fallback, or anything that can possible solve the situation:
function errorHandler(err) {
/// set error flag
hasErrors = true;
/// notify user, post an error message etc. something..
console.log('Could not load image:', this.src, err);
/// call loadHandler() from here to decrement counter and
/// handle situation when all images has reported in for duty (or not)
loadHandler();
}
And then modify the loadHandler to be error aware (just an alert here, but replace with something more useful):
function loadHandler() {
/// decrement the counter
count--;
/// when 0 all images has loaded
if (count === 0) {
if (hasErrors) {
alert('Sorry mate! Error during image loading');
return;
}
startGame();
}
}
That being said, you can also do this by using an image loader that can handle all the setup and events (loading, errors, progress etc.) for you. These are made, incl. my own if I may, YAIL loader (and there are others), for the purpose of saving developers from a lot of problems and headache - and they are free. Something to consider..
Hope this helps!

Internet Explorer 9 sometimes draws blank images on canvas

I have a web application that generates images on the fly and then renders (segments of) them on the client using canvas - the general code looks something like this (simplified)...
var width = ...
var height = ...
var img = new Image();
img.onload = function(){ // Set onload before setting image source ;)
var canvas = document.createElement("canvas");
canvas.setAttribute('width',width);
canvas.setAttribute('height',height);
var context = canvas.getContext("2d");
context.drawImage(this,0,0,width,height,0,0,width,height);
}
img.src = ...
In firefox and chrome, this works fine. In IE9 (and sometimes IE10), this sometimes results in a blank image. As a workaround, I put in...
var width = ...
var height = ...
var img = new Image();
img.onload = function(){
var canvas = document.createElement("canvas");
canvas.setAttribute('width',width);
canvas.setAttribute('height',height);
var context = canvas.getContext("2d");
window.setTimeout(function(){
context.drawImage(img,0,0,width,height,0,0,width,height);
}, 10); // 10 millisecond delay here seems to give IE time to make sure the image is actually ready
}
img.src = ...
Obviously, putting in random delays is making me nervous - I would much rather figure out why this happens. Is there another event type / method / property on images that may be used? Could there perhaps be something in the http headers sent down with the images / the encoding / transfer method that is causing this?
Since drawing images on the canvas seems to be something that trips up new users a lot, searches mostly yield instances where the developer set the source before the onload method. The closest other question / reply I was able to find was this : https://stackoverflow.com/a/15186350/470062
Edit:
I have updated the examples to include width and height definitions at the start
Edit 29/Apr/2013:
The 10 millisecond delay is not always sufficient - I have changed the code to check the canvas pixels to determine if anything has been drawn - This feels like such an ugly hack though - I would love to know if there is a better way :
... snip - this is at the end of the image load callback..
if((navigator.appVersion.indexOf("MSIE")) >= 0){
function checkAndRedraw(){
context.clearRect(0,0,width,height);
context.drawImage(img, 0, 0, width, height);
var imageData = context.getImageData(0, 0, width, height),
data = imageData.data,
len = data.length;
for (var i = 0; i < len; i += 4){
if(data[i] || data[i+1] || data[i+2]){
scheduleRedraw = null; // prevent memory leak
return; // A pixel contained some non empty value so something was drawn - done.
}
}
scheduleRedraw();
}
var redrawHackTimeout;
function scheduleRedraw(){
window.clearTimeout(redrawHackTimeout);
redrawHackTimeout = window.setTimeout(checkAndRedraw, 500);
}
scheduleRedraw();
}
//END IE9 Hack
I have the same issue in IE9, what I had to do is this too:
theImage = new Image();
theImage.onload = function() {
that = this;
setTimeout(function() {
that.onload2();
}, 100);
};
theImage.onload2 = function(){
#your old load function code
}
theImage.src = 'http://....';
I don't understand why ie9 triggers load image function when it's not actually loaded or not "usable" yet in a canvas. it makes me pull my hair out.
var img = new Image();
img.src = ...
img.onload = function(){ // Set onload before after setting image source ;)
var canvas = document.createElement("canvas");
canvas.setAttribute('width',width);
canvas.setAttribute('height',height);
var context = canvas.getContext("2d");
context.drawImage(this,0,0,width,height,0,0,width,height);
}
You must set the src, then wait for the image to load.

How to reference this preloaded image

How would I reference an image preloaded from this javascript? This code comes from the question at
stackoverflow: preload hidden CSS images
<script language="JavaScript">
function preloader()
{
// create object
imageObj = new Image();
// set image list
images = new Array();
images[0]="image1.jpg"
imageObj.src=images[0];
}
</script>
It's a logical error in your code as pointed out by Matt H.
Instead creating a separate Image object for each individual images. You are just creating one object and keep changing the src of that object.
function preloader() {
// counter
var i = 0;
// set image list
images = new Array();
images[0] = "image1.jpg"
images[1] = "image2.jpg"
images[2] = "image3.jpg"
images[3] = "image4.jpg"
//create an array to hold all the Image objects
imageObjs = [];
// start preloading
for (i = 0; i <= 3; i++) {
var imageObj = new Image(); //create new Image object for each image
imageObj.src = images[i]; //set the src of new Image object to current image
imageObjs.push(imageObj); //add the current Image object to the array
}
}
In your code, you are depending on the browser cache as the "preloader". Only the last images[i] source is actually held in the imageObj variable. Just setting the src value of an image will pull it from the browser cache.
A proper method of preload would be to create an array of imageObj image objects. You still reference through `.src', but now you have image objects in Javascript memory rather than the browser cache.

Categories