Click here to Skip to main content
Licence Apache
First Posted 16 Feb 2011
Views 16,410
Downloads 426
Bookmarked 39 times

Signature Box that Makes the Signature Look Right

By | 21 Feb 2011 | Article
A simple .NET signature box for handheld application. Usage of Bézier curves makes the resulting signature look more like the pen and paper version.

Introduction

Have you ever noticed how childish and imprecise your signature looks when you write your name in a handheld signature box? The small size of the stylus compared to a standard pen, the nearly frictionless stylus-on-touch-screen interaction and the fact that the handheld is often hanging in the air instead of firmly lying on a table are three physical explanations for these ugly signatures. Another reason is the frequent input errors that are sent to your control from the touch screen. Those errors may vary between 1 and 4 pixels. Unnoticeable when clicking in the middle of a button, they are a pest when sampled in a signature box. By lowering the sampling rate and using Bézier curve interpolation, it's possible to reduce the impact of all these factors.


Without Bézier interpolation, the signature looks clumsy

Using Bézier interpolation, most sampling errors are corrected

Background

While working on a project, a customer asked me to add a signature input box to his application. I looked around the Web to find an open source implementation of such a control and could not find any of sufficient quality. In the end, I decided to create my own and by the way, improving the concept using Bézier curves just to see how better the result could look. Making it open source was just natural to me. The project can now be found at SourceForge.net.

Bézier curves are used in many computer graphic applications. For example, true type fonts use cubic Bézier splines to render smooth character curves. Bézier interpolation is also used in 3D graphic animations to render smooth and natural movements. When used to smooth long and complex curves, it's better to use a Bézier path, which is a spline computed at every four points instead of the entire point set. Usage of cubic spline on a point set of four points is much faster than using the general Bézier recursive algorithm for the same result. Cubic spline, quadratic spline and linear interpolation are respectively used with samples of four, three and two points. When a sample contains more than four points, it becomes easier to use the general Bézier curve algorithm. For mathematical background and examples on Bézier curve, see the Wikipedia article. Animated GIFs and explanations given there are a good start on the subject.

Using the Code

Usage of the SignatureBox control is quite simple and straightforward. Just add the signature box to your application and make it appear the shape you want. The CreateImage method creates an image from the sampled points whether using Bézier or not (depending on the IsBezierEnabled property value). The Clear method is used to erase the content of the SignatureBox. So simple that there is nothing more to say about that control. That's why I'll expand on how I reduced the sampling rate of the control and how my Bézier implementation works in the next two sections.

Reducing the Sampling Rate

The algorithm judges if the current point is to be kept depending on its distance from the last point sampled using the pictureBox_MouseDown, pictureBox_MouseMouve and the pictureBox_MouseUp events. Upon pictureBox_MouseDown, the point given by the MouseEventArgs is added to the internal point list and set to the lastPoint field. On every pictureBox_MouseMove, the distance between the current point and the lastPoint is computed and if it's larger than the internal constant SAMPLING_INTERVAL, the point is kept. Then, when the pictureBox_MouseUp event occurs, a Point.Empty is added to the internal point list and set to the lastPoint field. The Point.Empty value is then interpreted by the drawing algorithm to reproduce the moment when the pen left the surface of the touch screen.

Now let's see how it's done in the code:

private const float SAMPLING_INTERVAL = 1.5f; // How far a new point 
				// must be from the previous one to be sampled.

private List<Point> points;
private Point lastPoint;

private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
    this.lastPoint = new Point(e.X, e.Y);
    this.points.Add(this.lastPoint);
}

private void pictureBox_MouseMove(object sender, MouseEventArgs e)
{
    Point newPoint = new Point(e.X, e.Y);
    if (Graph.Distance(this.lastPoint, newPoint) > SAMPLING_INTERVAL)
    {
        this.Draw(newPoint);
        this.lastPoint = newPoint;
        this.pictureBox.Refresh();
    }
}

private void pictureBox_MouseUp(object sender, MouseEventArgs e)
{
    this.StopDraw();
}

private void StopDraw()
{
    if (this.bezierEnabled)
    {
        if ((this.pointCount > 0) && (this.pointCount < 4))
        {
            Point[] p = new Point[this.pointCount];
            for (int i = 0; i < this.pointCount; i++)
                p[i] = this.points[this.points.Count - this.pointCount + i];
            this.graphics.DrawLines(this.pen, p);
        }
    }
    this.lastPoint = Point.Empty;
    this.points.Add(Point.Empty);
    this.pointCount = 0;
}

Graph.Distance is a simple distance calculation:

public static double Distance(Point a, Point b)
{
    return Math.Sqrt(Math.Pow(b.X - a.X, 2) + Math.Pow(b.Y - a.Y, 2));
}

With a distance of 1.5, it means that a point will be sampled only if it is at least 1.5 pixels away from the last sampled point. The following table shows how the pixels are sampled. Each box represents a pixel and contains the distance from the middle point which is the last point sampled:

Sampling demonstration
2.83 2.24 2.00 2.24 2.83
2.24 1.41 1.00 1.41 2.24
2.00 1.00 0.00 1.00 2.00
2.24 1.41 1.00 1.41 2.24
2.83 2.24 2.00 2.24 2.83

Bézier Général Algorithm

The general Bézier recursive algorithm is very simple. To have an idea how the recursion work, have a look at this animation from Wikipedia.org. There are five points for a total of four grey initial segments.

Bézier in action, courtesy of Wikipedia.org

For each segment in the point set, a new point is computed using linear interpolation at a fraction "t" which is between 0 and 1. This operation reduces the point set by one thus reducing the number of segments by one. The operation is repeated until the algorithm is called with only two points (see the magenta segment from the above GIF animation). At this moment, instead of calling the Bezier.Interpolate method another time, the last point is linearly interpolated from the last segment and is returned. To determine the precision of the algorithm and to draw the complete curve, repeat the Bézier interpolation "n" times for which "t = 1 / n".

My explanation is rather crude and may not be as mathematically exact as we were taught during undergraduate degree. I hope my code is clear enough to remove the fog I may have created with my explanations.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;

namespace GravelInnovation.BezierSignature
{
    public static class Bezier
    {
        /// ...
        public static Point[] Interpolate(int nbPoints, PointF[] points)
        {
            float step = 1.0f / (nbPoints - 1);
            Point[] retPoints = new Point[nbPoints];
            int i = 0;
            for (float t = 0; t < 1.0f; t += step)
            {
                PointF interpolatedPoint = InterpolatePoint(t, points)[0];
                retPoints[i] = new Point(
                    (int)Math.Round(interpolatedPoint.X), 
                    (int)Math.Round(interpolatedPoint.Y));
                i++;
            }

            PointF lastPoint = points[points.Length - 1];
            retPoints[retPoints.Length - 1] = new Point(
                (int)lastPoint.X, 
                (int)lastPoint.Y);

            return retPoints;
        }

        private static PointF[] InterpolatePoint(float t, params PointF[] points)
        {
            // There is only two points, return a simple linear interpolation.
            if (points.Length == 2)
                return new PointF[] {new PointF(
                    t * (points[1].X - points[0].X) + points[0].X, 
                    t * (points[1].Y - points[0].Y) + points[0].Y)};

            // For more than two points, call the Interpolate method with two
            // points to do a linear interpolation. This will reduce the 
            // number of points.
            PointF[] newPoints = new PointF[points.Length - 1];
            for (int i = 0; i < points.Length - 1; i++)
                newPoints[i] = InterpolatePoint(t, points[i], points[i + 1])[0];

            // This is where the recursion magic occurs
            return InterpolatePoint(t, newPoints);
        }
    }
}

Remark that instead of calling InterpolatePoint with "t" and two points to compute a linear interpolation, I should have created a private static Point LinearInterpolate(double t, Point p1, Point p2) method to make the whole code clearer. It's also clear that calling a recursive algorithm to resolve a third degree problem is overkill and less effective than using a cubic spline. Given the case of a signature, the overhead is not noticeable because there is only one point array. When used to smooth the movement of a 3D animation with thousands of point arrays themselves composed of thousands of points, any speed improvement in the algorithm is welcome.

Points of Interest

Using Bézier path improves significantly the quality of the signature without slowing too much the drawing rate on the control. If the functionality is unwanted, it's possible to set it off by changing the boolean property IsBezierEnabled.

History

  • 16th February, 2011: Initial post
  • 22nd February, 2011: Added code segments and a lot of explanations

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0

About the Author

Jean-Philippe Gravel

CEO
Gravel Innovation Inc.
Canada Canada

Member

Follow on Twitter Follow on Twitter


Organisation (No members)

Professional engineer, programmer, electronic hobbyist (read geek) and entrepreneur. Proud father of three great kids and my wife's greatest work-in-progress which will someday lead me to the Best Husband in the World award (nominee at least). I'm actually working in the RFID industry as a consultant in my own start-up at Gravel Innovation inc.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 5 PinmemberLeonardo Paneque9:09 10 May '12  
GeneralMy vote of 5 Pinmemberdasunnavoda22:46 22 Jan '12  
QuestionFree is Nice, but why no Open Source? Pinmemberjp2code8:45 18 Aug '11  
AnswerRe: Free is Nice, but why no Open Source? PingroupJean-Philippe Gravel8:42 13 Sep '11  
QuestionProblem with this project PinmemberMirkox684:22 13 Jun '11  
AnswerRe: Problem with this project PingroupJean-Philippe Gravel4:33 13 Jun '11  
AnswerRe: Problem with this project PingroupJean-Philippe Gravel5:16 15 Jun '11  
GeneralRe: Problem with this project PinmemberMirkox6821:12 20 Jun '11  
GeneralRe: Problem with this project PingroupJean-Philippe Gravel6:29 21 Jun '11  
GeneralMy vote of 5 PinmemberBenjano9:55 22 Feb '11  
GeneralMy vote of 5 Pinmemberjoelcarroll7:03 22 Feb '11  
GeneralRe: My vote of 5 PingroupJean-Philippe Gravel14:19 22 Feb '11  
GeneralMy vote of 5 PinmemberGary Wheeler0:27 22 Feb '11  
GeneralMy vote of 5 Pinmembersam.hill18:56 16 Feb '11  
GeneralMy vote of 4 PinmemberSledgeHammer019:47 16 Feb '11  
GeneralRe: My vote of 4 PingroupJean-Philippe Gravel14:15 16 Feb '11  
GeneralNice Pinmembervbfengshui6:46 16 Feb '11  
GeneralRe: Nice PingroupJean-Philippe Gravel14:25 16 Feb '11  
GeneralRe: Nice Pinmembervbfengshui14:36 16 Feb '11  
GeneralGreat concept, but... PinmemberDmitry Sharygin5:12 16 Feb '11  
GeneralRe: Great concept, but... Pinmembervbfengshui6:42 16 Feb '11  
GeneralRe: Great concept, but... PingroupJean-Philippe Gravel14:36 16 Feb '11  
GeneralRe: Great concept, but... PinmemberReiss0:02 21 Jul '11  
GeneralMy vote of 4 PinmemberPawel Gielmuda3:58 16 Feb '11  
GeneralRe: My vote of 4 PingroupJean-Philippe Gravel14:42 16 Feb '11  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120517.1 | Last Updated 22 Feb 2011
Article Copyright 2011 by Jean-Philippe Gravel
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid