理解笔触和数字墨迹
了解墨迹如何创建以及笔画如何管理。
引言
本文是对用于墨迹收集和识别的对象模型的初步探讨。我们将研究墨迹的收集机制,以及墨迹笔画的逻辑位置和标识。
让我们从讨论对象模型中最重要的类开始,即Ink
类。在大多数情况下,您将使用InkOverlay
类在应用程序中使用Ink
,但InkOverlay
和InkCollector
都会为您处理Ink
对象的实例化。.
图 1. 墨迹对象模型
InkCollector
:这是用于在用户输入时收集和渲染墨迹的基本对象。InkCollector
会将事件作为它们发生时触发到您的应用程序,还会为您将Cursor移动打包成笔画。您可以告诉InkCollector
您有兴趣接收哪些事件和数据,以及是否希望它在收集正在进行的笔画时进行绘制。InkCollector
的设计意图是,它没有InkOverlay
所带来的所有支持,因为它是一个组件技术的基础,您希望获得不同于InkOverlay
的行为。InkOverlay
:InkOverlay
对象是InkCollector
的超集,并增加了对已捕获墨迹的选择和擦除功能。对于几乎所有应用程序来说,InkOverlay
都是首选对象,因为它对选择相关功能的支持可以节省大量时间,并且通常是任何应用程序的要求。为了保持简单,图 1 使用的就是这个对象。
InkCollector
和InkOverlay
对象会触发大量事件。连接这些事件可使您响应墨迹输入或笔移动的所有重要阶段。
CursorInRange / CursorOutOfRange 事件指定用户已将光标移到墨迹启用区域内。请注意,在某些设备上,光标不必接触屏幕即可触发这些事件。
在CursorInRange事件之后,如果光标未接触数字化仪,则会引发NewInAirPackets事件。
当光标接触数字化仪时,会触发CursorDown事件。
CursorDown事件之后是NewPackets事件,它指示正在收集墨迹。
当触发CursorOutOfRange事件时,您将收到一个Stroke或Gesture事件,具体取决于InkCollector
对象的CollectionMode属性的值。InkOverlay
对象也是如此。
您的应用程序通常会收到各种SystemGesture和Mouse事件。默认情况下,Tablet PC 平台支持许多系统手势,其中许多手势模仿传统的鼠标事件。例如,tap事件映射到鼠标单击,而drag、hold和hover事件映射到相同类型的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。
图 2. 我用草书和印刷体写我的名字的笔画计数。
请注意,当我用草书和印刷体写我的名字时,笔画数量的差异。我按下、书写、抬起四次写草书,九次写印刷体。哇,那真是额外的工作。所以,一个笔画包含从我接触数字化仪到我移开的数据包数据。Strokes基本上是一个贝塞尔曲线,代表在CursorDown和CursorUp事件之间收集的墨迹。
这里有很多事情正在发生。
- 每次按下笔/移动笔/抬起笔操作都会创建一个Stroke对象。
- Stroke包含所有数据包数据(包括压力等)。
- Stroke的视觉表示默认是其贝塞尔曲线(但这可以在绘图属性中更改)。
让我们进一步使用StrokeViewer示例(包含在本文随附的下载文件中),看看当我们添加、选择和删除笔画时发生了什么。我们将向窗口添加三个笔画,并显示它们的笔画 ID。
图 3. 我创建了三个笔画。
我创建了三个笔画,可以看到它们被标记为 1、2、3,在这种情况下的笔画 ID。现在我将应用程序模式更改为“delete”,然后删除一些笔画。
图 4. 我擦除了一个笔画。
当我添加另一个笔画时,您会期望看到 ID 4,对吗?嗯,您不会。
图 5. 笔画 ID 4 怎么了?
如上图所示,新笔画的 ID 为 5。那么 4 去了哪里?我在删除模式下执行的每一次笔操作都会创建一个新的笔画;因此 ID 会递增。这很有趣,因为实际上没有绘制任何墨迹。让我们用选择模式试试,您会看到笔画 ID 类似的进展。
图 6. 实验选择对笔画的影响。
切换到选择模式,选择一个Stroke,然后返回到墨迹模式并添加一个新的Stroke。
图 7. 笔画 ID 跳跃的序列。
现在我回到了墨迹模式,并绘制了另一个笔画,标记为 ID 7。ID 6 用于选择过程。
最后,当我们进入点擦除模式并将橡皮擦划过所有笔画,将每个笔画分成两个时,您可以看到相同的现象。删除笔画的操作实际上创建了一个来表示被删除的内容。
图 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);
}
如前所述,实际工作是在以下方法中完成的,该方法用于渲染Ink和Stroke.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对象公开了将墨迹坐标转换为设备坐标的方法,反之亦然。这些方法包括InkSpaceToPixel和PixelToInkSpace。Renderer对象还负责将Ink
实际渲染到 HDC。Renderer对象使用Draw和DrawStroke方法来完成此任务。最后,Renderer对象支持对Ink
数据的操作,包括Strokes的变换、缩放、重新定位和调整大小。
结论
我鼓励您尝试一下,给笔画一个机会,您可以利用它们做很多事情。
Frank Gocinski