0

I try to draw several images in a canvas. I have an issue because sometimes all the images are drawn and sometimes several are missing. I try on chrome and firefox.

There is the code:

 for (i=0; i<tileList.length; i++)
{

   // var img_src = new Image();
   var img_src = document.createElement("img");
   var c = tileList[i].y ;
   var r = tileList[i].x;
   img_src.onload = function (){
        ctx.drawImage(img_src, r * tileSize, c * tileSize, tileSize * tileList[i].qw, tileSize * tileList[i].qh);
    }

    img_src.src = './viewer/images/'+path+'/LOD'+glod+'/tiles_'+ c + '_' + r +'.jpeg';

I try both new Image() and document.createElement("img"), the result is the same.

Lilice
  • 47
  • 5
  • Check `.onerror` event... – Rayon Jun 23 '16 at 06:57
  • [_Create a Minimal, Complete, and Verifiable example_](http://stackoverflow.com/help/mcve) – Rayon Jun 23 '16 at 07:09
  • This is a closure problem. Some suggestions here: https://stackoverflow.com/questions/17578280/how-to-pass-parameters-into-image-load-event/27250713#27250713 –  Jun 23 '16 at 07:58

1 Answers1

0

This is a closure problem. As the image loading is asynchronous the values, still referenced to the parent scope from inside the handler, does no longer contain the values you'd expect them to hold.

For this you would need a way to hold on to those value until the image has properly loaded. You could use closure, or as here binding -

function handler() {
  var i = this.i;
  ctx.drawImage(this.img_src, 
                this.r * tileSize, 
                this.c * tileSize, 
                tileSize * tileList[i].qw, tileSize * tileList[i].qh);
}

Notice it takes an object reference. Inside the loop we can now do:

for (var i = 0; i < tileList.length; i++) {
   var o = {
     img_src: new Image,
     c: tileList[i].y,
     r: tileList[i].x,
     i: i
   };   
   o.img_src.onload = handler.bind(o);  // bind this object (o)
   o.img_src.src = './viewer/images/'+path+'/LOD'+glod+'/tiles_'+ c + '_' + r +'.jpeg';
}

So we create a new object instance inside the loop. We store the data we need for later. Then we set the handler but bound to the object which will keep it in memory until we no longer reference it. Binding it also allows us to use this to reference the original object we created for the handler.

This is a clean approach, does not need anonymous functions and does not hamper the image object itself.

Conceptual code example

// conceptual example
var ctx = c.getContext("2d"),
    list = [  // pseudo list serving this example
      {x:2, y:5, src: "//i.imgur.com/kPX1264b.jpg"},
      {x: 160, y:7, src: "//i.imgur.com/IgPTywZb.jpg"}
    ];

function handler() {
  console.log(this.i, this.r, this.c, this.img_src.src);
  ctx.drawImage(this.img_src, this.r, this.c);
}

for (i=0; i < list.length; i++) {
  var o = {
    img_src: new Image,
    c: list[i].y,
    r: list[i].x,
    i: i
  };   
  o.img_src.onload = handler.bind(o);  // bind this object (o)
  o.img_src.src = list[i].src;
}
<canvas id=c></canvas>

Also see this thread for other approaches.

Resources:

Community
  • 1
  • 1
  • Thank you, It's work ! Do you know where I can find information about Closures ? – Lilice Jun 23 '16 at 08:28
  • @alind no problem! More information about closures can be found here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures –  Jun 23 '16 at 08:31
  • @alind and bind() (as used as solution here): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind –  Jun 23 '16 at 08:32