mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-16 01:40:46 +08:00
格式化工具部分: 1. pre-commit 引入 python 的格式化工具,包括 black(代码格式化)和 isort(对“包导入顺序”的规范) 2. 允许 prettier 对文档站的 markdown 文件格式化 3. 不允许 prettier 对 markdown 文件中的代码块的代码本身进行格式化 4. 升级了 pre-commit 的各个 hook 的版本 5. 优化了 pre-commit 的日志文本显示 容器总览部分: 1. 由原来的单一轻量环境转为区分空环境、轻量环境和全量环境 2. 空环境是裸 Linux 镜像(Ubuntu),为默认环境 3. 轻量环境适合开发文档站前端 4. 全量环境适合开发 MaaCore 5. 目前,全量环境完整包含了轻量环境,轻量环境完整包含了空环境 6. 在仓库 README.md 中更新了三个环境的描述,并将链接分别设置为对应环境的创建链接 **注意:没有修改文档站中的对应文件** 7. 在各个语言的开发指南的最后,移除了 Codespace 部分的“安装额外依赖”相关描述,且将链接设置为全量环境的创建链接 **注意:没有添加“开发文档站”的指南和对应 codespace 的使用方式** 容器的轻量环境和全量环境共有部分: 1. 安装 black 和 isort 包 2. 调整 VS Code 设置,取消先前(对 markdown 文件单独指定 markdownlint 扩展作为格式化工具)的错误设置,现在 markdown 文件仍然使用默认的 prettier 扩展作为格式化工具 3. 引入 markdown-all-in-one 扩展作为语法提示工具 4. 将 node_modules 和 3rdparty 排除在 VS Code 的文本的搜索路径之外 容器的全量环境部分: 1. 为 tools 下的所有 python 脚本安装依赖 2. 使用 tools/maadeps-download.py 下载 maadeps,且将必要二进制文件软链接到 /usr/local/bin/ 3. 使用 apt 安装 cmake 和 clangd-20,将后者通过 update-alternatives 设置为系统 clangd 的默认版本 4. 使用 cmake tools 扩展,并按照 Linux 编译方法进行配置 5. 使用 clang-format 作为 c/cpp 的格式化工具,clang-format 程序主体来自 maadeps(已经软链接到 /usr/local/bin/) 6. 使用 clangd 作为 c/cpp 的语法提示工具 7. 将 MaaDeps、install 和 build 排除在 VS Code 的文本搜索路径之外 其它手动调整: 1. 更新文档站的 package.json,指定 pnpm 包管理器的版本 2. 手动保证 markdown 文件中的列表前后有空行(注意到 MarkdownLint 官方规则不一定能精准定位所有“列表前后空行”的问题,详见:https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md032---lists-should-be-surrounded-by-blank-lines ) 3. 修改了部分 markdown 文件中的 json 代码块的语法问题 **注意:相同的问题并未全部发现,仅修改了两处** 4. 在 tools 目录中,一处 python 脚本的包名误用(本地包名和某个 pip 包重名),这里修改了相应代码 5. 在 tools 目录中,一处 python 脚本使用了弃用的包 cchardet 的问题,这里更换了推荐使用的功能相近的包并修改了相应代码 自动化脚本提交的修改: 1. 自动格式化了大量 tools 中的 python 脚本 2. 自动格式化了大量 docs 中的 markdown 文件 Commits: * chore: pre-commit引入black和isort规范py文件 * chore: Auto update by pre-commit hooks [skip changelog] * chore: devcontainer添加isort扩展,排序python导入 * chore: pre-commit任务命名及更名 * style: isort fix * chore: Auto update by pre-commit hooks [skip changelog] * chore: 更新pre-commit的hook版本 * fix: 模块名与第三方库重名,大忌 * chore: 容器构建时额外安装isort * docs: md -> markdown * chore: 容器安装python包和maadeps * fix: 修复过时python包 * chore: 指定pnpm版本 * chore: container支持选择轻量环境 * chore: 去掉rust * chore: add plain env * chore: 使用clangd语言服务器 * chore: 无需单独设置markdown的格式化工具 * chore: 更新安装的clangd版本 * docs: 简易文档适配 * docs: 在仓库README中重新编排codespaces相关指引 * chore: Auto update by pre-commit hooks [skip changelog] * style: 调整缩进 * chore: 格式化工具不用特意排除被gitignore忽略的文件 * chore: sh文件在gitattributes中单列一类 * chore: 格式化docs下的markdown文件 * chore: 暂时不修改md文件中的代码块 * style: 人为明确markdown中的部分列表相关格式 * docs: 补上部分markdown的json代码块中缺失的逗号 * chore: Auto update by pre-commit hooks [skip changelog] * chore: Auto update by pre-commit hooks [skip changelog] * fix: 补上tools的服务器排序相关代码中缺失的逗号 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: 使用maadeps的clangd * build: 更新maadeps工具链版本 * style: prettier fix * revert: 还原maadeps版本 * revert: 取消使用maadeps的clangd依赖,改用系统apt安装 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
299 lines
9.4 KiB
Python
299 lines
9.4 KiB
Python
import argparse
|
|
import tkinter as tk
|
|
from tkinter import simpledialog
|
|
|
|
from PIL import Image, ImageTk
|
|
|
|
# predifined image paths (for testing)
|
|
imgs = []
|
|
|
|
# Whether the previous rectangles will be cleared when creating a new one
|
|
KEEP_RECT = True
|
|
|
|
# The prompt message
|
|
PROMPT_TEXT = "Press 'q': quit, 'n': next image, 't': toggle text, 's': save last rect, 'c': clear all, 'i': input coords"
|
|
|
|
|
|
# resize the image to 1280x720. If the image is 16:9, scale it; if not, stretch it and return a false flag
|
|
def resize_image(image):
|
|
# Get the width and height of the image
|
|
w, h = image.size
|
|
|
|
# If the image is 16:9, scale it to 1280x720
|
|
if w / h == 16 / 9:
|
|
image = image.resize((1280, 720))
|
|
return image, True
|
|
|
|
# If the image is not 16:9, stretch it to 1280x720
|
|
image = image.resize((1280, 720), Image.BICUBIC)
|
|
return image, False
|
|
|
|
|
|
class ImageRectSelector:
|
|
def __init__(self, image_path):
|
|
# Load the image using PIL
|
|
self.image = Image.open(image_path)
|
|
|
|
# resize the image to 1280x720
|
|
self.image, self.is_16_9 = resize_image(self.image)
|
|
|
|
# Create a Tkinter window
|
|
self.root = tk.Tk()
|
|
self.root.title("Image Rect Selector")
|
|
|
|
# Create a Tkinter-compatible photo image, which can be used with the Canvas widget
|
|
self.tk_image = ImageTk.PhotoImage(self.image)
|
|
|
|
# Create a Canvas widget and display the photo image on it (leave some space below for the prompt message)
|
|
self.canvas = tk.Canvas(
|
|
self.root,
|
|
width=self.image.width,
|
|
height=self.image.height + 20,
|
|
cursor="tcross",
|
|
)
|
|
self.canvas.pack()
|
|
self.canvas_image = self.canvas.create_image(
|
|
0, 0, anchor=tk.NW, image=self.tk_image
|
|
)
|
|
|
|
# Print the prompt message below the image
|
|
self.canvas.create_text(
|
|
0,
|
|
self.image.height + 20,
|
|
text=PROMPT_TEXT,
|
|
fill="black",
|
|
anchor=tk.SW,
|
|
tags="prompt",
|
|
font=("Helvetica", 14),
|
|
)
|
|
|
|
# Bind the mouse event handlers to the Canvas widget
|
|
self.canvas.bind("<Button-1>", self.on_mouse_down)
|
|
self.canvas.bind("<B1-Motion>", self.on_mouse_drag)
|
|
self.canvas.bind("<ButtonRelease-1>", self.on_mouse_up)
|
|
|
|
# Bind the keyboard event handler to the root window
|
|
self.root.bind("<Key>", self.on_key)
|
|
|
|
# Set the flag so the program loops in run()
|
|
self.is_quit = False
|
|
|
|
# Set the flag if the whole program is quit (q pressed)
|
|
self.is_quit_all = False
|
|
|
|
# Set the flag if text widgets are hidden
|
|
self.is_text_hidden = False
|
|
|
|
# Initialize the rectangle coordinates
|
|
self.rect_start_x, self.rect_start_y = None, None
|
|
self.rect_end_x, self.rect_end_y = None, None
|
|
|
|
# Switch the status of all text widgets between hidden and normal
|
|
def toggle_text(self):
|
|
# Get all text widgets
|
|
text_widgets = self.canvas.find_withtag("text")
|
|
|
|
# Toggle the flag
|
|
self.is_text_hidden = not self.is_text_hidden
|
|
|
|
# Hide or show all text widgets depending on the flag
|
|
for text_widget in text_widgets:
|
|
if self.is_text_hidden:
|
|
self.canvas.itemconfig(text_widget, state=tk.HIDDEN)
|
|
else:
|
|
self.canvas.itemconfig(text_widget, state=tk.NORMAL)
|
|
|
|
# Crop and save the last drawn rectangle
|
|
def save_rect(self):
|
|
# Get the coordinates of the rectangle
|
|
x1, y1, x2, y2 = (
|
|
self.rect_start_x,
|
|
self.rect_start_y,
|
|
self.rect_end_x,
|
|
self.rect_end_y,
|
|
)
|
|
|
|
# Get the height and width of the rectangle
|
|
w, h = x2 - x1, y2 - y1
|
|
|
|
# Crop the image using the coordinates
|
|
rect = self.image.crop((x1, y1, x2, y2))
|
|
|
|
# Save the cropped image
|
|
rect.save(f"Cropped_rect_{x1}_{y1}_{w}_{h}.png")
|
|
|
|
def draw_rectangle(self, tags="rect"):
|
|
# Erase previous temporary rectangles
|
|
self.canvas.delete("tmp")
|
|
|
|
# Erase the previous rectangles if KEEP_RECT is set to False
|
|
if not KEEP_RECT:
|
|
self.canvas.delete("rect")
|
|
|
|
# Draw the rectangle with the given tags
|
|
self.canvas.create_rectangle(
|
|
self.rect_start_x,
|
|
self.rect_start_y,
|
|
self.rect_end_x,
|
|
self.rect_end_y,
|
|
outline="red",
|
|
tags=tags,
|
|
)
|
|
|
|
def on_mouse_down(self, event):
|
|
self.rect_start_x, self.rect_start_y = event.x, event.y
|
|
|
|
def on_mouse_drag(self, event):
|
|
# Draw the new temporary rectangle on the canvas
|
|
self.rect_end_x, self.rect_end_y = event.x, event.y
|
|
self.draw_rectangle("tmp")
|
|
|
|
def on_mouse_up(self, event):
|
|
# Draw the new rectangle on the canvas
|
|
self.rect_end_x, self.rect_end_y = event.x, event.y
|
|
self.draw_rectangle()
|
|
|
|
# Print the rectangle coordinates
|
|
coords = f"{self.rect_start_x}, {self.rect_start_y}, {self.rect_end_x - self.rect_start_x}, {self.rect_end_y - self.rect_start_y}"
|
|
print(coords)
|
|
|
|
# Create a background rectangle for the text widget
|
|
self.canvas.create_rectangle(
|
|
self.rect_start_x,
|
|
self.rect_start_y,
|
|
self.rect_start_x + 130,
|
|
self.rect_start_y + 20,
|
|
fill="white",
|
|
outline="white",
|
|
tags="text",
|
|
state=tk.HIDDEN if self.is_text_hidden else tk.NORMAL,
|
|
)
|
|
|
|
# Create a text widget with white background to display the rectangle coordinates on the canvas
|
|
self.canvas.create_text(
|
|
self.rect_start_x,
|
|
self.rect_start_y,
|
|
text=coords,
|
|
fill="black",
|
|
anchor=tk.NW,
|
|
tags="text",
|
|
font=("Helvetica", 10),
|
|
state=tk.HIDDEN if self.is_text_hidden else tk.NORMAL,
|
|
)
|
|
|
|
def on_key(self, event):
|
|
# Handle the 'q' key to quit the program
|
|
if event.char == "q":
|
|
self.is_quit = True
|
|
self.is_quit_all = True
|
|
|
|
# Delete the tk window
|
|
self.root.destroy()
|
|
# self.root.quit()
|
|
|
|
# Handle the 'n' key to load the next image
|
|
elif event.char == "n":
|
|
self.is_quit = True
|
|
|
|
# Delete the tk window
|
|
self.root.destroy()
|
|
# self.root.quit()
|
|
|
|
# Handle the 'c' key to clear all rectangles
|
|
elif event.char == "c":
|
|
self.canvas.delete("rect")
|
|
self.canvas.delete("tmp")
|
|
self.canvas.delete("text")
|
|
|
|
# Handle the 'i' key to take an input and draw the corresponding rectangle
|
|
elif event.char == "i":
|
|
self.prompt_rect_coords()
|
|
self.draw_rectangle()
|
|
|
|
# Handle the 't' key to toggle the text widget
|
|
elif event.char == "t":
|
|
self.toggle_text()
|
|
|
|
# Handle the 's' key to save the last drawn rectangle
|
|
elif event.char == "s":
|
|
self.save_rect()
|
|
|
|
def prompt_rect_coords(self):
|
|
# Prompt the user to enter the rectangle coordinates
|
|
coords_str = simpledialog.askstring(
|
|
"Input", "Enter rectangle coordinates (x1 y1 x2 y2):"
|
|
)
|
|
x, y, w, h = map(int, coords_str.replace(",", " ").split())
|
|
|
|
# Set the rectangle coordinates
|
|
self.rect_start_x, self.rect_start_y = x, y
|
|
self.rect_end_x, self.rect_end_y = x + w, y + h
|
|
|
|
def run(self):
|
|
# If the image is not 16:9, show a message box and continue
|
|
if not self.is_16_9:
|
|
tk.messagebox.showinfo(
|
|
"Warning", "The image is not 16:9! Proceeding to next image..."
|
|
)
|
|
self.root.destroy()
|
|
return
|
|
|
|
# Start the Tkinter event loop and periodically check for user input
|
|
while not self.is_quit:
|
|
self.root.update_idletasks()
|
|
self.root.update()
|
|
self.root.after(10) # wait 10 ms between event loop iterations
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import os
|
|
|
|
# get the path of the current file
|
|
current_file_path = os.path.abspath(__file__)
|
|
|
|
# get the directory containing the current file
|
|
current_dir_path = os.path.dirname(current_file_path)
|
|
|
|
# set the current working directory to the directory containing the current file so that we can save the cropped images in the same directory
|
|
os.chdir(current_dir_path)
|
|
|
|
# Define command-line arguments
|
|
parser = argparse.ArgumentParser(description="Select a rectangle on an image.")
|
|
parser.add_argument(
|
|
"image_paths", type=str, nargs="*", help="Path to the input image file."
|
|
)
|
|
|
|
# Parse command-line arguments
|
|
args = parser.parse_args()
|
|
|
|
# Handle image path in different situations
|
|
if args.image_paths:
|
|
image_paths = args.image_paths
|
|
elif imgs:
|
|
image_paths = imgs
|
|
else:
|
|
imgs = simpledialog.askstring("Input", "Enter image path(s):")
|
|
image_paths = imgs.replace(",", " ").split()
|
|
|
|
# Print the prompt
|
|
print(PROMPT_TEXT)
|
|
|
|
# Create an ImageRectSelector instance for each image path
|
|
for image_path in image_paths:
|
|
|
|
# Create an ImageRectSelector instance with the image path
|
|
selector = ImageRectSelector(image_path)
|
|
|
|
# Run the selector
|
|
selector.run()
|
|
|
|
# break if the user presses the 'q' key
|
|
if selector.is_quit_all:
|
|
# delete the class instance
|
|
del selector
|
|
|
|
break
|
|
|
|
# delete the class instance
|
|
del selector
|