Advertisement

Code Your Own Online “Paint Shop” App

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

Today we are going to replicate this excellent Flash based drawing application. But instead of sticking to good ol' Flash we are going to use the canvas element and some spiffy jQuery UI elements to achieve the same functionality. Intrigued? Let's dive in!

The application we are trying to replicate:


What our application looks like:



Required Functionality

Keeping in mind not to ratchet up the difficulty factor of this tutorial we are going to limit our functionality to that of the original linked application. Thus our list of required functions looks like so:

  • A pencil tool tracing our mouse movements on mousedown.
  • An eraser tool for the obvious reason.
  • Option to set the colour and width of the brush.
  • Ability to clear the drawing canvas.

In addition to all this, as mentioned above, we want to do all of this using only the canvas element.


The HTML

The markup for our application looks like so:

<!DOCTYPE html> 
<html lang="en-GB"> 
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
<title>Net Tuts - Fun With Canvas: A drawing application by Siddharth</title> 
<link type="text/css" href="css/smoothness/smoothness.css" rel="stylesheet" /> 
<script type="text/javascript" src="js/jquery.js"></script> 
<script type="text/javascript" src="js/jqueryui.js"></script> 
<script type="text/javascript" src="js/mocha.js"></script> 
</head> 
<body> 
<img src="header.png" alt="Header image"/> 
<div class="left"> 
<canvas id="paint" width="600" height="397"> 
</canvas></div> 
<div class="left"> 
<div class="box"> 
Pick a tool:  
<select id="tools"> 
  <option value="pencil">Pencil</option> 
  <option value="eraser">Eraser</option> 
</select> 
<a id="clear">Clear</a> 
</div> 
<div class="box"> 
Pick a colour  
<div id="red"></div> 
<div id="green"></div> 
<div id="blue"></div> 
<div id="swatch" class="ui-widget-content ui-corner-all"></div></div> 
<div class="box"> 
Choose the brush's width 
<div id="width"></div> 
</div> 
</div> 
</body> 
</html>

Other than the generic DIV elements to aid us in positioning there are a couple of points you need to make a note of. First up is the canvas element. Adding it is a matter of including simple <canvas> tags. The text inside the tags is fallback content and will not be showed on a supported browser.

A simple select element lets us pick a tool of our choice.

Next up is the containers for the slider elements. All 4 of them require a separate ID. We'll look into it in detail a bit later.

Finally an anchor element holds the link to clear the canvas.

If you are concerned about code validation, please do remember to use a HTML 5 validator. Current validators we're not made with the canvas element in mind.


The CSS

 
/* Application level styling */ 
 
	 body {  
    background: #E4E4E4 url(http://tutsplus.s3.amazonaws.com/tutspremium/web-development/012_PaintshopPaint/images/bg.png) repeat-x;  
    font-family:Verdana; 
    } 
     
	 a {  
    text-decoration:none;  
    colour:#000; 
    } 
     
    #paint {  
    border: 1px solid #000;  
    background:#FFF; 
    } 
    
	.left {  
    float:left;  
    padding: 20px; 
    } 
     
	 .box {  
    border: 1px solid #000;  
    background-color:#EEE;  
    margin-bottom: 10px;  
    padding: 10px; 
    } 
     
	 #tools {  
    width: 150px; 
    } 
     
    #red, #green, #blue, #width {  
    width: 300px;  
    margin: 15px;  
    } 
    
	 #swatch {  
    width: 120px;  
    height: 100px;  
    margin-left: 110px;  
    background-image: none;  
    } 
     
	 #red .ui-slider-range {  
    background: #ef2929;  
    } 
     
	 #green .ui-slider-range {  
    background: #8ae234;  
    } 
     
	 #blue .ui-slider-range {  
    background: #729fcf;  
    } 
     
	 #width .ui-slider-range {  
    background: #8881be;  
    }

Manual styling for our application is largely limited to floating our containers to position them and styling the sliders. Fortunately a custom download of the jQuery UI library lets you set a predefined theme for the widgets or create your own theme using Theme Roller. Here we are sticking to one of the predefined themes: Smoothness. Add the CSS styles to the themes' CSS file or create a separate CSS file with the above definitions. It's up to you. With respect to our CSS itself, there is nothing fancy going on up there. Just some basic styling and positioning to make it look a little pretty.


With our markup and CSS done, our page looks like so. Lets get started with our scripting now.


The Javascript

Before we start I have a few things to say. I strongly encourage readers to download the source code and have it on the side for reference. It's easier to look at the big picture and parse each function one by one than look at each function individually and then create the big picture in your mind.

Our application requires jQuery version 1.3.2 and jQuery UI Core version with the 1.7.2 Slider plugin. I haven't tested it out on older versions. Feel free to link to Google's libraries if need be. To do so, simply import the following script:

 
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>

Now let's get onto the code.


Initialization

 
  // Canvas Variables 
  var cv, ctx; 
   
  // Tool variables 
  var curTool = pencil; 
  var tool, x, y; 
  var boolMouseDown = false; 
    
  // Tool option variables 
  var colour, width; 
   
  cv = $("#paint").get(0); 
	 if (!cv.getContext)  
	 { 
		alert ("Failed"); 
		return; 
	 } 
	  
  ctx = cv.getContext('2d'); 
	 if (!ctx)  
	 { 
		alert ("Failed"); 
		return; 
	 }

We begin by creating two variables, cv for referencing the canvas element itself and ctx to reference the canvas' context. Next we try to obtain a reference to the canvas element's 2D context by calling the getContext method and throw an error if we are unable to. We then create other tool specific variables whose functions we'll discuss below.


The Pencil Class

In the interest of extensibility, readability and to avoid code bloat each of our tools has been abstracted to its own class. Each tool has its own separate function for each mouse event. Lets take a detailed look at the class.


 
function pencil () 
	   { 
		   this.mousedown = function (e)  
		   { 
			   ctx.beginPath(); 
			   ctx.lineWidth = width; 
			   ctx.lineCap = 'round'; 
			   ctx.lineJoin = 'round';			    
      		   ctx.moveTo(x, y); 
      		   boolMouseDown = true;	 
	       } 
		    
		   this.mousemove = function (e)  
		   { 
			   if (boolMouseDown)  
			   { 
      		   ctx.lineTo(x, y); 
			   ctx.strokeStyle = colour; 
      		   ctx.stroke(); 
               }   
		   } 
		    
		   this.mouseup = function (e)  
		   { 
			   if (boolMouseDown)  
			   { 
      		    tool.mousemove(e); 
                boolMouseDown = false; 
               }   
	       } 
	   }

Mousedown Event

On mousedown we do a number of things. First we call the beginPath method which essentially lets us tell the canvas that we intend to start a new path. Optionally we set a number of properties so the lines are nicely rounded instead of being very sharp. We now set the width of the line to the width variable which I will cover later. Through the moveTo method, we move the beginning of the path to the co-ordinates of the mouse. The parameters x and y are variables which are updated by a helper method we'll see later. Finally the boolean variable which stores whether a mousedown event is registered or not is set to true so that we know its time to start drawing.

Mousemove Event

First, we check the boolMousedown variable to see whether the current mouse movement was preceded by a mousedown event. If the user just moves the mouse over the canvas nothing happens. If it happens after a mousedown event we now know that the user is trying to draw and call the required methods.

We first call the lineTo to complete a path to the current position of the mouse. Do note that nothing can be actually seen on the screen just yet. We need to use either the fill method or the stroke method to actually draw the line. We are going to use the stroke method here. And finally just before calling the method, we set the colour of the line to the colour variable using the strokeStyle property.

Mouseup Event

Our mouseup event just checks whether the user is actually trying to draw. If yes, it calls the mousemove function to complete the line and then resets the boolMousedown variable.


The Eraser Class


 
  function eraser () 
	   { 
		   this.mousedown = function (e)  
		   { 
			   ctx.beginPath(); 
      		   ctx.moveTo(x, y); 
      		   boolMouseDown = true;	 
	       } 
		    
		   this.mousemove = function (e)  
		   { 
			   if (boolMouseDown)  
			   { 
      		   ctx.lineTo(x, y); 
			   ctx.strokeStyle = "#FFFFFF"; 
      		   ctx.stroke(); 
               }   
		   } 
		    
		   this.mouseup = function (e)  
		   { 
			   if (boolMouseDown)  
			   { 
      		    tool.mousemove(e); 
                boolMouseDown = false; 
               }   
	       } 
	   }

The Eraser class is almost the same barring a couple of differences. First and foremost, we set the strokeStyle property to white so the illusion of erasing is intact. Do note that you have to set the property to something else in case you decide to change the background of the canvas. Obvious but worth mentioning. Secondly, we do not need to set the line styling properties all over again as they are irrelevant in this operation. Other than the above two, the classes share everything else.


Initializing Sliders


Since we are using the jQuery UI library for all our interface elements, our UI coding is limited to initializing the widgets with the proper options.

 
  $("#red, #green, #blue").slider({ 
			range: "min", 
            min: 0, 
			max: 255, 
			value: 127, 
			slide: updColour, 
			change: updColour 
		}); 
	 
  $("#width").slider({ 
			range: "min", 
			min: 1, 
			max: 50, 
			value: 10, 
			slide: updWidth, 
			change: updWidth, 
		});

We initialize the 3 sliders for the colour picker together since they share the same options. We need a RGB value between 0 and 255 and thus we set the min and max values accordingly. Also we instruct the plugin to call the updColour function when its value changes or when the slider is moved.

Likewise with the width slider, we set the min and max values and then ask to call the updWidth function when a change occurs. Note that the max brush size is an arbitrary number I picked. Feel free to increase or decrease it as needed.


Update All the Variables

updTool - Updates curTool

 
  function updTool () 
  { 
	  var sel= $('#tools').val(); 
	  switch(sel) 
		{ 
			case 'pencil': 
			curTool = pencil; 
			break; 
			case 'eraser': 
			curTool = eraser; 
			break; 
		} 
  }

This function points the curTool variable towards the currently selected tool. This variables will later be used to instantiate a new object of the tool. We get the value of the selected item and then match it to the appropriate class.

updWidth - Updates width

 
  function updWidth () 
  { 
	  width = $("#width").slider("value"); 
  }

Another simple function. We just traverse the DOM looking for the slider and then assign the value it returns to the width variable. This variable is used directly in the lineWidth property to set the width of the brush.

updXY -Updates x and y

 
  function updXY(e) 
  { 
      var elem = e.target ; 
      var top = 0, left = 0; 
      while (elem.offsetParent) 
            { 
			   top += elem.offsetTop ; 
               left += elem.offsetLeft ;      
               elem = elem.offsetParent ; 
            } 
 
       x = e.pageX - left ; 
       y = e.pageY - top ; 
  }

A critical function which updates the x and y variables which in turn hold the co-ordinates of the mouse with respect to the canvas element. These variables are in turn required for every drawing operation in our application. The tricky thing here is browser compatibility. The layerX,Y property seems to work in Firefox while the offsetX,Y property does the same in Opera. So instead of conditional checking and then using the associated property we are going to do something new today.

First we obtain the canvas elements target and then acquire its parent element. We add the parent element's offset to the total and then go one level higher and redo everything. Once we have no higher to go, the final distance between the edge of the document and the position of the mouse is calculated.

Do note that for demo purposes, the pageX,Y property works adequately. But when the canvas elements starts getting nested inside other elements and when margins and/or padding's are applied, the approach fails miserably. Our new function handles all that with aplomb.

updColour - Updates colour

 
  function updColour()  
	{ 
		var red = $("#red").slider("value") 
			,green = $("#green").slider("value") 
			,blue = $("#blue").slider("value") 
			,hex = hexFromRGB(red, green, blue); 
		$("#swatch").css("background-color", "#" + hex); 
		colour = '#' + hex; 
	}

Relatively simple function which obtains the individual R, G and B values from the sliders and then passes it along to the hexFromRBG function. It then updates the swatch to show the chosen colour and then updates the colour variable with the selected colour. The colour variable is used in the strokeStyle property to specify the colour the brush uses.

hexFromRGB - Returns a Hex Value

 
  function hexFromRGB (r, g, b)  
	{ 
		var hex = [r.toString(16), g.toString(16), b.toString(16)]; 
		$.each(hex, function (nr, val) { 
			if (val.length == 1)  
			{ 
				hex[nr] = '0' + val; 
			} 
		}); 
		return hex.join('').toUpperCase(); 
	}

A simple enough RGB to hex converter picked directly off a jQuery demo. Gets the R,G and B values from the updColour function, converts it into the respective hex value and returns it.

clearAll - Clears the Canvas


 
  function clearAll () 
  { 
	  ctx.clearRect(0, 0, cv.width, cv.height); 
  }

Calls the clearRect method passing the canvas' width and height as parameters thus erasing everything the canvas holds.


Attach the Events to Their Event Handlers

 
  $("#paint").bind('mousedown mousemove mouseup', util); 
  $("#clear").bind('click', clearAll);

We use jQuery's bind function to attach the events to their respective event handlers. For the uninitiated
our first line essentially says: get the element with ID paint and call the util function every time
a mousedown, mousemove or mouseup event occurs. We also specify it to call the clearAll function when the element with ID clear is clicked.


The Catch-All Event Handler

 
  function util (e) 
  { 
	  updColour(); 
	  updTool(); 
	  updWidth(); 
	  updXY(e); 
	  tool = new curTool(); 
	  var call = tool[e.type]; 
	  call(e); 
  }

Our all purpose event handler for the canvas element. All the necessary mouse related events trigger this. A few nifty things go on inside this function. Let's take a look.

First off we update all the required values by calling the respective functions. These include updating the mouse co-ordinates at the instance of the event , x and y, by calling updXY, updating the colour of the brush, colour, by calling updColour, updating the width of the brush, width, by calling updWidth and finally update the currently selected tool, curTool, by calling updTool.

Now that all our values are updated we can now go on doing the actual work. The curTool variable references the currently selected tool. We create a new instance of the tool's class, create a new variable call, point it to the appropriate method of the tool object and then execute call.


Finishing Touches

For the most part we are done. We are just going to have to add a few little things before we are done.

First off, when the application loads initially the swatch shows its default background colour since the function to update its colour, updColour is only called when a mouse event occurs with respect to the canvas element. So we call the updColour function immediately after initializing the sliders.

Finally, we want to make sure our code begins to execute only after the DOM is loaded and ready. With that in mind, move all the code inside jQuery's $(document).ready() function.

That's it. We are done. Congrats on persevering this long. Run the application and draw some goofy stuff.


Extending Our Application

Before we start talking about how we can extend our application I must refer you to Paint.Web, an excellent open-source drawing application. Whatever function I am going to mention, those people have already implemented it there. With that out of the way, lets take a look at how you can take this application a lot farther than it is now.

  • First and foremost, all our variables are global in scope. Partially due to my laziness and partially due to my desire to cut back on parameter passing hell. Optimally, only the absolutely required variables are to be exposed while all others are to be encapsulated by their functions and passed along to their callers.
  • Now that we've covered the very essential pencil tool here in this tutorial, we must next try to integrate the ability to draw a variety of shapes and this is where things get very complicated. Live feedback is a necessary part of any drawing application which means you have to introduce a buffer canvas on which all the temporary drawing elements are rendered. After the drawing is complete, the finished shape can then be transferred to the original canvas. Again, this is beyond the scope of this tutorial. I strongly suggest you look at the Paint.Web source code to get an idea of what I am talking about.
  • With respect to adding new tools, it is relatively simple to integrate it with our application since we took a nicely abstracted approach. Create a new class for the tool and add the tool to the select element and you are done, assuming the tool logic is sound.

Summary


Today we learnt how to use the canvas element to create a simple canvas application, a look at the various methods and properties exposed by its API and how to use the slider plugin in conjunction with the canvas element to create a rudimentary drawing application. The resulting application should run in any browser which supports the canvas element and Javascript.

Advertisement