0

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.

1 Answer 1

1

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);
}
Sign up to request clarification or add additional context in comments.

7 Comments

thank you @bobby for your explanation! you introduced a local scope: in which way a local scope is different from a callback function? (I refer to my attempt #3). Could you explain what does the syntax (image, i) at the end of a callback (image.onload) mean? More generally, when is this technique used respect to callbacks or promises?
The onload = function(image, i) {...}(image, i) syntax is creating and immediately calling an anonymous function. The value of the loop's image and i variables are passed into the anonymous function and stored in the anonymous function's scope. The anonymous function then creates and returns second anonymous function that get assigned as the onload call back function. This second anonymous function will use the image and i values from the first anonymous function's scope. This results in each onload call back function have its own image and i values.
A Google search for "javascript closure scope" will return numerous articles that provide a fuller description of this approach. For example: developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
In your callback example in the loadImages function, the for loop was setup incorrectly causing the loop to never run. If you correct the for loop then the example would work. Your addImage function serves the same purpose as the anonymous function served in my example.
If you do a Google search for "javascript promises images" then you will find articles that do better job of describing promises that I can (e.g. javascriptkit.com/javatutors/javascriptpromises.shtml). Basically, you can use an array of promises (one promise for each image file) to handle when an individual image file is loaded and then use Promise.all() function to handle when all the files are loaded.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.