当前位置: 首页 > article >正文

tkinter快键画布

 这个画板仅仅是为了服务于个人设计的截图功能贴图功能中的画板,方便我直接进行继承拓展。

截图自制工具详见我的主页。个人认为设计可谓相当简洁优雅。

下面的快捷画布的使用方法:

①按住Ctrl键,调出画笔;松开即结束绘图;

②双击Ctrl键,调出调色板;

 ③按照Ctrl键并滚动鼠标中键,调整画笔粗细;

第一版:

import time
import ctypes
import tkinter as tk
from tkinter import colorchooser


class CustomCanvas(tk.Canvas):
    DOUBLE_CLICK_INTERVAL: float = 0.3
    LINE_MERGE_SLOPE_THRESHOLD: float = 0.05
    BIG_MOVE_THRESHOLD: int = 10
    PEN_SIZE_RANGE: tuple[int, int] = (1, 124)
    def __init__(self, parent, **kwargs):
        super().__init__(parent, **kwargs)
        self._bind_events()
        self.focus_set()
        self.__preview_items: list[str] = list()
        self.__stroke_history: list[str] = list()
        self.__current_stroke: list[str] = list()
        self.pen_color: str = 'black'
        self.drawing_mode: bool = False
        self.last_ctrl_press: float = 0.0
        self.pen_size: int = 3
        self.last_x: int = None
        self.last_y: int = None

    def _bind_events(self) -> None:
        # 控制键监听
        self.bind('<Control_L>', self.__start_drawing)
        self.bind('<Control-KeyRelease>', self.__stop_drawing)
        # 鼠标监听
        self.bind('<B1-Motion>', self.__draw_continuous)
        self.bind('<Motion>', self.__update_preview)
        self.bind('<Button-1>', self.__draw_single_point)
        self.bind("<ButtonRelease-1>", self.__finalize_stroke)
        # 功能快捷键
        self.bind('<Control-MouseWheel>', self.__adjust_pen_size)
        self.bind("<Control-z>", self.undo_last_stroke)

    def __start_drawing(self, event) -> None:
        if self._is_double_ctrl_click():
            self.__change_pen_color(event)
        self.drawing_mode = True
        self.config(cursor='pencil')

    def __stop_drawing(self, event) -> None:
        self.drawing_mode = False
        self.config(cursor='')

    def __finalize_stroke(self, event) -> None:
        self.last_x = self.last_y = None
        self.__stroke_history.append(self.__current_stroke)
        self.__current_stroke = []

    def __change_pen_color(self, event) -> None:
        color = colorchooser.askcolor()[1]
        if color:
            self.pen_color = color

    def __adjust_pen_size(self, event) -> None:
        min_size, max_size = self.PEN_SIZE_RANGE
        if event.delta > 0:
            self.pen_size = min(max_size, self.pen_size + 1)
        else:
            self.pen_size = max(min_size, self.pen_size - 1)
        self.__show_brush_preview(event)

    def __show_brush_preview(self, event) -> None:
        self.__update_preview(event, force=True)
        preview = self.__draw_single_point(event, record=False)
        self.__preview_items.append(preview)

    def __update_preview(self, event, force: bool = False) -> None:
        if force:
            self.__clear_preview()
        current_x, current_y = event.x, event.y
        if self.last_x is not None and self.last_y is not None:
            dx = abs(current_x - self.last_x)
            dy = abs(current_y - self.last_y)
            if dx < self.BIG_MOVE_THRESHOLD and dy < self.BIG_MOVE_THRESHOLD:
                return
        self.last_x, self.last_y = current_x, current_y
        if len(self.__preview_items) != 0:
            self.__clear_preview()

    def __clear_preview(self) -> None:
        for item in self.__preview_items:
            self.delete(item)
        self.__preview_items.clear()

    def _is_double_ctrl_click(self) -> bool:
        current_time = time.time()
        is_double_click = (current_time - self.last_ctrl_press) < self.DOUBLE_CLICK_INTERVAL
        self.last_ctrl_press = current_time
        return is_double_click

    def _is_collineation(self, line1_coords: tuple[int], line2_coords: tuple[int]) -> bool:
        x1, y1, x2, y2 = line1_coords
        x3, y3, x4, y4 = line2_coords

        dx1 = x2 - x1
        dy1 = y2 - y1
        dx2 = x4 - x3
        dy2 = y4 - y3
        
        if dx1 == 0 or dx2 == 0:
            return x1 == x3

        # 斜率几乎相等
        if abs(dy1 / dx1 - dy2 / dx2) < self.LINE_MERGE_SLOPE_THRESHOLD:
            # 同方向
            return (dx1 * dx2 >= 0) and (dy1 * dy2 >= 0)
        return False
    
    def _collineation_merge(self, event) -> tuple[int]:
        current_line_coords = (self.last_x, self.last_y, event.x, event.y)
        if len(self.__current_stroke) < 2:
            return current_line_coords
        last_line_id = self.__current_stroke[-1]
        last_line_coords = self.coords(last_line_id)
        if len(last_line_coords) != 4:
            return current_line_coords
        if self._is_collineation(last_line_coords, current_line_coords):
            self.delete(last_line_id)
            self.__current_stroke.pop()
            return (last_line_coords[0], last_line_coords[1], event.x, event.y)
        return current_line_coords

    def __draw_continuous(self, event, record: bool = True) -> str:
        line = ""
        if not self.drawing_mode:
            return line
        if self.last_x and self.last_y:
            line_coords = self._collineation_merge(event)
            line = self.create_line(
                *line_coords, fill=self.pen_color, width=self.pen_size,
                capstyle=tk.ROUND, joinstyle=tk.ROUND
            )
            if record:
                self.__current_stroke.append(line)
        self.last_x = event.x
        self.last_y = event.y
        return line

    def __draw_single_point(self, event, record: bool = True) -> str:
        point = ""
        if not self.drawing_mode:
            return point
        point = self.create_oval(
            event.x - self.pen_size // 2, event.y - self.pen_size // 2,
            event.x + self.pen_size // 2, event.y + self.pen_size // 2,
            fill=self.pen_color, outline=self.pen_color, 
        )
        self.last_x = event.x
        self.last_y = event.y
        if record:
            self.__current_stroke.append(point)
        return point

    def undo_last_stroke(self, event) -> None:
        if len(self.__stroke_history) == 0:
            return
        last_draw = self.__stroke_history.pop()
        for item in last_draw:
            self.delete(item)


if __name__ == '__main__':
    ctypes.windll.shcore.SetProcessDpiAwareness(1)
    root = tk.Tk()
    canvas = CustomCanvas(root, bg='white', width=1600, height=1200)
    canvas.pack()
    root.mainloop()

原文地址:https://blog.csdn.net/a494665/article/details/146303635
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/588486.html

相关文章:

  • 技术视界|构建理想仿真平台,加速机器人智能化落地
  • Polhemus Patriot 电磁式位置追踪系统空间定位系统
  • SpringBoot 和vue前后端配合开发网页拼图10关游戏源码技术分享
  • Linux操作系统6- 线程3(线程的取消,分离与独立存储)
  • 反射(第三篇)、代理模式、静态代理和动态代理、InvocationHandler实际应用
  • 第十六届蓝桥杯康复训练--2
  • 新手村:数据预处理-缺失值补充策略
  • 【RabbitMQ】事务机制、限流、负载均衡
  • 玩转python:通俗易懂掌握高级数据结构-collections模块之Counter
  • 零基础掌握分布式ID生成:从理论到实战的完整指南 [特殊字符]
  • 563采药
  • NocoBase 本周更新汇总:双因素身份认证(2FA)
  • 蓝桥杯学习-08序列二分
  • 【动手学深度学习】#2线性神经网络
  • 火焰图分析Java程序瓶颈
  • 第15章:ConvNeXt图像分类实战:遥感场景分类【包含本地网页部署、迁移学习】
  • git subtree在本地合并子仓库到主仓库
  • KY-038 声音传感器如何工作以及如何将其与 ESP32 连接
  • java 线程池Executor框架
  • 深入解析 Vue 3 Teleport:原理、应用与最佳实践