Python截图轻量化工具

一、兼容局限性

这是用Python做的截图工具,不过由于使用了ctypes调用了Windows的API, 同时访问了Windows中"C:/Windows/Cursors/"中的.cur光标样式文件, 这个工具只适用于Windows环境;

如果要提升其跨平台性的话,需要考虑替换ctypes的一些专属于Windows的API以及设置不同的.cur访问方式

二、轻量化

该模块只使用了PIL这一个第三方库,和别的使用pygame\pyautogui等模块不同,该工具着重强调轻量化,无关或没必要使用的库尽可能不使用。

目前截图功能已经高度接近微信截图,不过没有编辑功能(建议保存后再编辑);里面包含了一些必备的快捷功能,不过还没有绑定快捷键;有需要者自行为代码中加入功能。

三、基础功能演示

功能演示

四、功能阐述

①基本的截图后等待,可继续移动边框,拖动调整上下左右对角位置(类似微信)

②打开图片文件,可以直接显示在面板上;

③复制图片文件到剪切板上;

④保存当前视图上的图片。

⑤对截取过的图片进行翻页,快速查看不同截图;

⑥删除视图中的图片,不影响磁盘文件,只是删除内存中的图片引用;

⑦通过调节窗口可动态调整图片在视图中的大小(不改变根引用图片);

⑧窗口置顶,即无论怎么打开其他窗口,该窗口都始终置于顶部不会被遮挡,方便我们对截图后的图片放置到一侧,参照图片进行操作

五、完整代码

代码如下,单文件,请自行安装PIL模块,确保在Windows环境上运行。

import os
import ctypes
import subprocess as sp
import tkinter as tk
from threading import Thread
from tkinter import messagebox, filedialog
from PIL import Image, ImageGrab, ImageTk, UnidentifiedImageErrorScaleFactor = ctypes.windll.shcore.GetScaleFactorForDevice(0)
ctypes.windll.shcore.SetProcessDpiAwareness(1)
TEMP_FILE_PATH = os.path.join(os.getenv("TEMP"), "tempScreenshot")
os.makedirs(TEMP_FILE_PATH, exist_ok=True)class FlatButton(tk.Label):def __init__(self, parent, command=None, enter_fg="#000000", click_color="#25C253", *args, **kwargs):super().__init__(parent, *args, **kwargs)self.__fg = fg = kwargs.get("fg", "#3B3B3B")self.__enter_fg = enter_fgself.__click_fg = click_colorself.command = commandself.config(cursor="hand2", fg=fg)self.enable()if fg == enter_fg:raise ValueError("enter_fg must be different from fg")def enable(self):self.bind("<Enter>", lambda _: self.config(fg=self.__enter_fg))self.bind("<Leave>", lambda _: self.config(fg=self.__fg))self.bind("<Button-1>", lambda _: self.config(fg=self.__click_fg))self.bind("<ButtonRelease-1>", self.__command)def disable(self):for event in ("<Enter>", "<Leave>", "<Button-1>", "<ButtonRelease-1>"):self.unbind(event)def __command(self, event):try:if self.cget("fg") in (self.__enter_fg, self.__click_fg):self.command(event)self.config(fg=self.__fg)except tk.TclError:passexcept TypeError:self.config(fg=self.__fg)class AdjustableRect(object):"""The judgement seq is so important that you must care about:(right, bottom), (left, top), (right, top), (left, bottom),(center_x, top), (center_x, bottom), (left, center_y, ), (right, center_y)"""ANCHOR_SIZE = 3ANCHOR_HOVER_DISTANCE = 20CURSOR_FILES_NAME = ["aero_nwse_l.cur", "aero_nesw_l.cur", "aero_ns_l.cur", "aero_ew_l.cur"]CURSOR_FILES = [f"@C:/Windows/Cursors/{cursor_file}" for cursor_file in CURSOR_FILES_NAME]CURSORS = [CURSOR_FILES[0], CURSOR_FILES[0], CURSOR_FILES[1], CURSOR_FILES[1],CURSOR_FILES[2], CURSOR_FILES[2], CURSOR_FILES[3], CURSOR_FILES[3],"fleur", "arrow"]def __init__(self, parent, screenshot):self.parent: tk.Canvas = parentself.screenshot: ScreenshotUtils = screenshotself.__rect: int = 0self.__anchors: list[int] = []self.anchor_id: int = 0def rect_coords(self) -> tuple[int, int, int, int]:return self.parent.coords(self.__rect)def anchor_coords(self) -> tuple[int, int, int, int]:left, top, right, bottom = self.rect_coords()horizontal_middle = (left + right) // 2vertical_middle = (top + bottom) // 2return ((left, top), (horizontal_middle, top), (right, top), (right, vertical_middle),(right, bottom), (horizontal_middle, bottom), (left, bottom), (left, vertical_middle))def rect_width_height(self) -> tuple[int, int]:left, top, right, bottom = self.rect_coords()return int(right - left), int(bottom - top)def get_anchor(self, event) -> int:cls = self.__class__left, top, right, bottom = self.rect_coords()center_x, center_y = (left + right) // 2, (top + bottom) // 2def near(actual, target):return abs(actual - target) < cls.ANCHOR_HOVER_DISTANCE# 务必注意这个判断顺序,这与后面rect_adjust密切相关judgement_pos = ((right, bottom), (left, top), (right, top), (left, bottom),(center_x, top), (center_x, bottom), (left, center_y, ), (right, center_y))for index, pos in enumerate(judgement_pos):if near(event.x, pos[0]) and near(event.y, pos[1]):return indexif left < event.x < right and top < event.y < bottom:return 8return -1def create_anchors(self):cls = self.__class__for coord in self.anchor_coords():anchor = self.parent.create_rectangle(coord[0]-cls.ANCHOR_SIZE, coord[1]-cls.ANCHOR_SIZE,coord[0]+cls.ANCHOR_SIZE, coord[1]+cls.ANCHOR_SIZE,fill="#1AAE1A", outline="#1AAE1A")self.__anchors.append(anchor)def create_rect(self) -> None:self.__rect= self.parent.create_rectangle(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, outline='#1AAE1A', width=2)self.create_anchors()def move_anchors(self):cls = self.__class__for anchor, coord in zip(self.__anchors, self.anchor_coords()):self.parent.coords(anchor, coord[0]-cls.ANCHOR_SIZE, coord[1]-2, coord[0]+cls.ANCHOR_SIZE, coord[1]+cls.ANCHOR_SIZE)def on_press(self, event):self.screenshot.start_x = event.xself.screenshot.start_y = event.ydef on_release(self, _):self.screenshot.start_x, self.screenshot.start_y,\self.screenshot.end_x, self.screenshot.end_y = self.rect_coords()def on_hover(self, event):self.anchor_id = self.get_anchor(event)cursor = self.CURSORS[self.anchor_id]self.parent.config(cursor=cursor)def move_rect(self, event):offset_x = event.x - self.screenshot.move_start_xoffset_y = event.y - self.screenshot.move_start_yif self.screenshot.start_x + offset_x > 0 and self.screenshot.end_x + offset_x < SCREEN_WIDTH:self.screenshot.start_x += offset_xself.screenshot.end_x += offset_xif  self.screenshot.start_y + offset_y > 0 and self.screenshot.end_y + offset_y < SCREEN_HEIGHT:self.screenshot.start_y += offset_yself.screenshot.end_y += offset_yself.screenshot.move_start_x = event.xself.screenshot.move_start_y = event.yself.parent.coords(self.__rect, self.screenshot.start_x, self.screenshot.start_y, self.screenshot.end_x, self.screenshot.end_y)self.move_anchors()def rect_adjust(self, event):if self.anchor_id == 8:return self.move_rect(event)if self.anchor_id == 0:self.screenshot.end_x, self.screenshot.end_y = event.x, event.yelif self.anchor_id == 1:self.screenshot.start_x, self.screenshot.start_y = event.x, event.yelif self.anchor_id == 2:self.screenshot.end_x, self.screenshot.start_y = event.x, event.yelif self.anchor_id == 3:self.screenshot.start_x, self.screenshot.end_y = event.x, event.yelif self.anchor_id == 4:self.screenshot.start_y = event.yelif self.anchor_id == 5:self.screenshot.end_y = event.yelif self.anchor_id == 6:self.screenshot.start_x = event.xelif self.anchor_id == 7:self.screenshot.end_x = event.xelse:returnself.parent.coords(self.__rect, self.screenshot.start_x, self.screenshot.start_y, self.screenshot.end_x, self.screenshot.end_y)self.move_anchors()class ScreenshotUtils(object):"""截图的关键是坐标;这个类管理着图片的引用和截图坐标;"""def TkS(value) -> int:return int(ScaleFactor/100*value)ZOOM: int = 4ZOOM_WIDTH: float = TkS(28.75)ZOOM_SCREEN_SIZE: int = int(ZOOM_WIDTH*ZOOM)MAGNIFIER_OFFSET: int = 36WIDTH_HEIGHT_OFFSET = 40POS_TAG_GAP = 10RGB_TAG_GAP = 47MAGNIFIER_ADJUST = 70AJUST_BAR_WIDTH: int = TkS(100)def __init__(self):self.start_x = self.start_y = self.end_x = self.end_y = 0self.move_start_x = self.move_start_y = self.move_end_x = self.move_end_y = 0self.page_index: int = 0self.current_image: Image.Image = Noneself.pixel_reader = Noneself.final_images: list[Image.Image] = list()# 这种是只移动但不改变大小和内容的控件,只需移动无需重绘self.screenshot_move_widget = list()# 这种是移动和改变大小的控件,需要实时重绘self.screenshot_redraw_widget = list()@staticmethoddef TkS(value) -> int:return int(ScaleFactor/100*value)@classmethoddef move_widget_coords(cls, x, y) -> list[tuple[int, int, int, int]]:# 按照主框架,水平线,垂直线的顺序返回坐标main_frame_coord  = (x, y, x+cls.ZOOM_SCREEN_SIZE, y+cls.ZOOM_SCREEN_SIZE)horrontal_line_coord = (x, y+cls.ZOOM_SCREEN_SIZE // 2, x+cls.ZOOM_SCREEN_SIZE, y+cls.ZOOM_SCREEN_SIZE // 2)vertical_line_coord = (x+cls.ZOOM_SCREEN_SIZE // 2, y, x+cls.ZOOM_SCREEN_SIZE // 2, y+cls.ZOOM_SCREEN_SIZE)return [main_frame_coord, horrontal_line_coord, vertical_line_coord]def redraw_widget_coords(self, x, y) -> list[tuple]:# 按照"放大镜图像"、"长 × 宽"、"POS标签"、"RGB标签"的顺序返回坐标offset = self.__class__.MAGNIFIER_OFFSETzoom_size = self.__class__.ZOOM_SCREEN_SIZEif x + offset + zoom_size < SCREEN_WIDTH:x_offset = x + offsetelse:x_offset = x - offset - zoom_sizeif y + offset + zoom_size + self.__class__.MAGNIFIER_ADJUST < SCREEN_HEIGHT:y_offset = y + offsetelse:y_offset = y - offset - zoom_size - self.__class__.MAGNIFIER_ADJUSTwidth_height_y = max(min(self.start_y, self.end_y) - self.__class__.WIDTH_HEIGHT_OFFSET, 0)width_height_info = (max(min(self.start_x, self.end_x), 0), width_height_y)magnifier_coord = (x_offset, y_offset)pos_info = (x_offset, y_offset + zoom_size + self.__class__.POS_TAG_GAP)rgb_info = (x_offset, y_offset + zoom_size + self.__class__.RGB_TAG_GAP)return [magnifier_coord, width_height_info, pos_info, rgb_info]class MainUI(tk.Tk):def __init__(self):super().__init__()self.screenshot = ScreenshotUtils()self.set_window()self.menu_bar: tk.Frame = self.set_menubar()self.cut_btn: FlatButton = self.set_cut_btn()self.load_image_btn: FlatButton = self.set_load_image_btn()self.copy_btn: FlatButton = self.set_copy_btn()self.save_btn: FlatButton = self.set_save_btn()self.turn_left_btn: FlatButton = self.set_turn_left_btn()self.turn_right_btn: FlatButton = self.set_turn_right_btn()self.delete_btn: FlatButton = self.set_delete_btn()self.show_image_canvas: tk.Canvas = self.set_show_image_canvas()self.capture_win: tk.Toplevel = Noneself.full_screenshot_canvas: tk.Canvas = Noneself.adjust_rect: AdjustableRect = Noneself.adjust_bar: tk.Frame = Nonedef set_window(self):self.title("截图工具")width, height = self.screenshot.TkS(255), self.screenshot.TkS(30)self.minsize(width=width, height=height)  # 这里可以根据需要调整最小宽高self.attributes("-topmost", True)self.geometry(f"{width}x{height}")def set_menubar(self) -> tk.Frame:menubar = tk.Frame(self, bg="#FFFFFF", height=ScreenshotUtils.TkS(30))menubar.pack(fill=tk.X)menubar.pack_propagate(False)  # 阻止内部组件改变框架大小return menubardef set_cut_btn(self) -> FlatButton:btn_cut = FlatButton(self.menu_bar, text="✂", bg="#FFFFFF", font=("Segoe UI Emoji", 18),)btn_cut.pack(side=tk.LEFT, ipadx=0.1)return btn_cutdef set_load_image_btn(self) -> FlatButton:btn_load = FlatButton(self.menu_bar, text="📄", bg="#FFFFFF",font=("Segoe UI Emoji", 18, "bold"))btn_load.pack(side=tk.LEFT, ipadx=0.1)return btn_loaddef set_copy_btn(self) -> FlatButton:btn_copy = FlatButton(self.menu_bar, text="⎘", bg="#FFFFFF", font=("Segoe UI Symbol", 26),)btn_copy.pack(side=tk.LEFT, ipadx=0.1)return btn_copydef set_save_btn(self) -> FlatButton:btn_save = FlatButton(self.menu_bar, text="💾", bg="#FFFFFF", font=("Segoe UI Emoji", 18),)btn_save.pack(side=tk.LEFT, ipadx=0.1)return btn_savedef set_turn_left_btn(self) -> FlatButton:turn_left_btn = FlatButton(self.menu_bar, text="\u25C0", bg="#FFFFFF", font=("Segoe UI Emoji", 18),)turn_left_btn.pack(side=tk.LEFT, ipadx=0.1)return turn_left_btndef set_turn_right_btn(self) -> FlatButton:turn_page_btn = FlatButton(self.menu_bar, text="\u25B6", bg="#FFFFFF", font=("Segoe UI Emoji", 18),)turn_page_btn.pack(side=tk.LEFT, ipadx=0.1)return turn_page_btndef set_delete_btn(self) -> FlatButton:delete_btn = FlatButton(self.menu_bar, text="🗑", bg="#FFFFFF", font=("Segoe UI Symbol", 26, "bold"),)delete_btn.pack(side=tk.LEFT, ipadx=0.1)return delete_btndef set_cancel_btn(self, parent) -> FlatButton:cancel_btn = FlatButton(parent, self.clear_capture_info, text="×", bg="#FFFFFF",enter_fg="#DB1A21",fg="#CC181F", font=("微软雅黑", 20))cancel_btn.pack(side=tk.RIGHT, padx=5)return cancel_btndef set_confirm_btn(self, parent) -> FlatButton:confirm_btn = FlatButton(parent, self.confirm_capture, fg="#23B34C", text="√",enter_fg="#27C956", bg="#FFFFFF", font=("微软雅黑", 20))confirm_btn.pack(side=tk.RIGHT, padx=10)return confirm_btndef set_show_image_canvas(self) -> tk.Canvas:canvas = tk.Canvas(self, bg="white")return canvasdef set_adjust_bar(self) -> tk.Frame:self.adjust_bar = tk.Frame(self.full_screenshot_canvas, bg="#FFFFFF", height=50)cancel_btn = self.set_cancel_btn(self.adjust_bar)confirm_btn = self.set_confirm_btn(self.adjust_bar)cancel_btn.pack(side=tk.RIGHT, padx=5)confirm_btn.pack(side=tk.RIGHT, padx=10)def set_magnifier_frame(self, event) -> None:initial_coord = (0, 0, 0, 0)main_frame_id = self.full_screenshot_canvas.create_rectangle(*initial_coord, outline='#1AAE1A', width=1)horrontal_line = self.full_screenshot_canvas.create_line(*initial_coord, fill="#1AAE1A", width=2)vertical_line = self.full_screenshot_canvas.create_line(*initial_coord, fill="#1AAE1A", width=2)self.screenshot.screenshot_move_widget = [main_frame_id, horrontal_line, vertical_line]event.x = event.x + event.widget.winfo_rootx()event.y = event.y + event.widget.winfo_rooty()def set_full_screenshot_canvas(self, parent) -> tk.Canvas:img = ImageGrab.grab()self.screenshot.current_image = imgself.screenshot.pixel_reader = img.convert("RGB")photo = ImageTk.PhotoImage(img)full_screenshot_canvas = tk.Canvas(parent, bg="white", highlightthickness=0)full_screenshot_canvas.create_image(0, 0, anchor=tk.NW, image=photo)full_screenshot_canvas.image = photofull_screenshot_canvas.pack(fill=tk.BOTH, expand=True)return full_screenshot_canvasdef config_pos_rgb_info(self, parent: tk.Canvas, pos: str, rgb: str, pos_coord: tuple[int, int], rgb_coord: tuple[int, int]) -> tuple[int]:style = {"anchor": tk.NW, "font": ("微软雅黑", 9), "fill": "#FFFFFF"}pos_info = parent.create_text(*pos_coord, text=pos, **style)rgb_info = parent.create_text(*rgb_coord, text=rgb, **style)pos_bg = parent.create_rectangle(*parent.bbox(pos_info), outline="#000000", fill="#000000")rgb_bg = parent.create_rectangle(*parent.bbox(rgb_info), outline="#000000", fill="#000000")parent.tag_raise(pos_info)parent.tag_raise(rgb_info)return pos_info, rgb_info, pos_bg, rgb_bgclass ScreenshotTool(MainUI):def __init__(self):super().__init__()self.add_command()def add_command(self):self.protocol("WM_DELETE_WINDOW", self.on_close)self.show_image_canvas.bind("<Configure>", self.auto_adjust_image_size)self.cut_btn.command = self.start_captureself.load_image_btn.command = self.load_imageself.copy_btn.command = self.copy_imageself.save_btn.command = self.save_imageself.turn_left_btn.command = self.turn_pageself.turn_right_btn.command = self.turn_pageself.delete_btn.command = self.delete_imagedef initialize_screenshot_coords(self):self.is_drag = Falseself.screenshot.start_x = self.screenshot.start_y = 0self.screenshot.end_x = SCREEN_WIDTHself.screenshot.end_y = SCREEN_HEIGHTdef start_capture(self, event):self.attributes('-alpha', 0)self.update()self.capture_win = tk.Toplevel()self.capture_win.geometry(f"{SCREEN_WIDTH}x{SCREEN_HEIGHT}+0+0")self.capture_win.overrideredirect(True)self.full_screenshot_canvas = self.set_full_screenshot_canvas(self.capture_win)self.adjust_rect = AdjustableRect(self.full_screenshot_canvas, self.screenshot)self.initialize_screenshot_coords()self.adjust_rect.create_rect()self.set_magnifier_frame(event)self.update_magnifier(event)self.set_adjust_bar()self.full_screenshot_canvas.bind("<Button-1>", self.on_press)self.full_screenshot_canvas.bind("<Motion>", self.update_magnifier)self.full_screenshot_canvas.bind("<ButtonRelease-1>", self.on_release)def on_press(self, event) -> None:self.adjust_rect.on_press(event)self.full_screenshot_canvas.unbind("<Motion>")self.full_screenshot_canvas.bind("<Motion>", self.on_drag)def on_drag(self, event) -> None:self.adjust_rect.rect_adjust(event)self.update_magnifier(event)def on_release(self, event, resize=True) -> None:self.unbind_all()self.adjust_rect.on_release(event)self.full_screenshot_canvas.bind("<Button-1>", self.enter_adjust_mode)self.full_screenshot_canvas.bind("<Motion>", self.adjust_rect.on_hover)self.adjust_bar.place(x=min(self.screenshot.end_x - 300, SCREEN_WIDTH - 300), width=300,y=max(min(self.screenshot.end_y + 10, SCREEN_HEIGHT - ScreenshotUtils.TkS(40)), 0),)if resize:self.screenshot.end_x, self.screenshot.end_y = event.x, event.yself.conceal_move_widget()self.update_width_height_info(event)def rect_adjust(self, event) -> None:self.adjust_rect.rect_adjust(event)if self.adjust_rect.anchor_id != 8 or self.adjust_rect.anchor_id == -1:self.update_magnifier(event)def unbind_all(self):events = ("<Button-1>", "<Motion>", "<ButtonRelease-1>")for event in events:self.full_screenshot_canvas.unbind(event)def clear_redraw_widget(self) -> None:for redraw_widget in self.screenshot.screenshot_redraw_widget:self.full_screenshot_canvas.delete(redraw_widget)self.screenshot.screenshot_redraw_widget.clear()def conceal_move_widget(self):for widget in self.screenshot.screenshot_move_widget:self.full_screenshot_canvas.tag_lower(widget)def update_width_height_info(self, event) -> None:self.clear_redraw_widget()w, h = self.adjust_rect.rect_width_height()coord = self.screenshot.redraw_widget_coords(event.x, event.y)[1]wh_info_widget = self.full_screenshot_canvas.create_text(*coord, anchor=tk.NW, fill="white", text=f"{w} × {h}")self.screenshot.screenshot_redraw_widget = [wh_info_widget]def update_magnifier(self, event, ) -> None:x, y = event.x, event.ysize = ScreenshotUtils.ZOOM_WIDTHimg = self.screenshot.current_image.crop((x - size//2, y - size//2, x + size//2, y + size//2))img = img.resize((ScreenshotUtils.ZOOM_SCREEN_SIZE, ScreenshotUtils.ZOOM_SCREEN_SIZE))photo = ImageTk.PhotoImage(img)self.full_screenshot_canvas.image2 = photow, h = self.adjust_rect.rect_width_height()self.clear_redraw_widget()redraw_widget_coords = self.screenshot.redraw_widget_coords(x, y)magnifier_coord, width_height_info, pos_coord, rgb_coord = redraw_widget_coordszoom_img = self.full_screenshot_canvas.create_image(*magnifier_coord, anchor=tk.NW, image=photo)wh_info_widget = self.full_screenshot_canvas.create_text(*width_height_info, anchor=tk.NW, fill="white", text=f"{w} × {h}")pos_rgb_info = self.config_pos_rgb_info(self.full_screenshot_canvas, f"POS: ({x}, {y})", f"RGB: {self.screenshot.pixel_reader.getpixel((x, y))}", pos_coord, rgb_coord)self.screenshot.screenshot_redraw_widget = [zoom_img, wh_info_widget, *pos_rgb_info]self.update_magnifier_frame(*self.full_screenshot_canvas.coords(zoom_img))def update_magnifier_frame(self, x, y) -> None:coords = self.screenshot.move_widget_coords(x, y)for widget, coord in zip(self.screenshot.screenshot_move_widget, coords):self.full_screenshot_canvas.coords(widget, *coord)self.full_screenshot_canvas.tag_raise(widget)def enter_adjust_mode(self, event) -> None:self.screenshot.move_start_x = event.xself.screenshot.move_start_y = event.yself.adjust_bar.place_forget()for widget in self.screenshot.screenshot_redraw_widget:self.full_screenshot_canvas.delete(widget)for widget in self.screenshot.screenshot_move_widget:self.full_screenshot_canvas.tag_lower(widget)self.full_screenshot_canvas.bind("<B1-Motion>", self.rect_adjust)self.full_screenshot_canvas.bind("<ButtonRelease-1>", lambda e: self.on_release(e, False))def clear_capture_info(self, _) -> None:self.capture_win.destroy()self.full_screenshot_canvas.destroy()self.attributes('-alpha', 1)self.screenshot.screenshot_move_widget.clear()def check_capture_screenshot(self) -> None:if self.screenshot.start_x == self.screenshot.end_x or \self.screenshot.start_y == self.screenshot.end_y:self.initialize_screenshot_coords()def confirm_capture(self, event) -> None:x1, y1, x2, y2 = self.adjust_rect.rect_coords()self.clear_capture_info(event)image = self.screenshot.current_image.crop((x1, y1, x2, y2))result = self.show_image(image)self.screenshot.final_images.append(result)self.screenshot.page_index = len(self.screenshot.final_images) - 1self.attributes('-topmost', 1)def show_image(self, image: Image.Image, window_resize=True) -> None:if window_resize:self.geometry(f"{image.width}x{image.height+self.menu_bar.winfo_height()}")photo = ImageTk.PhotoImage(image)self.show_image_canvas.delete("all")self.show_image_canvas.create_image(0, 0, anchor=tk.NW, image=photo)self.show_image_canvas.image = photoself.show_image_canvas.pack(fill=tk.BOTH, expand=True)return imagedef current_limiting(self, canvas_w, canvas_h) -> bool:if not self.screenshot.final_images:return Truetry:coords = self.show_image_canvas.bbox("all")img_view_w, img_view_h = coords[2], coords[3]except TypeError:return Trueif (img_view_w == canvas_w and img_view_h < canvas_h) or \(img_view_h == canvas_h and img_view_w < canvas_w):return Truereturn Falsedef auto_adjust_image_size(self, _) -> None:canvas_w = self.show_image_canvas.winfo_width()canvas_h = self.show_image_canvas.winfo_height()if self.current_limiting(canvas_w, canvas_h):returnimage = self.screenshot.final_images[self.screenshot.page_index]width_ratio = canvas_w / image.widthheight_ratio = canvas_h / image.heightratio = min(width_ratio, height_ratio)new_width = int(image.width * ratio)new_height = int(image.height * ratio)resized_image = image.resize((new_width, new_height), Image.LANCZOS)self.show_image(resized_image, False)def load_image(self, _) -> None:file_types = (("Image files", "*.jpg *.png *.jpeg"),)img_path = filedialog.askopenfilename(filetypes=file_types)if not img_path:returntry:image = Image.open(img_path)except UnidentifiedImageError:return messagebox.showerror("错误", "无法识别该图片文件!")self.show_image(image)self.screenshot.final_images.append(image)self.screenshot.page_index = len(self.screenshot.final_images) - 1def copy_image(self, _) -> None:def __copy_image():self.copy_btn.disable()self.copy_btn.config(fg="#25C253")image = self.screenshot.final_images[self.screenshot.page_index]temp_name = f"{int.from_bytes(os.urandom(4), byteorder='big')}.png"temp_file = os.path.join(TEMP_FILE_PATH, temp_name)image.save(temp_file)startupinfo = sp.STARTUPINFO()startupinfo.dwFlags |= sp.STARTF_USESHOWWINDOWargs = ['powershell', f'Get-Item {temp_file} | Set-Clipboard']process = sp.Popen(args=args, startupinfo=startupinfo)process.wait()self.title("截图工具")self.copy_btn.enable()self.copy_btn.config(fg="#3B3B3B")if len(self.screenshot.final_images) == 0:return messagebox.showerror("复制失败", "未检测到截取图像")self.title("复制成功!")Thread(target=__copy_image, daemon=True).start()def save_image(self, _) -> None:try:image = self.screenshot.final_images[self.screenshot.page_index]filename = filedialog.asksaveasfilename(defaultextension=".png",filetypes=[("PNG files", "*.png"), ("JPEG files", "*.jpg")],initialfile=f"{image.width}x{image.height}.png")if not filename:returnimage.save(filename)except IndexError:messagebox.showerror("保存失败", "未检测到截取图像")def turn_page(self, event) -> None:if len(self.screenshot.final_images) == 0:return messagebox.showinfo("提示", "暂无图片可切换!")if event.widget == self.turn_left_btn:if self.screenshot.page_index == 0:return messagebox.showinfo("提示", "已经是第一张图片!")self.screenshot.page_index -= 1else:if self.screenshot.page_index == len(self.screenshot.final_images) - 1:return messagebox.showinfo("提示", "已经是最后一张图片!")self.screenshot.page_index += 1self.show_image(self.screenshot.final_images[self.screenshot.page_index])def delete_image(self, _) -> None:if len(self.screenshot.final_images) == 0:return messagebox.showinfo("提示", "暂无图片可删除!")if not messagebox.askokcancel("提示", "确认删除当前图片?"):returnself.screenshot.final_images.pop(self.screenshot.page_index)if self.screenshot.page_index == len(self.screenshot.final_images):self.screenshot.page_index -= 1if len(self.screenshot.final_images) == 0:self.show_image_canvas.delete("all")self.geometry(f"{self.screenshot.TkS(255)}x{self.screenshot.TkS(30)}")else:self.show_image(self.screenshot.final_images[self.screenshot.page_index])def on_close(self):def delete_tmp_files():for file in os.scandir(TEMP_FILE_PATH):os.remove(file)self.destroy()Thread(target=delete_tmp_files).start()if __name__ == "__main__":app = ScreenshotTool()SCREEN_WIDTH = app.winfo_screenwidth()SCREEN_HEIGHT = app.winfo_screenheight()app.mainloop()

六、打包程序

如无Python环境,可以通过以下链接下载:
链接:https://pan.quark.cn/s/f87bf5f9e825
提取码:nMGP

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/69556.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ labmbd表达式

文章目录 C++ Lambda 表达式详解1. Lambda 表达式的组成部分:2. Lambda 语法示例(1) 最简单的 Lambda(2) 带参数的 Lambda(3) 指定返回类型的 Lambda3. 捕获外部变量(1) 值捕获(复制)(2) 引用捕获(3) 捕获所有变量4. Lambda 在 STL 中的应用5. Lambda 作为 `std::function`6…

【Deepseek私有化部署】解决 Anything LLM 上传文档一直转圈上传失败问题

这里写自定义目录标题 一、问题描述二、原因分析&#xff08;一&#xff09;Embedder 在 Anything LLM 中的核心作用&#xff08;二&#xff09;默认配置与 Deepseek 的适配问题&#xff08;三&#xff09;未正确配置 nomic - embed - text 引发的异常 三、解决途径&#xff08…

神经网络|(九)概率论基础知识-泊松分布及python仿真

【1】引言 在前序学习进程中&#xff0c;我们已经知晓二项分布是多重伯努利分布&#xff0c;二伯努利分布对应的是可以无限重复、结果只有两种可能的随机试验。 相关文章链接为&#xff1a; 神经网络|(八)概率论基础知识-二项分布及python仿真-CSDN博客 上述文章还调用nump…

使用 OpenGL ES 渲染一个四边形

使用 OpenGL ES 渲染一个四边形 在 iOS 开发中,OpenGL ES 是一个强大的工具,用于实现高性能的 2D 和 3D 图形渲染。本文将通过一个完整的代码示例,详细解析如何使用 OpenGL ES 渲染一个简单的四边形。我们将从基础概念入手,逐步讲解代码的每个部分,帮助你理解 OpenGL ES …

《从0到1CTFer成长之路》逆向工程个人笔记--静态分析

上一篇文章&#xff1a;《从0到1CTFer成长之路》逆向工程个人笔记--逆向工程基础 IDA 使用入门 加载文件 打开 IDA&#xff0c;点击 GO&#xff0c;即可把程序拖拽到 IDA 中 IDA 分为 32bit 和 64bit 两种架构&#xff0c;选择哪种结构&#xff0c;可以在把程序拖拽到 IDA 后…

【机器学习】训练(Training)、验证(Validation)和测试(Testing)

机器学习中训练(Training)、验证(Validation)和测试(Testing)这三个阶段的作用和关系。 1. 训练阶段 (Training) - 使用训练集数据来训练模型 - 模型通过学习训练数据的特征和模式来调整其内部参数 - 这个阶段模型会不断优化以减少预测误差 - 通常使用最大的数据集比例&…

解锁 DeepSeek 模型高效部署密码:蓝耘平台深度剖析与实战应用

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

SQL自学,mysql从入门到精通 --- 第 15天,数据导入、导出

数据的导入、导出 -- 查看当前设置的目录路径,限制从数据库服务器读取和写入文件的操作只能在指定的目录中进行,在安全性和文件操作限制方面具有重要意义。root@mysqldb 14:19: [(none)]> SHOW VARIABLES LIKE "secure_file_priv"; +------------------+------…

IDEA升级出现问题Failed to prepare an update Temp directory inside installation

IDEA升级出现问题"Failed to prepare an update Temp directory inside installation…" 问题来源&#xff1a; 之前修改了IDEA的默认配置文件路径&#xff0c;然后升级新版本时就无法升级&#xff0c;提示"Failed to prepare an update Temp directory insid…

Faiss特征向量搜索

ubuntu 24.10系统上实现 安装OpenBLAS git clone https://github.com/xianyi/OpenBLAS.git 安装gfortran进行编译 sudo apt install gfortran cd OpenBLAS make FCgfortran make install ln -s /opt/OpenBLAS/lib/libopenblas.so /usr/lib/libopenblas.so LD_LIBRARY_PATH/…

查询已经运行的 Docker 容器启动命令

一、导语 使用 get_command_4_run_container 查询 docker 容器的启动命令 获取镜像 docker pull cucker/get_command_4_run_container 查看容器命令 docker run --rm -v /var/run/docker.sock:/var/run/docker.sock cucker/get_command_4_run_container 容器id或容器名 …

【C++高并发服务器WebServer】-14:Select详解及实现

本文目录 一、BIO模型二、非阻塞NIO忙轮询三、IO多路复用四、Select()多路复用实现 明确一下IO多路复用的概念&#xff1a;IO多路复用能够使得程序同时监听多个文件描述符&#xff08;文件描述符fd对应的是内核读写缓冲区&#xff09;&#xff0c;能够提升程序的性能。 Linux下…

活动预告 |【Part1】Microsoft 安全在线技术公开课:安全性、合规性和身份基础知识

课程介绍 通过参加“Microsoft 安全在线技术公开课&#xff1a;安全性、合规性和身份基础知识”活动提升你的技能。在本次免费的介绍性活动中&#xff0c;你将获得所需的安全技能和培训&#xff0c;以创造影响力并利用机会推动职业发展。你将了解安全性、合规性和身份的基础知识…

Dubbo 3.x源码(29)—Dubbo Consumer服务调用源码(1)服务调用入口

基于Dubbo 3.1&#xff0c;详细介绍了Dubbo Consumer服务调用源码。 此前我们学习了Dubbo服务的导出和引入的源码&#xff0c;现在我们来学习Dubbo服务调用的源码。 此前的文章中我们讲过了最上层代理的调用逻辑(服务引用bean的获取以及懒加载原理)&#xff1a;业务引入的接口…

java-初识List

List&#xff1a; List 是一个接口&#xff0c;属于 java.util 包&#xff0c;用于表示有序的元素集合。List 允许存储重复元素&#xff0c;并且可以通过索引访问元素。它是 Java 集合框架&#xff08;Java Collections Framework&#xff09;的一部分 特点&#xff1a; 有序…

01-SDRAM控制器的设计——案例总概述

本教程重点▷▷▷ 存储器简介。 介绍 SDRAM 的工作原理。 详细讲解SDRAM 控制的Verilog 实现方法。 PLL IP和FIFO IP 的调用&#xff0c;计数器设计&#xff0c;按键边沿捕获&#xff0c;数码管控制。 完成SDRAM控制器应用的完整案例。 Signal Tap 调试方法。 准备工作▷…

idea 找不到或者无法加载主类

idea项目&#xff0c;之前一直是正常运行的&#xff0c;放假了之后再回来就遇到启动不了的问题。 WebApplication这个类右键运行的时候&#xff0c;也提示找不到主类。 对于这种之前运行没有问题&#xff0c;突然出问题的项目。 我的点是没有改动代码和数据的情况下项目就跑不起…

鸿蒙harmony 手势密码

1.效果图 2.设置手势页面代码 /*** 手势密码设置页面*/ Entry Component struct SettingGesturePage {/*** PatternLock组件控制器*/private patternLockController: PatternLockController new PatternLockController()/*** 用来保存提示文本信息*/State message: string …

Python中3个与众不同的运算符 :=海象 ->箭头 //地板除法运算符

在python中&#xff0c;有一些和其他编程语言不太一样的运算符&#xff0c;今天就给大家介绍几种Python中的3个与众不同的运算符 :海象 -&#xff1e;箭头 // 地板除法运算符。 1. 海象运算符 : 这个运算符用来分配值并同时返回变量&#xff0c;英文 walrus (a : 5) print(a…

2025.1.8(qt图形化界面之消息框)

笔记&#xff08;后期复习补充&#xff09; 作业 1> 手动将登录项目实现&#xff0c;不要使用拖拽编程 并且&#xff0c;当点击登录按钮时&#xff0c;后台会判断账号和密码是否相等&#xff0c;如果相等给出登录成功的提示&#xff0c;并且关闭当前界面&#xff0c;发射一…