这段代码第一次拼接预览的时候没有居中显示,帮我变成居中显示
import os
import re
import sys
from PIL import Image, ImageTk
import glob
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import threading
class PhotoGridApp:
def __init__(self, root):
self.root = root
self.root.title("图片网格拼接工具")
self.root.geometry("1000x800")
self.root.minsize(800, 600)
self.root.resizable(True, True)
# 样式设置
self.style = ttk.Style()
self.font_size = 10
self.font_family = "Arial"
self.style.configure("TLabel", font=(self.font_family, self.font_size))
self.style.configure("TButton", font=(self.font_family, self.font_size))
self.style.configure("TEntry", font=(self.font_family, self.font_size))
self.style.configure("TLabelframe", font=(self.font_family, self.font_size))
self.style.configure("TLabelframe.Label", font=(self.font_family, self.font_size))
# 主框架
self.main_frame = ttk.Frame(root, padding="20")
self.main_frame.pack(fill=tk.BOTH, expand=True)
# 源文件夹选择
ttk.Label(self.main_frame, text="源文件夹:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.source_folder_var = tk.StringVar()
ttk.Entry(self.main_frame, textvariable=self.source_folder_var, width=70).grid(row=0, column=1, sticky=tk.W,
pady=5)
ttk.Button(self.main_frame, text="浏览...", command=self.browse_source_folder).grid(row=0, column=2, padx=5,
pady=5)
# 输出文件夹选择
ttk.Label(self.main_frame, text="输出文件夹:").grid(row=1, column=0, sticky=tk.W, pady=5)
self.output_folder_var = tk.StringVar()
ttk.Entry(self.main_frame, textvariable=self.output_folder_var, width=70).grid(row=1, column=1, sticky=tk.W,
pady=5)
ttk.Button(self.main_frame, text="浏览...", command=self.browse_output_folder).grid(row=1, column=2, padx=5,
pady=5)
# 网格方案选择
ttk.Label(self.main_frame, text="网格方案:").grid(row=2, column=0, sticky=tk.W, pady=5)
self.grid_option_var = tk.StringVar()
self.grid_option_combobox = ttk.Combobox(self.main_frame, textvariable=self.grid_option_var, width=15,
state="readonly")
self.grid_option_combobox.grid(row=2, column=1, sticky=tk.W, pady=5)
self.grid_option_combobox.bind("<<ComboboxSelected>>", self.on_grid_option_selected)
# 网格设置
grid_frame = ttk.LabelFrame(self.main_frame, text="网格设置")
grid_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
ttk.Label(grid_frame, text="列数:").grid(row=0, column=0, sticky=tk.W, padx=10, pady=5)
self.cols_var = tk.IntVar(value=3)
ttk.Entry(grid_frame, textvariable=self.cols_var, width=5).grid(row=0, column=1, sticky=tk.W, pady=5)
ttk.Label(grid_frame, text="行数:").grid(row=0, column=2, sticky=tk.W, padx=10, pady=5)
self.rows_var = tk.IntVar(value=3)
ttk.Entry(grid_frame, textvariable=self.rows_var, width=5).grid(row=0, column=3, sticky=tk.W, pady=5)
# 图片预览区域
preview_frame = ttk.LabelFrame(self.main_frame, text="拼接预览")
preview_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10)
preview_frame.rowconfigure(0, weight=1)
preview_frame.columnconfigure(0, weight=1)
# 带滚轮的画布
self.canvas = tk.Canvas(preview_frame, bg="#f0f0f0", highlightthickness=0)
scrollbar_y = ttk.Scrollbar(preview_frame, orient="vertical", command=self.canvas.yview)
scrollbar_x = ttk.Scrollbar(preview_frame, orient="horizontal", command=self.canvas.xview)
self.canvas.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)
# 布局滚动条和画布
scrollbar_y.pack(side="right", fill="y")
scrollbar_x.pack(side="bottom", fill="x")
self.canvas.pack(side="left", fill="both", expand=True)
# 居中容器框架
self.center_frame = ttk.Frame(self.canvas)
self.canvas_window = self.canvas.create_window((0, 0), window=self.center_frame, anchor="center")
# 预览内容框架
self.preview_content = ttk.Frame(self.center_frame)
self.preview_content.pack(anchor="center", padx=5, pady=5)
# 绑定事件
self.center_frame.bind("<Configure>", self.on_center_frame_configure)
self.canvas.bind("<Configure>", self.on_canvas_configure)
self.canvas.bind_all("<MouseWheel>", self.on_mousewheel) # Windows
self.canvas.bind_all("<Button-4>", self.on_mousewheel) # Linux
self.canvas.bind_all("<Button-5>", self.on_mousewheel) # Linux
# 状态和进度条
self.status_var = tk.StringVar(value="就绪")
ttk.Label(self.main_frame, textvariable=self.status_var).grid(row=5, column=0, columnspan=3, sticky=tk.W,
pady=5)
self.progress_var = tk.DoubleVar(value=0)
self.progress_bar = ttk.Progressbar(self.main_frame, variable=self.progress_var, maximum=100)
self.progress_bar.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
# 操作按钮
button_frame = ttk.Frame(self.main_frame)
button_frame.grid(row=7, column=0, columnspan=3, pady=10)
ttk.Button(button_frame, text="预览拼接", command=self.preview_grid).grid(row=0, column=0, padx=10)
ttk.Button(button_frame, text="保存拼接", command=self.save_grid).grid(row=0, column=1, padx=10)
ttk.Button(button_frame, text="退出", command=root.quit).grid(row=0, column=2, padx=10)
# 图片相关变量
self.image_files = []
self.photo_grid = None
self.grid_photo = None
self.grid_label = None
self.grid_options = []
# 设置权重
self.main_frame.rowconfigure(4, weight=1)
self.main_frame.columnconfigure(1, weight=1)
# 绑定窗口大小变化事件
self.root.bind("<Configure>", self.on_main_window_resize)
def browse_source_folder(self):
folder = filedialog.askdirectory(title="选择源文件夹")
if folder:
self.source_folder_var.set(folder)
self.calculate_grid_options()
def calculate_grid_options(self):
source_folder = self.source_folder_var.get()
if not source_folder or not os.path.isdir(source_folder):
return
image_files = glob.glob(os.path.join(source_folder, '*'))
self.image_files = [f for f in image_files if f.lower().endswith(
('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.webp', '.tiff', '.tif')
)]
num_images = len(self.image_files)
if num_images == 0:
self.status_var.set("提示: 所选文件夹中未找到图片文件")
self.grid_option_combobox['values'] = []
self.grid_options = []
return
self.status_var.set(f"找到 {num_images} 张图片")
self.grid_options = []
max_dim = int(num_images ** 0.5) + 2
for cols in range(1, max_dim + 1):
rows = (num_images + cols - 1) // cols
self.grid_options.append((cols, rows))
if cols != rows:
cols2, rows2 = rows, cols
if cols2 * rows2 >= num_images:
self.grid_options.append((cols2, rows2))
self.grid_options = list(set(self.grid_options))
self.grid_options.sort(key=lambda x: (x[0] * x[1], abs(x[0] - x[1])))
self.grid_options = self.grid_options[:10]
option_texts = [f"{c}列 × {r}行" for c, r in self.grid_options]
self.grid_option_combobox['values'] = option_texts
if option_texts:
self.grid_option_combobox.current(0)
self.on_grid_option_selected(None)
def on_grid_option_selected(self, event):
if not self.grid_options:
return
selected_index = self.grid_option_combobox.current()
if selected_index >= 0 and selected_index < len(self.grid_options):
cols, rows = self.grid_options[selected_index]
self.cols_var.set(cols)
self.rows_var.set(rows)
def browse_output_folder(self):
folder = filedialog.askdirectory(title="选择输出文件夹")
if folder:
self.output_folder_var.set(folder)
def natural_sort_key(self, s):
filename = os.path.basename(s)
return [int(text) if text.isdigit() else text.lower()
for text in re.split(r'(\d+)', filename)]
def create_photo_grid(self, for_preview=True):
try:
source_folder = self.source_folder_var.get()
cols = self.cols_var.get()
rows = self.rows_var.get()
if not source_folder or not os.path.isdir(source_folder):
self.status_var.set("错误: 请选择有效的源文件夹")
return None
if cols <= 0 or rows <= 0:
self.status_var.set("错误: 列数和行数必须大于0")
return None
if not self.image_files:
image_files = glob.glob(os.path.join(source_folder, '*'))
self.image_files = [f for f in image_files if f.lower().endswith(
('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.webp', '.tiff', '.tif')
)]
if not self.image_files:
self.status_var.set("错误: 在所选文件夹中未找到图片文件")
return None
self.image_files.sort(key=self.natural_sort_key)
total_images = cols * rows
if len(self.image_files) < total_images:
self.status_var.set(f"提示: 需要 {total_images} 张图片,但只找到 {len(self.image_files)} 张")
total_images = len(self.image_files)
try:
with Image.open(self.image_files[0]) as img:
img_width, img_height = img.size
except Exception as e:
self.status_var.set(f"错误: 无法打开第一张图片: {e}")
return None
canvas_width = img_width * cols
canvas_height = img_height * rows
canvas = Image.new('RGB', (canvas_width, canvas_height))
total_files = len(self.image_files[:total_images])
for index, image_path in enumerate(self.image_files[:total_images]):
try:
with Image.open(image_path) as img:
if img.size != (img_width, img_height):
img = img.resize((img_width, img_height))
row = index // cols
col = index % cols
x = col * img_width
y = row * img_height
canvas.paste(img, (x, y))
if for_preview:
progress = (index + 1) / total_files * 100
self.root.after(0, lambda p=progress: self.progress_var.set(p))
self.root.after(0, lambda
s=f"处理中: {index + 1}/{total_files} - {os.path.basename(image_path)}": self.status_var.set(
s))
except Exception as e:
self.status_var.set(f"跳过无法处理的图片: {os.path.basename(image_path)} - {e}")
except Exception as e:
self.status_var.set(f"错误: {str(e)}")
messagebox.showerror("错误", f"处理过程中发生错误:\n{str(e)}")
return None
if for_preview:
self.root.after(0, lambda: self.progress_var.set(100))
self.root.after(0, lambda: self.status_var.set(f"预览已生成"))
return canvas
def preview_grid(self):
self.status_var.set("生成预览...")
self.progress_var.set(0)
for widget in self.preview_content.winfo_children():
widget.destroy()
self.grid_label = None
self.grid_photo = None
thread = threading.Thread(target=self._preview_grid_thread)
thread.daemon = True
thread.start()
def _preview_grid_thread(self):
self.photo_grid = self.create_photo_grid(for_preview=True)
if self.photo_grid:
# 使用延迟执行确保UI完全渲染后再更新预览
self.root.after(100, self.update_preview_size)
def on_main_window_resize(self, event=None):
if self.photo_grid and event and event.widget == self.root:
self.update_preview_size()
def update_preview_size(self):
if not self.photo_grid:
return
# 强制刷新尺寸信息
self.canvas.update_idletasks()
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
if canvas_width <= 0:
canvas_width = 800
if canvas_height <= 0:
canvas_height = 500
img_width, img_height = self.photo_grid.size
ratio = min(canvas_width / img_width, canvas_height / img_height)
max_ratio = 1.0
min_ratio = 0.1
ratio = max(min(ratio, max_ratio), min_ratio)
new_size = (int(img_width * ratio), int(img_height * ratio))
preview_img = self.photo_grid.resize(new_size, Image.LANCZOS)
# 使用after确保在下一事件循环中执行,确保UI已准备好
self.root.after(0, self._update_preview_ui, preview_img)
def _update_preview_ui(self, preview_img):
self.grid_photo = ImageTk.PhotoImage(preview_img)
if self.grid_label:
self.grid_label.config(image=self.grid_photo)
else:
self.grid_label = ttk.Label(self.preview_content, image=self.grid_photo)
self.grid_label.pack()
# 强制刷新布局并居中
self.center_frame.update_idletasks()
self.canvas.update_idletasks()
# 首次预览使用延迟执行确保居中
if not hasattr(self, 'first_preview_done'):
self.root.after(50, self.center_canvas_content)
self.first_preview_done = True
else:
self.center_canvas_content()
def center_canvas_content(self):
# 强制更新尺寸
self.canvas.update_idletasks()
self.center_frame.update_idletasks()
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
frame_width = self.center_frame.winfo_width()
frame_height = self.center_frame.winfo_height()
# 计算偏移量
x = (canvas_width - frame_width) / 2
y = (canvas_height - frame_height) / 2
# 移动窗口到计算出的位置
self.canvas.coords(self.canvas_window, x, y)
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def on_center_frame_configure(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
self.center_canvas_content()
def on_canvas_configure(self, event):
# 画布尺寸变化时重新居中
self.center_canvas_content()
# 如果有图片,调整大小
if self.grid_label:
self.update_preview_size()
def on_mousewheel(self, event):
# 垂直滚动
if event.num == 5 or event.delta < 0:
self.canvas.yview_scroll(1, "units")
elif event.num == 4 or event.delta > 0:
self.canvas.yview_scroll(-1, "units")
# 水平滚动(Shift键)
if event.state & 0x10:
if event.num == 5 or event.delta < 0:
self.canvas.xview_scroll(1, "units")
elif event.num == 4 or event.delta > 0:
self.canvas.xview_scroll(-1, "units")
def save_grid(self):
if not self.photo_grid:
messagebox.showinfo("提示", "请先预览拼接效果")
return
output_folder = self.output_folder_var.get() or os.getcwd()
if not os.path.exists(output_folder):
os.makedirs(output_folder)
total_images = len(self.image_files)
output_filename = f"{total_images}格.jpg"
output_path = os.path.join(output_folder, output_filename)
counter = 1
while os.path.exists(output_path):
output_filename = f"{total_images}_{counter}.jpg"
output_path = os.path.join(output_folder, output_filename)
counter += 1
try:
self.photo_grid.save(output_path)
self.status_var.set(f"成功保存图片网格: {output_path}")
messagebox.showinfo("成功", f"图片网格已成功保存!\n位置: {output_path}")
except Exception as e:
self.status_var.set(f"错误: 无法保存图片 - {str(e)}")
messagebox.showerror("错误", f"保存图片时发生错误:\n{str(e)}")
def main():
root = tk.Tk()
app = PhotoGridApp(root)
root.mainloop()
if __name__ == "__main__":
main()
最新发布