基于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 = False
self.layers = [QImage(self.size(), QImage.Format_ARGB32)]
self.layers[0].fill(Qt.transparent)
self.current_layer = 0
self.brush_type = "pencil"
self.brush_size = 5
self.brush_color = QColor(0, 0, 0)
self.last_point = QPoint()
# 参考图系统
self.reference_pixmap = None
self.reference_opacity = 0.5
self.reference_pos = QPointF(100.0, 100.0)
self.reference_scale = 1.0
self.dragging_ref = False
self.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 = True
self.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 = False
self.drawing = False
self.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 False
ref_size = QSizeF(self.reference_pixmap.size()) * self.reference_scale
ref_rect = QRectF(self.reference_pos, ref_size)
return ref_rect.contains(QPointF(pos))
def start_drawing(self, pos):
self.drawing = True
self.last_point = pos
self.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 = pos
self.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 pen
def drag_reference(self, pos):
delta = pos - self.last_drag_pos
self.reference_pos += QPointF(delta)
self.last_drag_pos = pos
self.update()
def zoom_reference(self, delta):
scale_factor = 1.0 + delta / 1200.0
self.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.0
self.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 = None
self.update()
def set_reference_opacity(self, value):
self.reference_opacity = value / 100.0
def 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 action
def 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 = color
def 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_())