OdeToCode IC Logo

Uploading Captured Canvas Images

Monday, January 7, 2013

In "Capturing HTML 5 Video To An Image", Chris asked if we could upload a captured image to the server.

Yes we can!

We will use the same markup from the previous post, but make a simple change to the script. Instead of creating an image on the client, we'll send the image to the server by passing the dataURL generated from the canvas.

(function() {
    "use strict";
    var video, $output;
    var scale = 1;

    var initialize = function() {
        $output = $("#output");
        video = $("#video").get(0);
        $("#capture").click(captureImage);                
    };

    var captureImage = function() {
        var canvas = document.createElement("canvas");
        canvas.width = video.videoWidth * scale;
        canvas.height = video.videoHeight * scale;
        canvas.getContext('2d')
              .drawImage(video, 0, 0,
                         canvas.width, canvas.height);

        $.post("/image/upload", { dataUrl: canvas.toDataURL() })
         .done(showImage);
    };

    var showImage = function(url) {
        var img = document.createElement("img");
        img.src = url;
        $output.prepend(img);
    };

    $(initialize);            

}());

On the server we need to parse the incoming data URL. The data URL for an image will contain base 64 encoded bits, like the following:

data:image/png;base64,F12VBORw0KGgoAAAAN...==

The ImageDataUrl class will use a regex to pick apart the string and decode the bytes inside.

public class ImageDataUrl
{
    public ImageDataUrl(string dataUrl)
    {            
        var match = _regex.Match(dataUrl);
        MimeType = match.Groups["mimeType"].Value;
        Format = match.Groups["mimeSubType"].Value;
        Bytes = Convert.FromBase64String(match.Groups["data"].Value);
    }

    public byte[] Bytes { get; protected set; }
    public string MimeType { get; set; }
    public string Format { get; protected set; }
    
    public string SaveTo(string folder)
    {
        var fileName = Guid.NewGuid().ToString() + "." + Format;
        var fullPath = Path.Combine(folder, fileName);

        using(var file = File.OpenWrite(fullPath))
        {
            file.Write(Bytes, 0, Bytes.Length);
        }
        return fileName;
    }

    private static readonly Regex _regex = new Regex(
        @"data:(?<mimeType>[\w]+)/(?<mimeSubType>[\w]+);\w+,(?<data>.*)",
        RegexOptions.Compiled
    );
}   

The ImageDataUrl class can also save the decoded bytes to disk, so all we need to do inside of a controller action is use the class, and return a URL to the newly saved image.

[HttpPost]
public string Upload(string dataUrl)
{
    var image = new ImageDataUrl(dataUrl);
    var fileName = image.SaveTo(Server.MapPath("~/uploads"));
    var url = Url.Content("~/uploads/" + fileName);
    return url;
}

That's one approach to uploading the image.