JefferyZhou

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

下面对于QT的绘制系统做一个简要说明, 这个系统主要由三部分组成,  QPainter, QPaintDevice, QPaintEngine。

QPainter 是一个绘制接口类,提供绘制各种面向用户的命令,而QPaintDevice 是一个QPainter绘制的目的地,相当于画布,   而QPaintEngine 是基本绘制命令的具体实现。

我们打交道比较多的是 QPainter , 注意对于Windows平台来说,当绘制目标是一个widget的时候,QPainter只能在 paintEvent() 里面或者由paintEvent()导致调用的函数里面使用。

QPainter 可以定制如下的一些参数:

font() 字体,辅助接口 fontInfo() 和 fontMetrics()

brush() 定义用填充模式绘制几何形状时候的画刷,主要是画刷的颜色和模式

pen() 定义花框图的时候线条的样条和颜色

backgroundMode() 定义是否存在 background(), 分为, Qt::OpaqueMode 和  Qt::TransparentMode 两个

background() 只有当 backgroundMode() 是 Qt::OpaqueMode, pen() 是 一个 一个stripple (各种虚线。。。。), 这个描述的是背景像素的颜色值。

brushOrigin() 画刷原点,正常情况下,画刷原点就是widget背景的原点

viewport, window() 和 worldTransform(), 一起构成painter的坐标系。

hasClipping() 告诉 painter 是否执行裁剪操作,裁剪的区域是 clipRegion()。

layoutDirection() , 表明的是在绘制文字项的时候,文字的排版方向

worldMatrixEnabled() 告诉绘制流程是否开启 world 变换

viewTransformEnabled() 告诉绘制流程是否开启 view 变换

上面的设置项,很多在绘制的 device 上也会由相应的设置,比如 QWdiget::font()。接口QPainter::begin() 或者是QPainter的构造函数,会从当前的device上拷贝那些属性。

对于QPainter来说,内部有一个状态堆栈,任何时候都可以通过调用 save() 和 restore() 对QPainter的内部状态执行 进栈保存和压栈还原的操作。

QPainter 提供了大部分基本二维几何元的绘制命令,如: drawPoint(), drawPoints(), drawLine(), drawRect(), drawRoundedRect(), drawEllipse(), drawArc(), drawPie(), drawChord(), drawPolyline(), drawPolygon(), drawConvexPolygon() and drawCubicBezier().

其中有两个遍历的函数 drawRects() and drawLines(), ,会根据当前设置的brush和pen绘制给定的QRects数组或者QLines数组。

QPainter 还两个了两个函数 fillRect 用来填充一个 QRect, 以及 eraseRect 用来擦除一个矩形。

上面的绘制命令提供一个整数参数版本,也提供一个浮点参数版本。

如果你需要绘制一个复杂的几何形状,而且需要反复的绘制,建议你使用QPainterPath 和 drawPth()。

QPainter 还提供了 fillPath 来填充 QPainterPath 组成的形状,还有strokePath()来绘制给定 path的边缘(勾边)。

QT也提供了一些列绘图命令来绘制 pixmaps 和 images,  它们是: drawPixmap(), drawImage() and drawTiledPixmap(). 其中 drawPixmap(),和 drawImage() 产生的效果是一样的,只是 drawPixmap在 屏幕上绘制比较快,而drawImage 在 QPrinter 和其他设备上绘制会比较快

文字绘制用接口 drawText(), 绘制文字需要提供坐标,还有boundingRect()

QPainter还有一个接口,用来在 painter device 上绘制一个 QPicture, 接口为 drawPicture(),  这个接口绘制的时候不会使用当前device的状态设置,因为QPicture有它自己的设置。

坐标变换

默认情况下,QPainter 使用的是 当前 device 的坐标系(以像素为单位),但是QPainter 对于坐标系变换提供了很好的支持,主要有如下的一些坐标系变换:

Image(134)

旋转, 缩放, 平移, shearing, 用 scale() 来缩放作响, rotate() 用来对坐标系进行顺时针旋转, translate() 对坐标系执行平移操作。也可以通过函数 shear() 对坐标系执行扭曲。类似对矩阵执行雅克比切变,让 坐标系的 x 和 y 不再是正交的向量。这里提到的变换都是作用在 worldTransform()的矩阵上。 还有一个矩阵 deviceTransform 用来把逻辑坐标变换到设备的坐标。

当用QPainter执行绘制的时候,我们指定的顶点坐标都是逻辑坐标,这个逻辑坐标最终会被转换成设备的物理坐标,从逻辑坐标到物理坐标的转换,是通过矩阵 combinedTransform()执行的,这个矩阵,结合了 viewport() , window(), 和 worldTransform()。 其中 viewport()代表的是物理坐标系中的任意的一个矩形区域,而window()是以逻辑坐标的形式描述viewport()指定的同一个矩形。其中worldTransform() 就等于变换矩阵。

裁剪

QPainter可以把绘制进行裁剪指定,裁剪区域可以是 rectange, region 或者 vector path, 当前裁剪区域可以通过clipRegion() 和 clipPath 访问。不同的裁剪区域在不同的paintEngine()上会获得不一样的性能,在QPainter 执行裁剪后,  painte device 还回执行一次裁剪。

组合模式

QPainter 提供了一个枚举 CompositionMode 类型,用来配置QPainter绘制命令的融合模式。

两个用得最多的是 Source 和 SourceOver,  Source 模式用来绘制那些不透明的对象,在这个模式下,source中的每个像素会代替destination中的相应像素。在SourceOver模式,主要用来绘制透明对象,在这个模式下,source中的像素不会直接替代destination中的像素,source中的像素会覆盖在destination上(没明白QT assist 中的这个是说什么,但猜测应该是进行 alpha 混合,至于 alpha 的混合是 SRCAPHA 还是 INV_SRCALPHA就不得而知了,需要实验一下)。

性能

QPainter 是一个丰富的绘制框架,为开发者提供了大量的图像绘制命令,比如 渐变(gradients),融合模式(composition modes), 矢量图像(vector graphics)。而且上述的绘制在大部分软硬件平台都是支持的,为了让大部分的软硬件环境都能运行QT,这种跨平台支持,自然让我们牺牲了一些性能,可以想象到如果要让QPainter中的每一个单独绘制命令都去完整的设置一次 composition modes, brushes, clipping, transformation 等等,这种渲染状态的海量设置几乎是不可能完成的任务。作为一个折中的版本,我们选择了QPainter API中的一个子集和一些后端的关键技术作为突破点,来保证这部分绘制命令的性能达到我们可以接受程度。

为了实现上述性能目标,我们聚焦的绘制后端技术有如下一些(我们尽力保证他们的性能):

[QT assist 中队这一段的描述,E文相对较绕,先贴下原文,可以对照:QPainter is a rich framework that allows developers to do a great variety of graphical operations, such as gradients, composition modes and vector graphics. And QPainter can do this across a variety of different hardware and software stacks. Naturally the underlying combination of hardware and software has some implications for performance, and ensuring that every single operation is fast in combination with all the various combinations of composition modes, brushes, clipping, transformation, etc, is close to an impossible task because of the number of permutations. As a compromise we have selected a subset of the QPainter API and backends, where performance is guaranteed to be as good as we can sensibly get it for the given combination of hardware and software.]

QT 主要实现的后端绘制技术:

  • Raster(光栅化) - 这个后端技术,用纯软件的方法实现渲染,并且他总是会渲染到一个QImage。为了优化性能,这里的渲染只使用下面的格式:,其他的任何告诉包括,光栅化的性能都很差。这个渲染引擎也是Windows和QWS上默认的渲染引擎。这个渲染引擎作为默认的图像系统可以运行在任何操作系统和软硬件平台上,在命令行中通过 -graphicssystem raster就可以指定用这个渲染引擎启动QT。
  • OpenGL 2.0(ES) 这个是一个主要的硬件加速的图像后端。这个可以运行在桌面机上,以及所有支持OpenGL 2.0 或者 OpenGL/ES 2.0的设备上。这也就意味着绝大部分图形芯片都是支持的。这个引擎通过命令行 -graphicssystem -opengl 启动 QPainter 在绘制 QGLWidget 的时候 使用 这个图形引擎。
  • OpenVG - 这个后端技术,主要是实现 Khronos 标准的 2D 图形和 矢量图形。 这个使得QT在支持 OpenVG的硬件设备上也是支持的,通过命令行 -graphicsssytem openvg开启。

性能得到保证的主要绘制相关操作包括:

  • 简单的变换,正常的平移和缩放,或者进行 0, 90, 180, 270,这种角度的旋转操作
  • drawPixmap() 结合简单的变换,绘制不透明的对象(这个时候CompositionMode 不要设置成QPainter::SmoothPixmapTransform, 这个时候不支持)
  • 矩形纯色填充,或者两个颜色的渐变填充,或者还加上一些简单的变换都是ok的。
  • 模式设置成 QPainter::CompositionMode_Source and QPainter::CompositionMode_SourceOver, 性能是最好的。
  • 用纯色或者两个颜色的渐变填充 圆角矩形 也是 ok的。
  • 用 qDrawBorderPixmap 进行 3*3的 pixmaps 修补 也是 ok的。


绘制引擎


Qt - painter

QT 的 绘制系统, 封装得比较严实,这里针对GraphicsView - Scene - Item 系统做一个介绍。

QT 的 Paint System 主要是基于 QPainter, QPainterDevice 和 QPaintEngine 三个类。

1.QPainter       

  用于完成绘制操作。

2.QPaintDevice  

  可以看成是一个2维的画板,包含一些画板的基本信息。直译的话就是绘图设备。

3.QPaintEngine

  提供了接口,QPainter 使用这些接口往不同类型的 device 上绘制。QPaintEngine不直接提供给开发人员使用。打个比方,如果你想使用windows自身的绘制设备绘制UI,那么Qt就选择默认地匹配windows的QPaintEngine进行界面的绘制;如果你想用OpenGL渲染界面,则需要使用OpenGL相关的QPaintEngine。Qt自带的QGLWidget可使用OpenGL进行渲染,其内部便使用了QGLPaintEngine。

4.QD3DPaintEngine

  如果我希望能用Windows下的DirectX9 图形API渲染Qt界面的话,我需要创建D3D相关的QPaintEngine。具体实现可以参照QGLWidget。Qt因为跨平台选择了支持OpenGL,对D3D就没提供内部支持了。

在各个平台上,绘制和渲染,通常有三个途径可走,

第一个,用操作系统提供的 api,  操作系统本身通常不会提供三维渲染,只会在基础显示设备的驱动之上封装绘制操作, 

第二个,用设备驱动上的渲染库, DirectX, OpenGL,  OpeXL

第三个,直接显示设备的操作硬件驱动,比如 皮克斯公司的 renderware ,这个相当于自己做了图形库

从前面知道原生的QT,是一个纯软件实现的光栅化渲染引擎,所以也就没有D3D和OpenGL一说了。不过QT又实现了一套QGLWidget,用OpenGL2.0进行渲染。

绘制流程


深入QT的 Painter 可以看到 ,QT的绘制引擎 种类很多,总共有11类绘制引擎。引擎QT界面库是一个相对底层的库。

我们先看下windows下的这个绘制流程

绘制命令从 Windows 的窗口消息 ,WM_PAINT 和 WM_ERASEBKGND 开始

Image(135)

Image(136)

然后这个会由一个绘制事件发送给 QCoreApplication , 然后又转到了 GraphicsView 的 viewportEvent, 这个中间的转换需要关注一下,是通过filter 转到View的。

Image(137)

这个直接的传递是通过把 相应的 View 当做一个 事件过滤器安装到  Application 上

接着由下面的接口处理绘制事件

Image(138)

我们知道  View 只是一个窗口,View本身没有内容,有内容的是 Scene , 一个Scene 根据层级树的方式挂载这 QGraphicsItem,所以绘制事件就会

传递给 QGraphicsScene ,用来绘制当前 Scene 中的所有 Item。

Image(139)

我们知道 scene 中的 item 是一个具备父子关系的树,所以绘制 scene的时候,我们只需要找到 所有子树的根节点,也就是QT中的toplevelItem。

然后在每个树根上执行递归绘制。

Image(140)

这个过程是一个递归的过程,  来看下 在一个递归循环里面的绘制函数

Image(141)

这个函数 执行一个完整的 绘制流程:

  开始 绘制 item 的那些位于 item 之后的 子 item 

Image(142)

   然后 绘制 item 自己

Image(143)

   最后绘制那些 位于 item 之前的 子item

Image(144)

   这样一个完整的item绘制流程就完成了。

接下来我们深入 item 本身内容的绘制中,  也就是 Image(145)

这个是一个虚接口函数,由QGraphicsItem的 各个子类实现,这里的绘制是一个高层绘制,也就说在使用 QPainter提供的基本绘制命令上组合图形,

基本绘制命令,有Rect, Pixmap, Point,  Lines 等

对于这些组合理论不深入了, 这里接着深入  QPainter 本身。

QPainter 是一个 绘制接口的 Wrapper, 具体的工作都交给 相应的 QPainterEngine

而QPainterEngine 在整个继承层次上,进行分工,分为 PathEngine, RasterEngine,  分别处理线条的绘制,以及 pixelData 的绘制。

前面提到QT支持跨平台,并且支持GL渲染,但这一部分是一个相对独立的系统,和QT的原生系统不一样。

QT原生系统是一个自己做的软件渲染库,基本上事情都在cpu上执行计算,而且整个渲染系统架构在一个自己写的渲染管线上,

QPainter提供的绘制命令只会把所有数据收集整理,然后用自己写的渲染管线执行,裁剪,光栅操作,最后把整理好的渲染位图,填充到一个光栅化的缓冲区,

注意这个缓冲区是内存,不是显存。所以QT是一个软件加速的渲染引擎。至于后面提到的D3D扩展那一套,就当别论了。

下面深入QPainter的 绘制领命, 绘制框图,绘制位图,绘制字体,三个方面。然后再深入介绍一下,QT怎么把它自己写的渲染管线的结果,也就最终渲染buffer,提交给设备。

这其中涉及一个重要的渲染类: QRasterBuffer,这个是QT绘制命令执行的目的地,

而下面的这个结构体是QT用来操作 QRasterBuffer中数据的辅助结构体

Image(146)

这里包括了各种位图操作。

前面我们知道了QT的渲染是软件光栅化,把所有内容都会绘入一个QImage,但它和windows是怎么交互的呢,怎么把QImage的内容显示在窗口上呢,看下下面的代码段:

Image(147)

这个位于:bool QETWidget::translatePaintEvent(const MSG &msg)

也就是

Image(148)

中的相应窗口的 WINDOWS 的PAINT消息。

有机会再分析 QT的 光栅渲染引擎。

posted on 2012-09-24 17:12  JefferyZhou  阅读(18163)  评论(0编辑  收藏  举报