警告
本节包含自动将 C++ 代码片段翻译成 Python 语言的片段,可能存在错误。
坐标系统#
有关绘图系统所使用的坐标系统信息。
坐标系统由QPainter
类控制。与QPaintDevice
和QPaintEngine
类一起,QPainter
构成了 Qt 绘图系统的基本,Arthur。QPainter
用于执行绘图操作,QPaintDevice
是一个可以使用QPainter
进行绘制的二维空间抽象,而QPaintEngine
则提供了画家用于在不同类型设备上绘制的接口。
QPaintDevice
类是可以进行绘制的对象的基础类:其绘图功能由QWidget
,QImage
,QPixmap
,和 QPicture
,以及QOpenGLPaintDevice
类继承。绘图设备的默认坐标系统以其左上角为原点。在基于像素的设备上,默认单元为单像素;在打印机上,默认单位为点(1 英寸的 1/72)。
PySide6.QtGui.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)
抗锯齿绘图
在绘图时,像素渲染受抗锯齿渲染提示的控制。
RenderHint
枚举用于指定可能或可能不被任何给定引擎尊重的标志位给QPainter。Antialiasing
值表示如果可能,引擎应抗锯齿原语边缘,即通过使用不同的颜色强度来平滑边缘。
但是,默认情况下画家是模糊的,并应用其他规则:当使用宽度为单像素的笔绘制时,像素将被渲染到数学定义点的右侧和下方。例如
painter = QPainter(self) painter.setPen(Qt.darkGreen) # Using the (x y w h) overload painter.drawRect(1, 2, 6, 4) painter = QPainter(self) painter.setPen(Qt.darkGreen) painter.drawLine(2, 7, 6, 1)
当使用具有偶数像素宽度的笔绘制时,像素将围绕数学定义点对称渲染,而当使用具有奇数像素宽度的笔绘制时,额外的像素将被渲染到数学点的右侧和下方,就像单像素的情况一样。下面是QRectF图的具体示例。
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的anti-aliasing
绘制提示,则像素将在数学定义点的两侧以对称方式渲染。
painter = QPainter(self) painter.setRenderHint( QPainter.Antialiasing) painter.setPen(Qt.darkGreen) # Using the (x y w h) overload painter.drawRect(1, 2, 6, 4) painter = QPainter(self) painter.setRenderHint( QPainter.Antialiasing) painter.setPen(Qt.darkGreen) painter.drawLine(2, 7, 6, 1)
转换
默认情况下,QPainter
在关联设备的坐标系中操作,但它还完全支持仿射坐标变换。
您可以使用 scale()
函数通过对坐标系乘以给定偏移量来进行缩放,使用 rotate()
函数进行顺时针旋转,以及使用 translate()
函数进行平移(即在点上加给定偏移量)。
您还可以使用 shear()
函数将坐标系围绕原点旋转。所有变换操作都作用于 QPainter
的转换矩阵,您可以使用 worldTransform()
函数来检索它们。矩阵将平面上的一个点转换到另一个点。
如果您需要重复使用相同的变换,也可以使用 QTransform
对象和 worldTransform()
以及 setWorldTransform()
函数。您可以在任何时候通过调用 save()
函数来保存 QPainter
的转换矩阵,该函数在内部堆栈上保存矩阵。使用 restore()
函数将其弹出。
变换矩阵的一个常见用途是在各种绘画设备上重用相同的绘图代码。没有变换,结果会与绘画设备的分辨率紧密相关。打印机具有高分辨率,例如每英寸600个点,而屏幕通常在72到100个点之间。
模拟时钟示例
模拟时钟示例展示了如何使用
QPainter
的转换矩阵绘制自定义小部件的内容。我们建议在继续阅读之前编译并运行此示例。特别是,尝试调整窗口为不同的大小。
def paintEvent(self, arg__0): QPoint hourHand[4] = { QPoint(5, 14), QPoint(-5, 14), QPoint(-4, -71), QPoint(4, -71) QPoint minuteHand[4] = { QPoint(4, 14), QPoint(-4, 14), QPoint(-3, -89), QPoint(3, -89) QPoint secondsHand[4] = { QPoint(1, 14), QPoint(-1, 14), QPoint(-1, -89), QPoint(1, -89) hourColor = QColor(palette().color(QPalette.Text)) minuteColor = QColor(palette().color(QPalette.Text)) secondsColor = QColor(palette().color(QPalette.Accent)) side = qMin(width(), height()) painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) painter.translate(width() / 2, height() / 2) painter.scale(side / 200.0, side / 200.0)我们将坐标系转换为点(0, 0)在部件中心,而不是在左上角。我们还通过
side
/ 200 的比例来缩放系统,其中side
是部件的宽度或高度中较短的边。我们希望时钟是正方形的,即使设备不是也是如此。这将给我们一个 200 x 200 的正方形区域,其中原点 (0, 0) 在中心,我们可以在这个区域内进行绘图。我们在部件内绘制的将显示在可容纳部件中最大的正方形内。
请参阅“窗口视口转换”部分。
painter.save() painter.rotate(30.0 * ((time.hour() + time.minute() / 60.0))) painter.drawConvexPolygon(hourHand, 4) painter.restore()我们通过旋转坐标系并调用
drawConvexPolygon()
来绘制时钟的时针。多亏了这个旋转,它指向正确的方向。多边形通过一个交替的 x, y 值数组和存储在
hourHand
静态变量(在函数开头定义)中来指定,这些值对应于三个点 (7, 8), (-7, 8), (0, -40)。围绕代码的
save()
和restore()
调用确保后续代码不会被我们使用的转换打扰。painter.setBrush(minuteColor) painter.save() painter.rotate(6.0 * time.minute()) painter.drawConvexPolygon(minuteHand, 4) painter.restore()之后,我们绘制时钟面的时针标志,由十二条30度间隔的短线条组成。当该循环完成后,绘图器已完整旋转回到其原始状态,因此我们无需保存和恢复状态。
painter.save() painter.rotate(6.0 * time.second()) painter.drawConvexPolygon(secondsHand, 4) painter.drawEllipse(-3, -3, 6, 6) painter.drawEllipse(-5, -68, 10, 10) painter.restore()对于时钟的时针,我们可以使用同样的方法,它由三个点 (7, 8), (-7, 8), (0, -70) 定义。这些坐标指定了一个比分钟手更细更长的时针。
for j in range(0, 60): painter.drawLine(92, 0, 96, 0) painter.rotate(6.0)最后,我们绘制时钟面的分钟标记,由60度间隔的短线条组成。我们跳过每个第五个分钟标记,因为我们不希望覆盖小时标记。这样,绘图器以一种不是非常有用的方式旋转,但我们已经完成了绘图,所以这并不重要。
有关变换矩阵的更多信息,请参阅QTransform
文档。
窗口-视口转换casting to this heading
使用 QPainter
绘图时,我们使用逻辑坐标来指定点,然后将其转换为绘图设备的物理坐标。
逻辑坐标到物理坐标的映射由 QPainter
的世界变换 worldTransform()
(在 坐标系 部分描述)以及 QPainter
的 viewport()
和 window()
实现。视口表示指定任意矩形的物理坐标。"窗口"描述的是相同矩形的逻辑坐标。默认情况下,逻辑坐标系统和物理坐标系统是重合的,并且等同于画设备的矩形。
使用窗口-视口转换,您可以使逻辑坐标系统适应您的偏好。此机制还可以用于使绘图代码与画设备无关。例如,您可以通过调用 setWindow()
函数使逻辑坐标从 (-50, -50) 到 (50, 50),原点在中心。
painter = QPainter(self) painter.setWindow(QRect(-50, -50, 100, 100))
现在,逻辑坐标 (-50, -50) 对应于画设备的物理坐标 (0, 0)。与画设备无关,您的绘图代码将始终在指定的逻辑坐标上操作。
通过设置“窗口”或视口矩形,您执行坐标的线性变换。请注意,“窗口”的每个角都映射到视口的相应角,反之亦然。因此,通常让视口和“窗口”保持相同的宽高比是一个好主意,以防止变形。
side = qMin(width(), height()) x = (width() - side / 2) y = (height() - side / 2) painter.setViewport(x, y, side, side)
如果我们使逻辑坐标系统成为一个正方形,我们也应该使用 setViewport()
函数将视口也变成一个正方形。在上面的例子中,我们使其成为适合画设备矩形的最大正方形。在设置窗口或视口时考虑画设备的大小,可以使绘图代码独立于画设备。
请注意,窗口-视口转换仅是线性变换,即它不会执行剪裁。这意味着,如果您在当前设置的“窗口”之外绘制,您的绘图仍将使用相同的线性代数方法转换为视口。
视口、“窗口”和变换矩阵决定了逻辑 QPainter
坐标如何映射到画设备的物理坐标。默认情况下,世界变换矩阵是单位矩阵,“窗口”和视口设置与画设备设置等效,即世界、“窗口”和设备坐标系是等效的,但如我们所见,可以通过变换操作和窗口-视口转换来操作这些系统。上面的插图描述了此过程。
另请参阅
Analog Clock