Click here to Skip to main content
15,881,248 members
Articles / Multimedia / GDI+

Signature Box that Makes the Signature Look Right

Rate me:
Please Sign up or sign in to vote.
4.79/5 (28 votes)
21 Feb 2011Apache6 min read 76.4K   2.7K   50   38
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:

C#
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:

C#
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.832.242.002.242.83
2.241.411.001.412.24
2.001.000.001.002.00
2.241.411.001.412.24
2.832.242.002.242.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.

C#
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


Written By
Systems Engineer Norda Stelo
Canada Canada
Professional engineer, developper, electronic hobbyist and entrepreneur (read geek). 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 at Norda Stelo as an Industrial System Engineer.
This is a Organisation

1 members

Comments and Discussions

 
QuestionVisual Studio 2012 Pin
Member 104421093-Dec-13 4:33
Member 104421093-Dec-13 4:33 
AnswerRe: Visual Studio 2012 Pin
Jean-Philippe Gravel30-Jan-14 2:45
professionalJean-Philippe Gravel30-Jan-14 2:45 
AnswerRe: Visual Studio 2012 Pin
Jean-Philippe Gravel30-Jan-14 2:48
professionalJean-Philippe Gravel30-Jan-14 2:48 
GeneralMy vote of 5 Pin
Leonardo Paneque10-May-12 9:09
Leonardo Paneque10-May-12 9:09 
GeneralRe: My vote of 5 Pin
Jean-Philippe Gravel13-Jun-12 2:33
professionalJean-Philippe Gravel13-Jun-12 2:33 
GeneralMy vote of 5 Pin
dasunnavoda22-Jan-12 22:46
dasunnavoda22-Jan-12 22:46 
nice one... great....
GeneralRe: My vote of 5 Pin
Jean-Philippe Gravel13-Jun-12 2:28
professionalJean-Philippe Gravel13-Jun-12 2:28 
QuestionFree is Nice, but why no Open Source? Pin
jp2code18-Aug-11 8:45
professionaljp2code18-Aug-11 8:45 
AnswerRe: Free is Nice, but why no Open Source? Pin
Jean-Philippe Gravel13-Sep-11 8:42
professionalJean-Philippe Gravel13-Sep-11 8:42 
QuestionProblem with this project Pin
Mirkox6813-Jun-11 4:22
Mirkox6813-Jun-11 4:22 
AnswerRe: Problem with this project Pin
Jean-Philippe Gravel13-Jun-11 4:33
professionalJean-Philippe Gravel13-Jun-11 4:33 
AnswerRe: Problem with this project Pin
Jean-Philippe Gravel15-Jun-11 5:16
professionalJean-Philippe Gravel15-Jun-11 5:16 
GeneralRe: Problem with this project Pin
Mirkox6820-Jun-11 21:12
Mirkox6820-Jun-11 21:12 
GeneralRe: Problem with this project Pin
Jean-Philippe Gravel21-Jun-11 6:29
professionalJean-Philippe Gravel21-Jun-11 6:29 
GeneralMy vote of 5 Pin
Benjano22-Feb-11 9:55
professionalBenjano22-Feb-11 9:55 
GeneralMy vote of 5 Pin
joelcarroll22-Feb-11 7:03
joelcarroll22-Feb-11 7:03 
GeneralRe: My vote of 5 Pin
Jean-Philippe Gravel22-Feb-11 14:19
professionalJean-Philippe Gravel22-Feb-11 14:19 
GeneralMy vote of 5 Pin
Gary Wheeler22-Feb-11 0:27
Gary Wheeler22-Feb-11 0:27 
GeneralMy vote of 5 Pin
sam.hill16-Feb-11 18:56
sam.hill16-Feb-11 18:56 
GeneralMy vote of 4 Pin
SledgeHammer0116-Feb-11 9:47
SledgeHammer0116-Feb-11 9:47 
GeneralRe: My vote of 4 Pin
Jean-Philippe Gravel16-Feb-11 14:15
professionalJean-Philippe Gravel16-Feb-11 14:15 
GeneralNice Pin
vbfengshui16-Feb-11 6:46
vbfengshui16-Feb-11 6:46 
GeneralRe: Nice Pin
Jean-Philippe Gravel16-Feb-11 14:25
professionalJean-Philippe Gravel16-Feb-11 14:25 
GeneralRe: Nice Pin
vbfengshui16-Feb-11 14:36
vbfengshui16-Feb-11 14:36 
GeneralGreat concept, but... Pin
Dmitry Sharygin16-Feb-11 5:12
Dmitry Sharygin16-Feb-11 5:12 

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

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