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.

user305883
  • 1,635
  • 2
  • 24
  • 48

1 Answers1

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);
}
Bobby Orndorff
  • 3,265
  • 1
  • 9
  • 7
  • 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? – user305883 Dec 04 '15 at 10:27
  • 1
    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. – Bobby Orndorff Dec 04 '15 at 16:00
  • 1
    A Google search for "javascript closure scope" will return numerous articles that provide a fuller description of this approach. For example: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures – Bobby Orndorff Dec 04 '15 at 16:03
  • 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. – Bobby Orndorff Dec 04 '15 at 16:27
  • I tried to wrap everything within a function, and would like to return canvas element when everything has been drawn. I would like to chain multiple drawing functions and return when everything is ready. I followed this answer [http://stackoverflow.com/a/11208459/305883] to notify canvas being updated *at each* element, but I am not able to chain functions with a `.then(function(canvas))` structure so to pass canvas through drawing functions and *return only after last one being drawn*. Could you please @Bobby indicate your approach or suggest a scaffold to structure the code neatly? – user305883 Dec 07 '15 at 18:29
  • 1
    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. http://www.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. – Bobby Orndorff Dec 08 '15 at 05:11
  • I learnt it is possible to store promises in array to: "This makes it easy to do something after all of the asynchronous tasks have completed, instead of after each task." - from the link you posted. Exactly what I was looking at, thank you again. – user305883 Dec 08 '15 at 09:28