添加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()
|
||||
BIN
Windows/图形切割/ImageSlicerToolSequential.exe
Normal file
BIN
Windows/图形切割/ImageSlicerToolSequential.exe
Normal file
Binary file not shown.
59
Windows/图形切割/使用说明.md
Normal file
59
Windows/图形切割/使用说明.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 图像分割工具 - 使用指南
|
||||
|
||||
## 工具简介
|
||||
|
||||
图像分割工具是一款用于将大图切割成多个小块图片的桌面应用程序,适用于游戏美术资源切片、图像处理等场景。
|
||||
|
||||
## 运行方式
|
||||
|
||||
双击 `ImageSlicerToolSequential.exe` 直接运行,或使用 Python 3 运行 `ImageSlicerTool.py`。
|
||||
|
||||
> 注意:运行 Python 版本需要安装 Pillow 库:`pip install Pillow`
|
||||
|
||||
## 主要功能
|
||||
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| 图像切割 | 将大图按指定尺寸分割成多个小块 |
|
||||
| 自定义尺寸 | 可设置每个切片的大小 |
|
||||
| 命名设置 | 支持自定义文件名前缀和后缀 |
|
||||
| 输出格式 | 支持 PNG 和 JPG 两种格式 |
|
||||
| 顺序编号 | 按从左到右、从上到下顺序编号 |
|
||||
|
||||
## 使用步骤
|
||||
|
||||
### 1. 选择源图像
|
||||
点击「浏览」按钮,选择需要分割的大图。支持 JPG、PNG、BMP、TIFF、GIF 等常见格式。
|
||||
|
||||
### 2. 设置分割参数
|
||||
- **分割大小**:每个切片块的像素尺寸(默认 512×512)
|
||||
- **前缀**:输出文件名的前缀部分(默认为 `slice`)
|
||||
- **后缀**:输出文件名的后缀部分(可留空)
|
||||
- **文件格式**:选择输出为 PNG 或 JPG 格式
|
||||
|
||||
### 3. 选择输出目录
|
||||
默认输出到桌面 `SlicedImages` 文件夹,点击「浏览」可自定义输出位置。
|
||||
|
||||
### 4. 执行分割
|
||||
点击「执行分割」按钮,工具将自动完成切割并在结果列表中显示所有生成的切片文件。
|
||||
|
||||
## 示例
|
||||
|
||||
假设有一张 1024×1024 的大图 `background.png`,设置如下:
|
||||
- 分割大小:`512`
|
||||
- 前缀:`tile`
|
||||
- 后缀:留空
|
||||
- 文件格式:PNG
|
||||
|
||||
执行后将在输出目录生成 4 个文件:
|
||||
- `tile_1.png` (左上角 512×512 区域)
|
||||
- `tile_2.png` (右上角 512×512 区域)
|
||||
- `tile_3.png` (左下角 512×512 区域)
|
||||
- `tile_4.png` (右下角 512×512 区域)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 切割顺序为从左到右、从上到下,适合游戏地图_tile_的拼接
|
||||
2. 如果图像尺寸不能被分割大小整除,边缘不完整的块也会被保留
|
||||
3. JPG 格式会将有透明通道的 PNG 图片自动转换为白色背景
|
||||
4. 建议输出到专用文件夹,避免文件混乱
|
||||
Reference in New Issue
Block a user