2.4 Python
Python 是一种可用户于多种类型软件开发的动态面向对象编程语言,它提供了强大的与其它语言和工具相互协作支持,拥有广泛的标准库,而且你可以在几天之内上手。很多Python 程序员都反映使用Python 获得了更高的生产力,更强壮的代码以及更易维护的特性。
2.4.1 导读
这一节来阐述两种伟大的技术:多点触摸用户界面和Python 编程语言。重点讲一下如何用Python 开发多点触摸应用程序。
现在介绍一下Python 里面的几个模块PyMT 以及演示一下它的范例,然后尝试去写一下多点触摸输入的小程序。PyMT 是一个开源的、用于快速开发多点触摸应用程序的Python 模块。
在许多方面,Python 和多点触摸在使电脑更容易操作这个方面用诸多的相同点。虽然作为几种编程语言,Python 需要更先进的技术,来让开发者能够快速而又轻松地编写代码。Python 常常说比其它语言更容易学习,许多Python 程序员也反映使用这种语言使他们更专注地解决问题、实现目的,而不是拘泥于繁杂的语法和严格的语言属性。Python 强调其代码的可读性和可以从开源社区获取大量的可用模块,Python 试图用简单和有趣的编程方式构建多点触摸用户界面,来让电脑变得更加自然和直观地使用。
虽然多点触摸的实用性得到了快速的发展,但是就目前的互动方式和应用来看,多点触摸还有极大的潜力等待我们去挖掘。尤其是现在很多的多点触摸项目仍然处于在实验室内的阶段,所以能够最大限度的快速构建多点触摸交互和应用蓝图是非常重要的。Python 的动态语言特性和快速开发的能力以及社区里的大量模块,都使得Python 成为快速开发多点触摸交互和应用的理想语言。
2.4.2 Python多点触摸的模块和项目(Modules and Projects)
以下简要概述Python 里面涉及到多点触摸的模块和项目。
PyMT
PyMT 是一个用于开发多点触摸的Python 模块,可以与OpenGL 通信。它最初是一个爱荷华州大学的研究项目,最近在很多团体和个人的大力支持和共同开发下,PyMT 一直保持着较快地发展。它可以运用在Windows,OS X 和Linux操作系统下,PyMT 的许可证是GPL License。我们将在下卖面的章节中详细探讨PyMT。
touchPy
Python 框架是与TUIO 协议协同工作的。touchPy 监听TUIO 的输入,并执行观察员模式(Observer pattern),开发人员可以使用其子类。观察员模式使得touchPy 平台和模块并不可知,例如它并不关心是哪一个框架来绘制屏幕。AlexTeiche 为它写了一个很重量级的教程。
PointIR
PointIR 是在2008 年PyCon 上演示的多点触摸系统,基于Python。虽然当年看上去非常有希望,但是最近似乎从网络上消失了(搜索试试?)。授权信息也不清楚。
libAVG
根据官网的描述:“libAVG 是一个高层次的媒体开发平台,专注于互动装置。”虽然从严格的意义上讲,它不算是多点触摸模块。但是它却能够接收TUIO的信息和追踪触点,所以可以作为多点触摸系统来使用。libAVG 可以在Mac OSX 和Linux 上运用,它也是开源的,许可证是LGPL。
pyTUIO
一个用于接收和分析TUIO 输如的Python 模块。在MIT 许可证下发布。
2.4.3 PyMT
PyMT 是一个用来开发多点触摸富媒体OpenGL 应用的Python 模块。主要目标是使开发变得更新奇,简便和快速自定义界面。本节将介绍讨论PyMT 详细的结构,并使用它来作为一个例讨论一些参与编写多点触摸界面的若干问题。
每一个有用的程序都做过两件事:获得输入和提供输出。如果没有输入,程序就只会输出相同的结果(例如"Hello World!"),那么这个程序是没有用的。如果没有输出,那么没人知道程序在做什么,或者根本就没有做什么。运用在多点触摸显示屏上的程序,最主要的输入方式就是触摸。图形的显式就是其主要输出。PyMT 尝试使输入和输出变得简单和灵活。在处理输入方面,PyMT 将TUIO 协议包装成一个事件驱动的框架,输出方面则是基于OpenGL,这样的话就可以使用图形硬件加速,在绘制图形上获得最大的自由性。
2.4.4 PyMT的构造
图5 展示的是PyMT 的主要结构,如前面所讲的,它使用TUIO/OpenGL作为输入/输出。当前版本的PyMT 依靠pyglet,这是一个跨平台的OpenGL 窗⼝和多媒体Python 库,特别是pyglet 的多媒体功能使得处理图像、音频和视频变得非常容易。大多数的媒体问件可以装载至单线(single line)的Python 代码中(也包括绘制的OpenGL 内容的重现)。
图5:PyMT 结构。PyMT 建立在Pyglet 基础之上,其中规定了OpenGL 的窗⼝和多媒体加载的各种问件格式。它同样通过TUIO 客户端监听输入信息。PyMT 将这些技术组合在一起,提供了一个面向对象的构件库和分级系统布局和事件传播。
2.4.5OpenGL
利用OpenGL 作为绘图后端利弊并存。虽然它可以在2D 和3D 渲染上拥有高性能和很好的灵活性,但是在毕竟处于比较低级的状态。而且它的学习曲线比较陡峭,也需要有基本的计算机图形学知识。
PyMT 试图抵消需要比较高级的知识才能驾驭的OpenGL 绘图功能,提供了基本的绘图功能。PyMT 包括drawCircle 、drawRectangle 、drawLine 和drawTexturedRectangle 函数以及其它无需高级OpenGL 知识的功能。使用OpenGL 作为基本的渲染引擎,可以使高级用户充分控制他们的程序的可视化输出。PyMT 也提供了辅助函数和类去帮助实现高级的OpenGL 编程。例如,PyMT可以用一行程序实现建立一个帧缓冲对象(Frame Buffer Object)和从glsl 着色。PyMT 还能利用引擎来处理投影矩阵和布局转变,这样就可以在不用调节他们参数的情况下直接运行。这个构思的原因是希望能够在大多数情况下能够最大限度的方便运用,但是如果需要用到更高级的技术或者需要自定义相关技术则必须要用到OpenGL 来编写。
对OpenGL 的描述还有其它更加详细的资料,关于OpenGL 的简单信息可以从[12][13]查阅到,标准的参考书是“The Red Book”,或者从nehe.gamedev.net网站上查阅经典的OpenGL 指导。
2.4.6Windows、Widgets、Events
大多数图像用户界面的开发包和框架都有一个叫作“工具(widget)”的概念,一个“工具(widget)”可以在同一个图形用户界面中构建模块,它是一个富有交互性及可视化的元素。在维基中是如下定义的:
PyMT 和其它GUI 工具板一样使⽤类似的概念,它以类似框架式的大部分提供一系列可以用于多点触摸的工具。但PyMT 的重点在于让程序员很容易地支持自定义工具以及尝试开发新的互动技术,而不是提供一套标准的工具。这是来自如下建议和设想的主要的动机:
在用户自然界面(NUI)里面只有极少数的“工具”和交互设计应用证明了自己是标准的。
NUI 本身就不同于传统的GUI/WIMP(窗⼝、图标、菜单、指向设备)
NUI 是非常具有内容化的,例如:可视化信息和交互应用是基于用户交互背景的。
传统的鼠标键盘系统已经无法提供实时的多用户协和操作,需要颠覆式地构思新的交互界面。
PyMT 运用时以树状目录组织widget. 根目录是应用程序窗⼝(一般是MTWindow). Widget 可以通过MTwidget 基类的add_widget 方法加进这样的目录中或者加进其他widget 中.各种事件如on_draw, on_resize, mouse event 和touchevent 可以通过这个树状目录到达所有的widget. 程序员可以利用这些层级机构来定义各种容器(container), 用以处理布局和控制widget 对事件的响应.
PyMT 提供了丰富的功能强大的widget 和实用的对象(object). 比如,所有的widget 可以用CSS 来定义风格. 除了用add_widget 来生成widget 之外,还可以用XML 来定义层级结构,然后自动生成widget. 由于PyMT 的功能实在太多,无法意义介绍,详情请参阅PyMT API 文档.
接下来的小例子将尝试展示PyMT 的关键概念. 图2 展示了该例子的最终效果,用一只手获取5 个输入点. 这段代码可以分为4 个部分. 评价一个NUI 交互系统的唯一也是最有效的方法就是亲自去做,去体验.
1. 导如PyMT 并初始化参数:第一行代码告诉python,我们要用PyMT,这行代码将会加载所有的PyMT 对象和函数。这段代码同时还设置了一个名叫touch_positions 的变量。这个变量用以存储触摸事件的坐标位置。
2. 定义一个新的类,名叫TestWidget:这个类继承自MTWidget 并定义了4个事件处理器。on_touch_down 和on_touch_up 事件处理器更新触摸位置。on_touch_up 处理器从触摸列表中删除触摸。draw 方法会在每个触摸事件的当前位置生成一个半径为40 的圆。这个圆通过PyMT 的drawCircle 方法,并调用touch_positions 的值生成。
3. 创建一个窗⼝来装载widget:MTWindow 是一个应用程序窗⼝,你可以通过add_widget 方法来加载widget。被加载的widget 会通过窗⼝接收touch 和draw事件,并渲染该窗⼝。
4. 启动程序:runTouchApp 函数会启动PyMT 的主程序,同时任命窗⼝,打开TUIO 侦听器,并开始发送事件。
程序1:PyMT 程序⽰例,在每个触点处⽣成⼀个红⾊的圆。
图6:程序1 所⽰程序运⾏截图。5 个触点(屏幕分辨率:640x480)
2.4.7 多点触摸输入编程
基于多点触摸设备的编程和交互设计与基于目标的界面的情况完全不同。前面章节讨论了NUI(自然交互界面)与传统的基于GUI(图形界面)的WIMP的区别。主要的区别在于多点触摸给交互界面带来的无限的拓展和可能性,同时也让编程变得更加复杂。
TUIO 方式,以及其他多点触摸协议和框架定义了3 种基本的触摸消息/事件。包括新的触摸事件,现有触摸事件的移动,触摸事件的移除。这些消息总是被标明“touchID”,这样程序以及这些事件调用函数就能把这些触摸事件各自区分开来。如程序1 中所示,这些触摸事件以下面所示的方式到达PyMT:
· on_touch_down(touches, touchID, x, y)
· on_touch_move(touches, touchID , x, y)
· on_touch_up(touches, touchID, x, y)
每个事件都携带着touchID 和x,y 坐标值。通过这些touchID 和坐标值,系统就区目⽬前所有的触点及其位置。实际上,系统不仅能区分目前所有触点的位置,还记录着目前每个触点的移动和加速度,这些都由TUIO 协议定义。PyMT也为TUIO 对象提供on_object_*事件(例如:图形标签的识别)。
很明显,解释多点触摸要比处理单一指点设备如目标复杂得多。任何一个触点都会影响到用户界面,随后的事件处理器在决定如何处理触摸事件之前必须兼顾所有其他可能发生的相互作用户。
在进行PyMT 编程的时候,一些基本的编程技巧已经被证实很有帮助。例如,让某个widget 对某个特定的触摸拥有所有权在很多案例中被证明很有效。当使用这个技巧的时候,其他的widget 会忽略某个特定touch_ID 所对应的touch_move 或者touch_up 事件。只有对这个事件拥有所有权的widget 才会处理这些事件。
基本上,多点触摸和基于多点触摸的交互界面潜力无限,如果一定要有什么限制的话,那么,这些限制就是我们的创造力和想象力。多点触摸的手势有无穷的组合方式。在接下来的例子里,你将看到,这些手势及其组合可以形成很多直观的交互操作,但是,这也意味着逻辑和算法变得更加复杂,更加困难。
2.4.8 例子:实现旋转/缩放/移动
这一部分,我们将讨论如何实现一个著名的操作。旋转/缩放/移动(图7)是最常见的多点触摸演示示例。这种直观的,用两个手指旋转/缩放/移动一个二维物体的方式,和我们在桌面上移动一张纸的情况很相似。我对这个方式感觉很直观,很自然然,因为两个触点始终在物体上一开始的那个位置,无论手指移动到哪⾥。
在PyMT 中,这个交互方式以ScatterWidget 对象实现,你可以在这个对象中添加其他widget,让他们成为scatter widget 的一部分。诚然,这个交互方式已经用了太多次了,有人或许会说这个方式已经用烂了。我们在这里讨论这种交互方式不是为了炫耀,而是因为它是一个非常不错的例子,一个典型的证明多点触摸编程复杂性的例子。尽管目前只用了两个触点,而整个交互非常自然,但是这里面涉及到的计算和数学知识已经比鼠标交互复杂了很多。
为了理解这个交互的具体实现,需要对矩阵变换有基本的了解。通过矩阵乘法,任何变形都可以通过一个矩阵实现(一般来说是4x4 矩阵)。比如一个矩阵表示沿x 轴移动5 个单位,可以乘上一个绕y 轴旋转90 度的矩阵。得到的矩阵则表示在沿x 周移动5 个单位的同时,绕y 轴旋转90 度。更多矩阵相关信息,参见[18]。
2.4.9 用矩阵变换来画图
程序2a 是做旋转/缩放/移动的第一部分。这一部分用于生成对象。Transform_mat 是一个变形矩阵。到目前为至它只用来保存标准矩阵,让所有的点保持不变。基于触点的位置和移动,对变形矩阵进行修改,达到改变对象的目的。
程序2a.旋转/缩放/移动实例。当生成对象的时候,应用了一个变形矩阵,这个矩阵会随着触点位置的变化发生改变。详细的代码参见pymt/ui/scatter.py 参考文献[2]
2.4.10 确定参数并计算变形
接下来的问题就是决定如何进行变换对象的变形矩阵。图8 描述了需要变换的部分参数。重要的一点是,对任何一个给定的事件,两个触点中,只有其中一个可以移动。因为,每个事件一次只能传送一个触点的信息。
为了计算变换,需要以下参数。事件发生前和事件发生后两个触点的距离(d1 和d2)。角度R,用来计量对象的旋转角度。旋转和缩放中芯点Pi(两个触点的其中一个)
图8 旋转/缩放/移动。两个触点(A 和B),对多个参数进行计算。缩放值(d2/d1), 旋转角度(R), 旋转和缩放中心(Pi).每次只有一个触点位置发生改变(A=Pi 或者B=Pi)
通过对参数的计算, 变形矩阵会做出相应的修改。程序2 展示了PyMT/OpenGL 是如何完成这些的。具体的步骤如下:
1. 初始矩阵被装载进transform_mat
2. 通过变换,让旋转中心的坐标为(0,0),因为,在OpenGL 中,旋转和缩放总是围绕原点进行。
3. 绕z 轴旋转(z 轴垂直屏幕)
4. 缩放
5. 把所有的物体移动到当初的位置
另外一件值得注意的事是,对象没有发生移动(平移)。运用这种技术,物体的运动通过绕固定的点旋转或者在不同方向上缩放实现。如果要移动对象的话, 比如用一个手指拖动, 那么这个拖动的动作必须添加到PyMT 的ScatterWidget 类中去。为了保持例子的简洁,这里就不深入探讨。
程序2b。应用变形参数示例。这个函数把变形参数应用到变形矩阵上。完整的程序见pymt/ui/scatter.py 参考文献[2].
2.4.11 触摸位置转译成计算参数
最终,程序必须根据触摸事件提供的位置单独计算参数。程序2c 展示了相应代码。为了简洁,这段代码假设get_second_touch 函数用于获取第一个触点的信息。这样的程序可以通过一个库来跟踪记录touchID,确保每次只有两个点在这个库中,然后返回与被传递的参数不同的那个。
这段代码也假设了一些基本的矢量函数,用于计算两点间的距离和角度。具体的实现参见PyMT 的vector 类[2]。或者参考[20][21]。这个计算基本上只是两个向量的大小和乘积。
旋转的角度通过A1-B 和A2-B 得出。缩放的比例通过d2/d1 得出。
程序2c。计算旋转/缩放/移动参数的程序示例。详细代码见pymt/ui/scatter.py 附录[2]。