简单封装了一些常用的批量图片处理操作,适用于没必要上PS的轻量级场合,比如快速制作机器学习的图片训练集,裁剪图片形状,又或者只是需要调整一下大小、旋转和翻转,并应用到所有指定图片上的情况。
为了方便使用,用tkinter写了简单的ui,用于批量调整训练集图片。
使用pyinstaller打包后,exe文件大小26.1MB,也算是一个方便的小工具。
语言:python3
封装的图片处理库:PIL, imageio
可视化UI:tkinter
1、选择添加图片文件后,可以单选或者多选,将确认的文件显示在列表中,此时可以选择一部分文件然后移除,也可以直接清空文件选择列表,文件添加可以多次操作。
2、完成文件选择后,选择所需要的操作,然后填入相应的操作参数,之后点击预览可以预览列表中选中的第一个图片文件,如果没有选中,则默认预览列表第一张。
3、在最下面选择输出的模型,再点击保存,确认操作后,修改保存将被执行。
from tkinter import Tk, Frame, Label, Button, Scrollbar, Listbox, filedialog, \
StringVar, messagebox, IntVar, Radiobutton, Entry
from tkinter.ttk import Combobox
from tkinter.filedialog import askdirectory
from tkinter.messagebox import askokcancel
from os import mkdir
from os.path import split as path_split
from os.path import realpath, exists, splitext
from PIL.Image import fromarray
from imageio import mimread, mimsave
from PIL.Image import open as Image_open
from PIL.ImageTk import PhotoImage as ImageTk_PhotoImage
from PIL.Image import ANTIALIAS, BILINEAR, BICUBIC, NEAREST, \
FLIP_LEFT_RIGHT, FLIP_TOP_BOTTOM, ROTATE_90, ROTATE_180, ROTATE_270
# 记录配置信息
class Config:
# 版本号
version = 'v1.3'
# 主窗口的长宽
root_height = 540
root_width = 960
# 预览图片最大长度限制
size_limit = 200
# 记录缓存数据
class Data:
# 程序运行路径
running_path = path_split(realpath(__file__))[0]
# 记录操作参数值
rotation = None
flip = None
resize_method = None
square_cut = None
percent_reduce = None
pixel_ratio = None
output_method = None
convert_method = None
# 高度文本框
height_entry = None
# 宽度文本框
width_entry = None
# 输出路径
output_path = None
# 预览图片
ori_img = None
preview_img = None
# 用于显示的图片
ori_img_display = None
preview_img_display = None
# 使用Image库打开图片
def load_image(img_path):
return Image_open(img_path)
# 将图片转换为适用于界面显示的gif
def image_to_display(img):
# 将长宽中较大值压到极限,等比例缩小
if max(img.size) > Config.size_limit:
rate = Config.size_limit / max(img.size)
shape = (int(img.size[0] * rate), int(img.size[1] * rate))
img = img.resize(shape)
return ImageTk_PhotoImage(img)
# 处理图片
def change_img(img, operate):
# 啥都不做
if operate[0] == "无操作":
pass
elif operate[0] == "旋转":
# 向左旋转90度
if operate[1] == 0:
img = img.transpose(ROTATE_90)
# 向右旋转90度
elif operate[1] == 1:
img = img.transpose(ROTATE_270)
# 旋转180度
elif operate[1] == 2:
img = img.transpose(ROTATE_180)
elif operate[0] == "翻转":
# 水平翻转
if operate[1] == 0:
img = img.transpose(FLIP_LEFT_RIGHT)
# 垂直翻转
elif operate[1] == 1:
img = img.transpose(FLIP_TOP_BOTTOM)
elif operate[0] == "缩放":
height, width, resize_method = operate[1]
type_dict = {0: ANTIALIAS, 1: NEAREST, 2: BILINEAR, 3: BICUBIC}
img = img.resize((width, height), type_dict[resize_method])
elif operate[0] == "正方形裁剪":
width, height = img.size
cut_length = max(img.size) - min(img.size)
crop_shape = (0, 0, width, height)
# crop_shape(left, upper, right, lower)
# 坐标系统的原点(0, 0)是左上角
# 保留居中
if operate[1] == 0:
# 裁剪宽
if width > height:
crop_shape = (cut_length//2, 0, cut_length//2 + height, height)
# 裁剪高
elif height > width:
crop_shape = (0, cut_length//2, width, cut_length//2+width)
# 保留左侧/顶部',
elif operate[1] == 1:
# 裁剪宽
if width > height:
crop_shape = (0, 0, width-cut_length, height)
# 裁剪高
elif height > width:
crop_shape = (0, 0, width, height-cut_length)
# 保留右侧/底部
elif operate[1] == 2:
# 裁剪宽
if width > height:
crop_shape = (cut_length, 0, width, height)
# 裁剪高
elif height > width:
crop_shape = (0, cut_length, width, height)
# 裁剪图片
img = img.crop(crop_shape)
elif operate[0] == "百分比缩放":
width, height = img.size
percent_reduce = operate[1]
img = img.resize((width*percent_reduce//100, height*percent_reduce//100), ANTIALIAS)
elif operate[0] == "像素化":
width, height = img.size
pixel_ratio = operate[1]
img = img.resize((width*pixel_ratio//100, height*pixel_ratio//100), ANTIALIAS)
img = img.resize((width, height), NEAREST)
elif operate[0] == "黑白化":
# 灰色图
if operate[1] == 0:
img = img.convert("L")
# 非黑即白
if operate[1] == 1:
img = img.convert("1")
img = img.convert("L")
return img
def main():
# 主窗口
root = Tk()
# 主窗口标题
root.title('图像批量处理工具')
# 主窗口大小
root.geometry(f'{Config.root_width}x{Config.root_height}')
# 主框架-----------------------------------------------------------------------
root_frame = Frame(root)
# 文件选择框架
file_field_frame = Frame(root_frame)
# 操作框架
operate_field_frame = Frame(root_frame)
# 保存操作框架
save_field_frame = Frame(root_frame)
# 信息框架
info_field_frame = Frame(root_frame)
# 文件选择框架-----------------------------------------------------------------------
# 顶部子框架
file_field_top_frame = Frame(file_field_frame)
# 中部子框架
file_field_middle_frame = Frame(file_field_frame)
# 底部子框架
file_field_bottom_frame = Frame(file_field_frame)
# 已选择的文件列表与下拉框
file_scrolbar = Scrollbar(file_field_bottom_frame)
file_listbox = Listbox(file_field_bottom_frame, yscrollcommand=file_scrolbar.set, selectmode="extended")
file_scrolbar.config(command=file_listbox.yview)
# 文件选择信息标签
file_field_label = Label(file_field_top_frame, text='选择你要处理的图片文件')
# 添加文件按钮
def add_choisen_file():
img_paths = filedialog.askopenfilenames(
filetypes=[('图片文件', ('.jpg', '.jpeg', '.png', '.bmp', ".gif")), ('所有文件', '*')])
# 将选中的图片文件路径添加到list_box内
for file in img_paths:
# 在添加时检验是否为合法的图片文件,无法正确打开的文件将被跳过
try:
load_image(file)
except:
messagebox.showinfo('图片文件错误', f'{file}不是正确的图片文件,将被跳过!')
else:
file_listbox.insert("end", file)
add_file_button = Button(file_field_top_frame, text='添加图片文件', command=add_choisen_file)
# 删除选中文件列表
def remove_choisen_file():
for index in file_listbox.curselection():
file_listbox.delete(index)
delete_file_button = Button(file_field_top_frame, text='移除选中的文件', command=remove_choisen_file)
# 清空选中文件列表
def clear_choisen_file():
file_listbox.delete(0, "end")
# 清空文件按钮
clear_file_button = Button(file_field_top_frame, text='清空选择列表', command=clear_choisen_file)
# 文件选择提示标签
file_choice_describe_label = Label(file_field_middle_frame, text='此时已选择的图片文件列表(按住 Shift 键或 Ctrl 键或拖拽鼠标进行多选):')
# 操作框架-----------------------------------------------------------------------
# 左侧子框架,图像操作参数区域,预览按钮
operate_field_left_frame = Frame(operate_field_frame)
# 右侧子框架,图像预览区域
operate_field_right_frame = Frame(operate_field_frame)
# 图像预览区域上下分为描述区和图像区,每个区域又分左右两部分
operate_field_right_top_frame = Frame(operate_field_right_frame)
operate_field_right_bottom_frame = Frame(operate_field_right_frame)
# 原始图片标签与预览图片描述标签
ori_desc_label = Label(operate_field_right_top_frame, text='原始图像(以选中的第一张为例):', anchor='nw')
preview_desc_label = Label(operate_field_right_top_frame, text='修改预览(以选中的第一张为例):', anchor='nw')
# 原始图片标签与预览图片显示标签,带有边框
ori_img_label = Label(operate_field_right_bottom_frame, text='暂无图像', relief="raised")
preview_img_label = Label(operate_field_right_bottom_frame, text='暂无图像', relief="raised")
# 作为可变对象,先pack
ori_desc_label.pack(side="left", fill="x", expand="yes")
preview_desc_label.pack(side="right", fill="x", expand="yes")
ori_img_label.pack(side="left", fill="both", expand="yes")
preview_img_label.pack(side="right", fill="both", expand="yes")
# 图片操作区
# 操作选择区
operate_field_left_top_frame = Frame(operate_field_left_frame)
# 参数配置区,默认空白
operate_field_left_bottom_frame = Frame(operate_field_left_frame)
# 显示无参数时的标签
operate_parameter_init_label = Label(operate_field_left_bottom_frame, text='暂无参数可用', anchor='w')
operate_parameter_init_label.pack(fill="x")
# 作为分页切换的特殊frame,提前打包
operate_field_left_bottom_frame.pack(side="bottom", fill="both", expand="yes")
operate_desc_label = Label(operate_field_left_top_frame, text='选择需要的操作:')
# 操作选择下拉框
xVariable = StringVar()
operater_combobox = Combobox(operate_field_left_top_frame, textvariable=xVariable, state="readonly")
# 可选的操作类型
operater_combobox["value"] = ("无操作", "旋转", "翻转", "缩放", "正方形裁剪", "百分比缩放", "像素化", "黑白化")
# 默认操作序号
operater_combobox.current(0)
# 更新参数框架的窗体布局
def update_operater(event):
# 针对图片参数配置区进行操作
# 首先摧毁旧frame里面的所有组件
for widget in operate_field_left_bottom_frame.winfo_children():
widget.destroy()
# 为frame配备新组件
# 获取操作名
operate = operater_combobox.get()
# 根据操作分配新的组件布局
if operate == "无操作":
# 显示无参数时的标签
operate_parameter_init_label = Label(operate_field_left_bottom_frame, text='暂无参数可用', anchor='w')
operate_parameter_init_label.pack(fill="x")
elif operate == "旋转":
Data.rotation = IntVar()
Data.rotation.set(0)
Radiobutton(operate_field_left_bottom_frame, text='向左旋转90度',
variable=Data.rotation, value=0, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='向右旋转90度',
variable=Data.rotation, value=1, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='旋转180度',
variable=Data.rotation, value=2, anchor='w').pack(side="top", fill="x")
elif operate == "翻转":
Data.flip = IntVar()
Data.flip.set(0)
Radiobutton(operate_field_left_bottom_frame, text='水平翻转',
variable=Data.flip, value=0, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='垂直翻转',
variable=Data.flip, value=1, anchor='w').pack(side="top", fill="x")
elif operate == "缩放":
# 三层小frame
# 长度frame
height_frame = Frame(operate_field_left_bottom_frame)
height_label = Label(height_frame, text='新高度(像素):', anchor='w')
height_label.pack(side="left", fill="both", expand="yes")
height_entry = Entry(height_frame)
height_entry.pack(side="left", fill="both", expand="yes")
Data.height_entry = height_entry
height_frame.pack(side="top", fill="x")
# 宽度frame
width_frame = Frame(operate_field_left_bottom_frame)
width_label = Label(width_frame, text='新宽度(像素):', anchor='w')
width_label.pack(side="left", fill="both", expand="yes")
width_entry = Entry(width_frame)
width_entry.pack(side="left", fill="both", expand="yes")
Data.width_entry = width_entry
width_frame.pack(side="top", fill="x")
# 缩放算法
Data.resize_method = IntVar()
Data.resize_method.set(0)
Label(operate_field_left_bottom_frame, text='请选择缩放算法:', anchor='w').pack(side="top", fill="both")
Radiobutton(operate_field_left_bottom_frame, text='高质量(推荐)',
variable=Data.resize_method, value=0, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='低质量',
variable=Data.resize_method, value=1, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='双线性',
variable=Data.resize_method, value=2, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='三次样条插值',
variable=Data.resize_method, value=3, anchor='w').pack(side="top", fill="x")
elif operate == "正方形裁剪":
Data.square_cut = IntVar()
Data.square_cut.set(0)
Label(operate_field_left_bottom_frame, text='将长方形图片裁剪为正方形的裁剪方式:',
anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='保留中间',
variable=Data.square_cut, value=0, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='保留左侧/顶部',
variable=Data.square_cut, value=1, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='保留右侧/底部',
variable=Data.square_cut, value=2, anchor='w').pack(side="top", fill="x")
elif operate == "百分比缩放":
Data.percent_reduce = IntVar()
Data.percent_reduce.set(33)
Label(operate_field_left_bottom_frame, text='选择需要缩放的比例:',
anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='10%',
variable=Data.percent_reduce, value=10, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='20%',
variable=Data.percent_reduce, value=20, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='25%',
variable=Data.percent_reduce, value=25, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='33%',
variable=Data.percent_reduce, value=33, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='50%',
variable=Data.percent_reduce, value=50, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='75%',
variable=Data.percent_reduce, value=75, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='150%',
variable=Data.percent_reduce, value=150, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='200%',
variable=Data.percent_reduce, value=200, anchor='w').pack(side="top", fill="x")
# Radiobutton(operate_field_left_bottom_frame, text='300%',
# variable=Data.percent_reduce, value=300, anchor='w').pack(side="top", fill="x")
elif operate == "像素化":
Data.pixel_ratio = IntVar()
Data.pixel_ratio.set(33)
Label(operate_field_left_bottom_frame, text='选择需要的像素化程度(建议预览查看效果):',
anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='10%',
variable=Data.pixel_ratio, value=10, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='20%',
variable=Data.pixel_ratio, value=20, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='25%',
variable=Data.pixel_ratio, value=25, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='33%',
variable=Data.pixel_ratio, value=33, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='50%',
variable=Data.pixel_ratio, value=50, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='75%',
variable=Data.pixel_ratio, value=75, anchor='w').pack(side="top", fill="x")
elif operate == "黑白化":
Data.convert_method = IntVar()
Data.convert_method.set(0)
Label(operate_field_left_bottom_frame, text='黑白化的方式:',
anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='灰色图',
variable=Data.convert_method , value=0, anchor='w').pack(side="top", fill="x")
Radiobutton(operate_field_left_bottom_frame, text='非黑即白',
variable=Data.convert_method , value=1, anchor='w').pack(side="top", fill="x")
# 打包新的frame
operate_field_left_bottom_frame.pack(side="bottom", fill="both", expand="yes")
# 下拉菜单选择操作
operater_combobox.bind("<>" , update_operater)
# 获取当前选择的操作,返回(操作名,参数列表)
def get_operate():
operate = operater_combobox.get()
if operate == "无操作":
return operate, None
elif operate == "旋转":
return operate, Data.rotation.get()
elif operate == "翻转":
return operate, Data.flip.get()
elif operate == "缩放":
return operate, [int(Data.height_entry.get()), int(Data.width_entry.get()), Data.resize_method.get()]
elif operate == "正方形裁剪":
return operate, Data.square_cut.get()
elif operate == "百分比缩放":
return operate, Data.percent_reduce.get()
elif operate == "像素化":
return operate, Data.pixel_ratio.get()
elif operate == "黑白化":
return operate, Data.convert_method.get()
# 保存操作框架-----------------------------------------------------------------------
# 左侧:输出描述,覆盖原文件,自定义输出路径,浏览
# 右侧:预览按钮,保存按钮
output_desc_label = Label(save_field_frame, text="输出配置:", anchor='w')
Data.output_method = IntVar()
Data.output_method.set(1)
output_method_0 = Radiobutton(save_field_frame, text='覆盖原图片文件',
variable=Data.output_method, value=0, anchor='w')
output_method_1 = Radiobutton(save_field_frame, text='输出到指定路径:',
variable=Data.output_method, value=1, anchor='w')
# 输出路径文本框
Data.output_path = StringVar()
Data.output_path.set(Data.running_path.replace('\\','/') + '/output')
output_path_label = Label(save_field_frame, textvariable=Data.output_path, anchor='w', bg='white')
# 选择路径按钮
def choose_output_path():
output_path = askdirectory()
if output_path:
Data.output_path.set(output_path)
choose_output_path_button = Button(save_field_frame, text='修改输出路径', command=choose_output_path)
# 预览按钮
# 图像预览区operate_field_right_bottom_frame
def preview_img():
# 针对图像预览区进行操作
# 首先摧毁旧frame里面的所有组件
for widget in operate_field_right_top_frame.winfo_children():
widget.destroy()
for widget in operate_field_right_bottom_frame.winfo_children():
widget.destroy()
# 为frame配备新组件
# 如果选择列表为空
if not file_listbox.size():
ori_desc_label = Label(operate_field_right_top_frame, text='原始图像(以选中的第一张为例):', anchor='nw')
preview_desc_label = Label(operate_field_right_top_frame, text='修改预览(以选中的第一张为例):', anchor='nw')
# 原始图片标签与预览图片显示标签,带有边框
ori_img_label = Label(operate_field_right_bottom_frame, text='暂无图像', relief="raised")
preview_img_label = Label(operate_field_right_bottom_frame, text='暂无图像', relief="raised")
# 取出选中的第一张图片作为预览图片,没有选中则取出列表中的第一张
else:
select_list = file_listbox.curselection()
# 如果选择的图片不为空
if select_list:
ori_img_path = file_listbox.get(file_listbox.curselection()[0])
# 否则取出列表第一个
else:
ori_img_path = file_listbox.get(0)
# 原始图像
Data.ori_img = load_image(ori_img_path)
# 用于显示的原始图像
Data.ori_img_display = image_to_display(Data.ori_img)
# 预览图像
try:
Data.preview_img = change_img(Data.ori_img, get_operate())
except:
messagebox.showinfo('长宽数据错误', f'请输入正确的长度与宽度!')
Data.preview_img = Data.ori_img
finally:
# 用于显示的预览图像
Data.preview_img_display = image_to_display(Data.preview_img)
ori_desc_label = Label(operate_field_right_top_frame,
text=f'原始图像(以选中的第一张为例) 高度:{Data.ori_img.size[1]} 宽度:{Data.ori_img.size[0]}', anchor='nw')
preview_desc_label = Label(operate_field_right_top_frame,
text=f'修改预览(以选中的第一张为例) 高度:{Data.preview_img.size[1]} 宽度:{Data.preview_img.size[0]}', anchor='nw')
ori_img_label = Label(operate_field_right_bottom_frame, image=Data.ori_img_display, relief="raised")
preview_img_label = Label(operate_field_right_bottom_frame, image=Data.preview_img_display, relief="raised")
# 图像预览区pack
ori_desc_label.pack(side="left", fill="x", expand="yes")
preview_desc_label.pack(side="right", fill="x", expand="yes")
ori_img_label.pack(side="left", fill="both", expand="yes")
preview_img_label.pack(side="right", fill="both", expand="yes")
preview_button = Button(save_field_frame, text='预览修改效果', command=preview_img)
# 对图片操作后保存
# 针对gif进行分解处理
def save_new_image(img_path, current_operate, save_path):
# 如果不是gif图片
if splitext(img_path)[1].lower() != ".gif":
change_img(load_image(img_path), current_operate).save(save_path)
# 如果是gif
else:
# 使用PIL获取gif的fps
fps = 1000 / Image_open(img_path).info['duration']
# 使用imageio库读出gif图片
gif_img = mimread(img_path)
# 将gif帧拆解分别处理
gif_img = map(lambda x: change_img(fromarray(x), current_operate), gif_img)
# 保存图片
mimsave(save_path, gif_img, 'GIF', fps=fps)
# 保存按钮
def save_img():
if not file_listbox.size():
messagebox.showinfo('无法保存', f'尚未选择任何图片文件')
elif operater_combobox.get() == "无操作":
messagebox.showinfo('无法保存', f'未选择任何要执行的操作')
else:
current_operate = get_operate()
# 是否可以保存
can_save = True
if operater_combobox.get() == "缩放":
can_save = False
# 测试书写的高宽是否正常
try:
select_list = file_listbox.curselection()
# 如果选择的图片不为空
if select_list:
ori_img_path = file_listbox.get(file_listbox.curselection()[0])
# 否则取出列表第一个
else:
ori_img_path = file_listbox.get(0)
Data.ori_img = load_image(ori_img_path)
Data.preview_img = change_img(Data.ori_img, current_operate)
except:
messagebox.showinfo('无法保存', f'高度与宽度数据填写有误!')
else:
# 检验合格
can_save = True
# 检查没问题,可以进行保存
if can_save:
# 获取保存模式
output_method = Data.output_method.get()
# 覆盖原文件输出
if output_method == 0:
# 进行一次确认提示
if askokcancel('保存确认',
'修改图片将覆盖图片原文件,该操作不可撤销,确认要对所有选择的图片文件执行该操作吗?'):
# 对所有原始图像操作并保存
for img_path in file_listbox.get(0, 'end'):
save_new_image(img_path, current_operate, img_path)
# 提示完成
messagebox.showinfo('保存完成', f'所有修改后的图片都已经覆盖原文件')
elif output_method == 1:
# 进行一次确认提示
if askokcancel('保存确认',
f'即将输出所有修改后的图片到指定文件夹{Data.output_path.get()},确认要对所有选择的图片文件执行该操作吗?'):
output_path = Data.output_path.get()
# 如果路径不存在,则创建输出文件夹
if not exists(output_path):
mkdir(output_path)
# 对所有原始图像操作并保存
for img_path in file_listbox.get(0, 'end'):
new_path = output_path + "/" + path_split(img_path)[1]
save_new_image(img_path, current_operate, new_path)
# 提示完成
messagebox.showinfo('保存完成', f'所有修改后的图片都已经输出到{Data.output_path.get()}')
save_button = Button(save_field_frame, text='保存修改图像', command=save_img)
# 信息框架-----------------------------------------------------------------------
info_label = Label(info_field_frame, text=f"编写者: starvapour 版本: {Config.version}", anchor='e')
# 组件pack-----------------------------------------------------------------------
# 文件选择区pack
file_scrolbar.pack(side="right", fill="y")
file_listbox.pack(side="left", fill="both", expand=True)
file_field_label.pack(side="left")
clear_file_button.pack(side="right")
delete_file_button.pack(side="right")
add_file_button.pack(side="right")
file_choice_describe_label.pack(side="left", fill="x")
file_field_top_frame.pack(fill="x", expand="yes")
file_field_middle_frame.pack(fill="x", expand="yes")
file_field_bottom_frame.pack(fill="x", expand="yes")
# 操作选择区pack
operate_field_right_top_frame.pack(side="top", fill="x")
operate_field_right_bottom_frame.pack(side="bottom", fill="both", expand="yes")
operate_desc_label.pack(side="left", fill="y", expand="yes")
operater_combobox.pack(side="right", fill="y", expand="yes")
operate_field_left_top_frame.pack(side="top", fill="x")
operate_field_left_frame.pack(side="left", fill="y")
operate_field_right_frame.pack(side="right", fill="both", expand="yes")
# 保存操作区pack
output_desc_label.pack(side="left", fill="y")
output_method_0.pack(side="left", fill="y")
output_method_1.pack(side="left", fill="y")
output_path_label.pack(side="left", fill="both")
choose_output_path_button.pack(side="left", fill="y")
save_button.pack(side="right", fill="y")
preview_button.pack(side="right", fill="y")
# 信息区pack
info_label.pack(side="right", fill="y")
# 主框架pack
file_field_frame.pack(fill="x", expand="yes")
operate_field_frame.pack(fill="both", expand="yes")
save_field_frame.pack(fill="x", expand="yes")
info_field_frame.pack(fill="x", expand="yes")
root_frame.pack(fill="both", expand="yes")
root.mainloop()
if __name__ == '__main__':
main()