How can I draw an image from the HTML5 File API on Canvas?
JavascriptCanvasDrawimageFileapiJavascript Problem Overview
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>
Javascript Solutions
Solution 1 - Javascript
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:
Solution 2 - Javascript
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.createObjectUrl
docs
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.
Solution 3 - Javascript
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>