手绘板工具:基于python以及pyqt5实现的手绘白板

基于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_())

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值