Qt Paint System 概述
- 一、概述
- 二、绘图设备和后端
- 1. Widget
- 2. Image
- 3. Pixmap
- 4. OpenGL绘制设备
- 5. Picture
- 6. 自定义绘制后端
- 三、绘图与填充
- 1. Drawing
- 2. 填充 Filling
- 四、坐标系统
- 1. 渲染
- Window-Viewport转换
- 五、读写图像文件
- 1. QMovie
- 六、绘图相关设备
一、概述
Qt的paint系统可以使用相同的API在屏幕和打印设备上进行绘图,它主要是基于QPainter、QPaintDevice和QPaintEnengine类。
QPainter用于执行绘制操作,QPaintDevice是一个二维空间的抽象,可以使用QPainter在其上进行绘制,QPaintEngine提供了 QPainter 用于在不同类型设备上绘制的界面。QPaintEngine 类由 QPainter 和 QPaintDevice 在内部被使用,并且对我们应用开发程序员隐藏,除非我们创建自己的绘图设备类型。
这种方法的主要好处是,所有绘制都遵循相同的绘制通道,这使得添加新功能的支持变得容易,并为不支持的功能提供默认实现。换句话说就是扩展性很强的。
二、绘图设备和后端
QPaintDevice 类是可绘制对象的基类,即 QPainter 可以在任何 QPaintDevice 的子类上绘制。QPaintDevice 的绘图功能由 QWidget、QImage、QPixmap、QPicture、QPrinter 和 QOpenGLPaintDevice 实现。
下面提到的这些类都是继承了 QPaintDevice 因此就具备了 绘制的功能
1. Widget
QWidget类是Qt Widgets模块中用户界面元素的基类。它从window系统接收鼠标、键盘和其他事件,并在屏幕上显示自己。就是窗体系统中的重要一环
2. Image
QImage类提供了一个独立于硬件的图像表示,为I/O和直接像素访问和操作进行了设计和优化。QImage支持多种图像格式,包括单色、8位、32位和alpha混合图像。
使用QImage作为绘制设备的两个优点:
- 可以以一种平台无关的方式保证任何绘制操作的像素准确性。
- 绘图可以在当前GUI线程之外的另一个线程中执行。
3. Pixmap
QPixmap类是为在屏幕上显示图像而设计和优化的屏幕外图像表示。与 QImage 不同,pixmap中的像素数据是内部的,由底层窗口系统管理,即像素只能通过 QPainter 函数或将 QPixmap 转换为 QImage 来访问。
为了优化使用QPixmap绘图,Qt提供了QPixmapCache类,该类可用于存储临时的pixmap,这些临时的pixmap生成成本很高,而不会使用超过缓存限制的存储空间。
Qt还提供了QBitmap便利类,它继承了QPixmap。QBitmap保证单色(1位深度)像素图,主要用于创建自定义QCursor和QBrush对象,构造QRegion对象。
4. OpenGL绘制设备
如前所述,Qt提供了一些类,使得在Qt应用程序中使用OpenGL变得容易。例如,QOpenGLPaintDevice启用了用于QPainter渲染的OpenGL API。
5. Picture
QPicture类是一个记录和回放QPainter命令的绘制设备。图片以独立于平台的格式将 painter 命令序列化到IO设备。QPicture也是分辨率独立的,即一个 QPicture 可以在不同的设备(例如svg, pdf, ps,打印机和屏幕)上显示,看起来相同。
Qt提供了 QPicture::load() 和 QPicture::save() 函数以及用于加载和保存图片的流操作符。
6. 自定义绘制后端
对新的后端的支持可以通过从QPaintDevice类派生并重新实现虚拟的 QPaintDevice::paintEngine() 函数来告诉 QPainter 应该使用哪个绘制引擎在这个特定的设备上绘制。
要真正能够在设备上绘制,这个绘制引擎必须是通过派生QPaintDevice 类创建的自定义绘制引擎。
三、绘图与填充
1. Drawing
QPainter提供了高度优化的函数来完成大多数GUI程序所需的绘图。它可以绘制任何东西,从简单的图形基元(由QPoint、QLine、QRect、QRegion和QPolygon类表示)到复杂的形状,如矢量路径。在Qt矢量路径由QPainterPath类表示。QPainterPath为绘制操作提供了一个容器,使图形形状能够被构建和重用。
1. QPainterPath
QPainterPath 是由直线和曲线组成的对象。例如,矩形由直线组成,椭圆由曲线组成。
与普通的绘制操作相比,painter paths的主要优点是复杂的形状只需要创建一次;然后只需调用QPainter::drawPath()函数就可以多次绘制它们。
QPainterPath对象可用于填充、轮廓和裁剪。要为给定的painter路径生成可填充的轮廓,使用QPainterPathStroker类。
线和轮廓使用QPen类绘制。一支笔是由它的样式(即它的线条类型)、宽度、笔刷、端点的绘制方式(cap-style)以及两条连接的线之间的连接方式(join-style)来定义的。钢笔的笔刷是一个QBrush对象,用于填充钢笔生成的笔触,即QBrush类定义了填充模式。
QPainter还可以绘制对齐的文本和像素地图。
绘制文本时,字体使用 QFont 类指定。Qt 将使用指定属性的字体,如果不存在匹配的字体,Qt将使用安装的最接近的字体。实际使用的字体属性可以使用 QFontInfo 类取得。此外,QFontMetrics 类提供字体测量,QFontDatabase 类提供有关底层窗口系统中可用字体的信息。
通常,QPainter在一个“自然”坐标系中绘制,但它能够使用 QTransform 类执行 视图 和 世界 坐标系之间的转换。有关更多信息,请参见坐标系统,它也描述了渲染过程,即逻辑表示和渲染像素之间的关系,以及反锯齿绘制的好处。
2. 反锯齿的绘图
绘制时,像素渲染由 QPainter::Antialiasing 渲染提示控制。枚举 QPainter::RenderHint 用于指定 QPainter 的标志,这些标志可能被任何给定的引擎使用,也可能不被任何给定的引擎使用。
QPainter::Antialiasing 值表示如果可能的话,引擎应该消除图元的边缘,即通过使用不同的颜色强度平滑边缘。
2. 填充 Filling
使用QBrush类填充形状。画笔是由它的颜色和样式(即它的填充模式)定义的。
Qt中的任何颜色都由QColor类表示,该类支持RGB、HSV和CMYK颜色模型。QColor还支持alpha混合轮廓和填充(指定透明效果),并且该类与平台和设备无关(使用QColormap类将颜色映射到硬件)。
可用的填充图案由Qt::BrushStyle枚举描述。这些包括基本模式,从统一的颜色到非常稀疏的模式,各种线条组合,梯度填充和纹理。Qt提供了QGradient类来定义自定义的渐变填充,而使用QPixmap类来指定纹理模式。
1. QGradient
QGradient类与QBrush类结合使用来指定梯度填充。
Qt 目前支持三种类型的渐变填充:线性渐变在起点和终点之间插入颜色,径向渐变在焦点和圆周上的终点之间插入颜色,圆锥渐变在中心点周围插入颜色。
四、坐标系统
坐标系统由QPainter类控制。与QPaintDevice和QPaintEngine类一起,QPainter构成了Qt绘画系统的基础。QPainter用于执行绘制操作,QPaintDevice是一个二维空间的抽象,可以使用QPainter在其上进行绘制,QPaintEngine提供了 QPainter 用于在不同类型设备上绘制的界面。
QPaintDevice 类是可绘制对象的基类:它的绘制功能由QWidget、QImage、QPixmap、QPicture和QOpenGLPaintDevice类继承。绘制设备的默认坐标系统起源于左上角。x值向右增大,y值向下增大。在基于像素的设备上,默认单位是一个像素,在打印机上是一个点(1/72英寸)。
逻辑QPainter坐标到物理QPaintDevice坐标的映射是由QPainter的转换矩阵、视口和 “窗口” 处理的。逻辑坐标系统和物理坐标系统默认重合。QPainter还支持坐标变换(例如旋转和缩放)。
1. 渲染
1. 逻辑表示
图形基元的大小(宽度和高度)总是对应于它的数学模型,而忽略渲染时使用的笔的宽度:
2. 非反锯齿绘画
绘制时,像素渲染由QPainter::Antialiasing渲染参数控制。
RenderHint枚举用于指定QPainter的标志,任何给定的引擎都可能使用这些标志,也可能不使用。QPainter::Antialiasing值表示如果可能的话,引擎应该消除图元的边缘,即通过使用不同的颜色强度平滑边缘。
但默认情况下,painter是带锯齿渲染的的,并且适用其他规则:当使用单像素宽的笔渲染时,像素将被渲染到数学定义的点的右侧和下方。例如:
当使用偶数像素的笔进行渲染时,像素将围绕数学定义的点进行对称渲染,而使用奇数像素的笔进行渲染时,空闲像素将被渲染到数学点的右侧和下方,就像 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()函数。
3. 反锯齿的绘画
如果你设置了QPainter的反锯齿渲染提示,像素将在数学定义点的两侧对称渲染:
4. 转换
默认情况下,QPainter在关联设备自己的坐标系统上操作,但它也完全支持仿射坐标变换。
我们可以使用QPainter::scale()函数按给定的偏移量缩放坐标系统,我们可以使用QPainter::rotate()函数顺时针旋转它,并且可以使用QPainter::translate()函数平移它(即向点添加给定的偏移量)。
你也可以使用QPainter::shear()函数围绕原点旋转坐标系统。所有的转换操作都是在QPainter的转换矩阵上进行的,你可以使用QPainter::worldTransform()函数来获取这个矩阵。矩阵将平面上的一个点变换为另一个点。
如果你需要反复使用相同的变换,你也可以使用 QTransform 对象以及 QPainter::worldTransform() 和 QPainter::setWorldTransform() 函数。你可以在任何时候通过调用 QPainter::save() 函数来保存 QPainter 的转换矩阵,该函数将矩阵保存在内部堆栈上。QPainter::restore() 函数弹出它。
在各种绘图设备上重用相同的绘图代码时,经常需要转换矩阵。如果没有变换,结果将与绘制设备的分辨率紧密绑定。打印机的分辨率很高,例如每英寸600点,而屏幕的分辨率通常在每英寸72到100点之间。
Analog Clock示例展示了如何使用QPainter的转换矩阵绘制自定义控件的内容。
我们建议在进一步阅读之前编译并运行此示例。特别是,尝试将窗口大小调整为不同的大小。
void AnalogClockWindow::render(QPainter *p)
{
static const QPoint hourHand[3] = {
QPoint(7, 8),
QPoint(-7, 8),
QPoint(0, -40)
};
static const QPoint minuteHand[3] = {
QPoint(7, 8),
QPoint(-7, 8),
QPoint(0, -70)
};
QColor hourColor(127, 0, 127);
QColor minuteColor(0, 127, 127, 191);
p->setRenderHint(QPainter::Antialiasing);
p->translate(width() / 2, height() / 2);
int side = qMin(width(), height());
p->scale(side / 200.0, side / 200.0);
我们转换坐标系统,使点(0,0)位于控件的中心,而不是位于左上角。我们还按边/ 100缩放系统,边是控件的宽度或高度,以最短的为准。我们希望时钟是方的,即使设备不是方的。
这将给我们一个200 × 200的正方形区域,原点(0,0)在中心,我们可以在上面绘图。我们绘制的内容将显示在适合控件的最大可能的正方形中。
另请参见窗口-视口转换部分。
QTime time = QTime::currentTime();
p->save();
p->rotate(30.0 * ((time.hour() + time.minute() / 60.0)));
p->drawConvexPolygon(hourHand, 3);
p->restore();
我们通过旋转坐标系并调用QPainter::drawConvexPolygon()来绘制时钟的时针。由于旋转,它被画在了正确的方向上。
多边形被指定为一个交替的x, y值数组,存储在hourHand静态变量中(在函数开始处定义),它对应于四个点(2,0),(0,2),(- 2,0)和(0,-25)。
对代码周围的 QPainter::save() 和 QPainter::restore() 的调用保证了后面的代码不会受到我们使用的转换的干扰。
p->save();
p->rotate(6.0 * (time.minute() + time.second() / 60.0));
p->drawConvexPolygon(minuteHand, 3);
p->restore();
我们对时钟的分针也是这样做的,分针由四个点 (1,0)、(0,1)、(- 1,0) 和 (0,-40) 定义。这些坐标指定了一个比分针更细更长的指针。
p->setPen(minuteColor);
for (int j = 0; j < 60; ++j) {
if ((j % 5) != 0)
p->drawLine(92, 0, 96, 0);
p->rotate(6.0);
}
最后,我们绘制时钟面,它由12条30度间隔的短线组成。
Window-Viewport转换
当使用QPainter绘图时,我们使用逻辑坐标指定点,然后将其转换为绘制设备的物理坐标。
逻辑坐标到物理坐标的映射是由QPainter的世界变换 worldTransform() (在变换部分描述)和 QPainter 的 viewport() 和 window() 处理的。视口表示指定任意矩形的物理坐标。“窗口”在逻辑坐标中描述相同的矩形。默认情况下,逻辑和物理坐标系统是重合的,相当于绘制设备的矩形。
使用窗口-视口转换,我们可以使逻辑坐标系统符合我们的偏好。所述还可用于使所述绘图代码独立于所述QPaintDevice 。例如,你可以通过调用QPainter::setwinwindow()函数,使逻辑坐标从(-50,-50)扩展到(50,50),中间是(0,0):
QPainter painter(this);
painter.setWindow(QRect(-50, -50, 100, 100));
现在,逻辑坐标(-50,-50)对应于绘制设备的物理坐标(0,0)。独立于绘制设备,我们的绘制代码将始终对指定的逻辑坐标进行操作。相当于我们可以手动的设置这种坐标关系。自己定义这种坐标的方便关系。
通过设置“窗口”或视口矩形,可以执行坐标的线性变换。请注意,“窗口”的每个角落都映射到viewport的相应角落,反之亦然。出于这个原因,让视口和“窗口”保持相同的宽高比以防止变形通常是一个好主意:
int side = qMin(width(), height())
int x = (width() - side / 2);
int y = (height() - side / 2);
painter.setViewport(x, y, side, side);
如果我们将逻辑坐标系统设置为正方形,我们也应该使用QPainter::setViewport()函数将视口设置为正方形。在上面的例子中,我们将其设置为适合绘制设备矩形的最大正方形。通过在设置窗口或视口时考虑绘制设备的大小,可以保持绘制代码独立于绘制设备。
请注意,窗口-视口转换只是一个线性转换,即它不执行裁剪。这意味着如果你在当前设置的“窗口”之外绘制,你的绘画仍然会使用相同的线性代数方法转换到视口。
视口、“窗口” 和 变换矩阵 决定了逻辑QPainter坐标如何映射到绘制设备的物理坐标。默认情况下,世界变换矩阵是单位矩阵,“窗口”和视口设置等同于绘制设备的设置,即世界、“窗口”和设备坐标系是等效的,但正如我们所看到的,系统可以使用转换操作和窗口-视口转换来操纵。
五、读写图像文件
读取图像最常见的方法是通过QImage和QPixmap的构造函数,或者调用QImage::load()和QPixmap::load()函数。此外,Qt提供了QImageReader类,它提供了对进程的更多控制。根据图像格式的底层支持,类提供的函数可以节省内存并加快图像加载速度。
同样,Qt提供了QImageWriter类,它支持在存储图像之前设置特定格式的选项,如伽马值、压缩级别和质量。如果我们不需要这些选项,我们可以使用QImage::save()或QPixmap::save()代替。
1. QMovie
QMovie是一个用于显示动画的方便类,在内部使用QImageReader类。创建后,QMovie类为运行和控制给定动画提供了各种函数。
QImageReader和QImageWriter类依赖于QImageIOHandler类,QImageIOHandler类是Qt中所有图像格式的通用图像I/O接口,QImageReader和QImageWriter内部使用QImageIOHandler对象来为Qt添加对不同图像格式的支持。
QImageReader::supportedImageFormats()和QImageWriter::supportedImageFormats()函数提供了支持的文件格式列表。Qt默认支持几种文件格式,此外还可以通过插件添加新格式。目前支持的格式在QImageReader和QImageWriter类文档中列出。
Qt的插件机制也可以用来编写自定义图像格式处理程序。这是通过从QImageIOHandler类派生,并创建一个QImageIOPlugin对象来完成的,该对象是创建QImageIOHandler对象的工厂。当插件安装后,QImageReader和QImageWriter会自动加载插件并开始使用它。
六、绘图相关设备
Paint 相关 | 功能 |
---|---|
QBitmap | 单色(1位深度)像素图 |
QBrush | 定义由QPainter绘制的形状的填充模式 |
QColor | 基于RGB、HSV或CMYK值的颜色 |
QColorSpace | 色彩空间抽象 |
QColorTransform | 颜色空间之间的转换 |
QColormap | 将独立于设备的QColors映射到依赖于设备的像素值 |
QConicalGradient | 与QBrush组合使用指定一个锥形梯度刷 |
QFont | 指定用于绘制文本的字体查询 |
QFontMetrics | 字体度量信息 |
QFontMetricsF | 字体度量信息 |
QGenericMatrix | 表示N列M行NxM变换矩阵的模板类 |
QGradient | 与QBrush组合使用来指定梯度填充 |
QIcon | 可伸缩的图标在不同的模式和状态 |
QIconEngine | QIcon渲染器的抽象基类 |
QImage | 独立于硬件的图像表示,允许直接访问像素数据,并可以用作绘制设备 |
QImageReader | 格式独立接口,用于从文件或其他设备读取图像 |
QImageWriter | 格式独立接口,用于将图像写入文件或其他设备 |
QLine | 使用整数精度的二维向量 |
QLineF | 二维向量使用浮点精度 |
QLinearGradient | 与QBrush组合使用来指定一个线性梯度刷 |
QMargins | 定义矩形的四个边距 |
QMarginsF | 定义矩形的四个边距 |
QPagedPaintDevice | 表示支持多个页面的绘制设备 |
QPaintDevice | 可以用QPainter绘制的对象的基类 |
QPaintEngine | QPainter如何在给定平台上绘制到给定设备的抽象定义 |
QPainter | 在部件和其他绘制设备上执行低级绘制 |
QPainterPath | 用于绘制操作的容器,使图形能够被构建和重用 |
QPainterPathStroker | 用于为给定的绘制路径生成可填充的轮廓 |
QPdfWriter | 类以生成可用作绘图设备的pdf |
QPen | 定义一个QPainter应该如何绘制线条和形状的轮廓 |
QPixmap | 可以用作绘画设备的屏幕外图像表示 |
QPoint | 使用整数精度定义平面中的一个点 |
QPointF | 在平面中使用浮点精度定义一个点 |
QPolygon | 使用整数精度的点向量 |
QPolygonF | 使用浮点精度的点向量 |
QRadialGradient | 使用组合与QBrush指定一个径向梯度刷 |
QRect | 使用整数精度在平面内定义一个矩形 |
QRectF | 在平面中使用浮点精度定义一个矩形 |
QRegion | 指定绘制器的剪辑区域 |
QRgba64 | Struct包含64位RGB颜色 |
QSize | 使用整数点精度定义二维对象的大小 |
QSizeF | 使用浮点精度定义二维对象的大小 |
QStylePainter | 用于在窗口组件中绘制QStyle元素的便利类 |
QSupportedWritingSystems | 在使用内部Qt fontdatabase注册字体时使用 |
QSvgGenerator | 用于创建SVG绘图的绘图设备 |
QSvgRenderer | 用于将SVG文件的内容绘制到绘图设备上 |
QSvgWidget | 用于显示可伸缩矢量图形(SVG)文件内容的控件 |
QTransform | 指定坐标系统的2D变换 |
QVector2D | 表示二维空间中的向量或顶点 |