企业网站空间多大,手机网站建设 如何获得更好的排名,wap网站快速开发,广州游戏网站建设本文目录 PyQt5桌面应用系列画画图#xff0c;喝喝茶QPainter和QPixmapQPixmapQPainter绘制事件 一个魔改的QLabelCanvas类主窗口主程序#xff1a; 总结 PyQt5桌面应用系列 PyQt5桌面应用开发#xff08;1#xff09;#xff1a;需求分析 PyQt5桌面应用开发#xff08;2… 本文目录 PyQt5桌面应用系列画画图喝喝茶QPainter和QPixmapQPixmapQPainter绘制事件 一个魔改的QLabelCanvas类主窗口主程序 总结 PyQt5桌面应用系列 PyQt5桌面应用开发1需求分析 PyQt5桌面应用开发2事件循环 PyQt5桌面应用开发3并行设计 PyQt5桌面应用开发4界面设计 PyQt5桌面应用开发5对话框 PyQt5桌面应用开发6文件对话框 PyQt5桌面应用开发7文本编辑语法高亮与行号 PyQt5桌面应用开发8从QInputDialog转进到函数参数传递 PyQt5桌面应用开发9经典布局QMainWindow PyQt5桌面应用开发10界面布局基本支持 PyQt5桌面应用开发11摸鱼也要讲基本法两个字16 PyQt5桌面应用开发12QFile与线程安全 PyQt5桌面应用开发13QGraphicsView框架 PyQt5桌面应用开发14数据库ModelViewQCharts PyQt5桌面应用开发15界面动画 PyQt5桌面应用开发16定制化控件-QPainter绘图
本文完整源代码gitcode.net
画画图喝喝茶
程序员的日子还是挺悠闲。只要快捷键设置得好摸鱼不要太简单。话说这天本人文青病又犯了就想着整点艺术。虽然艺术细胞不多……
先上艺术。 不过跟我儿子比还是差点 QPainter和QPixmap
前面都是胡扯这里其是想写的是PyQt5中自定义控件的技术。对于PyQt5而言所有的控件都把自己作为一种bitmap来绘制只有理解了这一点才能说掌握了自定义控件。
QPixmap
计算机图像学里所有的图形都是像素点阵列每个像素点都有自己的颜色这种点阵列就是bitmap。bitmap也可以序列化成图像文件不同的编码格式对应不同格式的文件常见的有bmp、jpg、png等。
PyQt5中所有的控件都是继承自QWidget而QWidget继承自QPaintDeviceQPaintDevice是一个抽象类它定义了一些绘制的接口。QPixmap和QImage都继承自QPaintDevice所以QPixmap和QImage都可以作为绘制的目标。在Device之上又有QPainterQPainter是一个绘制的工具它可以把图形绘制到QPaintDevice上。
控件的pixmap()函数都返回一个QPixmap对象这个对象可以用来绘制控件的内容。
QPainter
QPainter是一个绘制工具它可以把图形绘制到QPaintDevice上。QPainter的绘制函数有很多这里只介绍几个常用的
drawPoint(x, y)绘制一个点drawLine(x1, y1, x2, y2)绘制一条直线drawRect(x, y, width, height)绘制一个矩形drawEllipse(x, y, width, height)绘制一个椭圆drawArc(x, y, width, height, startAngle, spanAngle)绘制一个圆弧drawPie(x, y, width, height, startAngle, spanAngle)绘制一个扇形drawPolygon(points)绘制一个多边形drawText(x, y, text)绘制一段文本drawPixmap(x, y, pixmap)绘制一个图像drawImage(x, y, image)绘制一个图像fillRect(x, y, width, height, color)用color颜色填充一个矩形
此外QPainter还提供了函数设置画图的笔触。
pen()获取当前的画笔setPen(pen)设置画笔
这些函数提供了多种不同的重载形式具体查看文档就行。
绘制事件
QWidget的paintEvent()函数是绘制事件当控件需要绘制的时候就会调用这个函数。paintEvent() 函数的参数是QPaintEvent它包含了绘制的区域可以通过rect()函数获取。
paintEvent()函数的实现一般是先创建一个QPainter对象然后调用QPainter的绘制函数最后销毁QPainter对象。
def paintEvent(self, event):painter QPainter(self)painter.drawPixmap(self.rect(), self.pixmap)painter.end()QPainter提供了一对函数begin()和end()来管理绘制的生命周期这两个函数是成对出现的一般都是在begin() 函数中创建QPainter对象然后在end() 函数中销毁QPainter对象。但是QPainter的构造函数也可以直接传入一个QPaintDevice对象这样就不需要调用begin()。但是end() 函数一般是需要自行调用的。
基本的内容就是这些比较简单接下来增加一点点细节。
一个魔改的QLabel
我们选择一个QLabel控件作为父类定义一个Canvas类。在Canvas中提供鼠标绘图的功能所有的图形都是花在QLabel的pixmap()上。
希望提供的功能如下
鼠标绘制图形设置绘制的颜色设置线条粗细鼠标喷涂喷涂的过程增加一个框框有不同的形状保存绘制的图形清楚绘图区
界面的设计如下 下方工具栏颜色选择包括增加自定义颜色上方工具栏颜色、保存之外其它所有交互功能
Canvas类
Canvas类继承自QLabel它的构造函数中设置了一些属性包括
setMouseTracking(False)设置鼠标跟踪如果设置为True那么鼠标移动事件就会被触发否则只有鼠标按下和释放事件才会被触发。last_x, last_y记录鼠标的上一个位置用于绘制直线。pen_color记录当前的画笔颜色。is_spray记录是否是喷涂模式。gauss_size喷涂的大小。shape_type记录当前的绘制形状。is_rotate形状是否旋转is_gauss_sum_up是否用高斯叠加函数形成一个特殊笔刷
类中间定义了一个enum用于记录绘制的形状包括
NONE无RECT矩形CIRC圆形TRI三角形HEX六角形
相应地还定义了一系列槽函数。重要的功能在重载的几个函数中实现。
def resizeEvent(self, a0: QResizeEvent) - None:def mouseReleaseEvent(self, ev: QMouseEvent) - None:def mousePressEvent(self, ev: QMouseEvent) - None:def mouseMoveEvent(self, e: QMouseEvent):
resizeEvent() 函数在控件大小改变的时候被调用这里我们在这个函数中重新设置pixmap的大小保证pixmap的大小和控件大小一致。mousePressEvent和mouseReleaseEvent处理鼠标按下和释放事件mouseMoveEvent处理鼠标移动事件。
最后是一个功能函数把QPixamp保存为图片和内部函数绘制喷涂。
class Canvas(QLabel):class ShapeType:NONE 0RECT 1CIRC 2TRI 3HEX 4def __init__(self, parentNone):super(Canvas, self).__init__(parent)self.last_x, self.last_y None, Noneself.pen_color QColor(Qt.black)self.setMouseTracking(False)self.is_spray Falseself.gauss_size 10self.gauss_density 100self.shape_type self.ShapeType.NONEself.is_rotate Falseself.angle Noneself.line_width 4self.gauss_sum_up FalsepyqtSlot(bool)def set_gauss_sum_up(self, b: bool):self.gauss_sum_up bpyqtSlot(int)def set_line_width(self, w):self.line_width wpyqtSlot(int)def set_gauss_size(self, s):self.gauss_size spyqtSlot(int)def set_gauss_density(self, d):self.gauss_density dpyqtSlot(bool)def set_rotate(self, b: bool):self.is_rotate bpyqtSlot(int)def set_shape_type(self, shape_type: ShapeType):self.shape_type shape_typepyqtSlot(QColor)def set_pen_color(self, c):self.pen_color QColor(c)pyqtSlot(bool)def toggle_spray(self, b: bool):self.is_spray bpyqtSlot()def clear(self) - None:self.pixmap().fill(Qt.white)self.update()def resizeEvent(self, a0: QResizeEvent) - None:pixmap self.pixmap()if pixmap:self.setPixmap(pixmap.scaled(a0.size()))else:pixmap QPixmap(a0.size())pixmap.fill(Qt.white)self.setPixmap(pixmap)def mouseReleaseEvent(self, ev: QMouseEvent) - None:self.last_x, self.last_y None, Nonedef mousePressEvent(self, ev: QMouseEvent) - None:self.last_x, self.last_y ev.x(), ev.y()if self.is_spray:self._spray(ev.x(), ev.y(), QPainter(self.pixmap()))self.update()def mouseMoveEvent(self, e: QMouseEvent):painter QPainter(self.pixmap())p painter.pen()p.setColor(self.pen_color)p.setWidth(self.line_width)painter.setPen(p)if self.is_spray:self._spray(e.x(), e.y(), painter)else:painter.drawLine(self.last_x, self.last_y, e.x(), e.y())painter.end()self.update()self.last_x, self.last_y e.x(), e.y()def save_png(self):fid, _ QFileDialog.getSaveFileName(self, Save PNG, canvas.png, PNG (*.png))if fid:self.pixmap().save(fid, PNG, 100)def _spray(self, x, y, painter):p painter.pen()p.setWidth(1)p.setColor(self.pen_color)painter.setPen(p)painter.translate(x, y)# draw shape in local coordinatesif self.is_rotate:self.angle random.randrange(0, 360)painter.rotate(self.angle)if self.shape_type self.ShapeType.RECT:angle random.randrange(0, 360)painter.drawRect(QRectF(- self.gauss_size, - self.gauss_size, 2 * self.gauss_size, 2 * self.gauss_size))if self.shape_type self.ShapeType.CIRC:painter.drawEllipse(QPointF(0, 0), self.gauss_size, self.gauss_size)if self.shape_type self.ShapeType.TRI or self.shape_type self.ShapeType.HEX:painter.drawPolygon(QPolygonF([QPointF(- math.sqrt(3) * self.gauss_size, self.gauss_size),QPointF( math.sqrt(3) * self.gauss_size, self.gauss_size),QPointF(0, - self.gauss_size * 2.0)]))if self.shape_type self.ShapeType.HEX:painter.drawPolygon(QPolygonF([QPointF(- math.sqrt(3) * self.gauss_size, - self.gauss_size),QPointF( math.sqrt(3) * self.gauss_size, - self.gauss_size),QPointF(0, self.gauss_size * 2.0)]))if self.is_rotate:painter.rotate(-self.angle)xi, yi 0, 0for i in range(self.gauss_density):if self.gauss_sum_up:xi random.gauss(0, self.gauss_size / 3.0)yi random.gauss(0, self.gauss_size / 3.0)else:xi, yi random.gauss(0, self.gauss_size / 3.0), random.gauss(0, self.gauss_size / 3.0)painter.drawPoint(QPointF(xi, yi))# back to global coordinatespainter.translate(-x, -y)主窗口
主窗口要构造所有的界面元素并提供保存按键组合的响应。
class MainWindow(QMainWindow):def __init__(self):super(MainWindow, self).__init__()self.toolbar_button_width 30self.canvas Canvas()self.setCentralWidget(self.canvas)self.palette_widget QToolBar(self)self.palette_widget.setWindowTitle(Palette)self.current_color QLabel()self.current_color.setFixedWidth(self.toolbar_button_width)self.current_color.setStyleSheet(background-color: #000000)self.palette_widget.addWidget(self.current_color)add_color QPushButton(新)add_color.setFixedWidth(self.toolbar_button_width)add_color.clicked.connect(self.pick_color)self.palette_widget.addWidget(add_color)self.palette_widget.addSeparator()for color in COLORS:button QPaletteButton(color, self.palette_widget, self.toolbar_button_width)self.palette_widget.addWidget(button)button.clicked.connect(partial(self.canvas.set_pen_color, QColor(color)))button.clicked.connect(partial(self.current_color.setStyleSheet, fbackground-color: {color}))self.addToolBar(Qt.BottomToolBarArea, self.palette_widget)self.palette_widget.setMovable(False)self.pen_style QToolBar(self)self.pen_style.setWindowTitle(Pen Style)checkbox QCheckBox(喷涂, self.pen_style)checkbox.clicked.connect(self.canvas.toggle_spray)sumup QCheckBox(高斯叠加, self.pen_style)sumup.clicked.connect(self.canvas.set_gauss_sum_up)size_tunner QSpinBox(self.pen_style)size_tunner.setMinimum(1)size_tunner.setMaximum(200)size_tunner.setValue(50)size_tunner.valueChanged.connect(self.canvas.set_gauss_size)size_tunner.setSingleStep(1)size_tunner.setFixedWidth(100)density_tunner QSpinBox(self.pen_style)density_tunner.setMinimum(0)density_tunner.setMaximum(5000)density_tunner.setValue(500)density_tunner.valueChanged.connect(self.canvas.set_gauss_density)density_tunner.setSingleStep(10)density_tunner.setFixedWidth(100)clear_button QPushButton(Clear, self.pen_style)clear_button.clicked.connect(self.canvas.clear)clear_button.setStyleSheet(background-color: #ff0000; color: #ffffff;)self.pen_style.addWidget(clear_button)self.pen_style.addSeparator()self.pen_style.addWidget(checkbox)self.pen_style.addWidget(sumup)self.pen_style.addSeparator()self.pen_style.addWidget(QLabel(喷涂大小:))self.pen_style.addWidget(size_tunner)self.pen_style.addSeparator()self.pen_style.addWidget(QLabel(喷涂数目:))self.pen_style.addWidget(density_tunner)self.outline QComboBox(self.pen_style)self.outline.addItem(无)self.outline.addItem(方形)self.outline.addItem(圆形)self.outline.addItem(三角形)self.outline.addItem(六角形)self.outline.currentIndexChanged.connect(self.canvas.set_shape_type)self.pen_style.addSeparator()self.pen_style.addWidget(QLabel(边框:))self.pen_style.addWidget(self.outline)is_rotate QCheckBox(旋转, self.pen_style)is_rotate.clicked.connect(self.canvas.set_rotate)is_rotate.setChecked(False)self.pen_style.addWidget(is_rotate)self.pen_style.addSeparator()self.pen_style.addWidget(QLabel(线条宽度:))self.pen_style.addSeparator()line_width QSpinBox(self.pen_style)line_width.setMinimum(1)line_width.setMaximum(10)line_width.setValue(4)line_width.valueChanged.connect(self.canvas.set_line_width)line_width.setSingleStep(1)self.pen_style.addWidget(line_width)self.pen_style.setMovable(False)self.addToolBar(Qt.TopToolBarArea, self.pen_style)pyqtSlot()def pick_color(self):color QColorDialog.getColor()if color.isValid():button QPaletteButton(color.name(), self.palette_widget, self.toolbar_button_width)self.palette_widget.addWidget(button)button.clicked.connect(partial(self.canvas.set_pen_color, color))button.clicked.connect(partial(self.current_color.setStyleSheet, fbackground-color: {color.name()}))button.clicked.emit()# resize the palette widget to fit all the buttonsself.palette_widget.resize(self.palette_widget.sizeHint())def keyPressEvent(self, ev: QKeyEvent) - None:if ev.matches(QKeySequence.Save):self.canvas.save_png()这里的代码乏善可陈唯一好玩的是那个颜色选择界面。
COLORS sorted({#000000, #808080, #800000, #808000, #008000, #008080, #000080, #800080, #808040, #004040,#004080, #0000ff, #004080, #0080ff, #0040ff, #0080c0, #00ffff, #00ff00, #00ff80, #00ffbf,#00ffff, #00bfff, #00bfbf, #00b080, #00bf80, #00b040, #00bf40, #00bf00, #00ff40, #00ffbf,#00ffff, #80ff00, #80ff80, #80ffbf, #80ffff, #80bfff, #80bfbf, #80b080, #80bf80, #80b040,#80bf40, #80bf00, #80ff40, #80ffbf, #80ffff, #bf0000, #bf0080, #bf0080, #bf8000, #bf8040,#bf8080, #bf80c0, #bf80ff, #bf40ff, #bf00ff, #bf00bf, #bf0080, #bf0040, #bf0000, #bf4000,#bf4040, #bf4080, #bf40c0, #bf40ff, #bf00ff, #bf00bf, #bf0080, #bf0040, #bf0000, #bf4000,#bf4040, #bf4080, #bf40c0, #bf40ff, #bf00ff, #bf00bf, #bf0080, #bf0040, #bf0000, #bf4000,#bf4040, #bf4080, #bf40c0, #bf40ff, #bf00ff, #bf00bf, #bf0080, #bf0040, #bf0000, #bf4000,#bf4040, #bf4080, #bf40c0, #bf40ff, #bf00ff, #bf00bf, #bf0080, #bf0040, #bf0000, #bf4000,#bf4040, #bf4080, #bf40c0, #bf40ff, #bf00ff, #bf00bf, #bf0080, #bf0040, #bf0000, #bf4000,#bf4040, #bf4080, #00ff00, #00ff80, #00ffbf, #00ffff, #00bfff, #00bfbf, #00b080, #00bf80,#00b040, #00bf40, #00bf00, #00ff40, #00ffbf, #00ffff, #80ff00, #80ff80, #80ffbf, #80ffff,#80bfff, #80bfbf, #80b080, #80bf80, #80b040, #80bf40, #80bf00, #80ff40, #80ffbf, #80ffff,#bf0000, #bf0080, #bf0080, #bf8000, #bf8040, #bf8080, #bf80c0, #bf80ff, #bf40ff, #bf00ff,#bf00bf, #bf0080, #ffff00, #ffff80, #ffffbf, #ffffff, #bfffff, #bfbfbf, #bf8080, #bfbf80,#bf4040, #bfbf40, #bf0000, #bfbf00, #bf00bf, #bfbfbf, #bf8080, #bfbf80})class QPaletteButton(QPushButton):def __init__(self, color, parentNone, size24):super(QPaletteButton, self).__init__(parent)self.setFixedSize(size, size)self.setStyleSheet(fbackground-color: {color})这里就是随便写了一堆颜色然后用一个按钮的子类来实现这样就可以直接用按钮的clicked信号来实现颜色的选择了。
主程序
if __name__ __main__:app QApplication(sys.argv)window MainWindow()window.setWindowTitle(Painter)window.resize(1400, 800)window.show()sys.exit(app.exec_())
总结
这里面需要注意的技术要素包括
信号与槽的使用事件处理特别是鼠标事件按下、释放、移动等画图的基本原理包括画笔、画刷、画布等画图中我们用了一个移动的技术把画布移动到一个位置以那个位置为0点画图可以转动然后移动回去。
Canvas的resizeEvent实际上承担了两个功能初始创建的时候会调用resizeEvent然后在resize的时候也会调用resizeEvent所以我们需要在resizeEvent里面做一些初始化的工作比如创建画布然后在resize的时候我们需要重新创建画布然后把画布的内容复制到新的画布上面然后删除旧的画布。