Extracting Point Data from Signature in SignaturePad for UI for .NET MAUI
Environment
| Product | UI for .NET MAUI SignaturePad |
| Version | Current |
Description
I want to extract point and line data from the signature created using the SignaturePad control of UI for .NET MAUI. The goal is to format the signature data as a string that represents the x, y coordinates of the strokes in the signature. Different formats for the output data are required, such as a custom format or an SVG-like format.
This knowledge base article also answers the following questions:
- How to save signature data as points and strokes from SignaturePad UI for .NET MAUI?
- How to format SignaturePad data into custom or SVG-like string formats?
- How to process and scan SignaturePad image to extract point coordinates?
Solution
To achieve this, process the signature image generated by the SignaturePad component and extract vector data representing the point coordinates. Follow these steps:
1. Save the Signature as an Image
Save the signature as a PNG image using the SaveImageAsync method:
using var stream = new MemoryStream();
await SigPad1.SaveImageAsync(stream, new SaveImageSettings
{
ImageFormat = Telerik.Maui.Controls.SignaturePad.ImageFormat.Png,
BackgroundColor = Colors.AliceBlue,
StrokeColor = Colors.DarkBlue,
StrokeThickness = 2
});
var imageBytes = stream.ToArray();
GetVectorFromSignatureImage(imageBytes);
2. Process the Image to Extract Vector Data
Create a method to process the image and extract vector data. This involves three stages:
-
Prepare the Bitmaps.
- Decode the image bytes into
SKBitmap. - Create a grayscale bitmap for better contrast.
- Convert the grayscale bitmap into a binary bitmap (black and white) using a threshold.
- Decode the image bytes into
-
Scan the Bitmap and create point groups.
- Scan the binary bitmap for black pixels.
- Group neighboring black pixels into strokes using an algorithm.
- Store the grouped strokes as
List<List<SKPoint>>.
-
Serialize the grouped strokes into your preferred format:
- Custom:
"1,2;3,4;5,6/7,8;9,10;11,12" - SVG-like:
"M x y L x y L x y ..."
- Custom:
Below is the complete implementation:
public static string GetVectorFromSignatureImage(byte[] imageBytes, string formatType = "custom")
{
using var bitmap = SKBitmap.Decode(imageBytes);
var grayscaleBitmap = new SKBitmap(bitmap.Width, bitmap.Height);
var binaryBitmap = new SKBitmap(bitmap.Width, bitmap.Height);
// Convert to grayscale
for (var y = 0; y < bitmap.Height; y++)
{
for (var x = 0; x < bitmap.Width; x++)
{
var color = bitmap.GetPixel(x, y);
var gray = (byte)(0.3 * color.Red + 0.59 * color.Green + 0.11 * color.Blue);
grayscaleBitmap.SetPixel(x, y, new SKColor(gray, gray, gray));
}
}
// Convert to binary (black/white)
for (var y = 0; y < grayscaleBitmap.Height; y++)
{
for (var x = 0; x < grayscaleBitmap.Width; x++)
{
var color = grayscaleBitmap.GetPixel(x, y);
var value = color.Red < 128 ? SKColors.Black : SKColors.White;
binaryBitmap.SetPixel(x, y, value);
}
}
var pxScanned = new bool[binaryBitmap.Width, binaryBitmap.Height];
var groupedStrokes = new List<List<SKPoint>>();
int[] dx = { -1, 0, 1, 0, -1, -1, 1, 1 };
int[] dy = { 0, -1, 0, 1, -1, 1, -1, 1 };
for (var y = 0; y < binaryBitmap.Height; y++)
{
for (var x = 0; x < binaryBitmap.Width; x++)
{
if (pxScanned[x, y] || binaryBitmap.GetPixel(x, y) != SKColors.Black)
continue;
pxScanned[x, y] = true;
var queue = new Queue<SKPoint>();
queue.Enqueue(new SKPoint(x, y));
var stroke = new List<SKPoint>();
while (queue.Count > 0)
{
var point = queue.Dequeue();
stroke.Add(point);
for (var i = 0; i < dx.Length; i++)
{
var nx = (int)point.X + dx[i];
var ny = (int)point.Y + dy[i];
if (nx >= 0 && ny >= 0 && nx < binaryBitmap.Width && ny < binaryBitmap.Height &&
!pxScanned[nx, ny] && binaryBitmap.GetPixel(nx, ny) == SKColors.Black)
{
pxScanned[nx, ny] = true;
queue.Enqueue(new SKPoint(nx, ny));
}
}
}
if (stroke.Count > 0)
groupedStrokes.Add(stroke);
}
}
var sb = new System.Text.StringBuilder();
var ic = System.Globalization.CultureInfo.InvariantCulture;
var fm = "N1";
switch (formatType.ToLower())
{
case "custom":
for (var i = 0; i < groupedStrokes.Count; i++)
{
for (var j = 0; j < groupedStrokes[i].Count; j++)
{
sb.Append(groupedStrokes[i][j].X.ToString(fm, ic));
sb.Append(",");
sb.Append(groupedStrokes[i][j].Y.ToString(fm, ic));
if (j < groupedStrokes[i].Count - 1) sb.Append(";");
}
if (i < groupedStrokes.Count - 1) sb.Append("/");
}
break;
case "svg":
for (var gs = 0; gs < groupedStrokes.Count; gs++)
{
sb.Append(gs == 0 ? "M " : " L ");
for (var p = 0; p < groupedStrokes[gs].Count; p++)
{
sb.Append(groupedStrokes[gs][p].X.ToString(fm, ic));
sb.Append(" ");
sb.Append(groupedStrokes[gs][p].Y.ToString(fm, ic));
}
}
break;
}
return sb.ToString();
}