Modern X Tech. Chinese Translation Project
[[xr_cross_device_rendering_for_vector_graphics]]
Last edit on
Aug 5, 2006
10:23 AM
by Anonymous
Xr:交叉设备矢量图形渲染
引言:
Xr有一个矢量渲染的API,支持X 窗口系统和本地图像缓存的输出。postscript和pdf的输出格式也在包含在内。Xr的设计目的是通过X渲染扩展以结合硬件图形加速保证在各种输出方式上均取得完全相同的效果。
Xr提供的图形API是完全基于状态的,并且支持PDF1.4的渲染模式。Xr提供的操作包括描线、填充贝塞尔曲线、样条曲线;转换、合成带透明度的图像;渲染抗锯齿的文本。在当前C语言的Xr实现当中完全使用了PostScript的绘图模型。一些PDF1.4需要的扩展也被加了进去,这种在本地编程语言当中嵌入PostScript绘图模型的整合方式为图形程序开发提供了一个简单而且强大的开发工具。
介绍:
设计Xr库的目的是想使各种程序操作都有一个高质量渲染效果的统一图形操作接口,无论从标签到按钮的阴影再到画图绘图程序中的核心图形操作都可以胜任。无论是在显示器,打印机,还是本地图形缓存(内存)中的输出都可以使用一个统一的渲染模型,以致程序能通过调用一套API来开发程序,而不必考虑输出的媒介是什么。
Xr库提供一套完全独立于设备的API,既能输出到X窗口系统里的程序,也能直接操作内存中的图像,它也能利用外在的X渲染扩展来加强能力,但没有也行,利用x渲染扩展的目的只是为了增加对postscript和pdf1.4格式输出的支持。
从原先在X窗口系统里旧有的图形操作模式转到现在一个完全独立于输出设备的渲染模式必将让未来的图形程序开发更加激动人心。
矢量图形:
在当前显示设备上,一个程序要想显示任何信息就必须利用各种抽象的几何物体来描述,但是这些抽象的物体在渲染过程中又必须被转变成像素才可能显示出来(因为现有显示设备都是基于象素的),这个转变越晚发生越好,因为直接对象素格式图形进行操作会带来大量的失真。
因为当前在运行时里的程序对图形操作的能力是非常有限的(相对于专业图形程序),所以图片都是以象素格式来存储的,如果程序都有足够强大的图形处理能力,那么就可以使用矢量图形格式,并且会带来无损放大缩小的强大效果。
图1:光栅和矢量图形在原大小下的对比
图2:光栅放大4倍
图1-3的例子展示了矢量图形的优势。图1左边的企鹅是Larry Ewing所花的原图。右边的企鹅是Simon Budig通过Xr矢量渲染绘制的,基本完全还原了原画。在一倍大小下区别是不明显的。
然而,当图形放大的时候,光栅(象素格式)和矢量图片的区别就很明显了。图2是原光栅图在GIMP里放大4倍后的效果。放大后的失真是非常明显的,首先是图像边缘的锯齿非常严重。当然GIMP为了避免锯齿的出现已经在内部进行了过滤,但是效果却是把整个图形变模糊了。对比图3(矢量图通过Xr放大4倍)因为矢量图形是与分辨率无关的,所以自然就没有失真和模糊了。
图3:矢量图形放大4倍
矢量渲染模型
我们非常幸运,在二维图形世界中有一个统一的图形渲染模型。随着桌面出版系统以及postscript打印机的推出,程序开发者现在都在使用这个模型。当前一些对这个模型的扩展也都被做进了PDF1.4,但是基本的架构还是一样的。Postscript提供了一个简单的打印机模型;每个渲染操作都把新绘制的图形放置在输出层的最上层。PDF1.4扩展这个模型包含了Porter/Duff图像渲染以及其他为了让postscript符合现代程序要求的图形操作。
Postscript和PDF通过画任意路径的线条以及贝塞尔曲线、样条曲线来构建复杂的几何图形。用来构建图形的坐标可以通过矩阵来进行各种转换,这就使在实际显示各种复杂图形前,对物件进行各种变换并且无损放大成为可能,文字也被看作是一种预编译的路经,这样就和其他的部分无缝衔接起来。
Xr 编程接口
Xr库的目标是提供一个提供pdf1.4的图形模式。但是PDF并没有提供任何编程程序语言接口,所以Xr引入了postscript的编程接口,并且用c为他从新编写了接口部分,也希望程序员放弃postscript而采用c语言来进行渲染程序的核心编码,c语言显著减少了postscript的操作子个数,但postscript的所有功能都已被现有的c函数库所包含。
Postscript把当前的渲染状态保存在一个全局的不透明的对象当中,并且提供简单的操作子来对其的各种参数,线宽,样式,曲线。。。进行修改。然而全局对象在c语言里会引来诸多问题,Xr现在把所有状态保存在一个数据结构中,然后利用指针在各个函数中以参数进行传递。
从postscript到Xr的转变是直进式的,举例来说,lineto操作子转变为XrLineTo函数,任何的路径进行操作完的结点的坐标都存储在Xr的状态对象当中。
API和例子
这个部分提供程序编程接口(API)的教程,举例介绍API所有的特色,并且每个粒子的源代码都能在附录A中找到。
Xr的初始化
图4:一个最简单的Xr程序
图4展示了一个最基本的Xr程序,这个程序什么图形效果也没有,它只有Xr的初始化和最后的Xr销毁过程。
在包含了Xr的头文件以后,第一个要做的是调用XrCreate这个函数,这个函数返回一个指向XrState对象的指针,所有Xr程序操作的数据都保存在XrState里面,所有函数都必须把XrState的指针作为一个参数来传递。
在任何绘图程序被调用前,Xr必须有一个对象来接受图形。Xr支持多种图形输出类型,目前,Xr支持在内存中以及可画的X窗口系统中进行输出。
程序调用XrSetTargetImage函数来把图像转换为由4字节ARGB格式的的像素阵列中,相似的XrSetTargetDrawable是把一个图形输出到一个可画的X窗口上(比如窗口或位图)。
当程序利用Xr完成处理后,应该立即调用XrDestroy。在XrDestroy里,XrState对象被销毁了,所以,直到下一次调用XrCreate之前,Xr无法访问XrState对象了(因为不存在,销毁了)。所有操作的结果保存在输出上的都不变,并且程序可以再一次调用那个结果。(比如把结果存在文件里,再读出来)
转换
从程序代码发送到Xr里所有的图像状态信息都被保存在"用户空间"里,然后根据输出设备物理格式转换成"设备空间"里存的像素信息进行显示。这个过程被Xr里的CTM控制。
初始的CTM建立在大约96DPI上,以整数像素数值进行保存。这是想要平衡用户空间转换和真实图像和设备的像素边界,理想情况下,象素边界可以足够小以至于被人忽略,可现实中的大约100DPI的分辨率下的像素边界现象还是非常明显的。
CTM能够被用户修改,可以定位,放大,旋转子对象。这些操作通过调用XrTranslate,XrScale,以及XrRotate来进行。此外,XrConcatMatrix把一个现存的矩阵导入到CTM中,也可以利用XrSetMatrix把当前的CTM导出到一个现有的矩阵,XrDefaultMatrix函数会把CTM恢复到初始状态。
图5:通过使用XrTranslate和XrRotate生成的放射性线图
在图5中,每一个放射性线条都是使用同样的路经,有不同的角度是因为调用了XrRotate,这幅图的源代码在图11中。
保存/还原图形状态
程序使用结构化方法把图形分层保存。比如,程序通过使用树型结构的绘画方法很有可能会修改CTM里的参数,当前颜色,线宽。。。在任何层次上。
Xr通过在XrState对象中创建图形堆来实现这种分层,XrSave函数会把当前状态保存在堆的顶端,任何修改只是对当前状态进行的,XrRestore函数恢复到最后一次调用XrSave保存的状态,当前一切修改都将丢失。
这个模型在C语言写的程序里看起来不错。大多数绘画函数可能都被写成下面那种样子,把一段独立的图形函数包括XrSave和XrRestore写在一个大函数里:
Void draw_something (XrState *xrs)
{
XrSave (xrs);
XrRestore (xrs);
}
这种方法的好处就是任何对XrState的修改都只是在函数内,在函数外XrState毫无变化,将会带来更好的代码独立性和代码可重用性。有时一个函数会包含很多组XrState和XrRestore,为了加强代码可读性,有些人在函数内比如各自的XrSave后的图形操作又加了括号。图12就是这种样式。
构建路经
Xr图形状态对象当中的最重要的首先是当前路径,一个路径可能会包含一个或更多的相互独立的子路经,他们可能是一套直线也可能是曲线,任何非空路径都有一当前点,就是当前线段绘画过程中最终停留在的哪个点,调用构建路径的函数一般都会读取并且更形当前点坐标的。
Xr提供很多函数来构建路径,比如XrNewPath会创建一个空路径,删除以前所有路径,在XrNewPath之后应该调用XrMoveTo函数来把当前点移动到某一确定位置以进行操作,在当前路径非空时通过调用XrMoveTo可以创建一个新的子路径。
XrLineTo会绘制一条从当前点到某指定点位置的一条直线,XrCurveTo会绘制一个从当前点到某一确定位置点的贝塞尔曲线(就是一个由当前电和3个其他的点确定的一个多边形)。
XrClosePath会关闭当前子路经,这个操作也会在当前点到起始点(一般就是最开始调用XrMove移动到的那个点)之间画一条直线,虽然也是画直线,但是调用XrClosePath并不等同于调用XrLineTo话一条直线,区别是一个闭合的子路径在连接地方都有连接(就是由公用一点时,一般折线那种),而非闭合子路径在线段两边端点只有帽,即使两条险段的端点恰好重合在一点,那也只是帽,在2.5部分有这个更详细的讨论。
使用相对路径一般比使用绝对路径要方便一点,所以,Xr提供了XrRelMoveTo,XrRelLineTo,XrRelCurveTo.图6展示了一个调用XrMoveTo和循环调用4次XrRelLineTo的构建路径的例子,源代码请看图13
图6:嵌套的箱子(来自Al Seckel),调用XrMoveTo和循环调用XrRelLineTo创建的
因为矩形路径十分实用,Xr提供了一个快捷的函数来绘制矩形路径,叫做XrRectangle(xrs, x, y, width, height),如果要用LineTo来写就是下面的效果
XrMoveTo (xrs, x, y);
XrRelLineTo (xrs, width, 0);
XrRelLineTo (xrs, 0, height);
XrRelLineTo (xrs, -width, 0);
XrClosePath (xrs);
当路径被创建完成后,实际上并没有图象,这时可以用2种方法来填充颜色,一种是描边(XrStroke)或填充内部颜色(XrFill)
路径描边
XrStroke在当前用户空间用以只有当前线宽(可以通过调用XrSetLineWidth来设定)这么粗的笔头来给给定的路径描边,XrStroke的规格依据Guibas, Ramshaw and Stolfi他们所订制的饶多边形跟踪算法制定的 ,这种算法有很高的性能,这种算法允许一定的绘图精度错误,并且是单线性的。
所有的子路径里也都被画上了,他们的转折点都是通过重不同样式来绘制的,(bevel, miter, or round3种样式)样式可以用XrSetLineJoin进行设定,封闭的路径在终点也是连接(和转折点一样),未封闭的路径两边都是帽,帽又有不同的样式可选,(butt, square, or round),XrSetLineCap可以用来设定帽的样式。
图7展示了3种不同的帽和连接的样式效果,源代码在图12,调用了XrSetLineJoin和XrSetLineCap以及XrTranslate,XrSave,XrRestore这些函数。
图7:不同的帽和结合点的样式
路径填充
XrFill函数填充当前路径的内部,Xr有2种填充法则,1是轮廓填充法,2是奇数填充法。通过调用XrSetFillRule来选择是法则1还是法则2,函数具体的参数值分别是XrFillRuleWinding和XrFillRuleEvenOdd.
图8展示了2种填充具体的效果,轮廓填充法把整个星星填充了,而奇数填充法认为星星的中间已经是外部了(由于叠加,奇数法则),所以没有填充,图15时这个粒子的源代码。
图8:不同填充法则的效果
控制精度
Xr图形渲染是建立在一个可以由用户自定义容错值上的,(当然这个值在机器允许的范围内)。XrSetTolerance这个函数就是用来设定最大容错值的(允许输出设备像素错误数)。
容错值对渲染效果(尤其曲线,直线无影响)有很大影响,测试结果表示如果容错值大于0.1,就可以被人察觉了,所以Xr的默认容错值就是0.1个设备像素(输出设备)。
用户也可以增加容错值来提高性能,图9就是用不同的容错值来渲染一条曲线的,图14包含它的源代码。
图9:分别用0.1,0。5,1,5,10各种容错值来渲染曲线的效果
画图
到目前为止我们所有举的例子里调用的函数都是用不透明颜色来做的,这颜色能够通过调用用XrSetRGBColor函数来设定。Xr还支持很多有趣的扩展,但是首先色彩必须是透明的,XrSetAlpha这个函数就是用来设定色彩透明度的,alpha的取值范围是从0(透明)到1(不透明)。
当Xr图形操作要复合2个半透明表面时,那就会有很多种不同的方法来结合原颜色和目标颜色,Xr完全支持Porter/Duff复合操作当然也支持在X 渲染扩展里的扩展函数操作,用哪一种复合方法是通过调用XrSetOperator来设定的。XrOperatorOver默认是Porter/Duff Over操作方式。
最后,XrSetPattern函数允许任何XrSurface以静态或者可重复的方式引入,等待下一步的图形操作,图案可能来自外部图像也可能是刚才的Xr渲染的结果。
图10原先只是一个小3*2的黑白矩形,然后被放大,过滤,并且被当作3种XrFill操作子的原图。这个例子也演示了一种在Xr里绘制斜线的方法。
图10:轮廓影响色彩过渡的色深,这个例子调用了XrFill和XrSetPattern。
位图
Xr除了对于矢量路径提供支持外,还支持位图作为预制图形来处理,图形被转换(可选择的被过滤),在CTM里和其他对象调用同样的方法。为了显示一个位图,需要首先为这个位图创建一个XrSurface,然后再调用XrShowSurface函数来显示这个层,XrShowSurface只是把位图按照其原来大小放置到用户空间里,然后通过调用XrTranslate来确定位置。
XrShowSurface函数除了能显示位图以外,还有一个重要功能,当我们进行Porter/Duff 图象复合操作时,我们总是想先把所有各种透明图像先在临时板上组合起来,然后再输出到目标板。这个功能和我们在pdf1.4里看见的透明度操作十分类似,也可以用下列代码来实现:
XrSave (xrs);
XrSetTargetSurface (xrs, surface);
XrRestore (xrs);
XrShowSurface (xrs, surface);
在这个例子当中,我们首先放置了一个临时板,然后我们就在这个临时板上绘图。当调用XrRestore时,原来的输出板就被还原了,然后调用XrShowSurface又输出了临时板上的图象。
这种技术可以符合无数层临时板,每一次操作在子板,最后输出到父面。
当然图形来源也可能是Xr环境以外的图像数据,可能是文件,外部设备,甚至是窗口系统。因为Xr内部使用的图像格式完全都是由外部应用程序决定的,这种操作方法快速并且高效。
Xr的实现
到目前为止,Xr已经可以完美支持这里提到的所有函数,还有一些在postscript图形模型里非常重要但没提到的有文字/字体的支持,裁剪,颜色管理。Xr目前包括对文字,裁剪实验性的支持,但是这些都还需要进一步的完善。
Xr系统包含3个主要的部件libXr,libXc和libIc库。LibXr提供对用户一级API的支持。
LibXc是Xr系统的自己的函数库,它提供一个统一,抽象,面对各种不同低层次的图形系统。当前,libXc对于X window系统和内存中图像操作提供支持。并且libXc与X渲染扩展紧密结合,当扩展可用时自动调用。
LibIc是当libXc要进行内存内部图像操作时调用的库。并且当libXc不能对应接口时,LibIc就能提供对于系统更底层图像的操作。在这种情况下,libIc直接在内存当中绘制图形,并且把结果提供给底层系统。这就是当没有X渲染扩展时libXc需要向X系统绘图的方法。
LibIc基于X渲染扩展的源代码,当然如果能利用各种X渲染扩展的实现来替代libIc那就更好了。
这3个库大约包含7000行C语言代码。
相关的工作:
Xr和当今现存的很多图形工作都有关系。
postscript和显示postscript
就象在介绍里说的那样,Xr继承和拓展了postscript渲染模型。然而,postscript并不仅是一个渲染模型,因为他包含一个完整的编程语言。显示postscript在窗口系统中嵌入了一个postscript解释器。绘画其实就是程序产生postscript代码,然后交给窗口系统来处理。
使用postscript的一个明显优点就是能够用同样的代码来在任何一个地方生成同样的图片,只要有一台支持postscript的打印机。明显的缺点就是,图象永远不是在程序内存空间内生成的,所以当postscript不可用时就比较麻烦了。
opengl
以后的工作
Xr库当前还在积极的开发阶段,在这片文章中面熟的任何一件东西都在完善当中,但是还有很多的事要做以使得Xr对于程序开发更加有用。
文字支持
打印支持
颜色管理
如何获取
Xr是一个免费软件