一、兼容局限性
这是用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, UnidentifiedImageError
ScaleFactor = 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_fg
self.__click_fg = click_color
self.command = command
self.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:
pass
except 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 = 3
ANCHOR_HOVER_DISTANCE = 20
CURSOR_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 = parent
self.screenshot: ScreenshotUtils = screenshot
self.__rect: int = 0
self.__anchors: list[int] = []
self.anchor_id: int = 0
def 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) // 2
vertical_middle = (top + bottom) // 2
return (
(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) // 2
def 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 index
if left < event.x < right and top < event.y < bottom:
return 8
return -1
def 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.x
self.screenshot.start_y = event.y
def 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_x
offset_y = event.y - self.screenshot.move_start_y
if self.screenshot.start_x + offset_x > 0 and self.screenshot.end_x + offset_x < SCREEN_WIDTH:
self.screenshot.start_x += offset_x
self.screenshot.end_x += offset_x
if self.screenshot.start_y + offset_y > 0 and self.screenshot.end_y + offset_y < SCREEN_HEIGHT:
self.screenshot.start_y += offset_y
self.screenshot.end_y += offset_y
self.screenshot.move_start_x = event.x
self.screenshot.move_start_y = event.y
self.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.y
elif self.anchor_id == 1:
self.screenshot.start_x, self.screenshot.start_y = event.x, event.y
elif self.anchor_id == 2:
self.screenshot.end_x, self.screenshot.start_y = event.x, event.y
elif self.anchor_id == 3:
self.screenshot.start_x, self.screenshot.end_y = event.x, event.y
elif self.anchor_id == 4:
self.screenshot.start_y = event.y
elif self.anchor_id == 5:
self.screenshot.end_y = event.y
elif self.anchor_id == 6:
self.screenshot.start_x = event.x
elif self.anchor_id == 7:
self.screenshot.end_x = event.x
else:
return
self.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 = 4
ZOOM_WIDTH: float = TkS(28.75)
ZOOM_SCREEN_SIZE: int = int(ZOOM_WIDTH*ZOOM)
MAGNIFIER_OFFSET: int = 36
WIDTH_HEIGHT_OFFSET = 40
POS_TAG_GAP = 10
RGB_TAG_GAP = 47
MAGNIFIER_ADJUST = 70
AJUST_BAR_WIDTH: int = TkS(100)
def __init__(self):
self.start_x = self.start_y = self.end_x = self.end_y = 0
self.move_start_x = self.move_start_y = self.move_end_x = self.move_end_y = 0
self.page_index: int = 0
self.current_image: Image.Image = None
self.pixel_reader = None
self.final_images: list[Image.Image] = list()
# 这种是只移动但不改变大小和内容的控件,只需移动无需重绘
self.screenshot_move_widget = list()
# 这种是移动和改变大小的控件,需要实时重绘
self.screenshot_redraw_widget = list()
@staticmethod
def TkS(value) -> int:
return int(ScaleFactor/100*value)
@classmethod
def 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_OFFSET
zoom_size = self.__class__.ZOOM_SCREEN_SIZE
if x + offset + zoom_size < SCREEN_WIDTH:
x_offset = x + offset
else:
x_offset = x - offset - zoom_size
if y + offset + zoom_size + self.__class__.MAGNIFIER_ADJUST < SCREEN_HEIGHT:
y_offset = y + offset
else:
y_offset = y - offset - zoom_size - self.__class__.MAGNIFIER_ADJUST
width_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 = None
self.full_screenshot_canvas: tk.Canvas = None
self.adjust_rect: AdjustableRect = None
self.adjust_bar: tk.Frame = None
def 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 menubar
def 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_cut
def 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_load
def 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_copy
def 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_save
def 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_btn
def 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_btn
def 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_btn
def 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_btn
def 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_btn
def set_show_image_canvas(self) -> tk.Canvas:
canvas = tk.Canvas(self, bg="white")
return canvas
def 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 = img
self.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 = photo
full_screenshot_canvas.pack(fill=tk.BOTH, expand=True)
return full_screenshot_canvas
def 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_bg
class 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_capture
self.load_image_btn.command = self.load_image
self.copy_btn.command = self.copy_image
self.save_btn.command = self.save_image
self.turn_left_btn.command = self.turn_page
self.turn_right_btn.command = self.turn_page
self.delete_btn.command = self.delete_image
def initialize_screenshot_coords(self):
self.is_drag = False
self.screenshot.start_x = self.screenshot.start_y = 0
self.screenshot.end_x = SCREEN_WIDTH
self.screenshot.end_y = SCREEN_HEIGHT
def 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.y
self.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.y
size = ScreenshotUtils.ZOOM_WIDTH
img = 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 = photo
w, 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_coords
zoom_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.x
self.screenshot.move_start_y = event.y
self.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) - 1
self.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 = photo
self.show_image_canvas.pack(fill=tk.BOTH, expand=True)
return image
def current_limiting(self, canvas_w, canvas_h) -> bool:
if not self.screenshot.final_images:
return True
try:
coords = self.show_image_canvas.bbox("all")
img_view_w, img_view_h = coords[2], coords[3]
except TypeError:
return True
if (img_view_w == canvas_w and img_view_h < canvas_h) or \
(img_view_h == canvas_h and img_view_w < canvas_w):
return True
return False
def 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):
return
image = self.screenshot.final_images[self.screenshot.page_index]
width_ratio = canvas_w / image.width
height_ratio = canvas_h / image.height
ratio = 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:
return
try:
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) - 1
def 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_USESHOWWINDOW
args = ['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:
return
image.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 -= 1
else:
if self.screenshot.page_index == len(self.screenshot.final_images) - 1:
return messagebox.showinfo("提示", "已经是最后一张图片!")
self.screenshot.page_index += 1
self.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("提示", "确认删除当前图片?"):
return
self.screenshot.final_images.pop(self.screenshot.page_index)
if self.screenshot.page_index == len(self.screenshot.final_images):
self.screenshot.page_index -= 1
if 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