GUI原理2 - 矢量线条

先来说说画线,画线要从直线说起。直线嘛,我想大家都会觉得简单,不就是一条线嘛。不过,简单的线条要用计算机的语言去实现,也是有些复杂的。线条有两种分类——锯齿线和平滑线。

锯齿线最为简单,直接使用int类型就可以完成所有的重绘工作。int??这里所说的int是整数类型,为的是要区别后面讲的Fixed类型。一般,我们认为一条直线就是从起点到终点的连接线,不过,要受到图形显示的最小单位——点——的限制,所以线条呈现齿条状。我们还需要知道线的斜率或者角度,这个可以通过X和Y轴的偏移量来测得。一般来说,通过反正切函数,可以得到角度值,但我们不需要这么麻烦。通过ABS,就是取某个数的绝对值函数,可以得到以下公式ABS(X2-X1),再与ABS(Y2-Y1)进行比较,看谁的偏移量大。通常,我们还需要考虑到偏移量为0的情况。一般说来,水平和垂直的直线最好画,而有着倾斜角度的直线难画些。我这里就把画倾斜直线的方法称之为“步进法”,除了水平和垂直直线外,都使用这种方法,且选择一銎屏孔畲蟮闹帷? lt;/P>

如下的图,X轴的偏移量较大。具体的方法是,X轴每偏移一次,即一个点的单位,Y轴偏移不到一个点的单位,并且四舍五入。确定好坐标后,将颜色画到屏幕上,就得到如下的线条。

举例,从(0,0)到(100,40)画线,则X轴偏移量为100,Y轴偏移量为40。以X轴为主方向,每次移动1个像素,则Y轴每次移动0.4个像素。因为要受到屏幕最小单位点的影响,Y轴每次偏移后要进行四舍五入。比如在X轴方向的第8个点,也就是X=8时,则Y=3.2,四舍五入后为3。则我们在(8,3)的位置画上一个点。

画线并不是这么简单的,要考虑到多方因素,比如速度问题。一般来说,好的程序员都不会使用浮点类型,也就是小数。因为浮点在所有计算中都会比整数来的慢,而且在嵌入式CPU上整数的除法就是浮点的乘法,实际上就是除数的倒数用来做剩发而已。因此,我们必须引入一种新的数据类型,来加速所有的运算。

Fixed是业界使用最广的一种类型,他并没有在标准C或者其他语言中定义,程序员可以灵活的使用Fixed类型,可以定义自己想要的Fixed类型。那么什么是Fixed类型呢?Fixed类型是用来取代浮点,使用4字节的高2个字节表示整数位,低2个字节表示浮点位。每个字节有8个bit位,4个字节32个bit位,因此,我们把这种Fixed称为16.16Fixed。当然,也有使用 24.8的Fixed,这就要看需求和精确度了。比如Fixed中的数字1,就是65536,也就是(1«16)。这里用了位移公式,就是将1左移16位,也就是2个字节,左移16等于剩以65536,只不过位移来的非常快,快过加法。(这里理解不了就算了)比如数字32.5,等于 (32«16)|32768,等于32*65536+32768。为什么要这么麻烦呢?有了Fixed,就好像操作整数一样,都是整数运算了。Fixed的四舍五入也很简单,比如我们将X四舍五入到整数类型,就是(X+32768)»16。32768就是半个Fixed的1,也就是浮点的0.5。右移16位就是除以65536,为的是将高2字节移到正常的整数位上。我都说晕了,以后慢慢解释给大家,呵呵。

Fixed类型说了一堆,究竟来做什么的?

比如上例中,Y轴每次都要偏移0.4,而这个数是个浮点,严重影响了运算速度。比如,我们后台有一个数,用来计量Y轴本次的坐标,就叫做变量YY吧。X每次都加1,也就是XX++,Y每次加0.4,也就是YY+=0.4。为了提高速度,我们将YY升级到Fixed类型,YY每次加Fixed的0.4,也就是0.4*65536=26214,然后再四舍五入到整数类型,即YY+=26214,Y=(YY+32768)»16。这样,就得到了每次的整数Y,并且都是整数的加减和位运算,速度非常快

下面要讨论怎样实现平滑直线,不过,首先要实现浮点的点。由于浮点的速度较慢,因此使用Fixed类型来替代浮点。浮点的点来做什么的呢?比如,我们所认为的点都是整数点,(20,30)就是一个整数点,而浮点的点就好像(20.4,30.8)这样的点。在一个这样的坐标下画点,就需要将一个单位的颜色平均到4个物理点上,会影响到(20,30),(21,30),(21,31),(20,31),并且将颜色分散到4个点上。分配的方法是考虑离哪个点近,离哪个点远,一般都要做平方开方的运算,但是为了加快速度,只考虑X轴和Y轴的相对距离。

分配的方法就是个加权平均,具体的算法就不再写了,免得我写的累,大家伙看得也累。

至于颜色的绘制,就需要一个重要的函数了,这就是AlphaBlend。在Windows2000之后,微软的GDI+逐渐兴起,其中最重要的就是支持了 AlphaBlend。这是一个可以让鼠标透明有阴影,让图标支持消除锯齿的增强型Icon格式,让窗口半透明的函数。微软在GDI+的计划中添加了这一 API,硬件厂商也跟着支持,所有的厂家都加入到这个新的游戏中来。其实,Photoshop早在1.0版本的时候,就有这个功能了,毕竟软件和系统的考虑和出发点不一样,微软支持的要晚些,因为他要考虑到整个系统的性能和内存占用情况。

使用AlphaBlend,就必须将RGB格式的Canvas升级为ARGB的透明格式。比如,我们将黑色看成是RGB都为0的颜色,计算机的表达式为0x000000,每两个0表示16进制的1个字节,而ARGB格式则为0xFF000000,其中最高字节的0xFF就是10进制的255,也就是说这个黑色是全部显示的。注意,计算机的术语和我们平时理解的百分比不一样,一般都以256为100%的概念,这也和RGB的取值范围一样。比如,一个半透明的黑色,就是0x80000000,0x80正好是10进制的128。AlphaBlend除了要考虑画布上原来的颜色,还要考虑到叠加颜色的RGB值和透明度,这一公式较复杂,而且也是技术核心,这里就不再罗列了,有兴趣的可以参考很多关于Photoshop实现原理的软件。

使用AlphaBlend,我们可以将一个单位的黑色,分配到邻近的4个点上,将0x25000000这样的颜色,也就是半透明的黑色,叠加到原来白色的区域上。不过,这一区域也可能有其他的颜色,所以,一个区域如果不停的叠加黑色,也就会越来越黑。

由于对颜色进行了分配,使得Photoshop在选择铅笔和毛笔画同样一个倾斜角度的时候,我们会发现铅笔画的黑色非常纯,而毛笔画的比较淡。另外,由于分配了颜色,扩大了区域,使得毛笔画的线条显得更宽,不过,也更加细致。

我们会发现,本来只需要画一个点的,结果变成了4个点,而且加入了AlphaBlend,显得速度更慢。这就是为什么游戏在支持消除锯齿的时候,都会降低很多的速度,而且4X和16X消锯齿的速度差别很大。

一般来说,再画垂直和水平线的时候,都不需要使用消除锯齿的画法。所以,在程序中,或者设计中,不要过多的考虑实时的绘制,则会降低系统性能。消除锯齿的画法,通常用在比如钟面指针的情况下。

使用“步进法”和Fixed点画法,我们就可以得到平滑的直线。

不过,这里的步进,不再是单独的X或者Y的增加1,而是这个沿线条方向步进1。使用Fixed类型,因此每次要步进65536,并单独计算X和Y轴的步进长度,这里要使用正玄和余玄函数,根据角度计算X和Y的单位长度的偏移量。每次X和Y都偏移一定的长度,然后在这个Fixed点上,使用 AlphaBlend和颜色分配原理,画上一个浮点的点。依次进行,就可以得到平滑线。

下面要讲到Bezier曲线。当然,我们这里跳过了圆、弧之类的对象,而直接上升到Bezier。理论上来说,Bezier可以表达任意一种曲线类型,不过现在为了加速运算,都省掉了幂和开方运算,使用近似算法。我们不必关心曲线是否100%的正确,只关心曲线是否能表达我们的思想,就让那些复杂的算法,连同数学家一边去吧。

先来看一下什么是Bezier曲线。

Bezier曲线,如果用TTF的矢量字来描述,其上的每个点只有两种情况——on Curve or not oncurve。我们将曲线上的点称之为锚点,不在曲线上的点称之为控制点。锚点是用来定位用的,而控制点则是表现曲线的方向和张力。锚点和控制点连成的线,确定了该段曲线的切线方向,而线的长度则是代表了该段曲线的曲率,距离越长,曲率越大,张力也就越大。

Bezier曲线分两种类型——Bezier3和Bezier4。比如Bezier3,他的点数量是3的倍数加1,而Bezier4的点就是4的倍数。

Bezier3 多出来的1,就是第一个起点,也就是第一个锚点。这只是起个定位的作用,之后的点才能决定曲线的形状。我们将以后的每3个点看作是一组,第2、3、4的点是第一组,依此类推。注意,每一组的前两个点是控制点,后一个点才是锚点,这样就每3个点一个锚点,比较好计算。

Bezier4就简单些,每4个点组成一组,第1、4个是锚点,第2、3个是控制点。为了线条的连续,通常将第一组的最后一个点和第二组的第一个点重叠。

Bezier3 和Bezier4的差别在于,同样能够表达相同的曲线,可后者可以对曲线进行拆解,也就是我们说的断开曲线,将一条断成多条。这个在Flash软件中用得很多,拆分后还可以继续拆分。不过,大多数的软件,为了节约内存,都选择前者,但要注意前者要多出个1。

使用Bezier曲线画圆。

只要控制得当,就能够使用Bezier3画圆。共有13个点,第1个和第13个点重叠。每个控制点距离相应的锚点距离都一致。

圆在图形学中有专门的画法,且速度非常快,为什么要用Bezier曲线来画圆呢?Flash软件都是如此,使用Bezier曲线,更便于今后的修改,而且都什么时代了,谁没事儿只画圆啊,我们程序员提供一堆的画圆画弧函数,根本就没有人用。Bezier曲线只需要建模,不需要实现具体画法,由Bezier完成。

另外,用Bezier也可以画多边形。将锚点和控制点都重叠,没有了张力,多边形就出来了。因此,上面已经说了,Bezier曲线可以近似的表达任意一种曲线或直线。

下面来讨论每段曲线的具体实现方法,也就是计算机语言。

前面只说了直线的画法,我们也知道了平滑直线的实现方式。Bezier曲线使用微分原理,将曲线看作是多段直线组成的多边形。只要分的够细,多边形也可以组成曲线,Flash软件亦是如此实现的。

使用对分法,将锚点和控制点连线的中心看作是一个新的锚点,并且分出新的控制点。这里的灰色点为新的锚点,但没有演示怎样分出新的控制点来。一般,将微分的最小线段长度控制在2个Pixel单位,也就是两个物理点的长度,这样的多边形曲线看起来就比较平滑了。

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License