Capturing User Signatures in Mobile Applications

One growing trend that I have seen in mobile & tablet applications is the creation of tools that enable your workforce to perform their job better. This can be in the case of mobile data retrieval, streamlined sales process with apps for door-to-door sales, mobile business process efficiency, etc…

One of the topics that comes up is how do you capture a signature and store it within your application? This might be for validation that the signer is who they say they are, or for legal/contractual reasons. Imagine a few scenarios:

  • Your cable TV can’t be installed until you sign the digital form on the installation tech’s tablet device
  • You agree to purchase a service from a sales person (door to door, or in-store kiosk) – your signature is required to make this legally binding.
  • Your signature is required to accept an agreement before confidential data is presented to you.

These are just a few random scenarios, I’m sure there are many more.   In this post, I will focus on 2 (yes, I said two) cross-platform solutions to handle this task – one built with Adobe Flex & AIR, and one built with HTML5 Canvas & PhoneGap.  

Source for both solutions is available at: https://github.com/triceam/Mobile-Signature-Capture

Watch the video below to see this in action, then we’ll dig into the code that makes it work.

The basic flow of the application is that you enter an email address, sign the interface, then click the green “check” button to submit to the signature to a ColdFusion server.  The server then sends a multi-part email to the email address that you provided, containing text elements as well as the signature that was just captured.

If you’d like to jump straight to specific code portions, use the links below:



The Server Solution

Let’s first examine the server component of the sample application.   The server side is powered by ColdFusion. There’s just a single CFC that is utilized by both the Flex/AIR and HTML/PhoneGap front-end applications.   The CFC exposes a single service that accepts two parameters: the email address, and a base-64 encoded string of the captured image data.

<cffunction name="submitSignature" access="remote" returntype="boolean">
    <cfargument name="email" type="string" required="yes">
    <cfargument name="signature" type="string" required="yes">
    
    <cfmail SUBJECT ="Signature"
        FROM="#noReplyAddress#"
        TO="#email#"
        username="#emailLoginUsername#"
        password="#emailLoginPassword#"
        server="#mailServer#" 
        type="HTML" >
        
        <p>This completes the form transaction for <strong>#email#</strong>.</p>
        
        <p>You may view your signature below:</p>
        <p><img src="cid:signature" /></p>
        
        <p>Thank you for your participation.</p>
        
        <cfmailparam
            file="signature"
            content="#toBinary( signature )#"
            contentid="signature"
            disposition="inline" />
    
    </cfmail>

    <cfreturn true />
</cffunction>

Note: I used base-64 encoded image data so that it can be a single server component for both user interfaces. In Flex/AIR you can also serialize the data as a binary byte array, however binary serialization isn’t quite as easy with HTML/JS… read on to learn more.


The Flex/AIR Solution

The main user interface for the Flex/AIR solution is a simple UI with some form elements. In that UI there is an instance of my SignatureCapture user interface component. This is a basic component that is built on top of UIComponent (the base class for all Flex visual components), which encapsulates all logic for capturing the user signature. The component captures input based on mouse events (single touch events are handled as mouse events in air). The mouse input is then used to manipulate the graphics content of the component using the drawing API. I like to think of the drawing API as a language around the childhood game “connect the dots”. In this case, you are just drawing lines from one point to another.

When the form is submitted, the graphical content is converted to a base-64 encoded string using the Flex ImageSnapshot class/API, before passing it to the server.

You can check out a browser-based Flex version of this in action at http://tricedesigns.com/portfolio/sigCaptureFlex/ – Just enter a valid email address and use your mouse to sign within the signature area. When this is submitted, it will send an email to you containing the signature.

You can check out the SignatureCapture component code below, or check out the full project at https://github.com/triceam/Mobile-Signature-Capture/tree/master/flex%20client. This class will also work in desktop AIR or browser/based Flex applications. The main application workflow and UI is contained with Main.mxml.

package
{
	import flash.display.DisplayObject;
	import flash.display.Graphics;
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	
	import mx.core.UIComponent;
	import mx.graphics.ImageSnapshot;
	import mx.managers.IFocusManagerComponent;
	
	import spark.primitives.Graphic;
	
	public class SignatureCapture extends UIComponent
	{
		private var captureMask : Sprite;
		private var drawSurface : UIComponent;
		private var lastMousePosition : Point;
		
		private var backgroundColor : int = 0xEEEEEE;
		private var borderColor : int = 0x888888;
		private var borderSize : int = 2;
		private var cornerRadius :int = 25;
		private var strokeColor : int = 0;
		private var strokeSize : int = 2;
		
		public function SignatureCapture()
		{
			lastMousePosition = new Point();
			super();
		}
		
		override protected function createChildren():void 
		{
			super.createChildren();
			
			captureMask = new Sprite();
			drawSurface = new UIComponent();
			this.mask = captureMask;
			addChild( drawSurface );
			addChild( captureMask );
			
			this.addEventListener( MouseEvent.MOUSE_DOWN, onMouseDown );
		}
		
		protected function onMouseDown( event : MouseEvent ) : void
		{
			lastMousePosition = globalToLocal( new Point( stage.mouseX, stage.mouseY ) );
			stage.addEventListener( MouseEvent.MOUSE_MOVE, onMouseMove );
			stage.addEventListener( MouseEvent.MOUSE_UP, onMouseUp );
		}
		
		protected function onMouseMove( event : MouseEvent ) : void
		{
			updateSegment();
		}
		
		protected function onMouseUp( event : MouseEvent ) : void
		{
			updateSegment();
			stage.removeEventListener( MouseEvent.MOUSE_MOVE, onMouseMove );
			stage.removeEventListener( MouseEvent.MOUSE_UP, onMouseUp );
		}
		
		protected function updateSegment() : void
		{
			var nextMousePosition : Point = globalToLocal( new Point( stage.mouseX, stage.mouseY ) );
			renderSegment( lastMousePosition, nextMousePosition );
			lastMousePosition = nextMousePosition;
		}
		
		
		public function clear() : void
		{
			drawSurface.graphics.clear();
		}
		
		override public function toString() : String
		{
			var snapshot : ImageSnapshot = ImageSnapshot.captureImage( drawSurface );
			return ImageSnapshot.encodeImageAsBase64( snapshot );
		}
		
		override protected function updateDisplayList(w:Number, h:Number):void
		{
			super.updateDisplayList(w,h);
			
			drawSurface.width = w;
			drawSurface.height = h;
			
			var g : Graphics = this.graphics;
			
			//draw rectangle for mouse hit area
			g.clear();
			g.lineStyle( borderSize, borderColor, 1, true );
			g.beginFill( backgroundColor, 1 );
			g.drawRoundRect( 0,0,w,h, cornerRadius, cornerRadius );
		
			
			//fill mask
			g.clear();
			g = captureMask.graphics;
			g.beginFill( 0, 1 );
			g.drawRoundRect( 0,0,w,h, cornerRadius, cornerRadius );
		}
		
		protected function renderSegment( from : Point, to : Point ) : void
		{
			var g : Graphics = drawSurface.graphics;
			g.lineStyle( strokeSize, strokeColor, 1 );
			g.moveTo( from.x, from.y );
			g.lineTo( to.x, to.y );
		}
	}
}



The HTML5/PhoneGap Solution

The main user interface for the HTML5/PhoneGap solution is also a simple UI with some form elements. In that UI there is a Canvas element that is used to render the signature. I created a SignatureCapture JavaScript class that encapsulates all logic for capturing the user signature. In browsers that support touch events (mobile browsers), this is based on the touchstart, touchmove and touchend events. In browsers that don’t support touch (aka desktop browsers), the signature input is based on mousedown, mousemove and mouseup events. The component captures input based on touch or mouse events, and that input is used to manipulate the graphics content of the Canvas tag instance. The canvas tag also supports a drawing API that is similar to the ActionScript drawing API. To read up on Canvas programmatic drawing basics, check out the tutorials at http://www.adobe.com/devnet/html5/html5-canvas.html

When the form is submitted, the graphical content is converted to a base-64 encoded string using the Canvas’s toDataURL() method. The toDataURL() method returns a base-64 encoded string value of the image content, prefixed with “data:image/png,”. Since I’ll be passing this back to the server, I don’t need this prefix, so it is stripped, then sent to the server for content within the email.

You can check out a browser-based version of this using the HTML5 Canvas in action at http://tricedesigns.com/portfolio/sigCapture/ – Again, just enter a valid email address and use your mouse to sign within the signature area. When this is submitted, it will send an email to you containing the signature. However, this example requires that your browser supports the HTML5 Canvas tag.

You can check out the SignatureCapture code below, or check out the full project at https://github.com/triceam/Mobile-Signature-Capture/tree/master/html%20client. This class will also work in desktop browser applications that support the HTML5 canvas. I used Modernizr to determine whether touch events are supported within the client container (PhoneGap or desktop browser). The main application workflow is within application.js.

Also a note for Android users, the Canvas toDataURL() method does not work in Android versions earlier than 3.0. However, you can implement your own toDataURL() method for use in older OS versions using the technique in this link: http://jimdoescode.blogspot.com/2011/11/trials-and-tribulations-with-html5.html (I did not update this example to support older Android OS versions.)

function SignatureCapture( canvasID ) {
	this.touchSupported = Modernizr.touch;
	this.canvasID = canvasID;
	this.canvas = $("#"+canvasID);
	this.context = this.canvas.get(0).getContext("2d");	
	this.context.strokeStyle = "#000000";
	this.context.lineWidth = 1;
	this.lastMousePoint = {x:0, y:0};
	
	this.canvas[0].width = this.canvas.parent().innerWidth();
    
	if (this.touchSupported) {
		this.mouseDownEvent = "touchstart";
		this.mouseMoveEvent = "touchmove";
		this.mouseUpEvent = "touchend";
	}
	else {
		this.mouseDownEvent = "mousedown";
		this.mouseMoveEvent = "mousemove";
		this.mouseUpEvent = "mouseup";
	}
	
	this.canvas.bind( this.mouseDownEvent, this.onCanvasMouseDown() );
}

SignatureCapture.prototype.onCanvasMouseDown = function () {
	var self = this;
	return function(event) {
		self.mouseMoveHandler = self.onCanvasMouseMove()
		self.mouseUpHandler = self.onCanvasMouseUp()

		$(document).bind( self.mouseMoveEvent, self.mouseMoveHandler );
		$(document).bind( self.mouseUpEvent, self.mouseUpHandler );
		
		self.updateMousePosition( event );
		self.updateCanvas( event );
	}
}

SignatureCapture.prototype.onCanvasMouseMove = function () {
	var self = this;
	return function(event) {

		self.updateCanvas( event );
     	event.preventDefault();
    	return false;
	}
}

SignatureCapture.prototype.onCanvasMouseUp = function (event) {
	var self = this;
	return function(event) {

		$(document).unbind( self.mouseMoveEvent, self.mouseMoveHandler );
		$(document).unbind( self.mouseUpEvent, self.mouseUpHandler );
		
		self.mouseMoveHandler = null;
		self.mouseUpHandler = null;
	}
}

SignatureCapture.prototype.updateMousePosition = function (event) {
 	var target;
	if (this.touchSupported) {
		target = event.originalEvent.touches[0]
	}
	else {
		target = event;
	}

	var offset = this.canvas.offset();
	this.lastMousePoint.x = target.pageX - offset.left;
	this.lastMousePoint.y = target.pageY - offset.top;

}

SignatureCapture.prototype.updateCanvas = function (event) {

	this.context.beginPath();
	this.context.moveTo( this.lastMousePoint.x, this.lastMousePoint.y );
	this.updateMousePosition( event );
	this.context.lineTo( this.lastMousePoint.x, this.lastMousePoint.y );
	this.context.stroke();
}

SignatureCapture.prototype.toString = function () {

	var dataString = this.canvas.get(0).toDataURL("image/png");
	var index = dataString.indexOf( "," )+1;
	dataString = dataString.substring( index );
	
	return dataString;
}

SignatureCapture.prototype.clear = function () {

	var c = this.canvas[0];
	this.context.clearRect( 0, 0, c.width, c.height );
}

Source for the ColdFusion server, as well as Flex/AIR and HTML5/PhoneGap clients is available at: https://github.com/triceam/Mobile-Signature-Capture

  • Keith

    Although you method uses images, an application that captures signatures can, in theory, record the length, pressure and angle of every stroke. Having those data, it would be very trivial to make slight variations to those parameters and produce perfect forgeries that even experts would not be able to tell apart from the original. Due to this, I think that, at some point, hand signatures will no longer be used. In the mean time, storing that kind of information should probably follow stricter rules than the storage of passwords.

  • Pingback: Andrew Trice » Blog Archive » Sketching with HTML5 Canvas and “Brush Images”()

  • http://auditform.com Robert

    I’m currently using this technique to store the signature in base64 format in a local sqlite database until the program gets synced at which point the signature will get uploaded and saved as an image.

    What I’d like to be able to do is to reload this base64 string back into the drawable surface (and continue drawing if they want) so that when they return to the signature screen they can see what they have already drawn. Is there any way of doing this?

    • http://www.tricedesigns.com Andrew

      You could load the base64 image content into a normal Image instance, and then render that into the Canvas using the graphics context’s drawImage function: context.drawImage(img, x, y);

  • http://auditform.com Robert

    Apologies I didn’t mention that I am using the Flex/AIR solution. And I think the function you mentioned is for Phonegap?

    I can’t find any similar functions for AIR that let me pass an Image object back into the graphics object/draw surface.

    I load the base64 text into an image using the below (s being the base64 string):

    var photoImage:Image = new Image();
    var decoder:Base64Decoder = new Base64Decoder();
    decoder.decode(s);
    photoImage.source = decoder.toByteArray();

    but I’m not sure how to get from there back to the graphic/drawable surface.

    And is there a way to get the height/width from the base64 text (as presumably that’s something I’ll need to feed back into it).

    • http://www.tricedesigns.com Andrew

      The drawImage function that I mentioned is specific to HTML5 Canvas, but you can use the same concept in Flex/AIR applications. You can draw the content of any image into the graphics of any DisplayObject using beginBitmapFill of the Graphics context:

      graphics.beginBitmapFill(photoImage.bitmapData, new Matrix(), false, false);
      graphics.drawRect(x, y, photoImage.width, image.height);
      graphics.endFill();

  • http://auditform.com Robert

    The bitmapData property is appearing as a null value, should this be getting populated as soon as I set the source of the image or is there a function I need to call?

    I’d tried something very similar before but I came across that same issue before.

  • http://auditform.com Robert

    Well I’ve had a bit more progress since my last comment. I’m now able to get access to the bitmapData variable by doing the following:

    var decoder:Base64Decoder = new Base64Decoder();
    decoder.decode(s);
    var bytes:ByteArray = decoder.toByteArray();
    var loader:Loader = new Loader();
    loader.loadBytes(bytes);
    loader.contentLoaderInfo.addEventListener(Event.COMPLETE,loadImage_completeHandler);

    and then in the event handler I have:

    var loader:LoaderInfo = e.target as LoaderInfo;
    var bmp:Bitmap = Bitmap(loader.content);
    var g : Graphics = drawSurface.graphics;
    g.clear();
    g.beginBitmapFill(bmp.bitmapData, new Matrix(), false, false);
    g.drawRect(x, y, bmp.width, bmp.height);
    g.endFill();

    However even though the x, y, width and height are all correct nothing seems to happen after that section of code runs.

  • Antonio

    I love the signature area and think its looks great but dont use cf. Is there a simple way to utilize the default email browser on the device and possibly add this as an attachment or preferably fill this as the body of the email? Using the flex option btw.

    thanks again for great work

    • http://www.tricedesigns.com Andrew

      You *might* be able to use a mailto link and pass parameters, but I really don’t know if it will be supported… I’ll have to play around with it later to test.

  • https://auditform.com Robert

    Finally found what I was doing wrong. I thought the x and y positions on the Rect were the global positions not relative. The two functions I added to the class are below for any others wanting to load the signature back in from a base 64 string.

    public function fromString(s:String):void
    {
    var decoder:Base64Decoder = new Base64Decoder();
    decoder.decode(s);
    var bytes:ByteArray = decoder.toByteArray();
    var loader:Loader = new Loader();
    loader.loadBytes(bytes);
    loader.contentLoaderInfo.addEventListener(Event.COMPLETE,loadImage_completeHandler);
    }

    protected function loadImage_completeHandler(e:Event):void
    {
    var loader:LoaderInfo = e.target as LoaderInfo;
    var bmp:Bitmap = Bitmap(loader.content);
    drawSurface.graphics.clear();
    drawSurface.graphics.beginBitmapFill(bmp.bitmapData, new Matrix(), false, false);
    drawSurface.graphics.drawRect(0, 0, bmp.width, bmp.height);
    drawSurface.graphics.endFill();
    }

  • Greg

    Could this work on a server that is running CF 6? The CONTENT attribute for the CFPARAM tag is invalid. Can I upload the signature to the server as an image and make a link to it in the database?

    • http://www.tricedesigns.com Andrew

      My guess is that it should be possible, but I don’t remember specifics for CF6 & CFMail. The CF6 docs are no longer available on adobe.com, so I really am not sure.

  • Josh

    Hey I as well am interested in running this on flash builder 4.6 mobile app, but I dont have access to cf server my host does not provide it. Is it possible to send a request to a php file?

    • http://www.tricedesigns.com Andrew

      Yes, you can also do this with PHP. The data is serialized as a base-64 encoded text string, which will work with any backend technology.

  • Josh

    yeah i spent a few days and found a nice secure way of sending data to php from flex 4.6 mobile

  • Akash

    I tried the html drawImage function to get the image back onto canvas but for some reason there seems to be some loss in the image,like its dotted at places, smooth at elsewhere, renedering the signature useless for business

    • http://www.tricedesigns.com Andrew

      Any detail you can provide on the type of device/platform, and whether you are using the Flex or AIR solution? Also, are you scaling the image at all? If you capture it really small and upscale, it will be very pixelated. If you capture it big and shrink it, it will stay crisp. I have already successfully used this approach for legally binding contracts in business applications.

  • http://gmail Akash

    I am using desktop and your html5 solution. I am not scaling the image at all. I have 2 canvases, one is of type “SignatureCapture”, i.e. your canvas. The other one is a simple canvas of the same dimension as the previous one but not intialized as SignatureCapture. The following is the code that produces the dotted image
    Your toString method has been modified, i no longer split the string at the appearence of “,” , it wasn’t working with the split.

    var sig = sigCapture.toString();

    var img = new Image();
    img.src = sig;

    img.onload = function()
    {

    var canvas = document.getElementById(“myCanvas”);
    var context = canvas.getContext(“2d”);

    context.drawImage(img,0,0, 300,200);
    context.lineWidth = 5;

  • http://Gmail Akash

    The above produces the image of the same size as the original albeit dotted….

    • http://www.tricedesigns.com Andrew

      I can’t really tell from the code in the comments what the problem is. You should be able to use this without pixellation.

  • khurram

    how can save signature on database im using phonegap ,jquerymobile , android,eclipse

    • http://www.tricedesigns.com Andrew

      The base64 encoded string can be written directly into a database as-is. It is no different than a long text string.

  • khurram

    how do i apply two signatures in one html page using phonegap

    • http://www.tricedesigns.com Andrew

      You can create two Signature Capture instances. It might need a few changes, since I only tested with one instance, but the code should be portable enough as-is.

  • http://www.suavesolutions.net khurram

    how can i create two instances can you give any code

  • http://www.suavesolutions.net khurram

    im using phonegap html5 and xcode. i need two signature instances in one html page but when i try two instances nothing work

  • Thiru

    I’m trying to implement using html5/Phonegap solution. But when I’m trying to sign in the canvas area in android default browser, I’m seeing it as line between one point to another due to less touch sensitivity/more javascript call behind the screen.
    Any solution to fix this issue/optimize the java script call to make the signature looks real?

    • http://www.tricedesigns.com Andrew

      You may need to capture all input on the touch events, then use a deferred drawing implementation. By deferring the actual drawing, it reduces processor bandwidth, and could enable better event capture. I haven’t tried this yet, but it could work. The underlying problem is that the HTML5 canvas is not hardware accelerated in the web view on most Android devices.

  • Lalit

    your browser based samples using HTML5 didn’t work on
    1) Chrome on Android 4.1.1 (Galaxy Note 10.1)
    2) IE 10 on Windows 8 Pro Desktop

    but if works fine on Chrome on Windows8 Pro and on Samsung ATIV Smart PC Pro 700T

    what could be reason for the above failures.

    TIA

    • http://www.tricedesigns.com Andrew

      Since I don’t have those devices, and they weren’t available when I published that post, I really don’t know. Do you have a more detailed error message from the browser? It’s probably a small JS error based on different behavior of the browsers.

  • vipul saluja

    Hi,

    I am using it with HTML5 and phonegap.

    It is working very well on index.html(home screen).

    But when I use it on a page which I render on a specific html page having only it doesnt’t works…
    please tell me what I am doing wrong.

    Back
    Signature

    Submit
    Clear

    • http://www.tricedesigns.com Andrew

      Hi Vipul, can you elaborate on your issue, or provide sample code? I’m not able to determine any issues from your question.

  • Andrew C

    Must this app be used on a Cold Fusion server?
    I see that call to the file
    var url = ” /Services.cfc”;
    Which contains the email information, however I cannot get it to send email after signing.
    Can this be modified easier so that Gmail can just send the email out. Thanks
    Has anyone modified it to use PHP or ASPX page easily?

    • http://www.tricedesigns.com Andrew

      No, there is no dependency on ColdFusion. You can download the content and save using any application server technology.

  • http://gps.about.com/b/2013/02/23/cobra-iradar.htm funjet vacations

    Hello! Do you know if they make any plugins to safeguard
    against hackers? I’m kinda paranoid about losing everything I’ve worked hard on.

    Any recommendations?

  • bhines

    Thank you soooooo much for this awesome article. One thing I want to share with people who download this solution is the order of the script files with using jQuery Mobile. Your application works great until you introduce jquery.mobile-1.2.0.min.js. I struggled with this for a couple of hours. The canvasContainer would resize automatically but the canvas would have a width of 100px. This would then stretch and your pen would not line up with your signature. The solution was to move your application.js file to be one of the very last scripts. Basically, the size was trying to be determined before the control was completely rendered. Moving the file to the end finally solved the problem.

  • ahabib

    When i’m trying to draw a signature by fingure in phone canvas it draws two lines side by side but resulted signature image has one line. But in emulation when i draw a signature by mouse one signature drawn as it should work. But why i got two line side by side on phone canvas when i draw signature?? please help me. My project is delayed for that issue.

  • Tim

    Is this open source? What terms and conditions apply to this code? Would love to use this in my class project! THhanks

  • jaikumar

    I,m Using Phonegap, unable to change line width and color, i need increase the line width and change the color for signature in canvas.