Kevin Beckner
Kevin Beckner

Reputation: 21

How to draw a signature and save it to disc as a Bitmap?

I'm trying to do a signature capture program and save the customer signature to a PNG or BMP format. I have the Picturebox code working great and the results look better than using draw. I can't get the image to save.

Imports System.Drawing
Public Class Form1
    Dim color As System.Drawing.Pen = Pens.Black
    Dim bmp As Bitmap

    Private Sub form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        bmp = New Bitmap(PictureBox1.Width, PictureBox1.Height)
        PictureBox1.Image = bmp
    End Sub

    Private Sub PictureBox1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBox1.MouseMove
        Static last As New Point
        If e.Button = Windows.Forms.MouseButtons.Left Then
            PictureBox1.CreateGraphics.DrawLine(color, last.X, last.Y, e.X, e.Y)
        End If
        last = e.Location
    End Sub

    Private Sub CmdClear_Click(sender As Object, e As EventArgs) Handles cmdClear.Click
        bmp = New Bitmap(PictureBox1.Width, PictureBox1.Height)
        PictureBox1.Image = bmp
    End Sub

    Private Sub CmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
        If PictureBox1.Image IsNot Nothing Then
            bmp = PictureBox1.Image
            bmp.Save("c:\temp\test1.bmp")
        End If
    End Sub
End Class

I would like to see the image of the signature.

Upvotes: 1

Views: 5010

Answers (3)

Jimi
Jimi

Reputation: 32278

Never use [Control].CreateGraphics, unless you need this object for immediate use in a specific environment. To measure the size of text when drawn in a specific graphics context, for example.
When you need the drawing to persist, as painting on the surface of a control, use the Graphics object provided by the PaintEventArgs of the Paint event (or similar event, as the DrawItem event of the ComboBox, ListBox, ListView controls).
You'll find this kind of recommendations all over SO (and the Web, in general).

How to proceed:

  1. We need an object that can store the Mouse/Pen movements that define a hand-made curve.
  2. The object needs to store a new curve definition each time the left button of the mouse is released (or the Pen touch is lost and re-acquired).
  3. We need a Graphics object that can convert the cardinal spline curves, defined by the points created by Mouse/Pen movements, to Bezier curves (combinations of curves - outlines that represents a series of smooth vector lines - are often referred to as Paths in vector graphics).

Here, the object that stores the movements is a Dictionary(Of Integer, List(Of Point)), where the Key represents a curve and the Value represents the collection of Points that define that curve.
Each time the Left Button of the Mouse is pressed, a new Key is created and a new List(Of Point) is associated with the Key.
When the Mouse/Pen is moved, the new Point location is added to the current curve's List(Of Point).

The GraphicsPath class can convert the List(Of Point) collection to Control Points of a Bezier curve, using the GraphicsPath.AddCurve() method.
This method accepts an array of Points and a Tension value as arguments. The Tension is a value between 0 and 1 that defines the amount of bending applied to a curve when connecting Points. A value of 0.5f is used here.

► When we need to paint the drawing on a Bitmap to save the results to disc, we apply the same logic to the Graphics object derived, in this case, from a Bitmap object.
Thus, just one method is used to draw both on the surface of a Control and onto a Bitmap object.
The DrawSignature(g As Graphics) method in this code.

This is how it works:

Signature Drawing

Code that reproduces the procedure described:

pBoxSignature is the PictureBox where the signature is drawn
pBoxSavedSignature is the (smaller) PictureBox used to show the last image saved
btnClearSignature is the Button used to clear the signature
btnSaveSignature is the Button used to save the signature to a new Image
txtSignatureFileName is the TextBox used to enter the signature's file name

Private signatureObject As New Dictionary(Of Integer, List(Of Point))
Private signaturePen As New Pen(Color.Black, 4)
Private currentCurvePoints As List(Of Point)
Private currentCurve As Integer = -1

Private Sub pBoxSignature_MouseDown(sender As Object, e As MouseEventArgs) Handles pBoxSignature.MouseDown
    currentCurvePoints = New List(Of Point)
    currentCurve += 1
    signatureObject.Add(currentCurve, currentCurvePoints)
End Sub

Private Sub pBoxSignature_MouseMove(sender As Object, e As MouseEventArgs) Handles pBoxSignature.MouseMove
    If e.Button <> MouseButtons.Left OrElse currentCurve < 0 Then Return
    signatureObject(currentCurve).Add(e.Location)
    pBoxSignature.Invalidate()
End Sub

Private Sub btnClearSignature_Click(sender As Object, e As EventArgs) Handles btnClearSignature.Click
    currentCurve = -1
    signatureObject.Clear()
    pBoxSignature.Invalidate()
End Sub

Private Sub btnSaveSignature_Click(sender As Object, e As EventArgs) Handles btnSaveSignature.Click
    Dim signatureFileName = txtSignatureFileName.Text.Trim()
    If String.IsNullOrEmpty(signatureFileName) Then Return
    If currentCurve < 0 OrElse signatureObject(currentCurve).Count = 0 Then Return

    imgSignature As Bitmap = New Bitmap(pBoxSignature.Width, pBoxSignature.Height, PixelFormat.Format32bppArgb) 
    Using g As Graphics = Graphics.FromImage(imgSignature)
        DrawSignature(g)

        Dim signaturePath As String = Path.Combine(Application.StartupPath, $"{signatureFileName}.png")
        imgSignature.Save(signaturePath, ImageFormat.Png)
        pBoxSavedSignature.Image?.Dispose()
        pBoxSavedSignature.Image = imgSignature
    End Using
End Sub

Private Sub pBoxSignature_Paint(sender As Object, e As PaintEventArgs) Handles pBoxSignature.Paint
    If currentCurve < 0 OrElse signatureObject(currentCurve).Count = 0 Then Return
    DrawSignature(e.Graphics)
End Sub

Private Sub DrawSignature(g As Graphics)
    g.CompositingQuality = CompositingQuality.HighQuality
    g.SmoothingMode = SmoothingMode.AntiAlias

    For Each curve In signatureObject
        If curve.Value.Count < 2 Then Continue For
        Using gPath As New GraphicsPath()
            gPath.AddCurve(curve.Value.ToArray(), 0.5F)
            g.DrawPath(signaturePen, gPath)
        End Using
    Next
End Sub

C# Version

private Dictionary<int, List<Point>> signatureObject = new Dictionary<int, List<Point>>();
private Pen signaturePen = new Pen(Color.Black, 4);
private List<Point> currentCurvePoints = null;
private int currentCurve = -1;

private void pBoxSignature_MouseDown(object sender, MouseEventArgs e)
{
    currentCurvePoints = new List<Point>();
    currentCurve += 1;
    signatureObject.Add(currentCurve, currentCurvePoints);
}

private void pBoxSignature_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button != MouseButtons.Left || currentCurve < 0) return;
    signatureObject[currentCurve].Add(e.Location);
    pBoxSignature.Invalidate();
}

private void btnClearSignature_Click(object sender, EventArgs e)
{
    currentCurve = -1;
    signatureObject.Clear();
    pBoxSignature.Invalidate();
}

private void btnSaveSignature_Click(object sender, EventArgs e)
{
    string signatureFileName = txtSignatureFileName.Text.Trim();
    if (string.IsNullOrEmpty(signatureFileName)) return;
    if (currentCurve < 0 || signatureObject[currentCurve].Count == 0) return;

    var imgSignature = new Bitmap(pBoxSignature.Width, pBoxSignature.Height, PixelFormat.Format32bppArgb);
    using (var g = Graphics.FromImage(imgSignature)) {
        DrawSignature(g);
        string signaturePath = Path.Combine(Application.StartupPath, $"{signatureFileName}.png");
        imgSignature.Save(signaturePath, ImageFormat.Png);
        pBoxSavedSignature.Image?.Dispose();
        pBoxSavedSignature.Image = imgSignature;
    }
}

private void pBoxSignature_Paint(object sender, PaintEventArgs e)
{
    if (currentCurve < 0 || signatureObject[currentCurve].Count == 0) return;
    DrawSignature(e.Graphics);
}

private void DrawSignature(Graphics g)
{
    // In case you have composited background
    g.CompositingQuality = CompositingQuality.HighQuality;
    g.SmoothingMode = SmoothingMode.AntiAlias;

    foreach (var curve in signatureObject) {
        if (curve.Value.Count < 2) continue;

        using (var gPath = new GraphicsPath()) {
            gPath.AddCurve(curve.Value.ToArray(), 0.5f);
            g.DrawPath(signaturePen, gPath);
        }
    }
}

Upvotes: 11

user3368570
user3368570

Reputation: 64

Thanks Jimi.

In case someone need it in C#:

public partial class FSignature : Form
{
    private Dictionary<int, List<Point>> signatureObject = new Dictionary<int, List<Point>>();
    private Pen signaturePen = new Pen(Color.Black, 4);
    private List<Point> currentCurvePoints;
    private int currentCurve = -1;

    public FSignature()
    {
        InitializeComponent();
    }





    private void pBoxSignature_MouseDown(object sender, MouseEventArgs e)
    {
        currentCurvePoints = new List<Point>();
        currentCurve += 1;
        signatureObject.Add(currentCurve, currentCurvePoints);
    }

    private void pBoxSignature_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.Button != MouseButtons.Left || currentCurve < 0)
            return;
        signatureObject[currentCurve].Add(e.Location);
        pBoxSignature.Invalidate();
    }

    private void btnClearSignature_Click(object sender, EventArgs e)
    {
        currentCurve = -1;
        signatureObject.Clear();
        pBoxSignature.Invalidate();
    }

    private void btnSaveSignature_Click(object sender, EventArgs e)
    {
        var signatureFileName = txtSignatureFileName.Text.Trim();
        if (string.IsNullOrEmpty(signatureFileName))
            return;
        if (currentCurve < 0 || signatureObject[currentCurve].Count == 0)
            return;
        using (Bitmap imgSignature = new Bitmap(pBoxSignature.Width, pBoxSignature.Height, PixelFormat.Format32bppArgb))
        {
            using (Graphics g = Graphics.FromImage(imgSignature))
            {
                DrawSignature(g);
                string signaturePath = Path.Combine(Application.StartupPath, $"{signatureFileName}.png");
           
                imgSignature.Save(signaturePath, ImageFormat.Png);
                pBoxSavedSignature.Image = new Bitmap(imgSignature);
            }
        }
    }

    private void pBoxSignature_Paint(object sender, PaintEventArgs e)
    {
        if (currentCurve < 0 || signatureObject[currentCurve].Count == 0)
            return;
        DrawSignature(e.Graphics);
    }

    private void DrawSignature(Graphics g)
    {
        g.CompositingMode = CompositingMode.SourceOver;
        g.CompositingQuality = CompositingQuality.HighQuality;
        g.SmoothingMode = SmoothingMode.AntiAlias;
        foreach (var curve in signatureObject)
        {
            if (curve.Value.Count < 2)
                continue;
            using (GraphicsPath gPath = new GraphicsPath())
            {
                gPath.AddCurve(curve.Value.ToArray(), 0.5F);
                g.DrawPath(signaturePen, gPath);
            }
        }
    }


}

Upvotes: 0

MrAvgProgrammer
MrAvgProgrammer

Reputation: 1

When wanting to save as BMP, the background needs to be white because BMP doesnt support Transparency. With that being said, I have added these 2 lines above the DrawPath call.

Dim Brsh As SolidBrush = New SolidBrush(Color.White)
g.FillRectangle(Brsh, 0, 0, pBoxSignature.Width, pBoxSignature.Height)
g.DrawPath(signaturePen, gPath)

BUT, Now I am no longer able to pickup my pen stroke otherwise all previous strokes get erased/cleared. (Meaning first and last names need to be written without pickup up pen) Could someone with more knowledge on images perhaps see a reason for this and point me in the direction of a solution?

UPDATE: Working Code

Here is the code:

Using imgSignature As Bitmap = New Bitmap(pBoxSignature.Width, pBoxSignature.Height, PixelFormat.Format32bppRgb)
    Using g As Graphics = Graphics.FromImage(imgSignature)
        g.FillRectangle(Brsh, 0, 0, pBoxSignature.Width, pBoxSignature.Height)
        Call DrawSignature(g)
    End Using
    imgSignature.Save(signaturePath, ImageFormat.Bmp)
    pBoxSavedSignature.Image = New Bitmap(imgSignature)
End Using

Upvotes: 0

Related Questions