-
Notifications
You must be signed in to change notification settings - Fork 20
Added new kb article extracting-point-data-from-signature-signaturepad-dotnet-maui #1274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,197 @@ | ||||||||||
| --- | ||||||||||
| title: Extracting Point Data from Signature in SignaturePad for UI for .NET MAUI | ||||||||||
| description: Learn how to extract point and line data from a signature in the SignaturePad component for UI for .NET MAUI. | ||||||||||
| type: how-to | ||||||||||
| page_title: How to Extract Point Data from SignaturePad in UI for .NET MAUI | ||||||||||
| meta_title: How to Extract Point Data from SignaturePad in UI for .NET MAUI | ||||||||||
| slug: extracting-point-data-from-signature-signaturepad-dotnet-maui | ||||||||||
| tags: signaturepad, ui-for-dotnet-maui, point-data, signature, vector-data | ||||||||||
| res_type: kb | ||||||||||
| ticketid: 1600847 | ||||||||||
| --- | ||||||||||
|
|
||||||||||
| ## Environment | ||||||||||
|
|
||||||||||
| <table> | ||||||||||
| <tbody> | ||||||||||
| <tr> | ||||||||||
| <td>Product</td> | ||||||||||
| <td>UI for .NET MAUI SignaturePad</td> | ||||||||||
| </tr> | ||||||||||
| <tr> | ||||||||||
| <td>Version</td> | ||||||||||
| <td>Current</td> | ||||||||||
| </tr> | ||||||||||
| </tbody> | ||||||||||
| </table> | ||||||||||
|
|
||||||||||
| ## Description | ||||||||||
|
|
||||||||||
| I want to extract point and line data from the signature created using the [SignaturePad](https://docs.telerik.com/devtools/maui/controls/signaturepad/overview) component 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. | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
|
||||||||||
| 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: | ||||||||||
|
|
||||||||||
| ```csharp | ||||||||||
| 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: | ||||||||||
|
|
||||||||||
| **Stage 1: Prepare Bitmaps** | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| - Decode the image bytes into `SKBitmap`. | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| - Create a grayscale bitmap for better contrast. | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| - Convert the grayscale bitmap into a binary bitmap (black and white) using a threshold. | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
|
||||||||||
| **Stage 2: Scan Bitmap and Create Point Groups** | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| - Scan the binary bitmap for black pixels. | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| - Group neighboring black pixels into strokes using an algorithm. | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| - Store grouped strokes as `List<List<SKPoint>>`. | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
|
||||||||||
| **Stage 3: Format the Data** | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| - Serialize the grouped strokes into a desired format. | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete. |
||||||||||
| - Supported formats: | ||||||||||
| - Custom: `"1,2;3,4;5,6/7,8;9,10;11,12"` | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete. |
||||||||||
| - SVG-like: `"M x y L x y L x y ..."` | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete. |
||||||||||
|
|
||||||||||
| Below is the complete implementation: | ||||||||||
|
|
||||||||||
| ```csharp | ||||||||||
| 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(); | ||||||||||
| } | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ## See Also | ||||||||||
|
|
||||||||||
| - [SignaturePad Overview](https://docs.telerik.com/devtools/maui/controls/signaturepad/overview) | ||||||||||
| - [SaveImageSettings Documentation](https://docs.telerik.com/devtools/maui/api/telerik.maui.controls.signaturepad.saveimagesettings) | ||||||||||
| - [SKBitmap Documentation](https://docs.microsoft.com/en-us/dotnet/api/skiasharp.skbitmap?view=skiasharp-2.80) | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed.