python自制截图工具

[复制链接]
发表于 2025-11-3 09:28:55 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
效果展示
界面展示:



截图效果:


OCR功能:




一、范围性

①兼容范围性

使用了ctypes调用了Windows的API, 同时访问了Windows中"C:/Windows/Cursors/"中的.cur光标样式文件, 这个工具只实用于Windows环境;
②图像编辑本领有限
该工具只写了一个最简单的画笔工具。(在截图钉在桌面后,按住Ctrl键可以唤出画笔,松开Ctrl键后画笔消散;)
③其他
性能一样平常,如贴图放大过快时会有卡顿;内存占用并不少,默认环境下在40MB;而且有一些快捷键上的Bug至今尚未修复。

二、上风

①轻量化

该模块只使用了PIL、Keyborad和win32clipboard这三个第三方库,和别的使用pygame\pyautogui等模块差异,该工具偏重夸大轻量化,无关或没须要使用的库尽大概倒霉用。整个步伐即便打包也只有10MB多一些的巨细。
②涵盖大部门根本功能

-- 涵盖底子设置,包罗了截图的各种快捷键设定,如自动生存,自动复制到剪切板,当截图数量到达上限时自动删除第一张截图等。
-- 在截图时还可以上下左右键微调选区;实现了截图框表里的亮暗色对比等等。截图时体现放大镜,体现POS和RGB值;
-- 截图后的图像体现:对于截图后的图像会立即体现在画布上。图像画布颠末了优化,其在视图上放大图像的性能刁悍,实现了类图形编辑器的放大效果。右键隐蔽菜单栏后可以很方便的检察截图内容。窗口是保持置顶的,无需担心窗口会被遮挡,方便用于左右对照。
-- 额外的OCR功能,其是基于微信OCR, 因此对于使用者而言必要本身安装微信才可以使用。代码会自动查找用户微信安装路径,并找到此中的OCR部门举行调用。无需启动微信即可使用到微信的OCR功能。固然,如果渴望完全离开微信,有额外的微信OCR离开版本可供下载使用,巨细在60MB;(这里思量过单独把OCR做成插件的情势,即用一个exe文件单独剖析图片输出笔墨的情势,步伐只负责调用这个exe文件。不外实际上由于懒照旧把代码写死了......)

三、最最古老的版本代码

由于展示空间有限,下面展示的代码仅仅是第一代,最最开始时的代码,功能还很大略。而且反面代码颠末了两三轮重构,尤其第三代代码和一代代码已经有了比力大的差异。
1.1版本的代码如下,单文件,仅使用的PIL模块。请自行安装PIL模块,确保在Windows环境上运行。该版本还相对大略。。。
1.2版本代码丢失,此处只剩下第三代代码,全部代码大概历时3周编写,4-5个文件,约1700多行代码。
完备代码已上传github: LightScreenshot/ at main · Just-A-Freshman/LightScreenshot
  1. import os
  2. import ctypes
  3. import subprocess as sp
  4. import tkinter as tk
  5. from threading import Thread
  6. from tkinter import messagebox, filedialog
  7. from PIL import Image, ImageGrab, ImageTk, UnidentifiedImageError
  8.    
  9. ScaleFactor = ctypes.windll.shcore.GetScaleFactorForDevice(0)
  10. ctypes.windll.shcore.SetProcessDpiAwareness(1)
  11. TEMP_FILE_PATH = os.path.join(os.getenv("TEMP"), "tempScreenshot")
  12. os.makedirs(TEMP_FILE_PATH, exist_ok=True)
  13. class FlatButton(tk.Label):
  14.     def __init__(
  15.             self, parent, command=None, enter_fg="#000000",
  16.             click_color="#25C253", *args, **kwargs
  17.         ):
  18.         super().__init__(parent, *args, **kwargs)
  19.         self.__fg = fg = kwargs.get("fg", "#3B3B3B")
  20.         self.__enter_fg = enter_fg
  21.         self.__click_fg = click_color
  22.         self.command = command
  23.         self.config(cursor="hand2", fg=fg)
  24.         self.enable()
  25.         if fg == enter_fg:
  26.             raise ValueError("enter_fg must be different from fg")
  27.     def enable(self):
  28.         self.bind("<Enter>", lambda _: self.config(fg=self.__enter_fg))
  29.         self.bind("<Leave>", lambda _: self.config(fg=self.__fg))
  30.         self.bind("<Button-1>", lambda _: self.config(fg=self.__click_fg))
  31.         self.bind("<ButtonRelease-1>", self.__command)
  32.     def disable(self):
  33.         for event in ("<Enter>", "<Leave>", "<Button-1>", "<ButtonRelease-1>"):
  34.             self.unbind(event)
  35.     def __command(self, event):
  36.         try:
  37.             if self.cget("fg") in (self.__enter_fg, self.__click_fg):
  38.                 self.command(event)
  39.             self.config(fg=self.__fg)
  40.         except tk.TclError:
  41.             pass
  42.         except TypeError:
  43.             self.config(fg=self.__fg)
  44. class AdjustableRect(object):
  45.     """
  46.     The judgement seq is so important that you must care about:
  47.     (right, bottom), (left, top), (right, top), (left, bottom),
  48.     (center_x, top), (center_x, bottom), (left, center_y, ), (right, center_y)
  49.     """
  50.     ANCHOR_SIZE = 3
  51.     ANCHOR_HOVER_DISTANCE = 20
  52.     CURSOR_FILES_NAME = ["aero_nwse_l.cur", "aero_nesw_l.cur", "aero_ns_l.cur", "aero_ew_l.cur"]
  53.     CURSOR_FILES = [f"@C:/Windows/Cursors/{cursor_file}" for cursor_file in CURSOR_FILES_NAME]
  54.     CURSORS = [
  55.         CURSOR_FILES[0], CURSOR_FILES[0], CURSOR_FILES[1], CURSOR_FILES[1],
  56.         CURSOR_FILES[2], CURSOR_FILES[2], CURSOR_FILES[3], CURSOR_FILES[3],
  57.         "fleur", "arrow"
  58.     ]
  59.     def __init__(self, parent, screenshot):
  60.         self.parent: tk.Canvas = parent
  61.         self.screenshot: ScreenshotUtils = screenshot
  62.         self.__rect: int = 0
  63.         self.__anchors: list[int] = []
  64.         self.anchor_id: int = 0
  65.     def rect_coords(self) -> tuple[int, int, int, int]:
  66.         return self.parent.coords(self.__rect)
  67.    
  68.     def anchor_coords(self) -> tuple[int, int, int, int]:
  69.         left, top, right, bottom = self.rect_coords()
  70.         horizontal_middle = (left + right) // 2
  71.         vertical_middle = (top + bottom) // 2
  72.         return (
  73.             (left, top), (horizontal_middle, top), (right, top), (right, vertical_middle),
  74.             (right, bottom), (horizontal_middle, bottom), (left, bottom), (left, vertical_middle)
  75.         )
  76.    
  77.     def rect_width_height(self) -> tuple[int, int]:
  78.         left, top, right, bottom = self.rect_coords()
  79.         return int(right - left), int(bottom - top)
  80.    
  81.     def get_anchor(self, event) -> int:
  82.         cls = self.__class__
  83.         left, top, right, bottom = self.rect_coords()
  84.         center_x, center_y = (left + right) // 2, (top + bottom) // 2
  85.         def near(actual, target):
  86.             return abs(actual - target) < cls.ANCHOR_HOVER_DISTANCE
  87.         # 务必注意这个判断顺序,这与后面rect_adjust密切相关
  88.         judgement_pos = (
  89.             (right, bottom), (left, top), (right, top), (left, bottom),
  90.             (center_x, top), (center_x, bottom), (left, center_y, ), (right, center_y)
  91.         )
  92.         for index, pos in enumerate(judgement_pos):
  93.             if near(event.x, pos[0]) and near(event.y, pos[1]):
  94.                 return index
  95.         if left < event.x < right and top < event.y < bottom:
  96.             return 8
  97.         return -1
  98.     def create_anchors(self):
  99.         cls = self.__class__
  100.         for coord in self.anchor_coords():
  101.             anchor = self.parent.create_rectangle(
  102.                 coord[0]-cls.ANCHOR_SIZE, coord[1]-cls.ANCHOR_SIZE,
  103.                 coord[0]+cls.ANCHOR_SIZE, coord[1]+cls.ANCHOR_SIZE,
  104.                 fill="#1AAE1A", outline="#1AAE1A"
  105.             )
  106.             self.__anchors.append(anchor)
  107.     def create_rect(self) -> None:
  108.         self.__rect= self.parent.create_rectangle(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, outline='#1AAE1A', width=2)
  109.         self.create_anchors()
  110.     def move_anchors(self):
  111.         cls = self.__class__
  112.         for anchor, coord in zip(self.__anchors, self.anchor_coords()):
  113.             self.parent.coords(
  114.                 anchor, coord[0]-cls.ANCHOR_SIZE, coord[1]-2,
  115.                 coord[0]+cls.ANCHOR_SIZE, coord[1]+cls.ANCHOR_SIZE
  116.             )
  117.     def on_press(self, event):
  118.         self.screenshot.start_x = event.x
  119.         self.screenshot.start_y = event.y
  120.     def on_release(self, _):
  121.         self.screenshot.start_x, self.screenshot.start_y,\
  122.         self.screenshot.end_x, self.screenshot.end_y = self.rect_coords()
  123.     def on_hover(self, event):
  124.         self.anchor_id = self.get_anchor(event)
  125.         cursor = self.CURSORS[self.anchor_id]
  126.         self.parent.config(cursor=cursor)
  127.     def move_rect(self, event):
  128.         offset_x = event.x - self.screenshot.move_start_x
  129.         offset_y = event.y - self.screenshot.move_start_y
  130.         if self.screenshot.start_x + offset_x > 0 and self.screenshot.end_x + offset_x < SCREEN_WIDTH:
  131.             self.screenshot.start_x += offset_x
  132.             self.screenshot.end_x += offset_x
  133.         if  self.screenshot.start_y + offset_y > 0 and self.screenshot.end_y + offset_y < SCREEN_HEIGHT:
  134.             self.screenshot.start_y += offset_y
  135.             self.screenshot.end_y += offset_y
  136.         self.screenshot.move_start_x = event.x
  137.         self.screenshot.move_start_y = event.y
  138.         self.parent.coords(self.__rect, self.screenshot.start_x, self.screenshot.start_y, self.screenshot.end_x, self.screenshot.end_y)
  139.         self.move_anchors()
  140.     def rect_adjust(self, event):
  141.         if self.anchor_id == 8:
  142.             return self.move_rect(event)
  143.         if self.anchor_id == 0:
  144.             self.screenshot.end_x, self.screenshot.end_y = event.x, event.y
  145.         elif self.anchor_id == 1:
  146.             self.screenshot.start_x, self.screenshot.start_y = event.x, event.y
  147.         elif self.anchor_id == 2:
  148.             self.screenshot.end_x, self.screenshot.start_y = event.x, event.y
  149.         elif self.anchor_id == 3:
  150.             self.screenshot.start_x, self.screenshot.end_y = event.x, event.y
  151.         elif self.anchor_id == 4:
  152.             self.screenshot.start_y = event.y
  153.         elif self.anchor_id == 5:
  154.             self.screenshot.end_y = event.y
  155.         elif self.anchor_id == 6:
  156.             self.screenshot.start_x = event.x
  157.         elif self.anchor_id == 7:
  158.             self.screenshot.end_x = event.x
  159.         else:
  160.             return
  161.         self.parent.coords(self.__rect, self.screenshot.start_x, self.screenshot.start_y, self.screenshot.end_x, self.screenshot.end_y)
  162.         self.move_anchors()
  163.    
  164. class ScreenshotUtils(object):
  165.     """
  166.     截图的关键是坐标;这个类管理着图片的引用和截图坐标;
  167.     """
  168.     def TkS(value) -> int:
  169.         return int(ScaleFactor/100*value)
  170.    
  171.     ZOOM: int = 4
  172.     ZOOM_WIDTH: float = TkS(28.75)
  173.     ZOOM_SCREEN_SIZE: int = int(ZOOM_WIDTH*ZOOM)
  174.     MAGNIFIER_OFFSET: int = 36
  175.     WIDTH_HEIGHT_OFFSET = 40
  176.     POS_TAG_GAP = 10
  177.     RGB_TAG_GAP = 47
  178.     MAGNIFIER_ADJUST = 70
  179.     AJUST_BAR_WIDTH: int = TkS(100)
  180.     def __init__(self):
  181.         self.start_x = self.start_y = self.end_x = self.end_y = 0
  182.         self.move_start_x = self.move_start_y = self.move_end_x = self.move_end_y = 0
  183.         self.page_index: int = 0
  184.         self.current_image: Image.Image = None
  185.         self.pixel_reader = None
  186.         self.final_images: list[Image.Image] = list()
  187.         # 这种是只移动但不改变大小和内容的控件,只需移动无需重绘
  188.         self.screenshot_move_widget = list()
  189.         # 这种是移动和改变大小的控件,需要实时重绘
  190.         self.screenshot_redraw_widget = list()
  191.     @staticmethod
  192.     def TkS(value) -> int:
  193.         return int(ScaleFactor/100*value)
  194.    
  195.     @classmethod
  196.     def move_widget_coords(cls, x, y) -> list[tuple[int, int, int, int]]:
  197.         # 按照主框架,水平线,垂直线的顺序返回坐标
  198.         main_frame_coord  = (x, y, x+cls.ZOOM_SCREEN_SIZE, y+cls.ZOOM_SCREEN_SIZE)
  199.         horrontal_line_coord = (x, y+cls.ZOOM_SCREEN_SIZE // 2, x+cls.ZOOM_SCREEN_SIZE, y+cls.ZOOM_SCREEN_SIZE // 2)
  200.         vertical_line_coord = (x+cls.ZOOM_SCREEN_SIZE // 2, y, x+cls.ZOOM_SCREEN_SIZE // 2, y+cls.ZOOM_SCREEN_SIZE)
  201.         return [main_frame_coord, horrontal_line_coord, vertical_line_coord]
  202.     def redraw_widget_coords(self, x, y) -> list[tuple]:
  203.         # 按照"放大镜图像"、"长 × 宽"、"POS标签"、"RGB标签"的顺序返回坐标
  204.         offset = self.__class__.MAGNIFIER_OFFSET
  205.         zoom_size = self.__class__.ZOOM_SCREEN_SIZE
  206.         if x + offset + zoom_size < SCREEN_WIDTH:
  207.             x_offset = x + offset
  208.         else:
  209.             x_offset = x - offset - zoom_size
  210.         if y + offset + zoom_size + self.__class__.MAGNIFIER_ADJUST < SCREEN_HEIGHT:
  211.             y_offset = y + offset
  212.         else:
  213.             y_offset = y - offset - zoom_size - self.__class__.MAGNIFIER_ADJUST
  214.         width_height_y = max(min(self.start_y, self.end_y) - self.__class__.WIDTH_HEIGHT_OFFSET, 0)
  215.         width_height_info = (max(min(self.start_x, self.end_x), 0), width_height_y)
  216.         magnifier_coord = (x_offset, y_offset)
  217.         pos_info = (x_offset, y_offset + zoom_size + self.__class__.POS_TAG_GAP)
  218.         rgb_info = (x_offset, y_offset + zoom_size + self.__class__.RGB_TAG_GAP)
  219.         return [magnifier_coord, width_height_info, pos_info, rgb_info]
  220. class MainUI(tk.Tk):
  221.     def __init__(self):
  222.         super().__init__()
  223.         self.screenshot = ScreenshotUtils()
  224.         self.set_window()
  225.         self.menu_bar: tk.Frame = self.set_menubar()
  226.         self.cut_btn: FlatButton = self.set_cut_btn()
  227.         self.load_image_btn: FlatButton = self.set_load_image_btn()
  228.         self.copy_btn: FlatButton = self.set_copy_btn()
  229.         self.save_btn: FlatButton = self.set_save_btn()
  230.         self.turn_left_btn: FlatButton = self.set_turn_left_btn()
  231.         self.turn_right_btn: FlatButton = self.set_turn_right_btn()
  232.         self.delete_btn: FlatButton = self.set_delete_btn()
  233.         self.show_image_canvas: tk.Canvas = self.set_show_image_canvas()
  234.         self.capture_win: tk.Toplevel = None
  235.         self.full_screenshot_canvas: tk.Canvas = None
  236.         self.adjust_rect: AdjustableRect = None
  237.         self.adjust_bar: tk.Frame = None
  238.     def set_window(self):
  239.         self.title("截图工具")
  240.         width, height = self.screenshot.TkS(255), self.screenshot.TkS(30)
  241.         self.minsize(width=width, height=height)  # 这里可以根据需要调整最小宽高
  242.         self.attributes("-topmost", True)
  243.         self.geometry(f"{width}x{height}")
  244.     def set_menubar(self) -> tk.Frame:
  245.         menubar = tk.Frame(self, bg="#FFFFFF", height=ScreenshotUtils.TkS(30))
  246.         menubar.pack(fill=tk.X)
  247.         menubar.pack_propagate(False)  # 阻止内部组件改变框架大小
  248.         return menubar
  249.     def set_cut_btn(self) -> FlatButton:
  250.         btn_cut = FlatButton(
  251.             self.menu_bar, text="✂",
  252.             bg="#FFFFFF", font=("Segoe UI Emoji", 18),
  253.         )
  254.         btn_cut.pack(side=tk.LEFT, ipadx=0.1)
  255.         return btn_cut
  256.    
  257.     def set_load_image_btn(self) -> FlatButton:
  258.         btn_load = FlatButton(
  259.             self.menu_bar, text="📄", bg="#FFFFFF",
  260.             font=("Segoe UI Emoji", 18, "bold")
  261.         )
  262.         btn_load.pack(side=tk.LEFT, ipadx=0.1)
  263.         return btn_load
  264.    
  265.     def set_copy_btn(self) -> FlatButton:
  266.         btn_copy = FlatButton(
  267.             self.menu_bar, text="⎘", bg="#FFFFFF", font=("Segoe UI Symbol", 26),
  268.         )
  269.         btn_copy.pack(side=tk.LEFT, ipadx=0.1)
  270.         return btn_copy
  271.     def set_save_btn(self) -> FlatButton:
  272.         btn_save = FlatButton(
  273.             self.menu_bar, text="💾", bg="#FFFFFF", font=("Segoe UI Emoji", 18),
  274.         )
  275.         btn_save.pack(side=tk.LEFT, ipadx=0.1)
  276.         return btn_save
  277.     def set_turn_left_btn(self) -> FlatButton:
  278.         turn_left_btn = FlatButton(
  279.             self.menu_bar, text="\u25C0", bg="#FFFFFF", font=("Segoe UI Emoji", 18),
  280.         )
  281.         turn_left_btn.pack(side=tk.LEFT, ipadx=0.1)
  282.         return turn_left_btn
  283.     def set_turn_right_btn(self) -> FlatButton:
  284.         turn_page_btn = FlatButton(
  285.             self.menu_bar, text="\u25B6", bg="#FFFFFF", font=("Segoe UI Emoji", 18),
  286.         )
  287.         turn_page_btn.pack(side=tk.LEFT, ipadx=0.1)
  288.         return turn_page_btn
  289.    
  290.     def set_delete_btn(self) -> FlatButton:
  291.         delete_btn = FlatButton(
  292.             self.menu_bar, text="🗑", bg="#FFFFFF", font=("Segoe UI Symbol", 26, "bold"),
  293.         )
  294.         delete_btn.pack(side=tk.LEFT, ipadx=0.1)
  295.         return delete_btn
  296.    
  297.     def set_cancel_btn(self, parent) -> FlatButton:
  298.         cancel_btn = FlatButton(
  299.             parent, self.clear_capture_info, text="×", bg="#FFFFFF",
  300.             enter_fg="#DB1A21",fg="#CC181F", font=("微软雅黑", 20)
  301.         )
  302.         cancel_btn.pack(side=tk.RIGHT, padx=5)
  303.         return cancel_btn
  304.     def set_confirm_btn(self, parent) -> FlatButton:
  305.         confirm_btn = FlatButton(
  306.             parent, self.confirm_capture, fg="#23B34C", text="√",
  307.             enter_fg="#27C956", bg="#FFFFFF", font=("微软雅黑", 20)
  308.         )
  309.         confirm_btn.pack(side=tk.RIGHT, padx=10)
  310.         return confirm_btn
  311.     def set_show_image_canvas(self) -> tk.Canvas:
  312.         canvas = tk.Canvas(self, bg="white")
  313.         return canvas
  314.    
  315.     def set_adjust_bar(self) -> tk.Frame:
  316.         self.adjust_bar = tk.Frame(self.full_screenshot_canvas, bg="#FFFFFF", height=50)
  317.         cancel_btn = self.set_cancel_btn(self.adjust_bar)
  318.         confirm_btn = self.set_confirm_btn(self.adjust_bar)
  319.         cancel_btn.pack(side=tk.RIGHT, padx=5)
  320.         confirm_btn.pack(side=tk.RIGHT, padx=10)
  321.     def set_magnifier_frame(self, event) -> None:
  322.         initial_coord = (0, 0, 0, 0)
  323.         main_frame_id = self.full_screenshot_canvas.create_rectangle(*initial_coord, outline='#1AAE1A', width=1)
  324.         horrontal_line = self.full_screenshot_canvas.create_line(*initial_coord, fill="#1AAE1A", width=2)
  325.         vertical_line = self.full_screenshot_canvas.create_line(*initial_coord, fill="#1AAE1A", width=2)
  326.         self.screenshot.screenshot_move_widget = [main_frame_id, horrontal_line, vertical_line]
  327.         event.x = event.x + event.widget.winfo_rootx()
  328.         event.y = event.y + event.widget.winfo_rooty()
  329.     def set_full_screenshot_canvas(self, parent) -> tk.Canvas:
  330.         img = ImageGrab.grab()
  331.         self.screenshot.current_image = img
  332.         self.screenshot.pixel_reader = img.convert("RGB")
  333.         photo = ImageTk.PhotoImage(img)
  334.         full_screenshot_canvas = tk.Canvas(parent, bg="white", highlightthickness=0)
  335.         full_screenshot_canvas.create_image(0, 0, anchor=tk.NW, image=photo)
  336.         full_screenshot_canvas.image = photo
  337.         full_screenshot_canvas.pack(fill=tk.BOTH, expand=True)
  338.         return full_screenshot_canvas
  339.    
  340.     def config_pos_rgb_info(
  341.             self, parent: tk.Canvas, pos: str, rgb: str,
  342.             pos_coord: tuple[int, int], rgb_coord: tuple[int, int]
  343.         ) -> tuple[int]:
  344.         style = {"anchor": tk.NW, "font": ("微软雅黑", 9), "fill": "#FFFFFF"}
  345.         pos_info = parent.create_text(*pos_coord, text=pos, **style)
  346.         rgb_info = parent.create_text(*rgb_coord, text=rgb, **style)
  347.         pos_bg = parent.create_rectangle(*parent.bbox(pos_info), outline="#000000", fill="#000000")
  348.         rgb_bg = parent.create_rectangle(*parent.bbox(rgb_info), outline="#000000", fill="#000000")
  349.         parent.tag_raise(pos_info)
  350.         parent.tag_raise(rgb_info)
  351.         return pos_info, rgb_info, pos_bg, rgb_bg
  352. class ScreenshotTool(MainUI):
  353.     def __init__(self):
  354.         super().__init__()
  355.         self.add_command()
  356.     def add_command(self):
  357.         self.protocol("WM_DELETE_WINDOW", self.on_close)
  358.         self.show_image_canvas.bind("<Configure>", self.auto_adjust_image_size)
  359.         self.cut_btn.command = self.start_capture
  360.         self.load_image_btn.command = self.load_image
  361.         self.copy_btn.command = self.copy_image
  362.         self.save_btn.command = self.save_image
  363.         self.turn_left_btn.command = self.turn_page
  364.         self.turn_right_btn.command = self.turn_page
  365.         self.delete_btn.command = self.delete_image
  366.     def initialize_screenshot_coords(self):
  367.         self.is_drag = False
  368.         self.screenshot.start_x = self.screenshot.start_y = 0
  369.         self.screenshot.end_x = SCREEN_WIDTH
  370.         self.screenshot.end_y = SCREEN_HEIGHT
  371.     def start_capture(self, event):
  372.         self.attributes('-alpha', 0)
  373.         self.update()
  374.         self.capture_win = tk.Toplevel()
  375.         self.capture_win.geometry(f"{SCREEN_WIDTH}x{SCREEN_HEIGHT}+0+0")
  376.         self.capture_win.overrideredirect(True)
  377.         self.full_screenshot_canvas = self.set_full_screenshot_canvas(self.capture_win)
  378.         self.adjust_rect = AdjustableRect(self.full_screenshot_canvas, self.screenshot)
  379.         self.initialize_screenshot_coords()
  380.         self.adjust_rect.create_rect()
  381.         self.set_magnifier_frame(event)
  382.         self.update_magnifier(event)
  383.         self.set_adjust_bar()
  384.         self.full_screenshot_canvas.bind("<Button-1>", self.on_press)
  385.         self.full_screenshot_canvas.bind("<Motion>", self.update_magnifier)
  386.         self.full_screenshot_canvas.bind("<ButtonRelease-1>", self.on_release)
  387.     def on_press(self, event) -> None:
  388.         self.adjust_rect.on_press(event)
  389.         self.full_screenshot_canvas.unbind("<Motion>")
  390.         self.full_screenshot_canvas.bind("<Motion>", self.on_drag)
  391.     def on_drag(self, event) -> None:
  392.         self.adjust_rect.rect_adjust(event)
  393.         self.update_magnifier(event)
  394.     def on_release(self, event, resize=True) -> None:
  395.         self.unbind_all()
  396.         self.adjust_rect.on_release(event)
  397.         self.full_screenshot_canvas.bind("<Button-1>", self.enter_adjust_mode)
  398.         self.full_screenshot_canvas.bind("<Motion>", self.adjust_rect.on_hover)
  399.         self.adjust_bar.place(
  400.             x=min(self.screenshot.end_x - 300, SCREEN_WIDTH - 300), width=300,
  401.             y=max(min(self.screenshot.end_y + 10, SCREEN_HEIGHT - ScreenshotUtils.TkS(40)), 0),
  402.         )
  403.         if resize:
  404.             self.screenshot.end_x, self.screenshot.end_y = event.x, event.y
  405.         self.conceal_move_widget()
  406.         self.update_width_height_info(event)
  407.     def rect_adjust(self, event) -> None:
  408.         self.adjust_rect.rect_adjust(event)
  409.         if self.adjust_rect.anchor_id != 8 or self.adjust_rect.anchor_id == -1:
  410.             self.update_magnifier(event)
  411.     def unbind_all(self):
  412.         events = ("<Button-1>", "<Motion>", "<ButtonRelease-1>")
  413.         for event in events:
  414.             self.full_screenshot_canvas.unbind(event)
  415.     def clear_redraw_widget(self) -> None:
  416.         for redraw_widget in self.screenshot.screenshot_redraw_widget:
  417.             self.full_screenshot_canvas.delete(redraw_widget)
  418.         self.screenshot.screenshot_redraw_widget.clear()
  419.     def conceal_move_widget(self):
  420.         for widget in self.screenshot.screenshot_move_widget:
  421.             self.full_screenshot_canvas.tag_lower(widget)
  422.     def update_width_height_info(self, event) -> None:
  423.         self.clear_redraw_widget()
  424.         w, h = self.adjust_rect.rect_width_height()
  425.         coord = self.screenshot.redraw_widget_coords(event.x, event.y)[1]
  426.         wh_info_widget = self.full_screenshot_canvas.create_text(*coord, anchor=tk.NW, fill="white", text=f"{w} × {h}")
  427.         self.screenshot.screenshot_redraw_widget = [wh_info_widget]
  428.     def update_magnifier(self, event, ) -> None:
  429.         x, y = event.x, event.y
  430.         size = ScreenshotUtils.ZOOM_WIDTH
  431.         img = self.screenshot.current_image.crop((x - size//2, y - size//2, x + size//2, y + size//2))
  432.         img = img.resize((ScreenshotUtils.ZOOM_SCREEN_SIZE, ScreenshotUtils.ZOOM_SCREEN_SIZE))
  433.         photo = ImageTk.PhotoImage(img)
  434.         self.full_screenshot_canvas.image2 = photo
  435.         w, h = self.adjust_rect.rect_width_height()
  436.         self.clear_redraw_widget()
  437.         redraw_widget_coords = self.screenshot.redraw_widget_coords(x, y)
  438.         magnifier_coord, width_height_info, pos_coord, rgb_coord = redraw_widget_coords
  439.         zoom_img = self.full_screenshot_canvas.create_image(*magnifier_coord, anchor=tk.NW, image=photo)
  440.         wh_info_widget = self.full_screenshot_canvas.create_text(
  441.             *width_height_info, anchor=tk.NW, fill="white", text=f"{w} × {h}"
  442.         )
  443.         pos_rgb_info = self.config_pos_rgb_info(
  444.             self.full_screenshot_canvas, f"POS: ({x}, {y})",
  445.             f"RGB: {self.screenshot.pixel_reader.getpixel((x, y))}", pos_coord, rgb_coord
  446.         )
  447.         self.screenshot.screenshot_redraw_widget = [zoom_img, wh_info_widget, *pos_rgb_info]
  448.         self.update_magnifier_frame(*self.full_screenshot_canvas.coords(zoom_img))
  449.     def update_magnifier_frame(self, x, y) -> None:
  450.         coords = self.screenshot.move_widget_coords(x, y)
  451.         for widget, coord in zip(self.screenshot.screenshot_move_widget, coords):
  452.             self.full_screenshot_canvas.coords(widget, *coord)
  453.             self.full_screenshot_canvas.tag_raise(widget)
  454.     def enter_adjust_mode(self, event) -> None:
  455.         self.screenshot.move_start_x = event.x
  456.         self.screenshot.move_start_y = event.y
  457.         self.adjust_bar.place_forget()
  458.         for widget in self.screenshot.screenshot_redraw_widget:
  459.             self.full_screenshot_canvas.delete(widget)
  460.         for widget in self.screenshot.screenshot_move_widget:
  461.             self.full_screenshot_canvas.tag_lower(widget)
  462.         self.full_screenshot_canvas.bind("<B1-Motion>", self.rect_adjust)
  463.         self.full_screenshot_canvas.bind("<ButtonRelease-1>", lambda e: self.on_release(e, False))
  464.     def clear_capture_info(self, _) -> None:
  465.         self.capture_win.destroy()
  466.         self.full_screenshot_canvas.destroy()
  467.         self.attributes('-alpha', 1)
  468.         self.screenshot.screenshot_move_widget.clear()
  469.     def check_capture_screenshot(self) -> None:
  470.         if self.screenshot.start_x == self.screenshot.end_x or \
  471.         self.screenshot.start_y == self.screenshot.end_y:
  472.             self.initialize_screenshot_coords()
  473.         
  474.     def confirm_capture(self, event) -> None:
  475.         x1, y1, x2, y2 = self.adjust_rect.rect_coords()
  476.         self.clear_capture_info(event)
  477.         image = self.screenshot.current_image.crop((x1, y1, x2, y2))
  478.         result = self.show_image(image)
  479.         self.screenshot.final_images.append(result)
  480.         self.screenshot.page_index = len(self.screenshot.final_images) - 1
  481.         self.attributes('-topmost', 1)
  482.     def show_image(self, image: Image.Image, window_resize=True) -> None:
  483.         if window_resize:
  484.             self.geometry(f"{image.width}x{image.height+self.menu_bar.winfo_height()}")
  485.         photo = ImageTk.PhotoImage(image)
  486.         self.show_image_canvas.delete("all")
  487.         self.show_image_canvas.create_image(0, 0, anchor=tk.NW, image=photo)
  488.         self.show_image_canvas.image = photo
  489.         self.show_image_canvas.pack(fill=tk.BOTH, expand=True)
  490.         return image
  491.     def current_limiting(self, canvas_w, canvas_h) -> bool:
  492.         if not self.screenshot.final_images:
  493.             return True
  494.         try:
  495.             coords = self.show_image_canvas.bbox("all")
  496.             img_view_w, img_view_h = coords[2], coords[3]
  497.         except TypeError:
  498.             return True
  499.         if (img_view_w == canvas_w and img_view_h < canvas_h) or \
  500.         (img_view_h == canvas_h and img_view_w < canvas_w):
  501.             return True
  502.         return False
  503.     def auto_adjust_image_size(self, _) -> None:
  504.         canvas_w = self.show_image_canvas.winfo_width()
  505.         canvas_h = self.show_image_canvas.winfo_height()
  506.         if self.current_limiting(canvas_w, canvas_h):
  507.             return
  508.         image = self.screenshot.final_images[self.screenshot.page_index]
  509.         width_ratio = canvas_w / image.width
  510.         height_ratio = canvas_h / image.height
  511.         ratio = min(width_ratio, height_ratio)
  512.         new_width = int(image.width * ratio)
  513.         new_height = int(image.height * ratio)
  514.         resized_image = image.resize((new_width, new_height), Image.LANCZOS)
  515.         self.show_image(resized_image, False)
  516.     def load_image(self, _) -> None:
  517.         file_types = (("Image files", "*.jpg *.png *.jpeg"),)
  518.         img_path = filedialog.askopenfilename(filetypes=file_types)
  519.         if not img_path:
  520.             return
  521.         try:
  522.             image = Image.open(img_path)
  523.         except UnidentifiedImageError:
  524.             return messagebox.showerror("错误", "无法识别该图片文件!")
  525.         self.show_image(image)
  526.         self.screenshot.final_images.append(image)
  527.         self.screenshot.page_index = len(self.screenshot.final_images) - 1
  528.     def copy_image(self, _) -> None:
  529.         def __copy_image():
  530.             self.copy_btn.disable()
  531.             self.copy_btn.config(fg="#25C253")
  532.             image = self.screenshot.final_images[self.screenshot.page_index]
  533.             temp_name = f"{int.from_bytes(os.urandom(4), byteorder='big')}.png"
  534.             temp_file = os.path.join(TEMP_FILE_PATH, temp_name)
  535.             image.save(temp_file)
  536.             startupinfo = sp.STARTUPINFO()
  537.             startupinfo.dwFlags |= sp.STARTF_USESHOWWINDOW
  538.             args = ['powershell', f'Get-Item {temp_file} | Set-Clipboard']
  539.             process = sp.Popen(args=args, startupinfo=startupinfo)
  540.             process.wait()
  541.             self.title("截图工具")
  542.             self.copy_btn.enable()
  543.             self.copy_btn.config(fg="#3B3B3B")
  544.         if len(self.screenshot.final_images) == 0:
  545.             return messagebox.showerror("复制失败", "未检测到截取图像")
  546.         self.title("复制成功!")
  547.         Thread(target=__copy_image, daemon=True).start()
  548.     def save_image(self, _) -> None:
  549.         try:
  550.             image = self.screenshot.final_images[self.screenshot.page_index]
  551.             filename = filedialog.asksaveasfilename(
  552.                 defaultextension=".png",
  553.                 filetypes=[("PNG files", "*.png"), ("JPEG files", "*.jpg")],
  554.                 initialfile=f"{image.width}x{image.height}.png"
  555.             )
  556.             if not filename:
  557.                 return
  558.             image.save(filename)
  559.         except IndexError:
  560.             messagebox.showerror("保存失败", "未检测到截取图像")
  561.     def turn_page(self, event) -> None:
  562.         if len(self.screenshot.final_images) == 0:
  563.             return messagebox.showinfo("提示", "暂无图片可切换!")
  564.         if event.widget == self.turn_left_btn:
  565.             if self.screenshot.page_index == 0:
  566.                 return messagebox.showinfo("提示", "已经是第一张图片!")
  567.             self.screenshot.page_index -= 1
  568.         else:
  569.             if self.screenshot.page_index == len(self.screenshot.final_images) - 1:
  570.                 return messagebox.showinfo("提示", "已经是最后一张图片!")
  571.             self.screenshot.page_index += 1
  572.         self.show_image(self.screenshot.final_images[self.screenshot.page_index])
  573.     def delete_image(self, _) -> None:
  574.         if len(self.screenshot.final_images) == 0:
  575.             return messagebox.showinfo("提示", "暂无图片可删除!")
  576.         if not messagebox.askokcancel("提示", "确认删除当前图片?"):
  577.             return
  578.         self.screenshot.final_images.pop(self.screenshot.page_index)
  579.         if self.screenshot.page_index == len(self.screenshot.final_images):
  580.             self.screenshot.page_index -= 1
  581.         if len(self.screenshot.final_images) == 0:
  582.             self.show_image_canvas.delete("all")
  583.             self.geometry(f"{self.screenshot.TkS(255)}x{self.screenshot.TkS(30)}")
  584.         else:
  585.             self.show_image(self.screenshot.final_images[self.screenshot.page_index])
  586.     def on_close(self):
  587.         def delete_tmp_files():
  588.             for file in os.scandir(TEMP_FILE_PATH):
  589.                 os.remove(file)
  590.         self.destroy()
  591.         Thread(target=delete_tmp_files).start()
  592. if __name__ == "__main__":
  593.     app = ScreenshotTool()
  594.     SCREEN_WIDTH = app.winfo_screenwidth()
  595.     SCREEN_HEIGHT = app.winfo_screenheight()
  596.     app.mainloop()
复制代码

四、打包步伐

如无Python环境,可以通过以下链接下载:
网盘链接中为老版本,新版本请自行前去github堆栈,链接:Just-A-Freshman/LightScreenshot: A tool use to make screenshot, contains some common funcitons.
通过百度网盘分享的文件:多版本截图工具.zip
链接:
提取码: h5yx
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表