Click here to Skip to main content
Click here to Skip to main content

Signature Box that Makes the Signature Look Right

, 21 Feb 2011 Apache
Rate this:
Please Sign up or sign in to vote.
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

Share

About the Author

Jean-Philippe Gravel
CEO Gravel Innovation Inc.
Canada Canada
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.
Group type: Organisation

1 members

Follow on   Twitter

Comments and Discussions

 
QuestionVisual Studio 2012 PinmemberMember 104421093-Dec-13 5:33 
AnswerRe: Visual Studio 2012 PingroupJean-Philippe Gravel30-Jan-14 3:45 
AnswerRe: Visual Studio 2012 PingroupJean-Philippe Gravel30-Jan-14 3:48 
GeneralMy vote of 5 PinmemberLeonardo Paneque10-May-12 10:09 
GeneralRe: My vote of 5 PingroupJean-Philippe Gravel13-Jun-12 3:33 
GeneralMy vote of 5 Pinmemberdasunnavoda22-Jan-12 23:46 
GeneralRe: My vote of 5 PingroupJean-Philippe Gravel13-Jun-12 3:28 
QuestionFree is Nice, but why no Open Source? Pinmemberjp2code18-Aug-11 9:45 
AnswerRe: Free is Nice, but why no Open Source? PingroupJean-Philippe Gravel13-Sep-11 9:42 
QuestionProblem with this project PinmemberMirkox6813-Jun-11 5:22 
AnswerRe: Problem with this project PingroupJean-Philippe Gravel13-Jun-11 5:33 
AnswerRe: Problem with this project PingroupJean-Philippe Gravel15-Jun-11 6:16 
GeneralRe: Problem with this project PinmemberMirkox6820-Jun-11 22:12 
GeneralRe: Problem with this project PingroupJean-Philippe Gravel21-Jun-11 7:29 
GeneralMy vote of 5 PinmemberBenjano22-Feb-11 10:55 
GeneralMy vote of 5 Pinmemberjoelcarroll22-Feb-11 8:03 
GeneralRe: My vote of 5 PingroupJean-Philippe Gravel22-Feb-11 15:19 
GeneralMy vote of 5 PinmemberGary Wheeler22-Feb-11 1:27 
GeneralMy vote of 5 Pinmembersam.hill16-Feb-11 19:56 
GeneralMy vote of 4 PinmemberSledgeHammer0116-Feb-11 10:47 
GeneralRe: My vote of 4 PingroupJean-Philippe Gravel16-Feb-11 15:15 
GeneralNice Pinmembervbfengshui16-Feb-11 7:46 
GeneralRe: Nice PingroupJean-Philippe Gravel16-Feb-11 15:25 
GeneralRe: Nice Pinmembervbfengshui16-Feb-11 15:36 
GeneralGreat concept, but... PinmemberDmitry Sharygin16-Feb-11 6:12 
GeneralRe: Great concept, but... Pinmembervbfengshui16-Feb-11 7:42 
GeneralRe: Great concept, but... PingroupJean-Philippe Gravel16-Feb-11 15:36 
GeneralRe: Great concept, but... PinmemberReiss21-Jul-11 1:02 
GeneralMy vote of 4 PinmemberPawel Gielmuda16-Feb-11 4:58 
GeneralRe: My vote of 4 PingroupJean-Philippe Gravel16-Feb-11 15:42 
GeneralInteresting topic PinmemberDaveAuld16-Feb-11 3:12 
GeneralRe: Interesting topic PingroupJean-Philippe Gravel16-Feb-11 15:38 
GeneralRe: Interesting topic PinmemberDaveAuld22-Feb-11 0:43 
GeneralRe: Interesting topic PingroupJean-Philippe Gravel22-Feb-11 1:54 
GeneralRe: Interesting topic PinmemberDaveAuld22-Feb-11 1:55 
GeneralMy vote of 5 PinmemberBasarat Ali Syed16-Feb-11 2:27 
GeneralRe: My vote of 5 PingroupJean-Philippe Gravel16-Feb-11 15:42 
GeneralRe: My vote of 5 PinmemberBasarat Ali Syed16-Feb-11 18:05 

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.

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