为了账号安全,请及时绑定邮箱和手机立即绑定

PyQt5 窗口绘图类控件

标签:
Python

15 窗口绘图类控件

PyQt5 绘图系统能渲染矢量图像、位图图像和轮廓字体文本。当我们想要更改或增强现有小部件,或者我们从头开始创建自定义小部件时,应用程序需要绘制。要进行绘图,我们使用 PyQt5 工具包提供的绘画 API。

在 PyQt5 中,一般可以通过 QPainter、QPen 和 QBrush 这三个类来实现绘图功能。此外,QPixmap 的作用是加载并呈现本地图像,而图像的呈现本质上也是通过绘图方式实现的,所以 QPixmap 也可以被视为绘图的一个类。

15.1 QPainter

QPainter 类在 QWidget(控件)上执行绘图操作,它是一个绘制工具,为大部分图形界面提供了高度优化的函数,使用 QPainter 类可以绘制从简单的直线到复杂的饼图等。它也可以画文本和图像。QPainter 可以在任何继承自 QPaintDevice 的对象上进行绘制。QpaintDevice 子类有QImage、QOpenGLPaintDevice、QWidget 等。所以, QPainter 可以在QImage、QOpenGLPaintDevice、QWidget 上进行绘制图形。

Painter的核心功能是绘图,但该类还提供了几个功能,允许您自定义QPainter的设置及其渲染质量,以及其他启用剪切的功能。此外,您可以通过指定painter的合成模式来控制不同形状的合并方式。

绘制操作在 QWidget.paintEvent() 中完成。绘制方法必须放在 QtGui.QPainter 对象的 begin()end() 之间。isActive() 函数指示painter 是否处于活动状态。painter 由 begin() 函数和带有 QPaintDevice 参数的构造函数激活。end()函数和析构函数将其停用。

QPainter 类在控件或其他绘图设备上执行较低级别的图形绘制功能,并通过如下所示的方法进行绘制。

方法 描述
begin() 开始在目标设备上绘制
drawArc() 在起始角度和最终角度之间画弧
drawEllipse() 在一个矩形内画一个椭圆
drawLine(int x1,int y1,int x2,int y2) 绘制一条指定了端点坐标的线
drawPixmap() 从图像文件中提取Pixmap并将其显示在指定的位置
drawPolygon() 使用坐标数组绘制多边形
drawRect(int x,int y,int w,int h) 以给定的高度和宽度从左上角坐标(x,y)绘制一个矩形
drawText() 显示给定坐标处的文字
fillRect() 使用QColor参数填充矩形
setBrush() 设置画笔风格
setPen() 设置用于绘制的笔的颜色、大小和样式

还可以设置画笔风格(PenStyle),这是一个枚举类,可以由 QPainter 类绘制。画笔风格如表:

枚举类型 描述
Qt.NoPen 没有线
Qt.SolidLine 一条简单的线
Qt.DashLine 由一些像素分隔的短线
Qt.DotLine 由一些像素分隔的点
Qt.DashDotLine 轮流交替的点和短线
Qt.DashDotDotLine 一条短线,两个点
Qt.MPenStyle 画笔风格的掩码

一般的使用方法是:

def paintEvent(self, paintEvent):
    painter = QPainter(self)
    painter.setPen(Qt.blue)
    painter.setFont(QFont("Arial", 30))
    painter.drawText(rect(), Qt.AlignCenter, "Qt")

Painter 与 QPaintDevice 和 QPaintEngine 类一起构成了 Qt 绘画系统的基础。QPainter 是用于执行绘图操作的类。QPaintDevice 表示可以使用 QPainter 绘制的设备。 QPaintEngine 提供 painter 用于绘制到不同类型设备上的界面。如果 painter 处于活动状态,则 device() 返回 painter 绘制的绘图设备,paintEngine() 返回 painter 当前正在操作的绘制引擎。

有时需要让别人在不寻常的 QPaintDevice 上绘画。 QPainter 支持静态函数来执行此操作,setRedirected()。

警告:当 paintdevice 是一个小部件时,QPainter 只能在 paintEvent() 函数内或 paintEvent() 调用的函数中使用。

您可以根据自己的喜好自定义几种设置以进行 QPainter 绘制:

  • font() 是用于绘制文本的字体。如果 painter isActive(),您可以分别使用 fontInfo()fontMetrics() 函数检索有关当前设置字体及其度量的信息。
  • brush() 定义用于填充形状的颜色或图案。
  • pen() 定义用于绘制线条或边界的颜色或点画。
  • backgroundMode() 定义是否有 background(),即它是 Qt.OpaqueModeQt.TransparentMode 的实例。
    background() 仅在 backgroundMode()Qt.OpaqueModepen() 为点画时适用。在这种情况下,它描述了点画中背景像素的颜色。
  • brushOrigin() 定义平铺画笔的原点,通常是小部件背景的原点。
  • viewport()window()worldTransform() 组成 painter 的坐标转换系统。
  • hasClipping()告诉 painter 是否剪切。
  • layoutDirection() 定义绘制文本时 painter 使用的布局方向。
  • worldMatrixEnabled()指示是否启用了世界转换。
  • viewTransformEnabled()指示是否启用了视图转换。

请注意,其中一些设置会镜像某些绘图设备中的设置,例如: QWidget.font()QPainter.begin() 函数(或等效的 QPainter 构造函数)从 paint 设备复制这些属性。

您可以通过调用 save() 函数随时保存 QPainter 的状态,该函数将所有可用设置保存在内部堆栈中。restore() 函数会弹回它们,即恢复原先的状态。

QPainter 提供绘制大多数基元的函数:drawPoint()drawPoints()drawLine()drawRect()drawRoundedRect()drawEllipse()drawArc()drawPie()drawChord()drawPolyline()drawPolygon()drawConvexPolygon()drawCubicBezier()。两个便捷函数 drawRects()drawLines() 使用当前的笔和画笔在给定的 QRects 或 QLines 数组中绘制给定数量的矩形或线。

QPainter 类还提供 fillRect() 函数,该函数使用给定的 QBrush 填充给定的 QRect,以及擦除给定矩形内的区域的 eraseRect() 函数。

15.1.1 文本涂鸦

import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QPainter, QColor, QFont
from PyQt5.QtCore import Qt


class Drawing(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
		self.setWindowTitle("在窗体中绘画出文字例子")
		self.resize(300, 200)
		self.text = '欢迎学习 PyQt5'

	def paintEvent(self, event):
		painter = QPainter()
		painter.begin(self)
		# 自定义的绘画方法
        self.drawText(event, painter)
		painter.end()

	def drawText(self, event, qp):
		# 设置笔的颜色
		qp.setPen(QColor(168, 34, 3))
		# 设置字体
		qp.setFont(QFont('SimSun', 20))
		# 画出文本
        qp.drawText(event.rect(), Qt.AlignCenter, self.text)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    demo = Drawing()
    demo.show()
    sys.exit(app.exec_())

效果:

15.1.2 点的绘画

import sys, math
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QPainter
from PyQt5.QtCore import Qt

class Drawing(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.resize(300, 200)
        self.setWindowTitle("在窗体中画点")

    def paintEvent(self, event):
        qp = QPainter()
        qp.begin(self)
        # 自定义画点方法
        self.drawPoints(qp)
        qp.end()

    def drawPoints(self, qp):
        qp.setPen(Qt.red)
        size = self.size() # 得到窗口的当前大小

        for i in range(1000):
            # [-100, 100] 两个周期的正弦函数图像
            x = 100 * (-1+2.0*i/1000) + size.width()/2.0
            y = -50 * math.sin((x - size.width()/2.0)*math.pi/50) + size.height()/2.0
            qp.drawPoint(x, y)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Drawing()
    demo.show()
    sys.exit(app.exec_())

效果:

15.1.3 在 QLabel 上画图

前面都是在 QWidget 上绘制图形的,也可以在 QLabel 上绘制:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.label = QtWidgets.QLabel()
        canvas = QtGui.QPixmap(400, 300)
        self.label.setPixmap(canvas)
        self.setCentralWidget(self.label)
        self.draw_something()

    def draw_something(self):
        painter = QtGui.QPainter(self.label.pixmap())
        painter.setPen(Qt.red)
        painter.drawLine(10, 10, 300, 200)
        painter.end()


app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

效果:

Method Description
drawLine(line) Draw a QLine instance
drawLine(line) Draw a QLineF instance
drawLine(x1, y1, x2, y2) Draw a line between x1, y2 and x2, y2 (int)
drawLine(p1, p2) Draw a line between p1 and p2 (both QPoint)
drawLine(p1, p2) Draw a line between p1 and p2 (both QPointF)

15.2 QPen

QPen(钢笔)是一个基本的图形对象,用于绘制直线、曲线或者给轮廓画出矩形、椭圆形、多边形及其他形状等。

15.2.1 画不同样式的线段

import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtCore import Qt


class Drawing(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setGeometry(500, 500, 280, 270)
        self.setWindowTitle('钢笔样式例子')

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.drawLines(qp)
        qp.end()

    def drawLines(self, qp):
        # 为了能更清晰地看清各线之间的差异,将颜色设置成黑色,宽度设置为2像素(px)
        pen = QPen(Qt.black, 2, Qt.SolidLine)  # Qt.SolidLine是预定义的线条样式之一
        qp.setPen(pen)
        qp.drawLine(20, 40, 250, 40)

        pen.setStyle(Qt.DashLine)
        qp.setPen(pen)
        qp.drawLine(20, 80, 250, 80)

        pen.setStyle(Qt.DashDotLine)
        qp.setPen(pen)
        qp.drawLine(20, 120, 250, 120)

        pen.setStyle(Qt.DotLine)
        qp.setPen(pen)
        qp.drawLine(20, 160, 250, 160)

        pen.setStyle(Qt.DashDotDotLine)
        qp.setPen(pen)
        qp.drawLine(20, 200, 250, 200)

        pen.setStyle(Qt.CustomDashLine)  # 创建线条样式
        pen.setDashPattern([1, 4, 5, 4])
        qp.setPen(pen)
        qp.drawLine(20, 240, 250, 240)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Drawing()
    demo.show()
    sys.exit(app.exec_())

效果:

15.2.2 画不同大小的点

import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPen, QColor


class Drawing(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setGeometry(500, 500, 280, 270)
        self.setWindowTitle('钢笔样式例子')

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.drawPoints(qp)
        qp.end()

    def drawPoints(self, qp):
        # 为了能更清晰地看清各点之间的差异,将颜色设置成黑色,宽度设置为 5 像素(px)
        pen = QPen(QColor('red'), 5)
        qp.setPen(pen)
        qp.drawPoint(40, 40)

        pen.setWidth(10)
        qp.setPen(pen)
        qp.drawPoint(40, 80)

        pen.setWidth(20)
        qp.setPen(pen)
        qp.drawPoint(40, 120)

        pen.setWidth(30)
        qp.setPen(pen)
        qp.drawPoint(40, 160)

        pen.setWidth(40)
        qp.setPen(pen)
        qp.drawPoint(40, 200)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Drawing()
    demo.show()
    sys.exit(app.exec_())

效果:

15.2.3 画一条带宽度的黄色线段

import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPen, QColor
from PyQt5.QtCore import QPoint


class Drawing(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setGeometry(500, 500, 280, 270)

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.drawYellowLine(qp)
        qp.end()

    def drawYellowLine(self, qp):
        pen = QPen(QColor('yellow'), 20)
        qp.setPen(pen)
        qp.drawLine(QPoint(20, 20), QPoint(200, 200))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Drawing()
    demo.show()
    sys.exit(app.exec_())

效果:

15.2.4 画矩形

import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPen, QColor
from PyQt5.QtCore import QPoint


class Drawing(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setGeometry(500, 500, 280, 270)

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.draw_something(qp)
        qp.end()

    def draw_something(self, qp):
        pen = QPen()
        pen.setWidth(3)
        pen.setColor(QColor("#FB51E0"))
        qp.setPen(pen)
        qp.drawRect(50, 50, 100, 100)
        qp.drawRect(60, 60, 150, 100)
        qp.drawRect(70, 70, 100, 150)
        qp.drawRect(80, 80, 150, 100)
        qp.drawRect(90, 90, 100, 150)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Drawing()
    demo.show()
    sys.exit(app.exec_())

效果:

其实,上面的画矩形的代码可简写为:

from PyQt5.QtCore import QRect
def draw_something(self, qp):
        pen = QPen()
        pen.setWidth(3)
        pen.setColor(QColor("#FB51E0"))
        qp.setPen(pen)
        qp.drawRects(QtCore.QRect(50, 50, 100, 100),
                     QtCore.QRect(60, 60, 150, 100),
                     QtCore.QRect(70, 70, 100, 150),
                     QtCore.QRect(80, 80, 150, 100),
                     QtCore.QRect(90, 90, 100, 150))

15.2.5 画圆角矩形

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor
import sys


class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 350, 400)
        self.setWindowTitle('Colours')
        self.show()

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.drawRectangles(qp)
        qp.end()

    def drawRectangles(self, qp):
        col = QColor(0, 0, 0)
        col.setNamedColor('green')
        qp.setPen(col)
        qp.drawRoundedRect(40, 40, 100, 100, 10, 10)
        qp.drawRoundedRect(80, 80, 100, 100, 10, 50)
        qp.drawRoundedRect(120, 120, 100, 100, 50, 10)
        qp.drawRoundedRect(160, 160, 100, 100, 50, 50)


def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

效果:

15.2.6 画椭圆

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QPen
import sys


class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 350, 400)
        self.setWindowTitle('Colours')
        self.show()

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.draw_something(qp)
        qp.end()

    def draw_something(self, painter):
        from random import randint
        pen = QPen()
        pen.setWidth(3)
        pen.setColor(QColor(204, 0, 0))  # r, g, b
        painter.setPen(pen)

        painter.drawEllipse(10, 10, 100, 100)
        painter.drawEllipse(10, 10, 150, 200)
        painter.drawEllipse(10, 10, 200, 300)


def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

效果:

也可以使用如下方式进行绘制:

painter.drawEllipse(QPoint(100, 100), 10, 10)
painter.drawEllipse(QPoint(100, 100), 15, 20)
painter.drawEllipse(QPoint(100, 100), 20, 30)
painter.drawEllipse(QPoint(100, 100), 25, 40)
painter.drawEllipse(QPoint(100, 100), 30, 50)
painter.drawEllipse(QPoint(100, 100), 35, 60)

效果为:

15.2.7 画文本

import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QPainter, QColor, QFont, QPen
from PyQt5.QtCore import Qt


class Drawing(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("在窗体中绘画出文字例子")
        self.resize(600, 200)

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        # 自定义的绘画方法
        self.draw_something(painter)
        painter.end()

    def draw_something(self, painter):
        from random import randint

        pen = QPen()
        pen.setWidth(1)
        pen.setColor(QColor('green'))
        painter.setPen(pen)

        font = QFont()
        font.setFamily('Times')
        font.setBold(True)
        font.setPointSize(30)
        painter.setFont(font)

        painter.drawText(50, 100, 'Hello, world!')


if __name__ == "__main__":
    app = QApplication(sys.argv)
    demo = Drawing()
    demo.show()
    sys.exit(app.exec_())

效果:

15.3 QBrush

QBrush 也是图像的一个基本元素。是用来填充一些物体的背景图用的,比如矩形,椭圆,多边形等。有三种类型:预定义、渐变和纹理。

import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QBrush
from PyQt5.QtCore import Qt


class Drawing(QWidget):
	def __init__(self):
		super().__init__()
		self.initUI()

	def initUI(self):
		self.setGeometry(300, 300, 365, 280)
		self.setWindowTitle('画刷例子')
		self.show()

	def paintEvent(self, e):
		qp = QPainter()
		qp.begin(self)
		self.drawLines(qp)
		qp.end()

	def drawLines(self, qp):
		brush = QBrush(Qt.SolidPattern)
		qp.setBrush(brush)
		qp.drawRect(10, 15, 90, 60)

		brush = QBrush(Qt.Dense1Pattern)
		qp.setBrush(brush)
		qp.drawRect(130, 15, 90, 60)

		brush = QBrush(Qt.Dense2Pattern)
		qp.setBrush(brush)
		qp.drawRect(250, 15, 90, 60)

		brush = QBrush(Qt.Dense3Pattern)
		qp.setBrush(brush)
		qp.drawRect(10, 105, 90, 60)

		brush = QBrush(Qt.DiagCrossPattern)
		qp.setBrush(brush)
		qp.drawRect(10, 105, 90, 60)

		brush = QBrush(Qt.Dense5Pattern)
		qp.setBrush(brush)
		qp.drawRect(130, 105, 90, 60)

		brush = QBrush(Qt.Dense6Pattern)
		qp.setBrush(brush)
		qp.drawRect(250, 105, 90, 60)

		brush = QBrush(Qt.HorPattern)
		qp.setBrush(brush)
		qp.drawRect(10, 195, 90, 60)

		brush = QBrush(Qt.VerPattern)
		qp.setBrush(brush)
		qp.drawRect(130, 195, 90, 60)

		brush = QBrush(Qt.BDiagPattern)
		qp.setBrush(brush)
		qp.drawRect(250, 195, 90, 60)


if __name__ == '__main__':
	app = QApplication(sys.argv)
	demo = Drawing()
	demo.show()
	sys.exit(app.exec_())

效果:

15.4 颜色

颜色是一个物体显示的 RGB 的混合色。RBG 值的范围是 0~255。我们有很多方式去定义一个颜色,最常见的方式就是 RGB 和 16 进制表示法,也可以使用 RGBA,增加了一个透明度的选项,透明度值的范围是0~1,0 代表完全透明。

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QBrush
import sys


class Example(QWidget):

    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 350, 100)
        self.setWindowTitle('Colours')
        self.show()

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        self.drawRectangles(qp)
        qp.end()

    def drawRectangles(self, qp):
        col = QColor(0, 0, 0)
        col.setNamedColor('#d4d4d4')
        qp.setPen(col)

        qp.setBrush(QColor(200, 0, 0))
        qp.drawRect(10, 15, 90, 60)

        qp.setBrush(QColor(255, 80, 0, 160))
        qp.drawRect(130, 15, 90, 60)

        qp.setBrush(QColor(25, 0, 90, 200))
        qp.drawRect(250, 15, 90, 60)


def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

效果:

15.5 贝塞尔曲线

贝塞尔曲线是立方线。PyQt5 中的贝塞尔曲线可以使用 QPainterPath 创建。绘制器路径是由许多图形构建基块(如矩形、椭圆、线条和曲线)组成的对象。

import sys
from PyQt5.QtGui import QPainter, QPainterPath
from PyQt5.QtWidgets import QWidget, QApplication


class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setGeometry(300, 300, 380, 250)
        self.setWindowTitle('Bézier curve')
        self.show()

    def paintEvent(self, e):
        qp = QPainter()
        qp.begin(self)
        qp.setRenderHint(QPainter.Antialiasing)
        self.drawBezierCurve(qp)
        qp.end()

    def drawBezierCurve(self, qp):
        path = QPainterPath()
        path.moveTo(30, 30)
        path.cubicTo(30, 30, 200, 350, 350, 30)

        qp.drawPath(path)


def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

效果:

15.6 图像类

在 PyQt5 中常用的图像类有 4 个,即 QPixmap、QImage、QPicture 和 QBitmap:

  • QPixmap 是专门为绘图而设计的,在绘制图片时需要使用 QPixmap。
  • QImage 提供了一个与硬件无关的图像表示函数,可以用于图片的像素级访问。
  • QPicture 是一个绘图设备类,它继承自 QPainter 类。可以使用 QPainter 的 begin() 函数在 QPicture 上绘图,使用 end() 函数结束绘图,使用 QPicture 的 save() 函数将 QPainter 所使用过的绘图指令保存到文件中。
  • QBitmap 是一个继承自 QPixmap 的简单类,它提供了 1bit 深度的二值图像的类。QBitmap 提供的单色图像,可以用来制作游标(QCursor)或者笔刷(QBrush)。

15.6.1 QPixmap

QPixmap 类用于绘图设备的图像显示,它可以作为一个 QPaintDevice 对象,也可以加载到一个控件中,通常是标签或按钮,用于在标签或按钮上显示图像。

QPixmap 可以读取的图像文件类型有 BMP、GIF、JPG、JPEG、PNG、PBM、PGM、PPM、XBM、XPM等。

QPixmap 类中的常用方法:

import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

if __name__ == '__main__':
	app = QApplication(sys.argv)
	win = QWidget()
	lab1 = QLabel()
	lab1.setPixmap(QPixmap("./images/python.jpg"))
	vbox = QVBoxLayout()
	vbox.addWidget(lab1)
	win.setLayout(vbox)
	win.setWindowTitle("QPixmap 例子")
	win.show()
	sys.exit(app.exec_())

效果:

15.6.2 鼠标绘图

下面的代码重写了 QtWidgets.QMainWindow 的鼠标左键移动事件,令鼠标移动画出点:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.label = QtWidgets.QLabel()
        canvas = QtGui.QPixmap(800, 800)
        self.label.setPixmap(canvas)
        self.setCentralWidget(self.label)

    def mouseMoveEvent(self, e):
        painter = QtGui.QPainter(self.label.pixmap())
        painter.setPen(Qt.green)
        painter.drawPoint(e.x(), e.y())
        painter.end()
        self.update()


app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

效果:

虽然,一定程度上实现了鼠标绘图,但是,鼠标移动的速度很快的话,点之间的间隔会很大。为此,需要使用 line 代替 point 来记录鼠标移动的轨迹。利用变量 self.last_x, self.last_y 记录鼠标的最新位置,且鼠标释放释放变量:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.label = QtWidgets.QLabel()
        canvas = QtGui.QPixmap(800, 800)
        self.label.setPixmap(canvas)
        self.setCentralWidget(self.label)
        self.last_x, self.last_y = None, None

    def mouseMoveEvent(self, e):
        if self.last_x is None:  # First event.
            self.last_x = e.x()
            self.last_y = e.y()
            return  # Ignore the first time.

        painter = QtGui.QPainter(self.label.pixmap())
        painter.setPen(Qt.green)
        painter.drawLine(self.last_x, self.last_y, e.x(), e.y())
        painter.end()
        self.update()

        # Update the origin for next time.
        self.last_x = e.x()
        self.last_y = e.y()

    def mouseReleaseEvent(self, e):
        self.last_x = None
        self.last_y = None


app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

效果:

效果还是不错的。但是,画笔的颜色只有一种,为此,可以添加一个调色板(palette):

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt

COLORS = [
    # 17 undertones https://lospec.com/palette-list/17undertones
    '#000000', '#141923', '#414168', '#3a7fa7', '#35e3e3', '#8fd970', '#5ebb49',
    '#458352', '#dcd37b', '#fffee5', '#ffd035', '#cc9245', '#a15c3e', '#a42f3b',
    '#f45b7a', '#c24998', '#81588d', '#bcb0c2', '#ffffff',
]


class Canvas(QtWidgets.QLabel):

    def __init__(self):
        super().__init__()
        pixmap = QtGui.QPixmap(800, 600)
        # 设置背景
        pixmap.fill(QtGui.QColor(200, 200, 20, 50))
        self.setPixmap(pixmap)

        self.last_x, self.last_y = None, None
        self.pen_color = QtGui.QColor('#000000')

    def set_pen_color(self, c):
        self.pen_color = QtGui.QColor(c)

    def mouseMoveEvent(self, e):
        if self.last_x is None:  # First event.
            self.last_x = e.x()
            self.last_y = e.y()
            return  # Ignore the first time.

        painter = QtGui.QPainter(self.pixmap())
        p = painter.pen()
        p.setWidth(4)
        p.setColor(self.pen_color)
        painter.setPen(p)
        painter.drawLine(self.last_x, self.last_y, e.x(), e.y())
        painter.end()
        self.update()

        # Update the origin for next time.
        self.last_x = e.x()
        self.last_y = e.y()

    def mouseReleaseEvent(self, e):
        self.last_x = None
        self.last_y = None


class QPaletteButton(QtWidgets.QPushButton):

    def __init__(self, color):
        super().__init__()
        self.setFixedSize(QtCore.QSize(24, 24))
        self.color = color
        self.setStyleSheet("background-color: %s;" % color)


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        super().__init__()

        self.canvas = Canvas()

        w = QtWidgets.QWidget()
        l = QtWidgets.QVBoxLayout()
        w.setLayout(l)
        l.addWidget(self.canvas)

        palette = QtWidgets.QHBoxLayout()
        self.add_palette_buttons(palette)
        l.addLayout(palette)

        self.setCentralWidget(w)

    def add_palette_buttons(self, layout):
        for c in COLORS:
            b = QPaletteButton(c)
            b.pressed.connect(lambda c=c: self.canvas.set_pen_color(c))
            layout.addWidget(b)


app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

效果:

也可以像下面的方式实现:按下鼠标左键在白色画布上进行绘制,实现了简单的涂鸦板功能。

import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QPainter, QPixmap
from PyQt5.QtCore import Qt, QPoint


class Winform(QWidget):
	def __init__(self, parent=None):
		super().__init__(parent)
		self.setWindowTitle("绘图例子")
		self.pix = QPixmap()
		self.lastPoint = QPoint()
		self.endPoint = QPoint()
		self.initUi()

	def initUi(self):
		#窗口大小设置为600*500
		self.resize(600, 500)
		# 画布大小为400*400,背景为白色
		self.pix = QPixmap(400, 400)
		self.pix.fill(Qt.white)

	def paintEvent(self, event):
		pp = QPainter(self.pix)
		# 根据鼠标指针前后两个位置绘制直线
		pp.drawLine(self.lastPoint, self.endPoint)
		# 让前一个坐标值等于后一个坐标值,
		# 这样就能实现画出连续的线
		self.lastPoint = self.endPoint
		painter = QPainter(self)
		painter.drawPixmap(0, 0, self.pix)

	def mousePressEvent(self, event):
		# 鼠标左键按下
		if event.button() == Qt.LeftButton:
			self.lastPoint = event.pos()
			self.endPoint = self.lastPoint

	def mouseMoveEvent(self, event):
		# 鼠标左键按下的同时移动鼠标
		if event.buttons() and Qt.LeftButton:
			self.endPoint = event.pos()
			#进行重新绘制
			self.update()

	def mouseReleaseEvent(self, event):
		# 鼠标左键释放
		if event.button() == Qt.LeftButton:
			self.endPoint = event.pos()
			#进行重新绘制
			self.update()


if __name__ == "__main__":
	app = QApplication(sys.argv)
	form = Winform()
	form.show()
	sys.exit(app.exec_())

效果如下:

15.7 QPrinter

打印图像是图像处理软件中的一个常用功能。打印图像实际上是在 QPaintDevice 中画图,与平常在QWidget、QPixmap 和 QImage 中画图一样,都是创建一个 QPainter 对象进行画图的,只是打印使用的是 QPrinter,它本质上也是一个 QPaintDevice(绘图设备)。

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QImage, QIcon, QPixmap
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel,  QSizePolicy, QAction
from PyQt5.QtPrintSupport import QPrinter, QPrintDialog
import sys


class MainWindow(QMainWindow):
	def __init__(self, parent=None):
		super().__init__(parent)
		self.setWindowTitle(self.tr("打印图片"))
       # 创建一个放置图像的QLabel对象imageLabel,并将该QLabel对象设置为中心窗体。
		self.imageLabel = QLabel()
		self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
		self.setCentralWidget(self.imageLabel)

		self.image = QImage()

       # 创建菜单,工具条等部件
		self.createActions()
		self.createMenus()
		self.createToolBars()

       # 在imageLabel对象中放置图像
		if self.image.load("./images/screen.png"):
			self.imageLabel.setPixmap(QPixmap.fromImage(self.image))
			self.resize(self.image.width(), self.image.height())

	def createActions(self):
		self.PrintAction = QAction(
			QIcon("./images/printer.png"), self.tr("打印"), self)
		self.PrintAction.setShortcut("Ctrl+P")
		self.PrintAction.setStatusTip(self.tr("打印"))
		self.PrintAction.triggered.connect(self.slotPrint)

	def createMenus(self):
		PrintMenu = self.menuBar().addMenu(self.tr("打印"))
		PrintMenu.addAction(self.PrintAction)

	def createToolBars(self):
		fileToolBar = self.addToolBar("Print")
		fileToolBar.addAction(self.PrintAction)

	def slotPrint(self):
	       # 新建一个QPrinter对象
		printer = QPrinter()
       # 创建一个QPrintDialog对象,参数为QPrinter对象
		printDialog = QPrintDialog(printer, self)

		'''
       判断打印对话框显示后用户是否单击“打印”按钮,若单击“打印”按钮,
       则相关打印属性可以通过创建QPrintDialog对象时使用的QPrinter对象获得,
       若用户单击“取消”按钮,则不执行后续的打印操作。 
		'''
		if printDialog.exec_():
	           # 创建一个QPainter对象,并指定绘图设备为一个QPrinter对象。
			painter = QPainter(printer)
			# 获得QPainter对象的视口矩形
			rect = painter.viewport()
			# 获得图像的大小
			size = self.image.size()
			# 按照图形的比例大小重新设置视口矩形
			size.scale(rect.size(), Qt.KeepAspectRatio)
			painter.setViewport(rect.x(), rect.y(), size.width(), size.height())
			# 设置QPainter窗口大小为图像的大小
			painter.setWindow(self.image.rect())
			# 打印
			painter.drawImage(0, 0, self.image)


if __name__ == "__main__":
	app = QApplication(sys.argv)
	main = MainWindow()
	main.show()
	sys.exit(app.exec_())

效果:

15.8 双缓冲绘图

本节讲解在画板上绘制矩形,还会讲解双缓冲绘图的概念。

15.8.1 绘制矩形,出现重影

演示绘制矩形的功能。其完整代码如下:

import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QPainter, QPixmap
from PyQt5.QtCore import Qt, QPoint


class Winform(QWidget):
	def __init__(self, parent=None):
		super().__init__(parent)
		self.setWindowTitle("绘制矩形图形例子")
		self.pix = QPixmap()
		self.lastPoint = QPoint()
		self.endPoint = QPoint()
		self.initUi()

	def initUi(self):
		#窗口大小设置为600*500
		self.resize(600, 500)
		# 画布大小为400*400,背景为白色
		self.pix = QPixmap(400, 400)
		self.pix.fill(Qt.white)

	def paintEvent(self, event):
		painter = QPainter(self)
		x = self.lastPoint.x()
		y = self.lastPoint.y()
		w = self.endPoint.x() - x
		h = self.endPoint.y() - y

		pp = QPainter(self.pix)
		pp.drawRect(x, y, w, h)
		painter.drawPixmap(0, 0, self.pix)

	def mousePressEvent(self, event):
		# 鼠标左键按下
		if event.button() == Qt.LeftButton:
			self.lastPoint = event.pos()
			self.endPoint = self.lastPoint

	def mouseMoveEvent(self, event):
		# 鼠标左键按下的同时移动鼠标
		if event.buttons() and Qt.LeftButton:
			self.endPoint = event.pos()
			#进行重新绘制
			self.update()

	def mouseReleaseEvent(self, event):
		# 鼠标左键释放
		if event.button() == Qt.LeftButton:
			self.endPoint = event.pos()
			#进行重新绘制
			self.update()


if __name__ == "__main__":
	app = QApplication(sys.argv)
	form = Winform()
	form.show()
	sys.exit(app.exec_())

效果如下:

可以尝试分别快速和慢速拖动鼠标来绘制矩形,结果发现,拖动速度越快,重影越少。其实,在拖动鼠标的过程中,屏幕已经刷新了很多次,也可以理解为 paintEvent() 函数执行了多次,每执行一次就会绘制一个矩形。知道了原因,就可以想办法来避免出现重影了。

15.8.2 使用双缓冲技术绘制矩形,避免出现重影

演示使用双缓冲技术绘制矩形,避免出现重影。其完整代码如下:

import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QPainter, QPixmap
from PyQt5.QtCore import Qt, QPoint


class Winform(QWidget):
	def __init__(self, parent=None):
		super(Winform, self).__init__(parent)
		self.setWindowTitle("双缓冲绘图例子")
		self.pix = QPixmap()
		self.lastPoint = QPoint()
		self.endPoint = QPoint()
		# 辅助画布
		self.tempPix = QPixmap()
		# 标志是否正在绘图
		self.isDrawing = False
		self.initUi()

	def initUi(self):
		#窗口大小设置为600*500
		self.resize(600, 500)
		# 画布大小为400*400,背景为白色
		self.pix = QPixmap(400, 400)
		self.pix.fill(Qt.white)

	def paintEvent(self, event):
		painter = QPainter(self)
		x = self.lastPoint.x()
		y = self.lastPoint.y()
		w = self.endPoint.x() - x
		h = self.endPoint.y() - y

		# 如果正在绘图,就在辅助画布上绘制
		if self.isDrawing:
			# 将以前pix中的内容复制到tempPix中,保证以前的内容不消失
			self.tempPix = self.pix
			pp = QPainter(self.tempPix)
			pp.drawRect(x, y, w, h)
			painter.drawPixmap(0, 0, self.tempPix)
		else:
			pp = QPainter(self.pix)
			pp.drawRect(x, y, w, h)
			painter.drawPixmap(0, 0, self.pix)

	def mousePressEvent(self, event):
		# 鼠标左键按下
		if event.button() == Qt.LeftButton:
			self.lastPoint = event.pos()
			self.endPoint = self.lastPoint
			self.isDrawing = True

	def mouseReleaseEvent(self, event):
		# 鼠标左键释放
		if event.button() == Qt.LeftButton:
			self.endPoint = event.pos()
			#进行重新绘制
			self.update()
			self.isDrawing = False


if __name__ == "__main__":
	app = QApplication(sys.argv)
	form = Winform()
	form.show()
	sys.exit(app.exec_())

效果如下:

在这个例子中,按下鼠标左键时标志正在绘图,当释放鼠标左键时则取消正在绘图的标志。运行程序,绘图正常,没有重影。

在这个例子中,需要添加一个辅助画布,如果正在绘图,也就是还没有释放鼠标左键时,就在这个辅助画布上进行;只有释放鼠标左键时,才在真正的画布上绘图。

双缓冲技术总结

在这个例子中,要实现使用鼠标在界面上绘制一个任意大小的矩形而不出现重影,需要两个画布,它们都是 QPixmap 实例,其中 tempPix 作为临时缓冲区,当拖动鼠标绘制矩形时,将内容先绘制到 tempPix 上,然后再将 tempPix 绘制到界面上;pix 作为缓冲区,用来保存已经完成的绘制。当释放鼠标按键完成矩形的绘制后,则将 tempPix 的内容复制到 pix 上。为了在绘制时不出现重影,而且保证以前绘制的内容不消失,那么每一次绘制都是在原来的图形上进行的,所以需要在绘制 tempPix 之前,先将 pix 的内容复制到 tempPix 上。因为这里有两个 QPixmap 对象,也可以说有两个缓冲区,所以称之为“双缓冲绘图”。

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消