Qt开发专栏:开发技术(点击传送门)
前话
红胖子,来也!
Qt的图形绘制系统,(分为2D图形和3D图形,本篇章主要介绍2D图形绘制系统)。
Qt绘图系统
Qt5中的图形主要是通过命令式QPainter API或Qt的声明性UI语言Qt Quick及其场景图后端来完成的。Qt5的图形功能还包括对打印以及加载和保存各种图像格式的支持
QPainter绘制2D图形
QPainter提供了将矢量图形、文本和图像绘制到不同面(可理解为画布)或QPaintDevice实例(如QImage、QOpenGlPaintDevice、QWidget和QPrinter)上的API。实际的绘图发生在QPaintDevice的QPaintEngine中。软件光栅器和OpenGL(ES)2.0后端是两个最重要的QPaintEngine实现。光栅绘制引擎是Qt的软件光栅化器,在绘制QImage或QWidget时使用。它在OpenGL绘制引擎上的优势在于启用抗锯齿时的高质量,以及完整的功能集。
- 绘制系统:概述QPainer类和架构。
- 坐标系统:说明QPainer坐标系的工作原理。
- 绘制和填充:说明QPainter如何执行矢量形状的填充和大纲绘制。
QPainer最重要的渲染目标是:
- QImage:一种与硬件无关的直接像素访问图像表示。QPainer将使用软件光栅器绘制QImage实例。
- QPixmap:一种适合在屏幕上显示的图像表示。QPainer将主要使用软件光栅器绘制到QPixmap实例。
- QOpenGLPaintDevice:一个要呈现到当前OpenGL 2.0上下文的绘制设备。QPainter将使用硬件加速的OpenGL调用来绘制QopenglPaintDevice实例。
- QBackingStore:顶级窗口的backbuffer。QPainer将主要使用软件光栅器绘制到QBackingStore实例。
- QWidget:用于预qt快速用户界面类的基类。qpainter将使用qbackingstore呈现小部件。
- QOpenGLWidget:画家也可以在QOpenGLWidget上打开。这是为了方便起见,因为从技术上讲,这与使用QopenGLPaintDevice没有什么不同。
QPainer和相关类是Qt GUI模块的一部分。
OpenGL and 3D
OpenGL是硬件加速和3D图形最广泛采用的图形API,在所有桌面平台以及几乎所有移动和嵌入式平台上实现。Qt库包含许多类,可方便的将OpenGL集成到其应用程序中。
- Qt GUI中的OpenGL—OpenGL如何与qt-gui模块集成的概述。
- QopenglWidget是一个小部件,允许将OpenGL场景添加到基于QWidget的用户界面中。
- OpenGL和Qt Quick 2.0-如何将OpenGL集成到Qt Quick 2.0场景图中。
- www.khronos.org/opengl-官方OpenGL页面。
- qt canvas 3d-一个附加模块,提供了一种使用javascript从qt quick调用OpenGL类3d绘图的方法。
在Qt5.0之前,Qt中的OpenGL支持由Qt OpenGL模块处理。这个模块仍然存在,但是新的代码应该旨在使用Qt GUI模块中的新类。类很容易根据其名称进行区分:不应使用带有QGL前缀的类。相反,更喜欢从QOpenGl开始的。
绘图系统
Qt的绘图系统可以使用相同的API在屏幕和打印设备上进行绘制,并且主要基于QPainter、QPaintDevice和QPaintEngine类。(对于一般绘图开发者主要关注QPainter)
QPainter用于执行绘图操作,QPaintDevice是可以使用QPainter绘制的二维空间的抽象,QPainter提供了用于绘制不同类型设备的界面。QPaintEngine类由QPainter和QPaintDevice在内部使用,除非应用程序程序员创建自己的设备类型,否则对他们隐藏。
这种方法的主要好处是,所有的绘制都遵循相同的绘制管道,因此很容易添加对新特性的支持,并为不支持的特性提供默认实现。
绘图类
这些类为在绘制设备上绘制提供支持。
序号 |
类名 |
描述 |
1 |
QLine |
使用整数精度的二维矢量(直线) |
2 |
QLineF |
使用浮点精度的二维矢量(直线) |
3 |
QMargins |
使用整数精度定义矩形的四个边距 |
4 |
QMarginsF |
使用浮点精度定义矩形的四个边距 |
5 |
QPoint |
使用整数精度定义平面中的点 |
6 |
QPointF |
使用浮点精度定义平面中的点 |
7 |
QRect |
使用整数精度定义平面中的矩形 |
8 |
QRectF |
使用浮点精度定义平面中的矩形 |
9 |
QSize |
使用整数精度定义二维对象的大小 |
10 |
QSizeF |
使用浮点精度定义二维对象的大小 |
11 |
QBitmap |
单色(1位深度)像素地图 |
12 |
QIcon |
不同模式和状态下的可缩放图标 |
13 |
QIconEngine |
QIcon呈现器的抽象基类 |
14 |
QImage |
与硬件无关的图像表示,允许直接访问像素数据,并可用作绘制设备。 |
15 |
QImageReader |
格式独立的接口,用于从文件或其他设备读取图像 |
16 |
QImageWriter |
格式独立的接口,用于将图像写入文件或其他设备 |
17 |
QPixmap |
可以用作绘制设备的屏幕外图像表示 |
18 |
QGenericMatrix |
表示具有n列和m行的NXM转换矩阵的模板类 |
19 |
QVector2D |
表示二维空间中的向量或顶点 |
20 |
QBrush |
定义由QPainer绘制的形状的填充图案 |
21 |
QConicalGradient |
与QBrush结合使用以指定圆锥形渐变画笔 |
22 |
QGradient |
与QBrush结合使用以指定渐变填充 |
23 |
QLinearGradient |
与QBrush结合使用以指定线性渐变画笔 |
24 |
QRadialGradient |
与QBrush结合使用以指定线性渐变画笔 |
25 |
QColor |
基于RGB、HSV或CMYK值的颜色 |
26 |
QPagedPaintDevice |
表示支持多页的绘画设备 |
27 |
QPaintDevice |
可以用QPainer绘制的对象的基类 |
28 |
QPaintEngine |
关于QPainer如何在给定平台上绘制给定设备的抽象定义 |
29 |
QPainter |
对小部件和其他绘制设备执行低级绘制 |
30 |
QPainterPath |
用于绘制操作的容器,允许构造和重用图形形状 |
31 |
QPainterPathStroker |
用于生成给定绘制路径的可填充轮廓 |
32 |
QPdfWriter |
类以生成可用作绘制设备的pdf |
33 |
QPen |
定义QPainer应如何绘制形状的线条和轮廓 |
34 |
QPolygon |
使用整数精度点向量 |
35 |
QPolygonF |
使用浮点精度点向量 |
36 |
QRegion |
为绘制者指定剪辑区域 |
37 |
QRgba64 |
结构包含64位RGB颜色 |
38 |
QTransform |
指定坐标系的二维转换 |
39 |
QFont |
指定用于绘制文本的字体 |
40 |
QFontMetrics |
使用整数精度字体度量信息 |
41 |
QFontMetricsF |
使用浮点精度字体度量信息 |
42 |
QPlatformFontDatabase |
使自定义如何发现字体以及如何呈现字体成为可能 |
43 |
QSupportedWritingSystems |
在内部Qt fontdatabase中注册字体时使用 |
44 |
QStylePainter |
用于在窗口中绘制QStyle元素的类 |
45 |
QColormap |
将与设备无关的QColors映射到与设备相关的像素值 |
46 |
QSvgGenerator |
用于创建SVG图形的绘制设备 |
47 |
QSvgRenderer |
用于将SVG文件的内容绘制到绘制设备上 |
48 |
QSvgWidget |
用于显示可缩放矢量图形(SVG)文件内容的窗口 |
绘制设备和后端处理
创建绘图设备
QPaintDevice类是可以绘制的对象的基类,即QPainter可以绘制任何QPaintDevice子类。QPaintDevice的绘图功能由QWidget、QImage、QPixmap、QPicture、QPrinter和QOpenGLPaintDevice实现。
QWidget
QWidget类是Qt Widgets模块中用户界面元素的基类。它从窗口系统接收鼠标、键盘和其他事件,并在屏幕上绘制自身的表示。
QImage
QImage类提供了一种独立于硬件的图像表示,它针对I/O和直接像素访问和操作进行了设计和优化。QImage支持多种图像格式,包括单色、8位、32位和阿尔法混合图像。
使用QImage作为绘制设备的一个优点是可以以平台独立的方式保证任何绘制操作的像素精确性。另一个好处是,可以在当前GUI线程之外的其他线程中执行绘制。
QPixmap
QPixmap类是一种非屏幕图像表示,它是为在屏幕上显示图像而设计和优化的。与QImage不同,pixmap中的像素数据是内部的,由底层窗口系统管理,即只能通过QPainter函数或通过将QPixmap转换为QImage来访问像素。
为了使用QPixmap优化绘图,Qt提供了QPixmapCache类,该类可用于存储生成成本高昂的临时pixmap,而不使用比缓存限制更多的存储空间。
Qt还提供QBitmap便利类,继承QPixmap。QBitmap保证单色(1位深度)像素映射,主要用于创建自定义QCursor和QBrush对象,构建QRegion对象。
OpenGL Paint Device
如前所述,Qt提供的类使在Qt应用程序中使用OpenGL变得容易。例如,QOpenGLPaintDevice启用OpenGL API以使用QPainter进行渲染。
QPicture
QPicture类是一个绘制设备,用于记录和重放QPainter命令。图片以独立于平台的格式将painter命令序列化到IO设备。QPicture也与分辨率无关,即QPicture可以显示在看起来相同的不同设备上(例如svg、pdf、ps、打印机和屏幕)。
Qt提供QPicture::load()和QPicture::save()函数以及用于加载和保存图片的流式运算符。
定义后端处理
可以通过从QPaintDevice类派生并重新实现重载QPaintDevice::paintEngine()函数来实现对新后端的支持,以告诉QPainter应该使用哪个绘制引擎来绘制此特定设备。要实际能够在设备上绘制,此绘制引擎必须是通过从QPaintEngine类派生而创建的自定义绘制引擎。
绘制和填充
绘制
Painter提供高度优化的功能来完成大多数图形用户界面程序所需的工作。它可以绘制从简单的图形原语(由QPoint、QLine、QRect、QRegion和QPolygon类表示)到矢量路径等复杂形状的所有内容。在qt中,矢量路径由QPainterPath类表示。QPainterPath提供了一个用于绘制操作的容器,使图形能够被构造和重用。
使用QPen类绘制线条和轮廓。笔由其样式(即线条类型)、宽度、画笔、端点的绘制方式(笔帽样式)以及两条连接线之间的连接方式(连接样式)定义。笔的画笔是用于填充笔生成的笔画的QBrush对象,即QBrush类定义填充图案。
QPainer还可以绘制对齐的文本和像素图。
绘制文本时,使用QFont类指定字体。Qt将使用具有指定属性的字体,或者如果不存在匹配的字体,Qt将使用最接近的匹配安装字体。实际使用的字体属性可以使用QFortInfo类来检索。此外,QFontMetrics类提供字体度量,而QFontDatabase类提供有关底层窗口系统中可用字体的信息。
通常,QPainer绘制一个“自然”坐标系,但它能够使用QTransform类执行视图和世界转换。有关详细信息,请参见坐标系,该坐标系还描述渲染过程,即逻辑表示与渲染像素之间的关系,以及抗锯齿绘制的好处。
抗锯齿绘图
绘制时,像素渲染由QPaint::Antialising指令控制。QPainter::RenderHint枚举用于指定任何给定引擎可能遵守或不遵守的QPainter标志。
QPaint::Antialising值指示引擎应尽可能消除基本体的边,即使用不同的颜色强度平滑边(抗锯齿)。
填充
使用QBrush类填充形状。画笔由其颜色和样式(即填充图案)定义。
Qt中的任何颜色都由支持RGB、HSV和CMYK颜色模型的QColor类表示。QColor还支持alpha混合的大纲和填充(指定透明度效果),类是平台和设备独立的(颜色使用QColormap类映射到硬件)。有关更多信息,请参见QColor类文档。
可用的填充模式由Qt::BrushStyle枚举描述。这些包括基本图案,从均匀的颜色到非常稀疏的图案,各种线条组合,渐变填充和纹理。Qt支持自定义填充背景,提供QGradient类来定义自定义渐变填充,而纹理模式是使用QPixmap类指定的。
坐标系统
坐标系由QPainer类控制。与QPaintDevice和QPaintEngine类一起,QPainter构成了qt绘画系统的基础,Arthur。QPainter用于执行绘图操作,QPaintDevice是可以使用QPainter绘制的二维空间的抽象,QPaintEngine提供了用于绘制不同类型设备的界面。
QPaintDevice类是可以绘制的对象的基类:其绘图功能由QWidget、QImage、QPixmap、QPicture和QOPenGLPaintDevice类继承。绘制设备的默认坐标系的原点位于左上角。X值向右增加,Y值向下增加。默认单位是基于像素的设备上的一个像素和打印机上的一个点(1/72英寸)。
逻辑QPainter坐标到物理QPaintDevice坐标的映射由QPainter的转换矩阵、视区和“窗口”处理。默认情况下,逻辑坐标系和物理坐标系是一致的。QPainter还支持坐标转换(例如旋转和缩放)。
渲染:逻辑表示
图形元素的大小(宽度和高度)始终对应于其数学模型,而忽略了用以下方式呈现的笔的宽度:
渲染:锯齿绘制
绘制时,像素渲染锯齿由QPainter::Antialising控制。
RenderHint枚举用于向QPainer指定任何给定引擎可能遵守或不遵守的标志。QPainter::Antialising值指示引擎应尽可能消除基本体的边(抗锯齿),即使用不同的颜色强度使边缘平滑。
默认情况下,QPainter 绘制时有锯齿,并且有其它规则:当用 1 像素宽的画笔绘制时,像素会被绘制在右下角。例如:
使用偶数像素的笔进行渲染时,像素将围绕数学定义的点对称渲染,而使用奇数像素的笔进行渲染时,备用像素将渲染到数学点的右侧和下方,就像在一个像素的情况下一样。具体示例见下面的QRectF图。
请注意,由于历史原因,QRect::right()和QRect::bottom()函数的返回值偏离了矩形的右下角。
QRect::right()函数返回left()+width()-1,bottom()函数返回top()+height()-1。图中右下角的绿点显示了这些函数的返回坐标。
建议使用QRectF替代,QRectF类使用浮点坐标定义平面中的一个矩形以实现精确性(QRect使用整数坐标),而QRectF::right()和QRectF::bottom()函数确实返回真正的右下角。
或者,使用QRect,应用x()+width()和y()+height()查找右下角,并避免使用right()和bottom()函数。
渲染:抗锯齿绘制
如果设置QPainter的抗混叠渲染,像素将在数学定义的点的两侧对称渲染:
渲染:抗锯齿枚举参数定义
序号 |
枚举 |
描述 |
1 |
QPainter::Antialiasing |
指示引擎应尽可能消除锯齿。 |
2 |
QPainter::TextAntialiasing |
指示引擎应尽可能消除文本的锯齿。若要强制禁用文本的抗锯齿,请不要使用此提示。相反,在字体的样式策略上设置QFont::NoAntialias。 |
3 |
QPainter::SmoothPixmapTransform |
指示引擎应使用平滑的pixmap转换算法(如双线性)而不是最邻近的。 |
4 |
QPainter::HighQualityAntialiasing |
此值已过时并将被忽略,请改用反锯齿渲染提示。 |
5 |
QPainter::NonCosmeticDefaultPen |
此值已过时,QPen的默认值现在为非外观 |
6 |
QPainter::Qt4CompatiblePainting |
兼容性提示,告诉引擎使用与qt 4中相同的基于x11的填充规则,其中混叠渲染的偏移量略小于半个像素。也会将默认构造的钢笔视为化妆品。将Qt4应用程序移植到Qt5时可能有用。 |
变换:常规操作
默认情况下,QPainter在相关设备自己的坐标系上运行,但它也完全支持仿射坐标转换
可以使用QPainter::scale()函数按给定的偏移量缩放坐标系,可以使用QPainter::rotate()函数顺时针旋转坐标系,也可以使用QPainter::translate()函数转换坐标系(即向点添加给定偏移量)。
还可以使用QPainer::shear()函数围绕原点扭曲坐标系。所有转换操作都是在QPainer的转换矩阵上进行的,您可以使用QPainer::worldTransform()函数来检索该矩阵。矩阵将平面中的一个点转换为另一个点。
如果您需要反复进行相同的转换,还可以使用qTransform对象和QPainer::worldTransform()和QPainer::setWorldTransform()函数。您可以随时通过调用QPainter::save()函数来保存QPainter的转换矩阵,该函数将矩阵保存在内部堆栈上。函数的作用是:恢复。
转换矩阵的一个常见需求是在各种绘制设备上重复使用相同的绘图代码。如果不进行转换,结果将与绘制设备的分辨率紧密相连。打印机具有高分辨率,例如每英寸600点,而屏幕通常每英寸72到100点。
变换:Analog Clock Example
模拟时钟示例演示如何使用qpainer的转换矩阵绘制自定义小部件的内容。我们建议您在进一步阅读之前编译并运行这个示例。尤其是,尝试将窗口大小调整为不同的大小。
转换坐标系,使点(0,0)在小部件的中心,而不是在左上角。我们还按side/100缩放系统,其中side是小部件的宽度或高度,以最短者为准。我们希望时钟是方形的,即使设备不是。
这将给我们一个200 x 200平方的面积,原点(0,0)在中间,我们可以利用。我们绘制的图形将显示在适合小部件的最大可能的正方形中。
通过旋转坐标系并调用QPainter::drawConverExpolygon()来绘制时钟的时针。感谢旋转,它画的方向是正确的。
多边形被指定为一个交替的x,y值数组,存储在hourhand静态变量(在函数开头定义)中,该变量对应于四个点(2,0),(0,2),(-2,0)和(0,-25)。
围绕代码对QPainter::save()和QPainter::restore()的调用保证了后面的代码不会被我们使用的转换所干扰。
我们对时钟的分针也这样做,它由四个点(1,0),(0,1),(-1,0)和(0,-40)定义。这些坐标指定一只手比分针更薄、更长。
最后,画出钟面,它由12条30度间隔的短线组成。最后,绘制方法采用了一种不太有用的方式旋转,但我们已经完成了绘画,所以这并不重要。
变换:窗口视区转换
使用QPainer绘制时,我们使用逻辑坐标指定点,然后将逻辑坐标转换为绘制设备的物理坐标。
逻辑坐标到物理坐标的映射由QPainter的世界转型worldtransform()(在“转换”部分中描述)和QPainer的viewport()和window()处理。视区表示指定任意矩形的物理坐标。“窗口”以逻辑坐标描述同一个矩形。默认情况下,逻辑和物理坐标系是一致的,并且相当于绘制设备的矩形。
使用窗口-视区转换,可以使逻辑坐标系适合您的首选项。该机构还可以使绘图代码独立于绘图设备。例如,可以通过调用QPainter::setWindow()函数,使逻辑坐标从(-50,-50)扩展到(50,50),其中(0,0)位于中心:
现在,逻辑坐标(-50,-50)对应于绘制设备的物理坐标(0,0)。独立于绘制设备,绘制代码将始终在指定的逻辑坐标上操作。
通过设置“窗口”或视区矩形,可以执行坐标的线性转换。请注意,“窗口”的每个角都映射到视区的相应角,反之亦然。因此,通常最好让视区和“窗口”保持相同的纵横比以防止变形:
如果我们将逻辑坐标系设为正方形,那么还应该使用QPainter::setViewPort()函数将视区设为正方形。在上面的示例中,我们将其等效为适合绘制设备矩形的最大正方形。通过在设置窗口或视区时考虑绘制设备的大小,可以使绘制代码独立于绘制设备。
请注意,窗口视区转换只是线性转换,即它不执行剪切。这意味着,如果在当前设置的“窗口”外绘制,则绘制仍将使用相同的线性代数方法转换为视区。
视区、“窗口”和转换矩阵决定逻辑QPainter坐标如何映射到绘制设备的物理坐标。默认情况下,世界变换矩阵是标识矩阵,“窗口”和“视区”设置等同于绘制设备的设置,即世界,“窗口”和设备坐标系是等效的,但正如我们所看到的,可以使用变换操作和窗口视区转换来操纵系统。上图描述了这个过程。
读取和写入图像文件
最常见的读取图像的方法是通过QPmage和QPixmap的构造函数,或者通过调用QImage::load()和QPixmap::load()函数。此外,qt还提供了QImageReader类,它提供了对进程的更多控制。根据图像格式中的底层支持,类提供的函数可以节省内存并加速图像加载。
同样,Qt提供了QImageWriter类,它支持在存储图像之前设置格式特定的选项,例如gamma级别、压缩级别和质量。如果不需要这些选项,可以使用QImage::save()或QPixmap::save()。
QMovie是一个显示动画类,在内部使用QImageReader类。创建后,QMovie类为运行和控制给定动画提供了各种功能。
QImageReader和QImageWriter类依赖于QImageIOHandler类,它是Qt中所有图像格式的通用图像I/O接口。QImageIOHandler对象由QImageReader和QImageWriter在内部使用,以向Qt添加对不同图像格式的支持。
通过QImageReader::SupportedImageFormats()和QImageWriter::supportedImageFormats()函数可以获得支持的文件格式列表。Qt默认支持几种文件格式,此外,还可以作为插件添加新格式。当前支持的格式列在QImageReader和QImageWriter类文档中。
Qt的插件机制也可以用于编写自定义图像格式处理程序。这是通过从QImageIOHandler类派生并创建一个QImageIOPlugin对象来完成的,该对象是用于创建QImageIOHandler对象的工厂。安装插件后,QImageReader和QImageWriter将自动加载插件并开始使用它。