#!/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()