基本筆跡分析範例顯示 墨水分析器類別如何將筆跡分割成各種文字和繪圖區段。
此範例是 筆跡分隔器範例的更新版本。 雖然 Ink 分隔器範例使用 分隔器 類別,但此範例會使用較新的且偏好使用的 InkAnalysis API。 InkAnalysis API 會將 RecognizerContext 和 Divider 合併成一個 API,並擴充兩者的功能。
當您更新表單時,範例會在每個分析單位周圍繪製矩形:文本、線條、段落、書寫區域、繪圖和項目符號。 表單會針對不同的單位使用不同的色彩。 矩形也會以不同的數量放大,以確保其他人不會遮蔽任何矩形。
下表指定每個分析單位的色彩和放大。
| 分析單位 | 矩形的色彩 | 矩形擴大(像素) |
|---|---|---|
| 詞 |
綠 |
1 |
| 線 |
品紅 |
3 |
| 段 |
藍 |
5 |
| 書寫區域 |
黃色 |
7 |
| 繪圖 |
紅 |
1 |
| 子彈 |
橙 |
1 |
您可以清除表單中的筆劃。 在範例應用程式中,您可以在 Ink 和 Erase 模式之間切換,以變更手寫筆的功能。
private void miInk_Click(object sender, System.EventArgs e)
{
// Turn on the inking mode
myInkOverlay.EditingMode = InkOverlayEditingMode.Ink;
// Update the state of the Ink and Erase menu items
miInk.Checked = true;
miErase.Checked = false;
// Update the UI
this.Refresh();
}
private void miErase_Click(object sender, System.EventArgs e)
{
// Turn on the ink deletion mode
myInkOverlay.EditingMode = InkOverlayEditingMode.Delete;
// Update the state of the Ink and Erase menu items
miInk.Checked = false;
miErase.Checked = true;
// Update the UI
this.Refresh();
}
當您新增或刪除筆劃時,範例資料會更新 InkAnalyzer。
private void myInkOverlay_Stroke(object sender, InkCollectorStrokeEventArgs e)
{
// Filter out the eraser stroke.
if (InkOverlayEditingMode.Ink == myInkOverlay.EditingMode)
{
// Add the new stroke to the InkAnalyzer's stroke collection
myInkAnalyzer.AddStroke(e.Stroke);
if (miAutomaticLayoutAnalysis.Checked)
{
// Invoke an analysis operation on the background thread
myInkAnalyzer.BackgroundAnalyze();
}
}
}
void myInkOverlay_StrokeDeleting(object sender, InkOverlayStrokesDeletingEventArgs e)
{
// Remove the strokes to be deleted from the InkAnalyzer's stroke collection
myInkAnalyzer.RemoveStrokes(e.StrokesToDelete);
// If automatic layout analysis is turned on, analyze the ink on the background thread
if ( miAutomaticLayoutAnalysis.Checked )
{
// Invoke an analysis operation on the background thread
myInkAnalyzer.BackgroundAnalyze ( );
}
}
請注意,在 [模式] 功能表中,[自動配置分析] 預設為開啟。 選擇此選項後,InkOverlay 物件的 Stroke 和 StrokesDeleting 事件處理程式在每次建立或刪除筆劃時,都會呼叫 BackgroundAnalyze 方法。
注意
調用 InkAnalyzer 物件的 Analyze 方法,當處理的筆劃數量超過少量時,會在應用程式中造成明顯的延遲。 這是因為分析是同步筆跡分析作業。 實際上,只有在您需要結果時,才呼叫 Analyze 方法。 否則,請使用非同步 BackgroundAnalyze 方法,如範例所示。
處理分析結果
此範例會建立兩個陣列來儲存各種矩形,無論是水平排列或旋轉排列的。 使用旋轉的邊界框來取得一行文字的寫入角度。 此範例顯示由 InkAnalyzer 所回傳的屬性,並根據功能表的選擇,顯示邊界方框或旋轉邊界方框。
private Rectangle[] GetHorizontalBBoxes(Guid nodeType, int inflate)
{
// Declare the array of rectangles to hold the result
Rectangle[] analysisRects;
// Get the division units from the division result of division type
ContextNodeCollection nodes = myInkAnalyzer.FindNodesOfType(nodeType);
// If there is at least one unit, we construct the rectangles
if ((null != nodes) && (0 < nodes.Count))
{
// We need to convert rectangles from ink units to
// pixel units. For that, we need Graphics object
// to pass to InkRenderer.InkSpaceToPixel method
using (Graphics g = drawArea.CreateGraphics())
{
// Construct the rectangles
analysisRects = new Rectangle[nodes.Count];
// InkRenderer.InkSpaceToPixel takes Point as parameter.
// Create two Point objects to point to (Top, Left) and
// (Width, Height) properties of rectangle. (Width, Height)
// is used instead of (Right, Bottom) because (Right, Bottom)
// are read-only properties on Rectangle
Point ptLocation = new Point();
Point ptSize = new Point();
// Index into the bounding boxes
int i = 0;
// Iterate through the collection of division units to obtain the bounding boxes
foreach (ContextNode node in nodes)
{
// Get the bounding box of the strokes of the division unit
analysisRects[i] = node.Location.GetBounds();
// The bounding box is in ink space unit. Convert them into pixel unit.
ptLocation = analysisRects[i].Location;
ptSize.X = analysisRects[i].Width;
ptSize.Y = analysisRects[i].Height;
// Convert the Location from Ink Space to Pixel Space
myInkOverlay.Renderer.InkSpaceToPixel(g, ref ptLocation);
// Convert the Size from Ink Space to Pixel Space
myInkOverlay.Renderer.InkSpaceToPixel(g, ref ptSize);
// Assign the result back to the corresponding properties
analysisRects[i].Location = ptLocation;
analysisRects[i].Width = ptSize.X;
analysisRects[i].Height = ptSize.Y;
// Inflate the rectangle by inflate pixels in both directions
analysisRects[i].Inflate(inflate, inflate);
// Increment the index
++i;
}
} // Relinquish the Graphics object
}
else
{
// Otherwise we return null
analysisRects = null;
}
// Return the Rectangle[] object
return analysisRects;
}
private System.Collections.ArrayList GetRotatedBBoxes(Guid nodeType, int inflate)
{
//Find the correct collection of results nodes.
ContextNodeCollection Nodes = myInkAnalyzer.FindNodesOfType(nodeType);
// Declare the array list to hold the results;
// This array represents the four points of a rectangle, with the first point
// copied again to complete the cycle of points.
ArrayList polygonPoints = new ArrayList(Nodes.Count);
// Cycle through each results node, get and convert the
// rotated bounding box points
foreach (ContextNode node in Nodes)
{
//Declare the point array
Point[] rotatedBoundingBox = null;
//Switch on the type of ContextNode to cast into the
//appropriate type. This is required to access the
//type specific property "RotatedBoundingBox" which
//is not found on all ContextNode types.
if (nodeType == ContextNodeType.InkWord)
{
rotatedBoundingBox = ((InkWordNode)node).GetRotatedBoundingBox();
}
else if (nodeType == ContextNodeType.Line)
{
rotatedBoundingBox = ((LineNode)node).GetRotatedBoundingBox();
}
else if (nodeType == ContextNodeType.Paragraph)
{
rotatedBoundingBox = ((ParagraphNode)node).GetRotatedBoundingBox();
}
else if (nodeType == ContextNodeType.WritingRegion ||
nodeType == ContextNodeType.InkDrawing ||
nodeType == ContextNodeType.InkBullet )
{
// Rotated Bounding Boxes are not a supported option for
// Writing Regions or Drawings. We return the axis aligned
// bounding box instead
Rectangle rect = node.Location.GetBounds();
// We need to create a looped list of 4 points to be consistent
// with the way InkAnalysis represents rotated bounding boxes.
rotatedBoundingBox = new Point[4];
rotatedBoundingBox[0] = new Point(rect.X, rect.Y);
rotatedBoundingBox[1] = new Point(rect.Right, rect.Y);
rotatedBoundingBox[2] = new Point(rect.Right, rect.Bottom);
rotatedBoundingBox[3] = new Point(rect.X, rect.Bottom);
}
if (null != rotatedBoundingBox)
{
// We need to convert rectangles from ink units to
// pixel units. For that, we need Graphics object
// to pass to InkRenderer.InkSpaceToPixel method
using (Graphics g = drawArea.CreateGraphics())
{
// convert each of the points from ink space to pixel space
for (int i = 0; i < rotatedBoundingBox.Length; i++)
{
myInkOverlay.Renderer.InkSpaceToPixel(g, ref rotatedBoundingBox[i]);
}
//inflate the points by calling helper method
InflateHelperMethod(ref rotatedBoundingBox, inflate);
// increment the node portion of the polygonPoints array
polygonPoints.Add(rotatedBoundingBox);
}
}
}
//Return the results
return polygonPoints;
}
剖析器會在分析期間計算 GetRotatedBoundingBox。 您可以從應用程式中旋轉的周框方塊存取資訊,原因有很多:
- 偵測或繪製單行、段落或其他單位的界限。
- 決定線條或段落寫入的角度。
- 實作線條、段落或其他單位的選取等功能。