添加Windows工具,以及分类。
This commit is contained in:
281
Windows/图形切割/ImageSlicerTool.py
Normal file
281
Windows/图形切割/ImageSlicerTool.py
Normal file
@@ -0,0 +1,281 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
图像分割工具
|
||||
将图片切割成指定大小的小块
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from tkinter import Tk, Label, Button, Entry, filedialog, messagebox, Frame, Scrollbar, Listbox, VERTICAL, HORIZONTAL, BOTH, END, StringVar, Radiobutton
|
||||
from PIL import Image
|
||||
|
||||
class ImageSlicerApp:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("图像分割工具")
|
||||
self.root.geometry("600x500")
|
||||
self.root.resizable(True, True)
|
||||
|
||||
# 初始化变量
|
||||
self.source_image_path = ""
|
||||
self.slice_size = 512
|
||||
self.output_dir = os.path.join(os.path.expanduser("~"), "Desktop", "SlicedImages")
|
||||
self.sliced_files = []
|
||||
|
||||
# 创建界面
|
||||
self.create_widgets()
|
||||
|
||||
def create_widgets(self):
|
||||
# 主框架
|
||||
main_frame = Frame(self.root, padx=20, pady=20)
|
||||
main_frame.pack(fill="both", expand=True)
|
||||
|
||||
# 源图像选择
|
||||
source_frame = Frame(main_frame, pady=10)
|
||||
source_frame.pack(fill="x")
|
||||
|
||||
Label(source_frame, text="源图像:", font=("SimHei", 10, "bold")).pack(anchor="w")
|
||||
|
||||
source_path_frame = Frame(source_frame)
|
||||
source_path_frame.pack(fill="x", pady=5)
|
||||
|
||||
self.source_entry = Entry(source_path_frame, width=50)
|
||||
self.source_entry.pack(side="left", fill="x", expand=True, padx=5)
|
||||
|
||||
Button(source_path_frame, text="浏览", command=self.browse_source).pack(side="left", padx=5)
|
||||
|
||||
# 分割设置
|
||||
settings_frame = Frame(main_frame, pady=10)
|
||||
settings_frame.pack(fill="x")
|
||||
|
||||
Label(settings_frame, text="分割设置:", font=("SimHei", 10, "bold")).pack(anchor="w")
|
||||
|
||||
# 分割大小
|
||||
size_frame = Frame(settings_frame)
|
||||
size_frame.pack(fill="x", pady=5)
|
||||
|
||||
Label(size_frame, text="分割大小:").pack(side="left", padx=5)
|
||||
self.size_entry = Entry(size_frame, width=10)
|
||||
self.size_entry.insert(0, str(self.slice_size))
|
||||
self.size_entry.pack(side="left", padx=5)
|
||||
Label(size_frame, text="x").pack(side="left", padx=5)
|
||||
self.size_entry.pack(side="left", padx=5)
|
||||
|
||||
# 命名设置
|
||||
naming_frame = Frame(settings_frame)
|
||||
naming_frame.pack(fill="x", pady=5)
|
||||
|
||||
Label(naming_frame, text="命名设置:", font=("SimHei", 9, "bold")).pack(anchor="w", padx=5)
|
||||
|
||||
# 前缀
|
||||
prefix_frame = Frame(naming_frame)
|
||||
prefix_frame.pack(fill="x", pady=2, padx=10)
|
||||
Label(prefix_frame, text="前缀:", width=8).pack(side="left", padx=5)
|
||||
self.prefix_entry = Entry(prefix_frame, width=20)
|
||||
self.prefix_entry.insert(0, "slice")
|
||||
self.prefix_entry.pack(side="left", padx=5)
|
||||
|
||||
# 后缀
|
||||
suffix_frame = Frame(naming_frame)
|
||||
suffix_frame.pack(fill="x", pady=2, padx=10)
|
||||
Label(suffix_frame, text="后缀:", width=8).pack(side="left", padx=5)
|
||||
self.suffix_entry = Entry(suffix_frame, width=20)
|
||||
self.suffix_entry.insert(0, "")
|
||||
self.suffix_entry.pack(side="left", padx=5)
|
||||
|
||||
# 排序方式
|
||||
sort_frame = Frame(naming_frame)
|
||||
sort_frame.pack(fill="x", pady=2, padx=10)
|
||||
Label(sort_frame, text="排序方式:", width=8).pack(side="left", padx=5)
|
||||
Frame(sort_frame).pack(side="left", padx=5)
|
||||
Label(sort_frame, text="顺序编号 (从左到右,从上到下)").pack(side="left", padx=5)
|
||||
self.sort_var = StringVar(value="sequential")
|
||||
|
||||
# 文件格式
|
||||
format_frame = Frame(naming_frame)
|
||||
format_frame.pack(fill="x", pady=2, padx=10)
|
||||
Label(format_frame, text="文件格式:", width=8).pack(side="left", padx=5)
|
||||
self.format_var = StringVar(value="png")
|
||||
Frame(format_frame).pack(side="left", padx=5)
|
||||
Radiobutton(format_frame, text="PNG", variable=self.format_var, value="png").pack(side="left", padx=5)
|
||||
Radiobutton(format_frame, text="JPG", variable=self.format_var, value="jpg").pack(side="left", padx=5)
|
||||
|
||||
# 输出目录
|
||||
output_frame = Frame(settings_frame)
|
||||
output_frame.pack(fill="x", pady=5)
|
||||
|
||||
Label(output_frame, text="输出目录:").pack(side="left", padx=5)
|
||||
self.output_entry = Entry(output_frame, width=40)
|
||||
self.output_entry.insert(0, self.output_dir)
|
||||
self.output_entry.pack(side="left", fill="x", expand=True, padx=5)
|
||||
Button(output_frame, text="浏览", command=self.browse_output).pack(side="left", padx=5)
|
||||
|
||||
# 执行按钮
|
||||
Button(main_frame, text="执行分割", command=self.slice_image, font=("SimHei", 12, "bold"), height=2, width=20).pack(pady=20)
|
||||
|
||||
# 结果显示
|
||||
result_frame = Frame(main_frame, pady=10)
|
||||
result_frame.pack(fill="both", expand=True)
|
||||
|
||||
Label(result_frame, text="分割结果:", font=("SimHei", 10, "bold")).pack(anchor="w")
|
||||
|
||||
# 结果列表
|
||||
list_frame = Frame(result_frame)
|
||||
list_frame.pack(fill="both", expand=True)
|
||||
|
||||
self.result_list = Listbox(list_frame, width=70, height=10)
|
||||
self.result_list.pack(side="left", fill="both", expand=True)
|
||||
|
||||
# 垂直滚动条
|
||||
vscrollbar = Scrollbar(list_frame, orient=VERTICAL, command=self.result_list.yview)
|
||||
vscrollbar.pack(side="right", fill="y")
|
||||
self.result_list.config(yscrollcommand=vscrollbar.set)
|
||||
|
||||
# 水平滚动条
|
||||
hscrollbar = Scrollbar(list_frame, orient=HORIZONTAL, command=self.result_list.xview)
|
||||
hscrollbar.pack(side="bottom", fill="x")
|
||||
self.result_list.config(xscrollcommand=hscrollbar.set)
|
||||
|
||||
def browse_source(self):
|
||||
"""浏览选择源图像"""
|
||||
file_path = filedialog.askopenfilename(
|
||||
title="选择源图像",
|
||||
filetypes=[("Image files", "*.jpg *.jpeg *.png *.bmp *.tiff *.gif"), ("All files", "*.*")]
|
||||
)
|
||||
if file_path:
|
||||
self.source_entry.delete(0, END)
|
||||
self.source_entry.insert(0, file_path)
|
||||
self.source_image_path = file_path
|
||||
|
||||
def browse_output(self):
|
||||
"""浏览选择输出目录"""
|
||||
dir_path = filedialog.askdirectory(title="选择输出目录")
|
||||
if dir_path:
|
||||
self.output_entry.delete(0, END)
|
||||
self.output_entry.insert(0, dir_path)
|
||||
self.output_dir = dir_path
|
||||
|
||||
def process_slice(self, img, x, y, width, height, prefix, suffix, index, file_format):
|
||||
"""处理单个图像切片"""
|
||||
# 计算切割区域
|
||||
start_x = x * self.slice_size
|
||||
start_y = y * self.slice_size
|
||||
end_x = min(start_x + self.slice_size, width)
|
||||
end_y = min(start_y + self.slice_size, height)
|
||||
|
||||
# 切割图像
|
||||
slice_img = img.crop((start_x, start_y, end_x, end_y))
|
||||
|
||||
# 生成文件名
|
||||
parts = []
|
||||
if prefix:
|
||||
parts.append(prefix)
|
||||
|
||||
# 顺序编号:使用连续的数字
|
||||
parts.append(f"{index-1}") # 从0开始
|
||||
|
||||
if suffix:
|
||||
parts.append(suffix)
|
||||
filename = "_".join(parts) + f".{file_format}"
|
||||
|
||||
# 保存切割后的图像
|
||||
output_path = os.path.join(self.output_dir, filename)
|
||||
if file_format == "jpg":
|
||||
# 对于JPG格式,需要确保图像模式为RGB
|
||||
if slice_img.mode == "RGBA":
|
||||
# 创建白色背景
|
||||
background = Image.new("RGB", slice_img.size, (255, 255, 255))
|
||||
# 粘贴图像,使用alpha通道作为蒙版
|
||||
background.paste(slice_img, mask=slice_img.split()[3])
|
||||
slice_img = background
|
||||
elif slice_img.mode != "RGB":
|
||||
slice_img = slice_img.convert("RGB")
|
||||
slice_img.save(output_path, "JPEG", quality=95)
|
||||
else:
|
||||
# 对于PNG格式,直接保存
|
||||
slice_img.save(output_path, "PNG")
|
||||
|
||||
# 添加到结果列表
|
||||
self.result_list.insert(END, output_path)
|
||||
self.sliced_files.append(output_path)
|
||||
|
||||
def slice_image(self):
|
||||
"""执行图像分割"""
|
||||
# 获取输入
|
||||
self.source_image_path = self.source_entry.get().strip()
|
||||
self.output_dir = self.output_entry.get().strip()
|
||||
|
||||
try:
|
||||
self.slice_size = int(self.size_entry.get().strip())
|
||||
if self.slice_size <= 0:
|
||||
raise ValueError("分割大小必须大于0")
|
||||
except ValueError as e:
|
||||
messagebox.showerror("错误", f"分割大小设置错误: {e}")
|
||||
return
|
||||
|
||||
# 验证源图像
|
||||
if not self.source_image_path:
|
||||
messagebox.showerror("错误", "请选择源图像")
|
||||
return
|
||||
|
||||
if not os.path.exists(self.source_image_path):
|
||||
messagebox.showerror("错误", "源图像文件不存在")
|
||||
return
|
||||
|
||||
# 创建输出目录
|
||||
if not os.path.exists(self.output_dir):
|
||||
try:
|
||||
os.makedirs(self.output_dir)
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"无法创建输出目录: {e}")
|
||||
return
|
||||
|
||||
try:
|
||||
# 获取命名设置
|
||||
prefix = self.prefix_entry.get().strip()
|
||||
suffix = self.suffix_entry.get().strip()
|
||||
sort_order = self.sort_var.get()
|
||||
file_format = self.format_var.get().lower()
|
||||
|
||||
# 禁用PIL的解压炸弹安全限制
|
||||
Image.MAX_IMAGE_PIXELS = None
|
||||
|
||||
# 打开图像
|
||||
with Image.open(self.source_image_path) as img:
|
||||
width, height = img.size
|
||||
|
||||
# 计算分割数量
|
||||
x_count = (width + self.slice_size - 1) // self.slice_size
|
||||
y_count = (height + self.slice_size - 1) // self.slice_size
|
||||
total_slices = x_count * y_count
|
||||
|
||||
# 清空结果列表
|
||||
self.result_list.delete(0, END)
|
||||
self.sliced_files = []
|
||||
|
||||
# 执行分割
|
||||
current = 0
|
||||
|
||||
# 行优先排序 (从上到下,从左到右) - 正确的拼接顺序
|
||||
for y in range(y_count):
|
||||
for x in range(x_count):
|
||||
current += 1
|
||||
self.process_slice(img, x, y, width, height, prefix, suffix, current, file_format)
|
||||
|
||||
# 显示完成消息
|
||||
messagebox.showinfo("完成", f"图像分割完成!\n共生成 {total_slices} 个图像块\n保存路径: {self.output_dir}")
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"分割图像时出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
root = Tk()
|
||||
app = ImageSlicerApp(root)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user