参考:Paint System。
Paint System 是使用相同 API 在屏幕或打印设备上绘画的系统。这里的 API 主要基于 QPainter,QPaintDevice,和 QPaintEngine 类。
QPainter
用于执行绘制操作,QPaintDevice
是可以使用QPainter
绘制的二维空间的抽象,并且QPaintEngine
提供了绘制器用来在不同类型的设备上绘制的接口。QPaintEngine
类由QPainter
和QPaintDevice
内部使用,并且除非它们创建自己的设备类型,否则对应用程序程序员而言是隐藏的。
更多细节参考 Topics:
1 绘图设备
QPaintDevice
类是可以用QPainter
进行绘图的设备对象(也可以视为画板)的基类。paint device(QPaintDevice
及其子类的实例对象)是可以使用QPainter
绘图的二维空间的一种抽象。其默认坐标系的原点位于左上角。X 向右增加,Y 向下方增加。单位是一个像素。
QPaintDevice 被 QBitmap, QImage, QPagedPaintDevice, QPicture, QPixmap, QGLFramebufferObject, QGLPixelBuffer, QPrinter, QSvgGenerator 继承。
QPaintDevice
的绘图功能由 QWidget[1], QImage[2], QPixmap[3], QPicture[4], 和 QOpenGLPaintDevice
[5]实现。QPaintDevice
构造一个绘画设备且只能从QPaintDevice
的子类调用此构造函数。
对新后端的支持可以通过从QPaintDevice
类派生并重新实现 virtual 函数 paintEngine()
来实现,以告诉QPainter
应该使用哪个绘画引擎在此特定设备上进行绘制。为了真正能够在设备上绘画,此绘画引擎必须是通过从QPaintEngine
类派生而创建的自定义绘画引擎。
警告:Qt 要求在创建任何绘画设备之前必须存在 QGuiApplication
对象。绘画设备访问窗口系统资源,并且在创建应用程序对象之前不会初始化这些资源。
QPaintDevice
类提供了几个返回各种设备指标(metrics)的函数:depth()
函数返回其位深度(位平面的数量)。height()
函数以默认坐标系单位(default coordinate system units,例如QPixmap
和QWidget
的像素)返回其高度,而heightMM()
返回以毫米为单位的设备高度。类似地,width()
和widthMM()
函数分别以默认坐标系单位和毫米为单位返回设备的宽度。或者,通过将所需的PaintDeviceMetric
指定为参数,可以使用 protected 函数metric()
(PySide2.QtGui.QPaintDevice.PaintDeviceMetric
)来检索度量信息。
logicalDpiX()
和 logicalDpiY()
函数以每英寸点数返回设备的水平和垂直分辨率(resolution)。physicalDpiX()
和 physicalDpiY()
函数也返回以英寸为单位的设备分辨率,但请注意,如果逻辑分辨率和物理分辨率不同,则相应的QPaintEngine
必须处理映射。最后,colorCount()
函数返回可用于绘制设备的不同颜色的数量。
1.1 OffScreen Rendering
GPU 屏幕渲染有两种方式:
- On-Screen Rendering 当前屏幕渲染,是指渲染操作是在当前用于显示屏幕缓冲区中进行的。
- Off-Screen Rendering 离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
另一种特殊的离屏渲染:CPU 渲染
如果我们重写了 drawRect
方法,并且使用任何 Core Graphics 的技术进行了绘制操作,就涉及到了 CPU 渲染。整个渲染过程由 CPU 在 App 内同步地完成,渲染得到的 bitmap 最后再交由 GPU 用于显示。
相比于当前屏幕渲染,离屏渲染的代价比较高,主要体现在两个方面:
- 创建新缓冲区
- 想要离屏渲染,就必须创建一个新的缓冲区;
- 上下文切换
- 离屏渲染的整个过程,需要多次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上。而上下文环境的切换是要付出很大代价的。
1.2 图像处理类
Qt 提供了四个用于处理图像数据的类:QImage
,QPixmap
,QBitmap
和QPicture
。
QImage
是为 I/O 设计的,并针对直接像素访问和操作进行了优化,而QPixmap
是为在屏幕上显示图像而设计和优化的。QBitmap
只是继承QPixmap
并确保深度为 1 的便捷类。如果QPixmap
对象确实是位图,则isQBitmap()
函数将返回True
,否则返回False
。最后,QPicture
类是一种绘画设备,可以记录并重放QPainter
命令。
2 Drawing
QPainter
提供了高度优化的功能,可以完成大多数 GUI 程序所需的功能。它可以绘制所有内容,从简单的图形基元(由QPoint
,QLine
,QRect
,QRegion
和 QPolygon
类表示)到复杂的形状(如矢量路径)。 在 Qt 中,矢量路径由QPainterPath
类表示。QPainterPath
提供了一个用于绘画操作的容器,从而可以构造和重用图形形状。
painter path 是由直线和曲线组成的对象。例如,矩形由直线组成,椭圆由曲线组成。与常规绘图操作相比,painter path 的主要优势在于,复杂的形状仅需要创建一次;那么仅使用 drawPath()
函数即可将它们绘制多次。
QPainterPath 对象可用于填充,概述和裁剪。要为给定的 painter path 生成可填充的轮廓,请使用 QPainterPathStroker 类。
使用 QPen 类绘制线条和轮廓(Lines and outlines)。笔由其样式(style,i.e. its line-type),宽度,画刷(brush),如何绘制端点(endpoints,cap-style)以及如何绘制两条相连线之间的连接(join-style)定义。笔刷是一个 QBrush 对象,用于填充用笔生成的笔划,即QBrush
类定义填充图案。
QPainter
还可以绘制对齐的文本和像素图。
绘制文本时,使用 QFont 类指定字体。Qt 将使用具有指定属性的字体,或者如果不存在匹配的字体,则 Qt 将使用最接近的匹配安装字体。可以使用 QFontInfo 类来检索实际使用的字体的属性。此外,QFontMetrics 类提供字体度量,而 QFontDatabase 类提供有关基础窗口系统中可用字体的信息。
通常,QPainter
会绘制“自然”坐标系,但是它能够使用 QTransform 类执行视图和世界变换。
绘制时,像素渲染由“抗锯齿”渲染提示控制。RenderHint
枚举用于指定QPainter
的标志,任何给定的引擎可能会或可能不会考虑这些标志。Antialiasing
值指示引擎应尽可能对图元的边缘进行抗锯齿,即通过使用不同的颜色强度对边缘进行平滑处理。
3 Filling
使用QBrush
类填充形状。画刷由其颜色和样式(即填充样式)定义。
Qt 中的任何颜色都由支持 RGB,HSV 和 CMYK 颜色模型的 QColor 类表示。QColor
还支持 alpha 混合的轮廓和填充(指定透明效果),并且该类与平台和设备无关(使用 QColormap 类将颜色映射到硬件)。
可用的填充模式由BrushStyle
枚举描述。这些包括从均匀颜色到非常稀疏的图案的基本图案,各种线条组合,渐变填充和纹理。Qt 提供了 QGradient 类来定义自定义渐变填充,而纹理图案是使用QPixmap
类指定的。
QGradient
类与QBrush
结合使用以指定渐变填充。
Qt 当前支持三种类型的渐变填充:线性渐变(Linear gradients)在起点和终点之间插值颜色;径向渐变(radial gradients)在焦点与终点之间的圆上的终点之间插值颜色;圆锥形渐变(conical gradients)在中心点周围插值颜色。
4 Coordinate System
有关绘制系统使用的坐标系的信息。坐标系由QPainter
类控制。QPainter
与QPaintDevice
和QPaintEngine
类一起构成了 Qt 绘画系统 Arthur 的基础。
paint device 的默认坐标系的原点位于左上角。X 值向右增加,Y 值向下增加。在基于像素的设备上,默认单位是一个像素,在打印机上,默认单位是一个点(1/72 英寸)。
logical QPainter
坐标到 physical QPaintDevice
坐标的映射由 QPainter
的转换矩阵,视口(viewport)和 “window”处理。默认情况下,逻辑坐标系和物理坐标系重合。QPainter
还支持坐标转换(例如旋转和缩放)。
4.1 Rendering
4.2 Logical Representation
图形基元的大小(宽度和高度)始终与其数学模型相对应,而忽略用以下方式呈现的笔的宽度:
4.3 Aliased Painting
绘制时,像素渲染由 Antialiasing
渲染提示控制。RenderHint
枚举用于指定QPainter
的标志,任何给定的引擎可能会或可能不会考虑这些标志。 Antialiasing
值指示引擎应尽可能对图元的边缘进行抗锯齿,即通过使用不同的颜色强度对边缘进行平滑处理。但是默认情况下,画家是别名的,并且适用其他规则:当使用一个像素宽的笔进行渲染时,像素将在数学上定义的点的右侧和下方渲染。 例如:
当使用像素数为偶数的笔进行渲染时,将在数学定义的点周围对称地渲染像素,而使用像素数为奇数的笔进行渲染时,备用像素将在数学点的右边和下方渲染 与一像素的情况一样。更多信息参考:Qt:QRect 和 QRectF。
请注意,由于历史原因,right()
和bottom()
函数的返回值偏离矩形的真实右下角。QRect
的right()
函数返回left()+ width() -1
,bottom()
函数返回top()+ height() -1
。我们建议您改用QRectF
:QRectF
类使用浮点坐标定义平面中的矩形以提高准确性(QRect
使用整数坐标),而right()
和bottom()
函数确实返回真正的右下角。或者,使用QRect
,应用x()+ width()
和y()+ height()
来找到右下角,并避免使用right()
和bottom()
函数。
4.4 Anti-aliased Painting
如果您设置QPainter的抗锯齿渲染提示(anti-aliasing render hint),则像素将在数学定义的点的两侧对称地渲染:
4.5 Transformations
默认情况下,QPainter
在关联设备自己的坐标系上运行,但是它也完全支持仿射坐标变换。您可以使用 scale()
函数按给定的偏移量缩放坐标系,可以使用 rotate()
函数顺时针旋转坐标系,并可以使用 translate()
函数平移坐标系(即将给定偏移量添加到点)。
您还可以使用shear()
函数围绕原点扭曲坐标系。 所有转换操作都在QPainter
的转换矩阵上进行,您可以使用worldTransform()
函数进行检索。矩阵变换将平面中的一个点转换为另一个点。如果需要反复进行相同的转换,则还可以使用QTransform
对象以及worldTransform()
和setWorldTransform()
函数。您随时可以调用save()
函数保存QPainter
的转换矩阵,该函数将矩阵保存在内部堆栈中。restore()
函数将其弹出。经常需要转换矩阵,这是在各种绘图设备上重复使用相同的绘图代码时。如果不进行转换,则结果将紧密绑定到绘画设备的分辨率。打印机具有高分辨率,例如 每英寸600点,而屏幕通常每英寸72至100点。
4.6 Window-Viewport Conversion
使用QPainter
进行绘制时,我们使用逻辑坐标指定点,然后将其转换为绘画设备的物理坐标。
逻辑坐标到物理坐标的映射由QPainter
的世界转换worldTransform()
以及viewport()
和window()
处理。视口表示指定任意矩形的物理坐标。“window” 在逻辑坐标中描述了相同的矩形。 默认情况下,逻辑坐标系和物理坐标系重合,并且等效于绘画设备的矩形。使用窗口视口转换,可以使逻辑坐标系适合您的首选项。该机制还可以用于使绘图代码独立于 paint device。例如,您可以通过调用setWindow()
函数使逻辑坐标从扩展到,并在中心具有:
painter = QPainter(self)
painter.setWindow(QRect(-50, -50, 100, 100))
现在,逻辑坐标对应于绘图设备的物理坐标。独立于绘制设备,绘制代码将始终在指定的逻辑坐标上运行。
通过设置“window”或视口矩形,可以对坐标进行线性变换。请注意,“window”的每个角都映射到视口的相应角,反之亦然。因此,通常最好使视口和“window”保持相同的宽高比以防止变形:
int side = qMin(width(), height())
int x = (width() - side / 2);
int y = (height() - side / 2);
painter.setViewport(x, y, side, side);
如果将逻辑坐标系设为正方形,则还应该使用setViewport()
函数将视口设为正方形。在上面的示例中,我们将其等效为适合绘画设备矩形的最大正方形。在设置窗口或视口时,通过考虑绘画设备的尺寸,可以使绘图代码独立于绘画设备。请注意,窗口-视口转换仅是线性转换,即不执行裁剪。这意味着,如果在当前设置的“window”之外进行绘制,则您的绘制仍将使用相同的线性代数方法转换为视口。
视口,"window"和转换矩阵确定逻辑QPainter
坐标如何映射到绘图设备的物理坐标。默认情况下,世界变换矩阵为单位矩阵,并且"window"和视口设置等同于绘画设备的设置,即世界,"window"和设备坐标系是等效的,但是正如我们所看到的,系统可以 使用转换操作和窗口视口转换进行操纵。上图说明了该过程。示例可参考:Analog Clock Example。
5 Reading and Writing Image Files
读取图像的最常见方式是通过QImage
和QPixmap
的构造函数,或者通过调用load()
函数。另外,Qt 提供了QImageReader类,该类提供了对该过程的更多控制。根据图像格式的基本支持,该类提供的功能可以节省内存并加快图像加载速度。
同样,Qt 提供了QImageWriter类,该类支持在存储图像之前设置格式特定的选项,例如 gamma 级别,压缩级别和质量。如果不需要此类选项,则可以改用save()
函数。
QMovie 是内部使用QImageReader
类显示动画的便捷类。创建后,QMovie
类将提供用于运行和控制给定动画的各种功能。
QImageReader
和QImageWriter
类依赖于QImageIOHandler类,该类是 Qt 中所有图像格式的通用图像 I/O 接口。QImageReader
和QImageWriter
在内部使用QImageIOHandler
对象向 Qt 添加对不同图像格式的支持。
可通过supportedImageFormats()
和supportedImageFormats()
函数获得受支持文件格式的列表。Qt 默认情况下支持多种文件格式,此外,可以将新格式添加为插件。QImageReader
和QImageWriter
类文档中列出了当前支持的格式。
Qt 的插件机制还可以用于编写自定义图像格式处理程序。这是通过派生QImageIOHandler
类并创建一个QImageIOPlugin
对象来完成的,该对象是用于创建QImageIOHandler
对象的工厂。安装插件后,QImageReader
和QImageWriter
将自动加载插件并开始使用。
-
QWidget
类是 Qt Widgets 模块中用户界面元素的基类。它从窗口系统接收鼠标,键盘和其他事件,并在屏幕上绘制其自身的表示。 ↩ -
QImage
类提供了独立于硬件的图像表示,该图像表示针对 I/O 以及直接像素访问和操作进行了设计和优化。QImage
支持多种图像格式,包括单色,8 位,32 位和 alpha 混合图像。将QImage
用作绘制设备的一个优点是可以以与平台无关的方式保证任何绘制操作的像素准确性。另一个好处是可以在当前 GUI 线程之外的其他线程中执行绘制。 ↩ -
QPixmap
类是一种屏幕外图像表示形式,其设计和优化用于在屏幕上显示图像。与QImage
不同,像素图中的像素数据是内部的,并由基础窗口系统管理,即像素只能通过QPainter
函数或将QPixmap
转换为QImage
来访问。为了使用QPixmap
优化绘图,Qt 提供了 QPixmapCache 类,该类可用于存储临时像素图,这些像素图的生成成本很高,并且不使用超出缓存限制的存储空间。Qt 还提供了 QBitmap 便利类,继承了QPixmap
。QBitmap
保证单色(1位深度)像素图,主要用于创建自定义 QCursor 和 QBrush 对象,构造 QRegion 对象。你可以使用QPixmap
的isQBitmap()
函数来确定这个QPixmap
是不是一个QBitmap
。 ↩ -
QPicture
类是一种绘画设备,可以记录和重放QPainter
命令。图片以平台无关的格式将Painter
命令序列化到 IO 设备。QPicture
也是与分辨率无关的,即QPicture
可以在看起来相同的不同设备(例如 svg,pdf,ps,打印机和屏幕)上显示。Qt 提供load()
和save()
函数,以及用于加载和保存图片的流运算符(streaming operators)。 ↩ -
OpenGL Paint Device,Qt 提供的类使在 Qt 应用程序中使用 OpenGL 变得容易。例如,
QOpenGLPaintDevice
启用 OpenGL API 以使用QPainter
进行渲染。 ↩