60

I would like to draw an image opened with the HTML5 File API on a canvas.

In the handleFiles(e) method, I can access the File with e.target.files[0] but I can't draw that image directly using drawImage. How do I draw an image from the File API on HTML5 canvas?

Here is the code I have used:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script>
window.onload = function() {
    var input = document.getElementById('input');
    input.addEventListener('change', handleFiles);
}

function handleFiles(e) {
    var ctx = document.getElementById('canvas').getContext('2d');
    ctx.drawImage(e.target.files[0], 20,20);
    alert('the image is drawn');
}
</script>
</head>
<body>
<h1>Test</h1>
<input type="file" id="input"/>
<canvas width="400" height="300" id="canvas"/>
</body>
</html>
Jonas
  • 121,568
  • 97
  • 310
  • 388

3 Answers3

96

You have a File instance which is not an image.

To get an image, use new Image(). The src needs to be an URL referencing to the selected File. You can use URL.createObjectURL to get an URL referencing to a Blob (a File is also a Blob): http://jsfiddle.net/t7mv6/86/.

var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image;
img.onload = function() {
    ctx.drawImage(img, 20,20);
    alert('the image is drawn');
}
img.src = URL.createObjectURL(e.target.files[0]);

Note: be sure to revoke the object url when you are done with it otherwise you'll leak memory. If you're not doing anything too crazy, you can just stick a URL.revokeObjectURL(img.src) in the img.onload function.

References:

Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
pimvdb
  • 151,816
  • 78
  • 307
  • 352
  • 10
    Please attach `onload` before `src`. The `load` event can trigger between those two lines. (If the image is cached, etc) – Raynos Jul 21 '11 at 12:31
  • @Raynos: Thanks. (Though the file must have been loaded extremely super fast in that case...) – pimvdb Jul 21 '11 at 12:32
  • Out of curiosity could you reference where it says "setting `.src` to a Blob/File" should work in the HTML5 spec? I have a slight snag that this might be a proprietary extension. It happens to work in Chrome / FF6 (Don't have opera, IE10 or Saf nightlies around to test) – Raynos Jul 21 '11 at 12:54
  • @Raynos: I'm not setting the `src` of a `File`. Rather there are ready-to-go `File`s inside `e.target.files` available. – pimvdb Jul 21 '11 at 13:00
  • `img.src = event.target.result;` your setting the `.src` of an Image to the `File/Blob` in `result` – Raynos Jul 21 '11 at 13:03
  • Thanks, this also works, but only online in Chrome, not if I have the .html file locally. – Jonas Jul 21 '11 at 13:08
  • 1
    @Raynos: My fault, I misread. It looks like this answers your doubt: http://www.w3.org/TR/FileAPI/#dfn-readAsDataURL. `Set the result attribute to be blob's data content represented as a Data URL [DataURL]; on getting, the result attribute returns the (complete) data of blob as a Data URL`. Obviously the Data URL can be set to the src of an `Image`; this will load fine. – pimvdb Jul 21 '11 at 13:08
  • 2
    @Jonas: Won't be fixed: http://code.google.com/p/chromium/issues/detail?id=60889. – pimvdb Jul 21 '11 at 13:10
  • @pimvdb thanks for clearing that up. I was looking for that :) – Raynos Jul 21 '11 at 13:16
  • @Raynos, it is my understanding that the `onload` event cannot be fired until *after* the currently executing script has finished, at which point the `onload` event will have been bound, which means that there is no issue with the order in which the `src` and `onload` attributes are set. – zzzzBov Aug 15 '11 at 22:32
  • @zzzzBov implementation specific. Some browsers are known to fire `onload` immediately when you set `.src` because it synchronously checks the cache and loads immediately. You need really thorough stress testing to see what is "safe" or always set `onload` before `src` – Raynos Aug 15 '11 at 22:48
  • @Raynos, if you've got an example that can show this effect i'd be interested to see it. – zzzzBov Aug 15 '11 at 23:47
  • @zzzzBov this is known to happen for `Image` in IE when the src is an URL. I don't know how IE10 handles blob urls but I'd rather just set `onload` before `src` then worry about this. [Test](http://jsfiddle.net/puksP/1/) however I don't have a copy of IE lying around. – Raynos Aug 16 '11 at 08:05
  • @Raynos, very interesting indeed. I could get the issue to crop up in IE9 compatibility mode, but I can't easily check IE7&8 to see where the issue lies (IE9 compat mode is similar to IE7, but not identical). Either way, I've learned something new. – zzzzBov Aug 16 '11 at 13:18
16

Live Example

function handleFiles(e) {
    var ctx = document.getElementById('canvas').getContext('2d');
    var url = URL.createObjectURL(e.target.files[0]);
    var img = new Image();
    img.onload = function() {
        ctx.drawImage(img, 20, 20);    
    }
    img.src = url;   
}

window.URL.createObjectUrldocs

You could also use the FileReader instead to create the object URL.

The FileReader has slightly better browser support.

The FileReader approach works in FF6 / Chrome. I'm not certain whether setting Img.src to a Blob is valid and cross-browser though.

Creating object urls is the correct way to do it.

Edit:

As mentioned in the commment window.URL support whilst offline seems unavailable in FF6/Chrome.

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • This works, but only online. If I try this with a local .html file in Chrome it doesn't work. – Jonas Jul 21 '11 at 13:02
  • The FileReader approach didn't work locally neither... Thanks. – Jonas Jul 21 '11 at 13:10
  • @Jonas that may be a permissions thing. I wouldn't be suprised if they are blocked by default. Turning it on is probably finding some obscure flag. – Raynos Jul 21 '11 at 13:11
  • 2
    @Jonas Seems like the obscure flag is `--allow-file-access-from-files.` for chrome. [Reference](http://code.google.com/p/chromium/issues/detail?id=60889#hc4) – Raynos Jul 21 '11 at 13:14
5

Here is a complete example (Fiddle) using FileReader (which has better browser support as mentioned by Raynos). In this example I also scale Canvas to fit the image.

In real life example you might scale the image to some maximum so that your form will not blow up ;-). Here is an example with scaling (Fiddle).

var URL = window.webkitURL || window.URL;

window.onload = function() {
    var input = document.getElementById('input');
    input.addEventListener('change', handleFiles, false);
    
    // set original canvas dimensions as max
    var canvas = document.getElementById('canvas');
    canvas.dataMaxWidth = canvas.width;
    canvas.dataMaxHeight = canvas.height;
}

function handleFiles(e) {
    var ctx = document.getElementById('canvas').getContext('2d');
    var reader  = new FileReader();
    var file = e.target.files[0];
    // load to image to get it's width/height
    var img = new Image();
    img.onload = function() {
        // setup scaled dimensions
        var scaled = getScaledDim(img, ctx.canvas.dataMaxWidth, ctx.canvas.dataMaxHeight);
        // scale canvas to image
        ctx.canvas.width = scaled.width;
        ctx.canvas.height = scaled.height;
        // draw image
        ctx.drawImage(img, 0, 0
            , ctx.canvas.width, ctx.canvas.height
        );
    }
    // this is to setup loading the image
    reader.onloadend = function () {
        img.src = reader.result;
    }
    // this is to read the file
    reader.readAsDataURL(file);
}

// returns scaled dimensions object
function getScaledDim(img, maxWidth, maxHeight) {
    var scaled = {
        ratio: img.width / img.height,
        width: img.width,
        height: img.height
    }
    if (scaled.width > maxWidth) {
        scaled.width = maxWidth;
        scaled.height = scaled.width / scaled.ratio;
    }
    if (scaled.height > maxHeight) {
        scaled.height = maxHeight;
        scaled.width = scaled.height / scaled.ratio;
    }
    return scaled;
}
canvas {
    border:1px solid black
}
<input type="file" id="input"/>
<div>
    <canvas width="400" height="300" id="canvas"/>
</div>
Nux
  • 9,276
  • 5
  • 59
  • 72