I am working on JavaScript code that will show a preview of a local image file before it is uploaded. A web page has a preview area that uses an HTML5 canvas for the preview so that the image can be scaled to fit in the area while keeping the original aspect ratio.

See the test page now at www.rectorsquid.com/previewtest.php.

The problems I faced while writing this code are:

How do I use a FileReader object in JavaScript?
How do I manage an image object in JavaScript?

This is a short list but a serious one.

I obtained some code and went about experimenting. The issues that cropped up along the way were:

Documentation about the FileReader is vague and online examples were all a little different.
Images are not available until after the browser has read the file. Width and height seemed set to zero when I first got the code working a little.

I finally found solutions to what seems like all of the problems I first encountered. First the HTML:

...
<div id="previewcanvascontainer">
    <canvas id="previewcanvas">
    </canvas>
</div>

<form action="/uploadfile.php" enctype="multipart/form-data" method="post">
    <input type="file" onchange="return ShowImagePreview( this.files );" />
</form>
...

The HTML is quite simple. There is a canvas element and a form with an upload button. The onchange attribute of the file upload input will get called whenever a file is selected. I have not tried to implement drag and drop yet so an upload form field is all that is available right now.

There is some CSS to make the canvas a reasonable width but it is not worth showing. Create a style with a width and height that fits your needs.

The JavaScript code is a bit more complicated but not so much:

function ShowImagePreview( files )
{
    if( !( window.File && window.FileReader && window.FileList && window.Blob ) )
    {
      alert('The File APIs are not fully supported in this browser.');
      return false;
    }

    if( typeof FileReader === "undefined" )
    {
        alert( "Filereader undefined!" );
        return false;
    }

    var file = files[0];

    if( !( /image/i ).test( file.type ) )
    {
        alert( "File is not an image." );
        return false;
    }

    reader = new FileReader();
    reader.onload = function(event) 
            { var img = new Image; 
              img.onload = UpdatePreviewCanvas; 
              img.src = event.target.result;  }
    reader.readAsDataURL( file );
}

function UpdatePreviewCanvas()
{
    var img = this;
    var canvas = document.getElementById( 'previewcanvas' );

    if( typeof canvas === "undefined" 
        || typeof canvas.getContext === "undefined" )
        return;

    var context = canvas.getContext( '2d' );

    var world = new Object();
    world.width = canvas.offsetWidth;
    world.height = canvas.offsetHeight;

    canvas.width = world.width;
    canvas.height = world.height;

    if( typeof img === "undefined" )
        return;

    var WidthDif = img.width - world.width;
    var HeightDif = img.height - world.height;

    var Scale = 0.0;
    if( WidthDif > HeightDif )
    {
        Scale = world.width / img.width;
    }
    else
    {
        Scale = world.height / img.height;
    }
    if( Scale > 1 )
        Scale = 1;

    var UseWidth = Math.floor( img.width * Scale );
    var UseHeight = Math.floor( img.height * Scale );

    var x = Math.floor( ( world.width - UseWidth ) / 2 );
    var y = Math.floor( ( world.height - UseHeight ) / 2 );

    context.drawImage( img, x, y, UseWidth, UseHeight );  
}

The ShowImagePreview() function will get called when the file input is changed. The files list is passed in when it is called and the list contains file information for each selected file. I only support a single file to simplify things right now. Multiple files require a loop and a unique way to preview the images but nothing else. Multiple canvases or a canvas drawing function that uses multiple images would do the trick.

The important part of ShowImagePreview() is setting the FileReader.onload() function correctly. A new function is created and the first operation creates a new image object.

The second thing that is done is the onload function is set. Be careful not to call the function by including the parentheses. I made that mistake the first time I wrote this post. I passed the img object to the function and it somehow worked depending on the timing of things. After the onload function is set correctly, set the .src value based on the FileReader results.

The UpdatePreviewCanvas() function is not too complicated. It sets the canvas width and height to value that match the canvas style width and height. This creates a 1 to 1 relationship between the pixels on the screen and the virtual pixels on the canvas. In other words, a canvas has two width and two height values. One set is the set that controls how things are rendered and the other is the actual screen width and height in pixels. For me, I always set them the same.

I recently (10/15/2012) updated and improved the test page for this code. The test page uses another interesting feature documented in this post, that styles the button used to browse the file system for images. The test page for this code can be found at www.rectorsquid.com/previewtest.php.