Sketching with HTML5 Canvas and “Brush Images”

In a previous post on capturing user signatures in mobile applications, I explored how you capture user input from mouse or touch events and visualize that in a HTML5 Canvas.  Inspired by activities with my daughter, I decided to take this signature capture component and make it a bit more fun & exciting.   My daughter and I often draw and sketch together… whether its a magnetic sketching toy, doodling on the iPad, or using a crayon and a placemat at a local pizza joint, there is always something to draw. (Note: I never said I was actually good at drawing.)

Olivia & the iPad

You can take that exact same signature capture example, make the canvas bigger, and then combine it with a tablet and a stylus, and you’ve got a decent sketching application.   However, after doodling a bit you will quickly notice that your sketches leave something to be desired.   When you are drawing on a canvas using moveTo(x,y) and lineTo(x,y), you are somewhat limited in what you can do. You have lines which can have consisten thickness, color, and opacity. You can adjust these, however in the end, they are only lines.

If you switch your approach away from moveTo and lineTo, then things can get interesting with a minimal amount of changes. You can use images to create “brushes” for drawing strokes in a HTML5 canvas element and add a lot of style and depth to your sketched content.  This is an approach that I’ve adapted to JavaScript from some OpenGL drawing applications that I’ve worked on in the past.  Take a look at the video below to get an idea what I mean.

Examining the sketches side by side, it is easy to see the difference that this makes.   The variances in stroke thickness, opacity & angle add depth and style, and provide the appearance of drawing with a magic marker.

Sketches Side By Side

It’s hard to see the subtleties in this image, so feel free to try out the apps on your own using an iPad or in a HTML5 Canvas-capable browser:

Just click/touch and drag in the gray rectangle area to start drawing.

Now, let’s examine how it all works.   Both approaches use basic drawing techniques within the HTML5 Canvas element.   If you aren’t familiar with the HTML5 Canvas, you can quickly get up to speed from the tutorials from Mozilla.

moveTo, lineTo

The first technique uses the canvas’s drawing context moveTo(x,y) and lineTo(x,y) to draw line segments that correspond to the mouse/touch coordinates.   Think of this as playing “connect the dots” and drawing a solid line between two points.

The code for this approach will look something like the following:

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

context.beginPath();
context.moveTo(a.x, a.y);
context.lineTo(b.x, b.y);
context.lineTo(c.x, c.y);
context.closePath();
context.stroke();

The sample output will be a line from point A, to point B, to point C:

lineTo(x,y) Stroke Sample

Brush Images

The technique for using brush images is identical in concept to the previous example – you are drawing a line from point A to point B.  However, rather than using the built-in drawing APIs, you are programmatically repeating an image (the brush) from point A to point B.

First, take a look at the brush image shown below at 400% of the actual scale.  It is a simple image that is a diagonal shape that is thicker and more opaque on the left side.   By itself, this will just be a mark on the canvas.

Brush Image (400% scale)

When you repeat this image from point A to point B, you will get a “solid” line.  However the opacity and thickness will vary depending upon the angle of the stroke.   Take a look at the sample below (approximated, and zoomed).

Brush Stroke Sample (simulated)

The question is… how do you actually do this in JavaScript code?

First, create an Image instance to be used as the brush source.

brush = new Image();
brush.src = 'assets/brush2.png';

Once the image is loaded, the image can be drawn into the canvas’ context using the drawImage() function. The trick here is that you will need to use some trigonometry to determine how to repeat the image. In this case, you can calculate the angle and distance from the start point to the end point. Then, repeat the image based on that distance and angle.

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

var halfBrushW = brush.width/2;
var halfBrushH = brush.height/2;

var start = { x:0, y:0 };
var end = { x:200, y:200 };

var distance = parseInt( Trig.distanceBetween2Points( start, end ) );
var angle = Trig.angleBetween2Points( start, end );

var x,y;

for ( var z=0; (z<=distance || z==0); z++ ) {
	x = start.x + (Math.sin(angle) * z) - halfBrushW;
	y = start.y + (Math.cos(angle) * z) - halfBrushH;
	context.drawImage(this.brush, x, y);
}

For the trigonometry functions, I have a simple utility class to calculate the distance between two points, and the angle between two points. This is all based upon the good old Pythagorean theorem.

var Trig = {
	distanceBetween2Points: function ( point1, point2 ) {

		var dx = point2.x - point1.x;
		var dy = point2.y - point1.y;
		return Math.sqrt( Math.pow( dx, 2 ) + Math.pow( dy, 2 ) );
	},

	angleBetween2Points: function ( point1, point2 ) {

		var dx = point2.x - point1.x;
		var dy = point2.y - point1.y;
		return Math.atan2( dx, dy );
	}
}

The full source for both of these examples is available on github at:

This example uses the twitter bootstrap UI framework, jQuery, and Modernizr.  Both the lineTo.html and brush.html apps use the exact same code, which just uses a separate rendering function based upon the use case.    Feel free to try out the apps on your own using an iPad or in a HTML5 Canvas-capable browser:

Just click/touch and drag in the gray rectangle area to start drawing.

Stylistic Sketchy

Stylistic Sketchy - Click to Get Started

  • Pingback: Andrew Trice » Blog Archive » Realtime Data & Your Applications

  • http://tjandpals.com David Komer

    Hmmmm… really interesting! I’m thinking of using it for flash… how is the performance of both methods compared?

    • http://www.tricedesigns.com Andrew

      I haven’t compared performance in HTML vs Flash, but it should definitely work. On desktop, I’m sure it will work just fine. You may have to experiment with mobile devices. Older devices might have performance issues.

  • Pingback: Andrew Trice » Blog Archive » Fun Apps w/ PhoneGap

  • Madeleine

    Hi Andrew,

    I am doing on my final year project(fyp) and chance upon your post regarding this canvas drawing which is great help to my fyp. I have implemented your source code into my project. However, I am facing some problems which I hope you can give me a hand with this..

    Currently i am able to draw on the canvas, but is there any way that i can implement on your codes to check if the user has drawn (from left to right) to the maximum of the canvas?

    Appreciate your help and hope to receive your reply soon.. Thanks very much..

  • http://georgedina.ro/ George Dina

    Don’t know how I got here but your article is really awesome.
    You got yourself a new subscriber!

  • jonny

    if I want to change the color brush what can i do?

    • http://www.tricedesigns.com Andrew

      Currently, there are no color filters… you would have to change the source image to change the brush color.

  • http://drawception.com Reed

    Hi Andrew,

    I’ve been playing w/ this code and it works great! I’m planning to adopt it for use on my site, and link to you for attribution and credit. I couldn’t find any license, so please let me know if this is a problem or if you’d like attribution in any other way.

    Thanks!

    • http://www.tricedesigns.com Andrew

      Reed, Feel free to use this code. If you don’t mind sharing, I’d like to see what you have built with it, whenever your application is online. The canvas sketching code code is available for use under the MIT license:

      Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

      The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

      THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

  • Reed

    Thanks Andrew! I’m using it for this game: http://drawception.com

    I just switched over to your code a few days ago and it’s working much better than what I had before.

    If you’d like to check it out w/o having to play or sign up, here’s the sandbox version: http://drawception.com/sandbox/

  • http://n/a Joe

    Hey guys, I love the script! Just one question- how do you get the EXPORT feature to allow you to download the image? On my iPad, if I download it, it shows up in Photos as a black image. On my computer, if I right click and Save Image as, nothing happens.

    Any ideas? Would love to include this in one of my next charity apps.

    // J

  • Pingback: Andrew Trice » Blog Archive » Lil’ Doodle Source Code Now Available

  • http://SketchingwithHTML5Canvasand“BrushImages” Chris

    Thanks for the code sample. It works great on iPad and iPhone, but is very slow on Android Tablet and it doesent work at all on Windows Phone 7. Do you have any recommendations for Android and WP7? Thanks very much.

    • http://www.tricedesigns.com Andrew

      Unfortunately, the HTML5 Canvas on WP7 and many Android devices is not hardware accelerated, and you won’t be able to get great performance out of it. Canvas is hw accelerated on *some* android devices in the default browser, and it is hardware accelerated inside of the Chrome browser on Android.

  • How optimize this for Retina ?

    Hi, this is the best drawing code for HTML canvas that i found so far… Great job ! I’m creating a mobile web app in jQuery mobile for the iPad 3 and earlier versions. Everything works great, except on the retina display the canvas don’t scale the image to the actual pixels of the retina, instead he scales it twice larger with has a pixel/blocking effect with smaller brushers. i found a Retina.js script for optimizing images in html… but i dont think, and don’t know how i can use this for your code. Any Idea’s ?

    • http://www.tricedesigns.com Andrew

      It should work just find. You will just need to change the size of the HTML Canvas element. In the HTML, it is statically sized to 1024×600. You will need to update this size. Also, be sure to set the metadata: Do not use “width=device-width”, otherwise it will be rendered at 1024×768, with device-pixel-ratio = 2.

  • Mathieu De Haeck

    I doubled the canvas and resized it with css, and this shows sharp brushes..

    But, the drawing is off, if you drag e.g.. from top left to bottom right, the distance between the brush and your fingers doubles when you get move down, resulting in only upper left quart of the canvas that can be drawed upon..

    something with the trigonometry?

    • http://www.tricedesigns.com Andrew

      It is probably something with the input coordinates reported by the iPad. Try taking the touch X and Y coordinates and multiplying them by window.devicePixelRatio, and you’ll probably get the exact location. I haven’t updated my code and tested it on an iPad 3, but my guess is that this will fix it.

  • Abraham Lopez

    Upon using your (excellent) JS library for an iPad HTML5 app I noticed the draw speed was suboptimal (even on an iPad 3 with iOS 6 the draw line had gaps and was linear rather than following the path of the finger) due the UIWebView Javascript engine having Nitro disabled, which is why this same code runs just fine on Mobile Safari.

    So, I analyzed the code and made 2 improvements that drastically increased the draw speed, at line 30 and 75 respectively in the following pastebin snippet:

    http://pastebin.com/eaKuiiic

    Hope it’s useful for everybody.

  • Abraham Lopez

    Use the following pastebin snippet instead of the previous one, as I fixed a small bug that caused the canvas to not be drawn when the canvas is initially hidden from view and then made visible:

    http://pastebin.com/DmcZfP9c

  • Ashutosh

    Thanks for the example…really helpful for my phonegap project….

  • Pingback: PhoneGap Exploration – Realtime Hardware Communication | ANDREW TRICE

  • Jawa-the-Hutt

    I wanted to let you know an issue I ran into on Windows 8 with Firefox. Apparently, FF on Win8 will pass the Modernizer test in Line 4 of the sketcher.js and show as a Touch Enabled browser, even if you are not running it on a touch enabled device. However, on a non-touch device, it will never register the ‘touchstart’ event and thus, will not start to draw.

    I made the following code change on Line 4 in the sketcher.js file to get it working:

    this.touchSupported = “false”;
    if (‘ontouchstart’ in document.documentElement) {
    this.touchSupported = true;
    };

    That being said, I have not tested FF on a Win8 tablet to see if this change has any impact on it working.

    BTW, thanks for the code and examples. It helped a lot.

  • Paula Pekovic

    Hi Andrew I was wandering if there is a way to amend your example to look like a smooth stroke rather than seeing image drawn at every pixel between two points. Hope this make sense. Thanks Neb Pekovic

    • http://tricedesigns.com/ Andrew Trice

      It is possible, but I do not have that kind of logic built into the application. Basically, you’d need to track the path of each stroke and extrapolate the direction between the points. Then, draw along a curve based on the path. This would be far easier if you were using the standard drawing API’s curve methods (for Bezier curves), since it’s manual, you have to figure out the curve in your own rendering logic.

  • yan

    you can do it like this

    var SupportsTouches = (“createTouch” in document),

    msPointer = window.navigator.msPointerEnabled,

    startEvent = SupportsTouches ? “touchstart” : (msPointer ? “MSPointerDown” : “mousedown”);

  • Joshua Gold

    Is there any way to add a pinch zoom function?