Files
Planner_Tools/Windows/图形切割/ImageSlicerTool.py
2026-05-29 13:51:52 +08:00

282 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()