Search code examples
javascriptcanvasdrawimage

javascript canvas drawimage


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.


Solution

  • 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: