65.9K
CodeProject 正在变化。 阅读更多。
Home

理解笔触和数字墨迹

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (11投票s)

2007 年 3 月 7 日

CPOL

7分钟阅读

viewsIcon

56998

downloadIcon

1184

了解墨迹如何创建以及笔画如何管理。

引言

本文是对用于墨迹收集和识别的对象模型的初步探讨。我们将研究墨迹的收集机制,以及墨迹笔画的逻辑位置和标识。

让我们从讨论对象模型中最重要的类开始,即Ink类。在大多数情况下,您将使用InkOverlay类在应用程序中使用Ink,但InkOverlayInkCollector都会为您处理Ink对象的实例化。.

Screenshot - StrokeViewerFigure1.jpg
图 1. 墨迹对象模型

  • InkCollector:这是用于在用户输入时收集和渲染墨迹的基本对象。InkCollector会将事件作为它们发生时触发到您的应用程序,还会为您将Cursor移动打包成笔画。您可以告诉InkCollector您有兴趣接收哪些事件和数据,以及是否希望它在收集正在进行的笔画时进行绘制。InkCollector的设计意图是,它没有InkOverlay所带来的所有支持,因为它是一个组件技术的基础,您希望获得不同于InkOverlay的行为。
  • InkOverlayInkOverlay对象是InkCollector的超集,并增加了对已捕获墨迹的选择和擦除功能。对于几乎所有应用程序来说,InkOverlay都是首选对象,因为它对选择相关功能的支持可以节省大量时间,并且通常是任何应用程序的要求。为了保持简单,图 1 使用的就是这个对象。

InkCollectorInkOverlay对象会触发大量事件。连接这些事件可使您响应墨迹输入或笔移动的所有重要阶段。

CursorInRange / CursorOutOfRange 事件指定用户已将光标移到墨迹启用区域内。请注意,在某些设备上,光标不必接触屏幕即可触发这些事件。

CursorInRange事件之后,如果光标未接触数字化仪,则会引发NewInAirPackets事件。

当光标接触数字化仪时,会触发CursorDown事件。

CursorDown事件之后是NewPackets事件,它指示正在收集墨迹。

当触发CursorOutOfRange事件时,您将收到一个StrokeGesture事件,具体取决于InkCollector对象的CollectionMode属性的值。InkOverlay对象也是如此。

您的应用程序通常会收到各种SystemGestureMouse事件。默认情况下,Tablet PC 平台支持许多系统手势,其中许多手势模仿传统的鼠标事件。例如,tap事件映射到鼠标单击,而dragholdhover事件映射到相同类型的Mouse事件。有关系统手势的更多信息,请参阅Tablet PC SDK中的System Gestures

笔画数据

Stroke对象由数据包信息(x,y,...)组成,这些信息代表坐标系中某个点的Ink,我们现在知道该坐标系从左上角开始为0,0。每个Stroke对象都会被分配一个相对于其所属Ink对象的唯一 ID(Stroke.ID)。它允许开发人员为自定义数据定义扩展属性,并公开影响数据渲染的DrawingAttributes

Strokes集合是Stroke对象引用的集合。Strokes集合提供了对其中包含的Stroke对象集合的常见有用操作,例如Rotate()Scale(),它们用于过滤、移动或调整Stroke数据的大小。

让我们来看一些Stroke信息。我正在使用StrokeIDViewer应用程序,该程序作为Building Tablet PC Applications书籍的一部分提供;我已重写了该应用程序的一部分以包含在本文和随附的下载文件中。在我的示例源代码中,它称为StrokeViewer

Screenshot - StrokeViewerFigure2.jpg
图 2. 我用草书和印刷体写我的名字的笔画计数。

请注意,当我用草书和印刷体写我的名字时,笔画数量的差异。我按下、书写、抬起四次写草书,九次写印刷体。哇,那真是额外的工作。所以,一个笔画包含从我接触数字化仪到我移开的数据包数据。Strokes基本上是一个贝塞尔曲线,代表在CursorDownCursorUp事件之间收集的墨迹。

这里有很多事情正在发生。

  1. 每次按下笔/移动笔/抬起笔操作都会创建一个Stroke对象。
  2. Stroke包含所有数据包数据(包括压力等)。
  3. Stroke的视觉表示默认是其贝塞尔曲线(但这可以在绘图属性中更改)。

让我们进一步使用StrokeViewer示例(包含在本文随附的下载文件中),看看当我们添加、选择和删除笔画时发生了什么。我们将向窗口添加三个笔画,并显示它们的笔画 ID。

Screenshot - StrokeViewerFigure3.jpg
图 3. 我创建了三个笔画。

我创建了三个笔画,可以看到它们被标记为 1、2、3,在这种情况下的笔画 ID。现在我将应用程序模式更改为“delete”,然后删除一些笔画。

Screenshot - StrokeViewerFigure4.jpg
图 4. 我擦除了一个笔画。

当我添加另一个笔画时,您会期望看到 ID 4,对吗?嗯,您不会。

Screenshot - StrokeViewerFigure5.jpg
图 5. 笔画 ID 4 怎么了?

如上图所示,新笔画的 ID 为 5。那么 4 去了哪里?我在删除模式下执行的每一次笔操作都会创建一个新的笔画;因此 ID 会递增。这很有趣,因为实际上没有绘制任何墨迹。让我们用选择模式试试,您会看到笔画 ID 类似的进展。

Screenshot - StrokeViewerFigure6.jpg
图 6. 实验选择对笔画的影响。

切换到选择模式,选择一个Stroke,然后返回到墨迹模式并添加一个新的Stroke

Screenshot - StrokeViewerFigure7.jpg
图 7. 笔画 ID 跳跃的序列。

现在我回到了墨迹模式,并绘制了另一个笔画,标记为 ID 7。ID 6 用于选择过程。

最后,当我们进入点擦除模式并将橡皮擦划过所有笔画,将每个笔画分成两个时,您可以看到相同的现象。删除笔画的操作实际上创建了一个来表示被删除的内容。

Screenshot - StrokeViewerFigure8.jpg
图 8. 擦除对笔画 ID 的影响。

让我们看看我为构建 StrokeViewer 编写的代码,它相当简单。

首先,在我的 C# Windows 调用中(请记住添加对 Microsoft.TabletPC.API 的引用)。

public Form1() 
{ 
    InitializeComponent();
    // Instantiate the InkOverlay. 
    m_InkOverlay = new Microsoft.Ink.InkOverlay(this.Handle); 
    m_InkOverlay.Enabled = true;
    // Create the event handler to respond to a StrokeAdded event.
    m_InkOverlay.Stroke += new InkCollectorStrokeEventHandler( InkStrokeAdded );
    // Hook up to the InkOverlay's event handlers. 
    m_InkOverlay.Painted += new InkOverlayPaintedEventHandler(InkPainted); 
}

当添加Stroke时,会调用InkStrokeAdded事件处理程序;我所做的是使窗体无效以强制重绘。

// Delegate called when a Stroke is added.
private void InkStrokeAdded( object sender, InkCollectorStrokeEventArgs e) 
{
    // Invalidate the form so we can force a repaint.
    this.Invalidate();
}

WM_Paint消息发送到我的窗体时,会调用InkPainted处理程序;因为我希望拥有这个过程的所有权,所以我拦截调用并随后调用一个执行实际工作的辅助函数。

// Delegate to respond to Paint request.
private void InkPainted( object sender, PaintEventArgs e)
{
    // Call the helper function that redraws the form. 
    RendererEx.DrawStrokeIds(e.Graphics, Font, m_InkOverlay.Ink);
}

如前所述,实际工作是在以下方法中完成的,该方法用于渲染InkStroke.ID

// Draw the Stroke IDs for a Strokes collection.
public static void DrawStrokeIds( 
    Renderer renderer, Graphics g, Font font, Strokes strokes) 
{ 
    // Iterate through every Stroke referenced by the collection.
    foreach (Stroke s in strokes) 
    { 
        // Make sure each Stroke has not been deleted. 
        if (!s.Deleted) 
        { 
            // Draw the Stroke's ID at its starting point. 
            string str = s.Id.ToString(); Point pt = s.GetPoint(0); 
            renderer.InkSpaceToPixel(g, ref pt); 
            g.DrawString( str, font, Brushes.White, pt.X-10, pt.Y-10);
            g.DrawString( str, font, Brushes.White, pt.X+1, pt.Y+1);
            g.DrawString( str, font, Brushes.Black, pt.X, pt.Y); 
        } 
    }
}

MSDN 移动 PC 开发中心有许多很棒的白皮书,其中更深入地探讨了笔画处理,供您阅读。请查看:

渲染墨迹

Renderer对象控制将Ink数据实际绘制到硬件设备上下文(HDC)上,无论是窗口、打印机还是其他 HDC。在渲染Ink时,您必须牢记 Tablet PC 设备支持的两个独立坐标系统:“设备坐标”和“墨迹坐标”。墨迹坐标系统是默认的。Renderer对象公开了将墨迹坐标转换为设备坐标的方法,反之亦然。这些方法包括InkSpaceToPixelPixelToInkSpaceRenderer对象还负责将Ink实际渲染到 HDC。Renderer对象使用DrawDrawStroke方法来完成此任务。最后,Renderer对象支持对Ink数据的操作,包括Strokes的变换、缩放、重新定位和调整大小。

结论

我鼓励您尝试一下,给笔画一个机会,您可以利用它们做很多事情。
Frank Gocinski

© . All rights reserved.