start blog post

Saving Drawn Images with Canvas and Cake.js

The code on this page will not work in Internet Explorer. IE does not support the HTML5 canvas tag. Someone might be able to adapt this to detect IE and use VML instead, but that is out of the scope of this post.

  1. Convenience functions
  2. Creating the canvas and scene
  3. Creating the brush
  4. Serializing the canvas image
  5. Storing the image with PHP
  6. "Code all together"

Convenience functions

The only external code this example relies on is Cake.js, a library that abstracts and automates a lot of the steps required in using the HTML5 canvas tag.

<!DOCTYPE html>
<html>
    <head>
        <style type="text/css">
            #canvas {
                width: 600px;
                height: 400px;
            }
        </style>
        <title>Canvas Experiment</title>
    </head>
    <body>
        <div id="canvas">
            <!-- This container will hold our canvas element. -->
        </div>
        <button type="button">Save Image</button>
        <div id="saved">
            <!-- We will output our images here so we know they got saved. -->
        </div>

        <!-- download and host on your own site -->
        <script type="text/javascript" src="cake.js"></script>

        <!-- main.js includes all of the javascript in this blog post -->
        <script type="text/javascript" src="main.js"></script>
    </body>
</html>
    // you can use your favorite library's CSS getStyle method here...
    function getStyle(element, property) {
        if (typeof element == 'string')
            element = document.getElementById(element);
        
        if (element.style[property]) {
            // inline style property
            return element.style[property];
        } else if (element.currentStyle) {
            // external stylesheet for Explorer
            return element.currentStyle[property];
        } else if (document.defaultView && document.defaultView.getComputedStyle) {
            // external stylesheet for Mozilla and Safari 1.3+
            property = property.replace(/([A-Z])/g, "-$1");
            property = property.toLowerCase();
            return document.defaultView.getComputedStyle(element, "").getPropertyValue(property);
        } else {
            return null;
        }
    }
    
    // we'll be calling this later to erase drawn images
    function resetCanvas() {
        canvas.clear = true;
        setTimeout(function() {
            canvas.clear = false;
        }, 100);
    }

Creating the canvas and scene

    // get the container's dimensions and create a new Cake.js Canvas
    var w = parseInt(getStyle('canvas', 'width'), 10);
    var h = parseInt(getStyle('canvas', 'height'), 10);
    var canvas = new Canvas(document.getElementById('canvas'), w, h);
    
    // setup our canvas
    canvas.frameDuration = 10;
    canvas.addEventListener('mousedown', function(e) {
        e.preventDefault();
    }, false);
    resetCanvas();
    
    /**
     * A CanvasNode can be used to group other nodes, but essentially
     * it is a drawing building block. In this case we will only need
     * one node.
     */
    var scene = new CanvasNode();

Creating the brush

    // Polygon is another Cake.js class
    var brush = new Polygon([], {
        closePath : false
    });
    
    // configure the brush size and color
    brush.strokeWidth = 2;
    brush.stroke = '#f60';
    
    // Frame frequency is based on frameDuration (which we set above)
    brush.addFrameListener(function() {
        var moves = this.root.mouseEvents.allWith(2).take(0, 1).flatten()
        if (moves.length > 0 || this.root.mouseDown) {
            
            // this block draws a series of line segments following the mouse's path
            this.segments = (this.segments.length) ? this.segments.slice(-2).concat(moves) : moves;
            if (this.segments.length == 2) {
                this.segments = this.segments.concat(this.segments);
            }
            this.visible = (this.segments.length > 0)

        } else {
            if (this.segments.length > 0) {
                this.segments = [];
            }
            this.strokeStarted = false;
            this.visible = false;
        }
    });

    // append elements
    scene.append(brush);
    canvas.append(scene);

Serializing the canvas image

    var button = document.getElementsByTagName('button')[0];
    button.addEventListener('click', function() {
        var data = canvas.canvas.toDataURL('image/png').split(/,/)[1];
        var query = 'data=' + encodeURIComponent(data);
        
        // you can use your favorite library's AJAX request method 
        var http = new XMLHttpRequest();
        http.open('POST', 'saveImage.php', true);
        http.onreadystatechange = function() {
            if (http.readyState === 4) { // XMLHttpRequest code for "Done"
                if (http.status === 200) { // HTTP status code for "OK"
                    // see below for what saveImage.php outputs
                    var img = document.createElement('img');
                    img.src = http.responseText;
                    
                    // append the new image
                    var saved = document.getElementById('saved');
                    saved.insertBefore(img, saved.childNodes[0]);
                    
                    // clear the drawing area
                    resetCanvas();
                }
            }
        };
        http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        http.setRequestHeader("Content-length", query.length);
        http.setRequestHeader("Connection", "close");
        http.send(query);
    }, false);

Storing the image with PHP

<?php
    $filename = md5($_POST['data']) . '.png';
    $data = base64_decode($_POST['data']);
    file_put_contents("./saved/$filename", $data);
    echo "./saved/$filename";
    exit;
?>

"Code all together"

In case you're wondering, this section title is paraphrased from a Mitch Hedberg joke from the comedy album Strategic Grill Locations.

(function() {
    function getStyle(element, property) {
        if (typeof element == 'string')
            element = document.getElementById(element);
        if (element.style[property]) {
            return element.style[property];
        } else if (element.currentStyle) {
            return element.currentStyle[property];
        } else if (document.defaultView && document.defaultView.getComputedStyle) {
            property = property.replace(/([A-Z])/g, "-$1");
            property = property.toLowerCase();
            return document.defaultView.getComputedStyle(element, "").getPropertyValue(property);
        } else {
            return null;
        }
    }
    function resetCanvas() {
        canvas.clear = true;
        setTimeout(function() {
            canvas.clear = false;
        }, 100);
    }
    var w = parseInt(getStyle('canvas', 'width'), 10);
    var h = parseInt(getStyle('canvas', 'height'), 10);
    var canvas = new Canvas(document.getElementById('canvas'), w, h);
    canvas.frameDuration = 10;
    canvas.addEventListener('mousedown', function(e) {
        e.preventDefault();
    }, false);
    resetCanvas();
    var scene = new CanvasNode();
    var brush = new Polygon([], {
        closePath : false
    });
    brush.strokeWidth = 2;
    brush.stroke = '#f60';
    brush.addFrameListener(function() {
        var moves = this.root.mouseEvents.allWith(2).take(0, 1).flatten()
        if (moves.length > 0 || this.root.mouseDown) {
            this.segments = (this.segments.length) ? this.segments.slice(-2).concat(moves) : moves;
            if (this.segments.length == 2) {
                this.segments = this.segments.concat(this.segments);
            }
            this.visible = (this.segments.length > 0)
        } else {
            if (this.segments.length > 0) {
                this.segments = [];
            }
            this.strokeStarted = false;
            this.visible = false;
        }
    });
    scene.append(brush);
    canvas.append(scene);
    var button = document.getElementsByTagName('button')[0];
    button.addEventListener('click', function() {
        var data = canvas.canvas.toDataURL('image/png').split(/,/)[1];
        var query = 'data=' + encodeURIComponent(data);
        var http = new XMLHttpRequest();
        http.open('POST', 'saveImage.php', true);
        http.onreadystatechange = function() {
            if (http.readyState === 4) {
                if (http.status === 200) {
                    var img = document.createElement('img');
                    img.src = http.responseText;
                    var saved = document.getElementById('saved');
                    saved.insertBefore(img, saved.childNodes[0]);
                    resetCanvas();
                }
            }
        };
        http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        http.setRequestHeader("Content-length", query.length);
        http.setRequestHeader("Connection", "close");
        http.send(query);
    }, false);
})();

If you copy the HTML, JavaScript, and PHP to your server, you should have a fully working image saver. Let me know if you have any trouble getting this set up and I'll see what I can do.

var tags = [, ];

  • share this post:
  • email a friend
  • float this post
  • digg this post
  • share on stumbleupon
  • submit to technorati
  • tweet this post

end blog post

most viewed this week

least viewed this week