Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I know when HTML5 Canvas finished drawing?

I'm a beginner to javascript. I'm trying to resize images on client side before uploading them to my server for storage I have found a way to do it using HTML5 Canvas: http://hacks.mozilla.org/2011/01/how-to-develop-a-html5-image-uploader/

My problem: It kept on uploading blank image. I think the reason for the blank image is that canvas is not done drawing when I grabbed the image from canvas. When I set a 3 second windows.timeout before grabbing image from canvas everything works fine. Is there a more reliable method than a timeout? Or is there any other method to do client-side image resizing?

The reason I'm doing client-side image resizing is because I'm dealing with people who uploads unnecessary big files and I think it is most efficient to do a client side image resizing.

Thank you for any hints/answers!

//resize image
Resizeimage.resize(file);

//***if i wrap this part with timeout it works
var canvas = document.getElementById('canvas');
var data = canvas.toDataURL(file.type);

// convert base64/URLEncoded data component to raw binary data held in a string
var bytestring;
if (data.split(',')[0].indexOf('base64') >= 0)
  bytestring = atob(data.split(',')[1]);
else
  bytestring = escape(data.split(',')[1]);

//get imagetype
var mimeString = data.split(',')[0].split(':')[1].split(';')[0];

// write the bytes of the string to a typed array
var ia = new Uint8Array(bytestring.length);
for (var i = 0; i < bytestring.length; i++) {
  ia[i] = bytestring.charCodeAt(i);
}

// create a blob from array
var blob = new Blob([ia], {
  type: mimeString
});

//upload files
$scope.upload = $upload.upload({
  url: 'articles/imageUpload',
  method: 'POST',
  file: blob
    //update progress bar
}).progress(function(event) {
  $scope.uploadInProgress = true;
  $scope.uploadProgress = Math.floor(event.loaded / event.total * 100);
  //file upload success
}).success(function(data, status, headers, config) {
  $scope.uploadInProgress = false;
  $scope.uploadedImage = JSON.parse(data);
  $scope.isDisabled = false;
  //file upload fail
}).error(function(err) {
  $scope.uploadInProgress = false;
  console.log('Error uploading file: ' + err.message || err);
});
//***

angular.module('articles').factory('Resizeimage', [
  function() {

    var imgSelectorStr = null;

    // Resizeimage service logic
    function render(src) {
      var image = new Image();

      image.onload = function() {
        var canvas = document.getElementById('canvas');
        
        //check the greater out of width and height
        if (image.height > image.width) {
          image.width *= 945 / image.height;
          image.height = 945;
        } else {
          image.height *= 945 / image.width;
          image.width = 945;
        }

        //draw (&resize) image to canvas
        var ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        canvas.width = image.width;
        canvas.height = image.height;
        ctx.drawImage(image, 0, 0, image.width, image.height);

      };
      image.src = src;
    }

    function loadImage(src) {
      // Create fileReader and run the result through the render
      var reader = new FileReader();

      reader.onload = function(e) {
        render(e.target.result);
      };
      reader.readAsDataURL(src);
    }

    // Public API
    return {
      resize: function(src) {
        loadImage(src);
        return true;
      }
    };
  }
]);
like image 203
Danil Avatar asked Nov 09 '22 20:11

Danil


1 Answers

Your problem is quite a simple one.

JavaScript will run one statement after another (and you need this because you want all statements executed when grabbing the image) as long as you dont attach to any events.

If you attach your code to an event (image.onload for example) this part of your code will wait for the event and the rest of your code will continue asynchronous.

So you are right - the image isn't drawn when you try to grab this.

Its bad style to solve it by a timeout (most inefficient/insecure) better solve this by wrapping your image-grabbing code in a function and run this function after your image re-size is done. It's like a callback you use to synchronize the asynchronous event.

Example

/* setTimeout will behave like an event */
setTimeout(
  function(){
    $('console').append("<div>SetTimeout</div>");
     SynchCodeAfterTimeout();
  }, 1000);

AsynchCodeAfterTimeout();

function AsynchCodeAfterTimeout(){
    $('console').append("<div>AsynchCodeAfterTimeout</div>");
}

function SynchCodeAfterTimeout(){
    $('console').append("<div>SynchCodeAfterTimeout</div>");
}
console{
  font-family: Consolas, monaco, monospace;
}
<script type="text/javascript" src="https://code.jquery.com/jquery-latest.min.js"></script>

<console></console>

Example with your code

render();

// Resizeimage service logic
function render() {
  var image = new Image();

  image.onload = function() {
    var canvas = document.getElementById('canvas');

    //check the greater out of width and height
    if (image.height > image.width) {
      image.width *= 945 / image.height;
      image.height = 945;
    } else {
      image.height *= 945 / image.width;
      image.width = 945;
    }

    //draw (&resize) image to canvas
    var ctx = canvas.getContext('2d');
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    canvas.width = image.width;
    canvas.height = image.height;
    ctx.drawImage(image, 0, 0, image.width, image.height);
    /*timeout not needed its just there so the browser renders the canvas contents so you can see them when the alert pops up*/
    setTimeout(function() {
      alert('yey all the drawing is done (this is synchronous)');
    });
  };
  image.src = 'https://loremflickr.com/320/240';
}
<canvas id="canvas"></canvas>
like image 140
Mx. Avatar answered Nov 14 '22 23:11

Mx.