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

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

相关文章:

Python截图轻量化工具

一、兼容局限性 这是用Python做的截图工具&#xff0c;不过由于使用了ctypes调用了Windows的API, 同时访问了Windows中"C:/Windows/Cursors/"中的.cur光标样式文件, 这个工具只适用于Windows环境&#xff1b; 如果要提升其跨平台性的话&#xff0c;需要考虑替换cty…...

Android图片加载框架Coil,Kotlin

Android图片加载框架Coil&#xff0c;Kotlin implementation("io.coil-kt:coil:1.4.0") import android.os.Bundle import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import coil.Coil i…...

重生之我要当云原生大师(十四)分析和存储日志

目录 一、简述常用的日志文件所存储的消息类型。 二、syslog的优先级&#xff1f; 三、维护准确时间的意义&#xff1f; 一、简述常用的日志文件所存储的消息类型。 1. 系统日志文件 /var/log/messages 消息类型&#xff1a;通用的系统日志文件&#xff0c;记录系统启动、…...

C++基础知识学习记录—补充

1、C新增的赋值语法 #include <iostream>using namespace std;int main() {//C新的赋值语法//1、传统的赋值语法int a,b;a100;b99;//C新的赋值语法int c(2);//相当于给c赋值2int d(c);//相当于把c的值赋值给dcout << "c" << c << endl;co…...

Linux常用命令——磁盘管理类

文章目录 du 查看文件和目录占用的磁盘空间df 查看磁盘空间使用情况free 显示内存占用情况lsblk 查看设备挂载情况mount/umount 挂载/卸载fdisk 分区Linux分区分区操作按键说明挂载硬盘操作 du 查看文件和目录占用的磁盘空间 ls-lh是查看文件大小du: disk usage 磁盘占用情况d…...

【AI学习】DeepSeek为什么强?

个人的一些思考&#xff0c;请大家批评指正。 这个问题&#xff0c;首先当然是在恰当的时间出现&#xff0c;模型性能跻身世界一流&#xff0c;又开源&#xff0c;戳破了OpenAI和英伟达潜心构造的叙事逻辑。 DeepSeek为什么强&#xff1f;四个方面&#xff1a;模型的智能水平…...

【快应用】原生广告下载状态监听案例

问题背景&#xff1a; 快应用中下载类原生广告监听下载状态变化接口调用没生效&#xff0c;在上报点击接口里触发下载监听后仅第一次返回状态&#xff0c;之后就不返回了&#xff0c;该如何处理&#xff1f; 问题分析&#xff1a; 快应用在1100版本新增了一个ad-button组件&a…...

C++语言的软件工程

C语言的软件工程 引言 在当今快速发展的技术领域&#xff0c;软件工程作为一门综合性的学科&#xff0c;越来越受到重视。而C语言&#xff0c;作为一种功能强大且具有广泛应用的编程语言&#xff0c;在软件工程中占据着重要的地位。本文将探讨C语言在软件工程中的应用与特点&…...

计算机网络结课设计:通过思科Cisco进行中小型校园网搭建

上学期计算机网络课程的结课设计是使用思科模拟器搭建一个中小型校园网&#xff0c;当时花了几天时间查阅相关博客总算是做出来了&#xff0c;在验收后一直没管&#xff0c;在寒假想起来了简单分享一下&#xff0c;希望可以给有需求的小伙伴一些帮助 目录 一、设计要求 二、…...

无人机避障——基于ESDF地图的JPS算法前端路径规划

原来是用栅格地图的方式&#xff0c;0表示可通行区域&#xff0c;1表示不可通行区域&#xff0c;然后采用JPS算法做路径规划&#xff0c;从起点到终点规划出一条路径。但是目前我需要做的是将栅格地图更换为ESDF地图&#xff0c;那么JPS算法计算代价的部分是否需要进行变化。 …...

【MQ】RabbitMQ 高可用延时功能的探究

延迟消息如果使用延时交换机来实现&#xff0c;如果数据量过大&#xff0c;就会很占 CPU 资源&#xff0c;轻则时间误差大&#xff0c;重则 RabbitMQ 宕机 一、针对一个 RabbitMQ 节点 &#xff08;1&#xff09;利用队列 ttl&#xff0c;将延迟消息根据 delay 的时间进行分级…...

Maven入门核心知识点总结

Maven 1. POM&#xff08;Project Object Model&#xff09;2. 坐标&#xff08;Coordinates&#xff09;3. 依赖管理&#xff08;Dependency Management&#xff09;4. 常用五个生命周期&#xff08;Life Circle&#xff09;5. Maven 仓库&#xff08;Maven Repository&#x…...

【力扣】138.随机链表的复制

AC截图 题目 代码 使用哈希存储<旧节点&#xff0c;新结点> /* // Definition for a Node. class Node { public:int val;Node* next;Node* random;Node(int _val) {val _val;next NULL;random NULL;} }; */class Solution { public:Node* copyRandomList(Node* hea…...

编程语言的深度剖析:从语法到性能优化

引言 随着软件开发的不断进化&#xff0c;编程语言的选择对项目的成功与否具有关键影响。今天的开发者面临着丰富多样的编程语言选择&#xff1a;每一种语言都有独特的优势、特性和适用场景。然而&#xff0c;语言的设计理念、运行机制和优化技巧背后的技术细节却常常被忽视。本…...

台湾精锐APEX减速机在半导体制造设备中的应用案例

半导体制造设备对传动系统的精度、可靠性和稳定性要求极高&#xff0c;台湾精锐APEX减速机凭借其低背隙、高精度和高刚性等优势&#xff0c;在半导体制造设备中得到了广泛应用。 案例一&#xff1a;晶圆切割设备 1.应用场景 在晶圆切割过程中&#xff0c;设备需要高精度的运…...

Rocketmq 和 Rabbitmq ,在多消费者的情况下,可以实现顺序消费吗

在多消费者的情况下&#xff0c;RocketMQ 和 RabbitMQ 都可以实现顺序消费&#xff0c;但它们的实现机制和适用场景有所不同。以下是对两者的详细分析和对比&#xff1a; 1. RocketMQ 的顺序消费 1.1 实现机制 顺序消息&#xff1a;RocketMQ 支持顺序消息&#xff08;Orderly …...

Springboot原理(面试高频)

目录 一、 配置优先级 ​编辑 二、Bean管理 ​​​​​​​2.1&#xff1a;获取Bean ​编辑 ​​​​​​​2.2&#xff1a;Bean作用域 ​​​​​​​​​​​​​​2.3&#xff1a;第三方Bean 三、Springboot底层原理 3.1&#xff1a;起步依赖 3.1.1&#xff1a;ma…...

2024 Rust现代实用教程:1.1Rust简介与安装更新

文章目录 一、Rust安装二、更新Rust三、Rust的Stable与Nightly版本四、卸载ubuntu安装的cargo和rustup五、rust源设置六、rust交叉编译工具链说明 rustup稳定版交叉编译步骤 步骤 1&#xff1a;安装目标组件步骤 2&#xff1a;安装交叉编译工具链步骤 3&#xff1a;配置环境变…...

yolov11模型在Android设备上运行【踩坑记录】

0) 参考资料: https://github.com/Tencent/ncnn?tabreadme-ov-file https://github.com/pnnx/pnnx https://github.com/nihui/ncnn-android-yolov5 https://github.com/Tencent/ncnn?tabreadme-ov-file 1) &#xff1a;将xxx.pt模型转化成 xxx.onnx ONNX&#xff08;Ope…...

提示工程:少样本提示(Few-shot Prompting)

少样本提示&#xff08;Few-shot Prompting&#xff09;是一种利用大语言模型从少量示例样本中学习并处理任务的方法。它的核心思想是利用大语言模型的上下文学习能力&#xff0c;通过在提示中增加“示例样本”来启发大语言模型达到举一反三的效果。这种方法避免了重新训练或者…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建

华为云FlexusDeepSeek征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”

2025年#高考 将在近日拉开帷幕&#xff0c;#AI 监考一度冲上热搜。当AI深度融入高考&#xff0c;#时间同步 不再是辅助功能&#xff0c;而是决定AI监考系统成败的“生命线”。 AI亮相2025高考&#xff0c;40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕&#xff0c;江西、…...

【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验

Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...

大数据治理的常见方式

大数据治理的常见方式 大数据治理是确保数据质量、安全性和可用性的系统性方法&#xff0c;以下是几种常见的治理方式&#xff1a; 1. 数据质量管理 核心方法&#xff1a; 数据校验&#xff1a;建立数据校验规则&#xff08;格式、范围、一致性等&#xff09;数据清洗&…...

echarts使用graphic强行给图增加一个边框(边框根据自己的图形大小设置)- 适用于无法使用dom的样式

pdf-lib https://blog.csdn.net/Shi_haoliu/article/details/148157624?spm1001.2014.3001.5501 为了完成在pdf中导出echarts图&#xff0c;如果边框加在dom上面&#xff0c;pdf-lib导出svg的时候并不会导出边框&#xff0c;所以只能在echarts图上面加边框 grid的边框是在图里…...

GAN模式奔溃的探讨论文综述(一)

简介 简介:今天带来一篇关于GAN的,对于模式奔溃的一个探讨的一个问题,帮助大家更好的解决训练中遇到的一个难题。 论文题目:An in-depth review and analysis of mode collapse in GAN 期刊:Machine Learning 链接:...

raid存储技术

1. 存储技术概念 数据存储架构是对数据存储方式、存储设备及相关组件的组织和规划&#xff0c;涵盖存储系统的布局、数据存储策略等&#xff0c;它明确数据如何存储、管理与访问&#xff0c;为数据的安全、高效使用提供支撑。 由计算机中一组存储设备、控制部件和管理信息调度的…...

el-amap-bezier-curve运用及线弧度设置

文章目录 简介示例线弧度属性主要弧度相关属性其他相关样式属性完整示例链接简介 ‌el-amap-bezier-curve 是 Vue-Amap 组件库中的一个组件,用于在 高德地图 上绘制贝塞尔曲线。‌ 基本用法属性path定义曲线的路径,可以是多个弧线段的组合。stroke-weight线条的宽度。stroke…...

Spring Boot 与 Kafka 的深度集成实践(二)

3. 生产者实现 3.1 生产者配置 在 Spring Boot 项目中&#xff0c;配置 Kafka 生产者主要是配置生产者工厂&#xff08;ProducerFactory&#xff09;和 KafkaTemplate 。生产者工厂负责创建 Kafka 生产者实例&#xff0c;而 KafkaTemplate 则是用于发送消息的核心组件&#x…...