How can I draw an image from the HTML5 File API on Canvas?
Asked Answered
S

3

61

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>
Stainless answered 21/7, 2011 at 12:8 Comment(0)
H
98

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:

Hull answered 21/7, 2011 at 12:29 Comment(14)
Please attach onload before src. The load event can trigger between those two lines. (If the image is cached, etc)Mcgrath
@Raynos: Thanks. (Though the file must have been loaded extremely super fast in that case...)Hull
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)Mcgrath
@Raynos: I'm not setting the src of a File. Rather there are ready-to-go Files inside e.target.files available.Hull
img.src = event.target.result; your setting the .src of an Image to the File/Blob in resultMcgrath
Thanks, this also works, but only online in Chrome, not if I have the .html file locally.Stainless
@Raynos: My fault, I misread. It looks like this answers your doubt: 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.Hull
@Jonas: Won't be fixed: code.google.com/p/chromium/issues/detail?id=60889.Hull
@Hull thanks for clearing that up. I was looking for that :)Mcgrath
@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.Wixted
@Wixted 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 srcMcgrath
@Raynos, if you've got an example that can show this effect i'd be interested to see it.Wixted
@Wixted 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 however I don't have a copy of IE lying around.Mcgrath
@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.Wixted
M
17

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.

Mcgrath answered 21/7, 2011 at 12:29 Comment(4)
This works, but only online. If I try this with a local .html file in Chrome it doesn't work.Stainless
The FileReader approach didn't work locally neither... Thanks.Stainless
@Stainless 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.Mcgrath
@Stainless Seems like the obscure flag is --allow-file-access-from-files. for chrome. ReferenceMcgrath
B
6

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>
Bobbiebobbin answered 13/10, 2015 at 21:5 Comment(3)
Answers should show code in the question, not only in an external link.Autopilot
@HereticMonkey I don't think that option was available on stackO back in 2015 😉, but I added the code.Bobbiebobbin
I was on Stack Overflow back in 2015. You could add code to your answer back then too. Maybe not in a Snippet, but you could definitely add code :P.Autopilot

© 2022 - 2024 — McMap. All rights reserved.