Windows 8: Sharing a HTML5 canvas as an image

Recently, I was working on a Win8 Javascript app where I drawing something on an HTML canvas. One of the requirements was that the user should be able to share the contents of the canvas as an image. The toDataURL function in HTML5 easily converts the content of the canvas to an image, so I thought how hard could it be to share this image. Boy, was I wrong. The toDataURL function returns a base64 encoded image, which needs to be decoded and then sent through the share contract. Here is how an encoded PNG image returned from toDataURL looks like:

1
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAA..."

If you want to save this image to a file, you will need to base64 decode the image. The image here is the part that is present after you strip out the “data:image/png;base64,” part. Once you have base64 decoded the image, you have two ways of sharing this image:

  1. You can save the data as bytes inside a file. Name the file with a PNG extension and then later share that file.
  2. Or you could try to share the image buffer across directly. I went through this route because I reasoned, why would I want to save the file to disk and read it back to memory, when I already have it in memory beforehand. In my smugness, I thought I could just manipulate the buffer to be a stream and then point the resource map to the stream.

Option 1 worked pretty well so I was quite confident that I will get option two working in 20 minutes tops. It… well, it took 0ver a month of trying this and that and asking around before I found the solution. And without further ado, I am presenting it here.

In the ready handler of your page, register the share handler:

1
2
var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView();
dataTransferManager.addEventListener("datarequested", dataRequested);

Here is the share event handler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
    function dataRequested(e) {
        var request = e.request;

        var dataPackage = new Windows.ApplicationModel.DataTransfer.DataPackage();
        dataPackage.properties.title = "title";
        dataPackage.properties.description = "This is description";
        var deferral = request.getDeferral();

        // create an html fragment
        var safeHtml = Windows.ApplicationModel.DataTransfer.HtmlFormatHelper.createHtmlFormat("<div><img src='shareImage.png' /></div>");

        // You can get a HTML5 canvas as a png image by calling toDataURL("image/png"). The string you get back looks like the following
        // "data:image/png;base64,iVBORw0K..."
        // Once you have parsed out the "data:image/png;base64," part, what is left is the actual png image in base64 encoded form. which can be used
        // as shown below.

       
        var imgData = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String("iVBORw0K... - Replace this with a real base64 image string");

        // You can uncomment this line (and comment the above) to see a whole flow of converting a canvas to an image.
        //var imgData = Windows.Security.Cryptography.CryptographicBuffer.decodeFromBase64String(getImageDataFromCanvas());

        var memoryStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
        var dataWriter = new Windows.Storage.Streams.DataWriter(memoryStream);
        dataWriter.writeBuffer(imgData);

        dataWriter.storeAsync().done(function () {
            dataWriter.flushAsync().done(function () {
                var imgStream = dataWriter.detachStream();
                imgStream.seek(0);
                var streamReference = Windows.Storage.Streams.RandomAccessStreamReference.createFromStream(imgStream);

                dataPackage.resourceMap["shareImage.png"] = streamReference;

                dataPackage.setHtmlFormat(safeHtml);
                request.data = dataPackage;
                deferral.complete();
            });
        });
    }

The above should get a base64 encoded image shared across. You can stop here if all you were looking for was to share an image buffer across. However, if you further want to look into how to to get a canvas element from the page and convert it to image, then read on.

Firstly, add the following canvas element to your html page.

1
<canvas id="MyCanvas" width="400" height="400" >This browser or document mode doesn't support canvas</canvas>

Then add the following extra code to your javascript file. Additionally, uncomment the line that contains getImageDataFromCanvas in the share event handler. Make sure to comment the line above it that sets the imgData variable directly from the base64 string.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    // converting canvas to image
    function getImageDataFromCanvas() {
        var canvas1 = document.getElementById("MyCanvas");
        var myImage = canvas1.toDataURL("image/png");      // Get the data as an image.
        return myImage.substr(22);
    }

    function drawOnCanvas() {
        // Create some graphics.    
        var canvas = document.getElementById("MyCanvas");
        if (canvas.getContext) {
            var ctx = canvas.getContext("2d");
            ctx.fillStyle = "white";
            ctx.beginPath();
            ctx.rect(5, 5, 300, 250);
            ctx.fill();
            ctx.stroke();
            ctx.arc(150, 150, 100, 0, Math.PI, false);
            ctx.stroke();
        }
    }