HTML5 Canvas: Creating a screenshot from multiple canvases in JavaScript

Programmatically creating screenshots from JavaScript of your canvas element is fairly straightforward; but how do you do it when your game or scene is composed of multiple overlaid canvases?

This example makes use of the canvas caching and it links into the animation loop for starting and stopping – your mileage may vary.

For simplicity, lets say we have 4 canvas elements in use, all of the same size (500×500) used in the following order:

  1. A far background: canvasFar
  2. A near background for a touch of Parallax scrolling: canvasNear
  3. A layer for player characters and other objects: canvasSprites
  4. A layer for special effects: canvasEffects

[code lang=”javascript”]
function TakeScreenshot() {
StopAnimating(); // Stop the animation loop

var bufferScreenshot = CreateCanvasCached("Screenshot");
bufferScreenshot.height = 500; bufferScreenshot.width = 500;
var contextScreenshot = bufferScreenshot.getContext("2d");
// Draw the layers in order
contextScreenshot.drawImage(
document.getElementById("canvasFar"), 0, 0, 500, 500);
contextScreenshot.drawImage(
document.getElementById("canvasNear"), 0, 0, 500, 500);
contextScreenshot.drawImage(
document.getElementById("canvasSprites"), 0, 0, 500, 500);
contextScreenshot.drawImage(
document.getElementById("canvasEffects"), 0, 0, 500, 500);

// Save to a data URL as a jpeg quality 9
var imgUrl = bufferScreenshot.toDataURL("image/jpeg", .9);

StartAnimating(); // Restart the animation loop
return imgUrl;
}
[/code]

Now calling TakeScreenshot() will return a data URL that contains the image – with this you can:

  • Set the src of an image element or css background
  • Open it in a new window to display it as an image (unfortunately you can’t set the content-disposition or filename of a data URL for automatic download)
  • Send it to your sever to base64 decode and create an image file which you can then to popup a download or embed in a page which can then be shared, tweeted, posted to Google+ or Facebook.
  • Use it as source image for more canvas fun or a WebGl texture

UPDATE: There is a toBlob() method on Canvas that allows the saving of generated files on the client-side which is coming in Chrome 14, hopefully other browsers will follow suit.

This should all work fine.  However if you are using images from different domains or a multiple sub-domains – either by using a CDN or for paralleling requests across domain names you will receive a:

SECURITY_ERR: DOM Exception 18

This is a security protection to ensure your code isn’t grabbing images it shouldn’t be, and sending them back to your server. I’ll post about how to work with this in a future post.

4 thoughts on “HTML5 Canvas: Creating a screenshot from multiple canvases in JavaScript”

  1. I have a multi layered picture that is made of multiple canvas objects. Your utility works great for combining them, but I need it to create a jpg or png file on the server so that other apps can use that rather than recreating the picture over and over.

    I was hoping for a link on how to send this data to my server and have it received by php to do the base64 decode. Preferably it would do this in the background without changing what the user sees. I cannot even find a method for submitting post data to another site using javascript.

  2. Hi Mike

    You need to strip the data type from the front of the URL then you will have just a base 64 encoded file. If you were using JQuery you’d do something like this:

    var imgUrl = bufferScreenshot.toDataURL(“image/jpeg”, .9);
    img = img.replace(“data:image/jpeg;base64,”, “”);

    $.post(“/ReceiveScreenshot.php”, { “data”: img });

    Then your server should receive the image base64 encoded in the data variable.

    HTH

  3. It seems this webserver processed my code as code and so it did not post. Sounds like a security flaw that needs to be plugged. I left off the opening and closing php tags this time.

    $handle = fopen(“pic.jpg”,”wb”);
    fwrite($handle, base64_decode($_POST[‘data’]));
    fclose($handle);

Comments are closed.