Esempio di analisi di base dell'inchiostro

L'esempio di analisi di base dell'inchiostro mostra come la classe InkAnalyzer divide l'inchiostro in vari segmenti di parole e disegni.

Questo campione è una versione aggiornata del campione Ink Divider . Mentre il campione di divisione dell'inchiostro utilizza la classe Divider , questo esempio utilizza l'API InkAnalysis preferita e più recente. L'API InkAnalysis combina la RecognizerContext e Divider in un'API ed espande la funzionalità di entrambi.

Quando si aggiorna il modulo, l'esempio disegna un rettangolo intorno a ogni unità analizzata: parole, linee, paragrafi, aree di scrittura, disegni e punti elenco. Il modulo utilizza colori diversi per le diverse unità. I rettangoli vengono anche ingranditi con quantità diverse per garantire che nessuno rettangolo sia oscurato da altri.

La tabella seguente specifica il colore e l'ingrandimento per ogni unità analizzata.

Unità analizzata Colore del rettangolo Ingrandimento del rettangolo (pixel)
Parola
Verde
1
Linea
Magenta
3
Paragrafo
Blu
5
Area di scrittura
Giallo
7
Disegno
Rosso
1
Pallottola
Arancia/Arancione
1

È possibile cancellare i tratti nel modulo. Nell'applicazione di esempio, è possibile alternare tra la modalità Scrittura e Cancella per modificare la funzione della penna.

        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();
        }

Quando si aggiungono o eliminano tratti, gli esempi si aggiornano nel 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 ( );
            }
        }

Si noti che nel menu Modalità, l'analisi automatica del layout è attivata per impostazione predefinita. Con questa opzione selezionata, i gestori eventi di InkOverlay per Stroke e StrokesDeleting chiamano il metodo BackgroundAnalyze ogni volta che un tratto viene creato o eliminato.

Nota

Chiamare il metodo Analyze dell'oggetto InkAnalyzer con più di pochi tratti presenti crea un ritardo notevole nell'applicazione. Ciò è dovuto al fatto che Analyze è un'operazione di analisi dell'input penna sincrona. In pratica, chiamare il metodo Analyze solo quando è necessario il risultato. In caso contrario, usare il metodo asincrono BackgroundAnalyze, come mostrato nell'esempio.

Gestione dei risultati dell'analisi

L'esempio crea due matrici per contenere i vari rettangoli, orizzontali o ruotati. Utilizzare un rettangolo ruotato per ottenere l'angolo su cui viene scritta una linea di testo. L'esempio mostra le proprietà restituite dal InkAnalyzer e visualizza il raccordo limitatore o il raccordo limitatore ruotato (a seconda della selezione nel menu).

      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;
        }

Il parser calcola GetRotatedBoundingBox durante l'analisi. È possibile accedere alle informazioni dai riquadri di selezione ruotati nella vostra applicazione per vari scopi utili:

  • Rilevare o disegnare i limiti di una singola riga, paragrafo o altra unità.
  • Determinare l'angolo in corrispondenza del quale viene scritta una riga o un paragrafo.
  • Implementare funzionalità come la selezione per una riga, un paragrafo o un'altra unità.

Riconoscimento di Base e Analisi dell'Inchiostro

Esempio di Modulo Cartaceo Scansionato

Divisore per Inchiostro Esempio