坐标系统
坐标系由QPainter 类控制。QPainter 与QPaintDevice 和QPaintEngine 类一起构成了 Qt 的绘制系统 Arthur 的基础。QPainter 用于执行绘制操作,QPaintDevice 是二维空间的抽象,可使用QPainter 在二维空间上绘制,而QPaintEngine 则提供了绘制者用于在不同类型的设备上绘制的接口。
QPaintDevice 类是可绘制对象的基类:它的绘制功能由QWidget,QImage,QPixmap,QPicture 和QOpenGLPaintDevice 类继承。绘画设备的默认坐标系原点位于左上角。x值向右增加,y值向下增加。基于像素的设备默认单位为一个像素,打印机默认单位为一个点(1/72 英寸)。
逻辑QPainter 坐标到物理QPaintDevice 坐标的映射由QPainter 的转换矩阵、视口和 "窗口 "处理。QPainter 还支持坐标变换(如旋转和缩放)。
渲染
逻辑表示
图形基元的大小(宽度和高度)始终与其数学模型相对应,而不考虑渲染时使用的笔的宽度:
![]() | ![]() |
QRect(QPoint(1, 2),QPoint(7, 6)) | QLine(QPoint(2, 7),QPoint(6, 1)) |
QLine(2, 7, 6, 1) | |
QRect(QPoint(1, 2),QSize(6, 4)) | |
QRect(1, 2, 6, 4) |
别名绘制
绘制时,像素渲染由QPainter::Antialiasing 渲染提示控制。
RenderHint 枚举用于向QPainter 指定标志,任何给定的引擎都可能遵守或不遵守这些标志。QPainter::Antialiasing 值表示引擎应尽可能反锯齿基元的边缘,即使用不同的颜色强度来平滑边缘。
但默认情况下,绘制者会对边缘进行反锯齿处理,并适用其他规则:当使用一像素宽的笔进行渲染时,像素将被渲染到数学定义点的右侧和下方。例如
![]() | ![]() |
使用偶数像素的画笔渲染时,像素将围绕数学定义的点对称渲染,而使用奇数像素的画笔渲染时,空余像素将在数学点的右侧和下方渲染,与单像素情况相同。具体示例请参见下面的QRectF 图。
QRectF | |
---|---|
![]() | ![]() |
逻辑表示 | 一像素宽的笔 |
![]() | ![]() |
两像素宽的笔 | 三像素宽钢笔 |
请注意,由于历史原因,QRect::right() 和QRect::bottom() 函数的返回值偏离矩形的真正右下角。
QRect right() 函数的返回值为 () + () - 1,而 () 函数的返回值为 () + () - 1。图中右下角的绿点表示这些函数的返回坐标。left width bottom top height
我们建议您使用QRectF 代替:QRectF 类使用浮点坐标(QRect 使用整数坐标)在平面上定义了一个矩形,而QRectF::right() 和QRectF::bottom() 函数确实会返回真正的右下角。
或者,使用QRect ,应用x() +width() 和y() +height() 查找右下角,避免使用right() 和bottom() 函数。
反锯齿绘制
如果设置QPainter 的anti-aliasing 渲染提示,像素将在数学定义点的两侧对称渲染:
![]() | ![]() |
变换
默认情况下,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 点之间。
模拟时钟示例 | |
---|---|
![]() | 模拟时钟示例展示了如何使用QPainter 的变换矩阵绘制自定义 widget 的内容。 我们建议您在继续阅读之前编译并运行该示例。尤其是,请尝试将窗口调整为不同大小。 |
void AnalogClock::paintEvent(QPaintEvent *) { static const QPoint hourHand[4] = { QPoint(5, 14), QPoint(-5, 14), QPoint(-4, -71), QPoint(4, -71) }; static const QPoint minuteHand[4] = { QPoint(4, 14), QPoint(-4, 14), QPoint(-3, -89), QPoint(3, -89) }; static const QPoint secondsHand[4] = { QPoint(1, 14), QPoint(-1, 14), QPoint(-1, -89), QPoint(1, -89) }; const QColor hourColor(palette().color(QPalette::Text)); const QColor minuteColor(palette().color(QPalette::Text)); const QColor secondsColor(palette().color(QPalette::Accent)); int side = qMin(width(), height()); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.translate(width() / 2, height() / 2); painter.scale(side / 200.0, side / 200.0); 我们平移坐标系,使点 (0, 0) 位于 widget 的中心,而不是左上角。我们还将坐标系缩放为 这将为我们提供一个 200 x 200 的正方形区域,原点(0,0)位于中央,我们可以在上面绘图。我们绘制的内容将显示在适合窗口小部件的最大正方形中。 另请参阅 "窗口-视口转换"部分。 { QPainterStateGuard guard(&painter); painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0))); painter.drawConvexPolygon(hourHand, 4); } 我们通过旋转坐标系并调用QPainter::drawConvexPolygon() 来绘制时钟的时针。多亏了旋转,时针才会指向正确的方向。 多边形被指定为一个交替x、y值数组,存储在 代码周围对QPainter::save() 和QPainter::restore() 的调用保证了后面的代码不会受到我们所使用的转换的干扰。 painter.setBrush(minuteColor); { QPainterStateGuard guard(&painter); painter.rotate(6.0 * time.minute()); painter.drawConvexPolygon(minuteHand, 4); } 之后,我们为钟面绘制时标,时标由 12 条间隔为 30 度的短线组成。循环结束后,绘画者已经旋转了一圈,回到了原始状态,因此我们不需要保存和恢复状态。 { QPainterStateGuard guard(&painter); painter.rotate(6.0 * time.second()); painter.drawConvexPolygon(secondsHand, 4); painter.drawEllipse(-3, -3, 6, 6); painter.drawEllipse(-5, -68, 10, 10); } 时钟的分针也是如此,它由三个点(7,8)、(-7,8)和(0,-70)定义。这些坐标指定了比分针更细更长的指针。 for (int j = 0; j < 60; ++j) { painter.drawLine(92, 0, 96, 0); painter.rotate(6.0); } 最后,我们为钟面绘制分钟刻度线,它由 60 条间隔为 6 度的短线组成。我们跳过每五个分钟标记,因为我们不想在小时标记上画线。最后,绘制者将以一种不太有用的方式进行旋转,但我们已经完成了绘制,所以这并不重要。 |
有关变换矩阵的更多信息,请参阅QTransform 文档。
窗口-视口转换
使用QPainter 绘图时,我们使用逻辑坐标指定点,然后将其转换为绘图设备的物理坐标。
逻辑坐标到物理坐标的映射由QPainter 的世界变换worldTransform() (在 "变换"部分进行了描述)以及QPainter 的viewport() 和window() 处理。视口 "表示指定任意矩形的物理坐标。窗口 "用逻辑坐标描述同一个矩形。默认情况下,逻辑坐标系和物理坐标系重合,并等同于绘制设备的矩形。
通过窗口-视口转换,您可以使逻辑坐标系符合您的偏好。该机制还可用于使绘图代码独立于绘图设备。例如,您可以通过调用QPainter::setWindow() 函数,使逻辑坐标从 (-50, -50) 扩展到 (50, 50),并以 (0, 0) 为中心:
现在,逻辑坐标 (-50,-50) 与喷涂设备的物理坐标 (0, 0) 相对应。与绘画设备无关,您的绘画代码将始终在指定的逻辑坐标上运行。
通过设置 "窗口 "或视口矩形,可以对坐标进行线性变换。请注意,"窗口 "的每个角都映射到视口的相应角,反之亦然。因此,通常最好让视口和 "窗口 "保持相同的长宽比,以防止变形:
int side = qMin(width(), height()); int x = (width() - side / 2); int y = (height() - side / 2); painter.setViewport(x, y, side, side);
如果我们将逻辑坐标系设置为正方形,那么也应该使用QPainter::setViewport() 函数将视口设置为正方形。在上面的示例中,我们将视口设置为适合绘画设备矩形的最大正方形。通过在设置窗口或视口时考虑绘画设备的尺寸,可以使绘图代码与绘画设备无关。
请注意,窗口-视口转换只是一种线性变换,即不执行剪切。这意味着,如果您在当前设置的 "窗口 "外进行绘画,您的绘画仍会使用相同的线性代数方法转换到视口。
视口、"窗口 "和变换矩阵决定了逻辑QPainter 坐标如何映射到绘画设备的物理坐标。默认情况下,世界变换矩阵为身份矩阵,"窗口 "和视口设置等同于绘画设备的设置,即世界、"窗口 "和设备坐标系是等同的,但正如我们所看到的,这些坐标系可以通过变换操作和窗口-视口转换进行操作。上图描述了这一过程。
另请参阅 模拟时钟。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.