基于python实现的手绘板工具
包含:钢笔工具,铅笔工具,橡皮擦,颜色选择,导出为图片。
当然图片临摹也必不可少。
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QToolBar, QColorDialog,QFileDialog, QLabel, QAction, QSlider, QSizePolicy
)
from PyQt5.QtGui import (QPainter, QPen, QColor, QImage, QPainterPath, QIcon, QPixmap, QCursor
)
from PyQt5.QtCore import (Qt, QPoint, QPointF, QSize, QSizeF, QRectF
)class Canvas(QWidget):def __init__(self):super().__init__()self.setFixedSize(800, 600)self.setMouseTracking(True)# 绘图系统self.drawing = Falseself.layers = [QImage(self.size(), QImage.Format_ARGB32)]self.layers[0].fill(Qt.transparent)self.current_layer = 0self.brush_type = "pencil"self.brush_size = 5self.brush_color = QColor(0, 0, 0)self.last_point = QPoint()# 参考图系统self.reference_pixmap = Noneself.reference_opacity = 0.5self.reference_pos = QPointF(100.0, 100.0)self.reference_scale = 1.0self.dragging_ref = Falseself.last_drag_pos = QPoint()# 历史记录self.history_stack = []self.redo_stack = []def paintEvent(self, event):painter = QPainter(self)# 绘制参考图if self.reference_pixmap:painter.setOpacity(self.reference_opacity)scaled_pixmap = self.reference_pixmap.scaled(self.reference_pixmap.size() * self.reference_scale,Qt.KeepAspectRatio, Qt.SmoothTransformation)painter.drawPixmap(self.reference_pos.toPoint(), scaled_pixmap)# 绘制用户图层painter.setOpacity(1.0)for layer in self.layers:painter.drawImage(0, 0, layer)def mousePressEvent(self, event):if event.button() == Qt.LeftButton:if self.in_reference_area(event.pos()):self.dragging_ref = Trueself.last_drag_pos = event.pos()self.setCursor(Qt.ClosedHandCursor)else:self.start_drawing(event.pos())elif event.button() == Qt.RightButton and self.reference_pixmap:self.reset_reference()def mouseMoveEvent(self, event):if self.dragging_ref:self.drag_reference(event.pos())elif self.drawing:self.draw_line(event.pos())self.update_cursor(event.pos())def mouseReleaseEvent(self, event):if event.button() == Qt.LeftButton:self.dragging_ref = Falseself.drawing = Falseself.setCursor(Qt.ArrowCursor)def wheelEvent(self, event):if event.modifiers() == Qt.ControlModifier and self.reference_pixmap:self.zoom_reference(event.angleDelta().y())# 新增的辅助方法def in_reference_area(self, pos):if not self.reference_pixmap:return Falseref_size = QSizeF(self.reference_pixmap.size()) * self.reference_scaleref_rect = QRectF(self.reference_pos, ref_size)return ref_rect.contains(QPointF(pos))def start_drawing(self, pos):self.drawing = Trueself.last_point = posself.save_state()def draw_line(self, pos):painter = QPainter(self.layers[self.current_layer])painter.setPen(self.create_pen())path = QPainterPath()path.moveTo(self.last_point)path.lineTo(pos)painter.drawPath(path)self.last_point = posself.update()def create_pen(self):pen = QPen()pen.setWidthF(self.brush_size)pen.setCapStyle(Qt.RoundCap)if self.brush_type == "eraser":pen.setColor(Qt.transparent)pen.setCompositionMode(QPainter.CompositionMode_Clear)else:pen.setColor(self.brush_color)return pendef drag_reference(self, pos):delta = pos - self.last_drag_posself.reference_pos += QPointF(delta)self.last_drag_pos = posself.update()def zoom_reference(self, delta):scale_factor = 1.0 + delta / 1200.0self.reference_scale = max(0.1, min(self.reference_scale * scale_factor, 5.0))self.update()def reset_reference(self):self.reference_pos = QPointF(100.0, 100.0)self.reference_scale = 1.0self.update()def update_cursor(self, pos):self.setCursor(Qt.OpenHandCursor if self.in_reference_area(pos) else Qt.ArrowCursor)def load_reference(self, path):self.reference_pixmap = QPixmap(path)self.update()def clear_reference(self):self.reference_pixmap = Noneself.update()def set_reference_opacity(self, value):self.reference_opacity = value / 100.0def save_state(self):if len(self.history_stack) >= 20:self.history_stack.pop(0)self.history_stack.append(self.layers[self.current_layer].copy())self.redo_stack.clear()def undo(self):if self.history_stack:self.redo_stack.append(self.layers[self.current_layer].copy())self.layers[self.current_layer] = self.history_stack.pop()self.update()def redo(self):if self.redo_stack:self.history_stack.append(self.layers[self.current_layer].copy())self.layers[self.current_layer] = self.redo_stack.pop()self.update()class MainWindow(QMainWindow):def __init__(self):super().__init__()self.canvas = Canvas()self.init_ui()def init_ui(self):self.setCentralWidget(self.canvas)self.setWindowTitle("专业手绘板 v2.0")self.setWindowIcon(QIcon.fromTheme("applications-graphics"))self.resize(1000, 700)self.create_toolbars()def create_toolbars(self):# 主工具main_toolbar = self.addToolBar("主工具")self.add_action(main_toolbar, "draw-pencil", "铅笔", self.set_pencil)self.add_action(main_toolbar, "draw-line", "钢笔", self.set_pen)self.add_action(main_toolbar, "edit-eraser", "橡皮擦", self.set_eraser)main_toolbar.addSeparator()self.add_action(main_toolbar, "color-picker", "颜色", self.choose_color)self.add_action(main_toolbar, "document-save", "保存", self.save_image)# 参考图工具ref_toolbar = self.addToolBar("参考图")self.add_action(ref_toolbar, "document-open", "加载参考图", self.load_reference)self.add_action(ref_toolbar, "edit-clear", "清除参考图", self.clear_reference)opacity_slider = QSlider(Qt.Horizontal)opacity_slider.setRange(0, 100)opacity_slider.setValue(50)opacity_slider.valueChanged.connect(lambda v: self.canvas.set_reference_opacity(v))ref_toolbar.addWidget(QLabel(" 透明度:"))ref_toolbar.addWidget(opacity_slider)# 编辑工具edit_toolbar = self.addToolBar("编辑")self.add_action(edit_toolbar, "edit-undo", "撤销", self.canvas.undo)self.add_action(edit_toolbar, "edit-redo", "重做", self.canvas.redo)def add_action(self, toolbar, icon_name, text, callback):action = QAction(QIcon.fromTheme(icon_name), text, self)action.triggered.connect(callback)toolbar.addAction(action)return actiondef set_pencil(self):self.canvas.brush_type = "pencil"self.statusBar().showMessage("铅笔模式", 2000)def set_pen(self):self.canvas.brush_type = "pen"self.statusBar().showMessage("钢笔模式", 2000)def set_eraser(self):self.canvas.brush_type = "eraser"self.statusBar().showMessage("橡皮擦模式", 2000)def choose_color(self):if (color := QColorDialog.getColor()).isValid():self.canvas.brush_color = colordef load_reference(self):if path := QFileDialog.getOpenFileName(self, "选择参考图", "", "图片文件 (*.png *.jpg *.bmp)")[0]:self.canvas.load_reference(path)def clear_reference(self):self.canvas.clear_reference()def save_image(self):if path := QFileDialog.getSaveFileName(self, "保存作品", "", "PNG文件 (*.png);;JPEG文件 (*.jpg)")[0]:merged = QImage(self.canvas.size(), QImage.Format_ARGB32)merged.fill(Qt.transparent)with QPainter(merged) as painter:for layer in self.canvas.layers:painter.drawImage(0, 0, layer)merged.save(path)if __name__ == "__main__":app = QApplication(sys.argv)window = MainWindow()window.show()sys.exit(app.exec_())