Python实现图片浏览器
Python实现图片浏览器
支持浏览多种常见图片格式:JPG, JPEG, PNG, GIF, BMP, TIFF, WEBP
通过"打开文件夹"按钮选择任何包含图片的文件夹
灵活的排序选项:
按时间排序(新→旧或旧→新)
按文件名排序(A→Z或Z→A)
键盘快捷键支持(左右箭头切换图片,Delete删除图片,F5刷新)
显示文件名和修改时间。当图片尺寸小于或等于显示区域时,会按原尺寸显示;只有当图片尺寸大于显示区域时,才会按比例缩放。同时,文件名标签会显示图片的原始尺寸,让你知道图片的实际大小。
运行效果
本程序要使用Pillow库的Image和ImageTk模块,它是第三方库,需要安装Pillow库(Pillow是PIL的一个分支,提供了更好的维护和更新)。详情可见https://blog.csdn.net/cnds123/article/details/126141838
源码如下:
import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk
import datetime
import reclass ImageViewer:def __init__(self, root, initial_dir=None):self.root = rootself.root.title("图片浏览器")self.current_dir = initial_dirself.current_index = 0self.images = []self.filtered_images = [] # 初始化为空列表self.photo = None # 保存当前显示的图片引用self.supported_formats = ('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp')# 创建UIself.create_ui()# 窗口大小变化时重新调整图片self.root.bind("<Configure>", self.on_resize)# 如果提供了初始目录,加载图片if self.current_dir and os.path.exists(self.current_dir):self.load_images()else:self.select_folder()def load_images(self):"""加载当前文件夹中的所有图片"""if not self.current_dir or not os.path.exists(self.current_dir):returnself.images = []files = os.listdir(self.current_dir)# 筛选支持的图片格式for file in files:if file.lower().endswith(self.supported_formats):# 获取文件修改时间file_path = os.path.join(self.current_dir, file)mod_time = os.path.getmtime(file_path)mod_time_str = datetime.datetime.fromtimestamp(mod_time).strftime('%Y-%m-%d %H:%M:%S')self.images.append((file, mod_time_str, mod_time))# 按修改时间排序,最新的在前面self.images.sort(key=lambda x: x[2], reverse=True)# 更新UIself.update_ui_after_load()def update_ui_after_load(self):"""更新加载图片后的UI状态"""# 更新文件夹标签self.folder_label.config(text=self.current_dir)# 更新滑动条范围self.filtered_images = self.images.copy()self.update_slider_range()# 显示第一张图片self.current_index = 0if self.filtered_images:self.show_image(0)else:self.clear_image()messagebox.showinfo("提示", "当前文件夹没有支持的图片文件")def create_ui(self):# 主框架main_frame = ttk.Frame(self.root, padding="10")main_frame.pack(fill=tk.BOTH, expand=True)# 顶部控制区域control_frame = ttk.Frame(main_frame)control_frame.pack(fill=tk.X, pady=(0, 10))# 当前文件夹显示folder_frame = ttk.Frame(control_frame)folder_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)ttk.Label(folder_frame, text="当前文件夹:").pack(side=tk.LEFT)self.folder_label = ttk.Label(folder_frame, text="未选择", font=("Arial", 9, "italic"))self.folder_label.pack(side=tk.LEFT, padx=5)# 打开文件夹按钮ttk.Button(folder_frame, text="打开文件夹", command=self.select_folder).pack(side=tk.LEFT, padx=5)# 排序选项sort_frame = ttk.Frame(main_frame)sort_frame.pack(fill=tk.X, pady=(0, 10))ttk.Label(sort_frame, text="排序方式:").pack(side=tk.LEFT)self.sort_var = tk.StringVar(value="时间(新→旧)")sort_options = ["时间(新→旧)", "时间(旧→新)", "名称(A→Z)", "名称(Z→A)"]sort_combo = ttk.Combobox(sort_frame, textvariable=self.sort_var, values=sort_options, width=12)sort_combo.pack(side=tk.LEFT, padx=5)sort_combo.bind("<<ComboboxSelected>>", self.sort_images)# 刷新按钮ttk.Button(sort_frame, text="刷新", command=self.refresh).pack(side=tk.RIGHT)# 图片显示区域self.image_frame = ttk.Frame(main_frame, borderwidth=1, relief="solid")self.image_frame.pack(fill=tk.BOTH, expand=True, pady=5)self.image_label = ttk.Label(self.image_frame)self.image_label.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)# 信息显示区域info_frame = ttk.Frame(main_frame)info_frame.pack(fill=tk.X, pady=(5, 10))self.file_label = ttk.Label(info_frame, font=("Arial", 10, "bold"))self.file_label.pack(side=tk.LEFT)self.time_label = ttk.Label(info_frame, font=("Arial", 10), foreground="#007acc")self.time_label.pack(side=tk.RIGHT)# 计数显示count_frame = ttk.Frame(main_frame)count_frame.pack(fill=tk.X, pady=(0, 5))self.count_label = ttk.Label(count_frame, font=("Arial", 9))self.count_label.pack(side=tk.RIGHT)# 滑动条slider_frame = ttk.Frame(main_frame)slider_frame.pack(fill=tk.X, pady=(0, 5))ttk.Button(slider_frame, text="◀", width=3, command=self.prev_image).pack(side=tk.LEFT)self.slider = ttk.Scale(slider_frame, orient=tk.HORIZONTAL, from_=0, to=0, command=self.slider_changed)self.slider.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)ttk.Button(slider_frame, text="▶", width=3, command=self.next_image).pack(side=tk.LEFT)# 底部按钮区域button_frame = ttk.Frame(main_frame)button_frame.pack(fill=tk.X, pady=(5, 0))ttk.Button(button_frame, text="在资源管理器中打开", command=self.open_in_explorer).pack(side=tk.LEFT)ttk.Button(button_frame, text="删除当前图片", command=self.delete_current).pack(side=tk.RIGHT)# 键盘快捷键self.root.bind("<Left>", lambda e: self.prev_image())self.root.bind("<Right>", lambda e: self.next_image())self.root.bind("<Delete>", lambda e: self.delete_current())self.root.bind("<F5>", lambda e: self.refresh())# 初始化标签self.clear_image()def select_folder(self):"""选择图片文件夹"""initial_dir = self.current_dir if self.current_dir else os.path.expanduser("~")new_dir = filedialog.askdirectory(title="选择图片文件夹", initialdir=initial_dir)if new_dir: # 如果用户选择了文件夹self.current_dir = new_dirself.load_images()def on_resize(self, event):"""窗口大小变化时重新调整当前图片"""# 只有当事件来自根窗口且有图片显示时才处理if (event.widget == self.root and hasattr(self, 'filtered_images') and self.filtered_images and hasattr(self, 'current_index')):# 使用延迟调用避免过于频繁的刷新if hasattr(self, 'resize_job'):self.root.after_cancel(self.resize_job)self.resize_job = self.root.after(100, lambda: self.show_image(self.current_index))def sort_images(self, event=None):"""根据选择的排序方式对图片进行排序"""if not hasattr(self, 'filtered_images') or not self.filtered_images:returnsort_method = self.sort_var.get()if sort_method == "时间(新→旧)":self.filtered_images.sort(key=lambda x: x[2], reverse=True)elif sort_method == "时间(旧→新)":self.filtered_images.sort(key=lambda x: x[2])elif sort_method == "名称(A→Z)":self.filtered_images.sort(key=lambda x: x[0].lower())elif sort_method == "名称(Z→A)":self.filtered_images.sort(key=lambda x: x[0].lower(), reverse=True)# 重新显示当前图片if self.filtered_images:self.current_index = 0self.show_image(0)def update_slider_range(self):"""更新滑动条范围"""max_val = max(0, len(self.filtered_images) - 1)self.slider.configure(to=max_val)if max_val > 0:self.slider.configure(state="normal")else:self.slider.configure(state="disabled")def slider_changed(self, value):"""滑动条值变化时的回调"""try:index = int(float(value))if index != self.current_index:self.show_image(index)except ValueError:passdef show_image(self, index):"""显示指定索引的图片"""if not hasattr(self, 'filtered_images') or not self.filtered_images or index >= len(self.filtered_images):self.clear_image()returnif 0 <= index < len(self.filtered_images):self.current_index = indexfilename, time_str, _ = self.filtered_images[index]filepath = os.path.join(self.current_dir, filename)if os.path.exists(filepath):# 加载图片try:image = Image.open(filepath)# 获取图片原始尺寸width, height = image.size# 获取显示区域大小self.root.update_idletasks() # 确保尺寸已更新frame_width = max(1, self.image_frame.winfo_width() - 20) # 确保至少为1frame_height = max(1, self.image_frame.winfo_height() - 20)# 判断图片是否需要缩放if width <= frame_width and height <= frame_height:# 小图片按原尺寸显示new_width, new_height = width, heightresized_image = imageelse:# 大图片按比例缩放scale = min(frame_width/max(1, width), frame_height/max(1, height))new_width = max(1, int(width * scale))new_height = max(1, int(height * scale))resized_image = image.resize((new_width, new_height), Image.LANCZOS)self.photo = ImageTk.PhotoImage(resized_image)self.image_label.config(image=self.photo)# 更新滑动条位置self.slider.set(index)# 更新文件名和时间标签self.file_label.config(text=f"{filename} ({width}x{height})")self.time_label.config(text=time_str)self.count_label.config(text=f"{index+1}/{len(self.filtered_images)}")# 更新窗口标题self.root.title(f"图片浏览器 - {filename}")except Exception as e:self.clear_image()self.file_label.config(text=f"无法加载图片: {str(e)}")else:self.clear_image()self.file_label.config(text="文件不存在")def clear_image(self):"""清除当前显示的图片"""self.image_label.config(image='')self.photo = Noneself.file_label.config(text="没有找到图片")self.time_label.config(text="")self.count_label.config(text="0/0")self.root.title("图片浏览器")def next_image(self):"""显示下一张图片"""if hasattr(self, 'filtered_images') and self.filtered_images and self.current_index < len(self.filtered_images) - 1:self.show_image(self.current_index + 1)def prev_image(self):"""显示上一张图片"""if hasattr(self, 'filtered_images') and self.filtered_images and self.current_index > 0:self.show_image(self.current_index - 1)def refresh(self):"""刷新当前文件夹中的图片"""if self.current_dir:current_file = Noneif hasattr(self, 'filtered_images') and self.filtered_images and self.current_index < len(self.filtered_images):current_file = self.filtered_images[self.current_index][0]self.load_images()# 尝试恢复到之前查看的图片if current_file and self.filtered_images:for i, (filename, _, _) in enumerate(self.filtered_images):if filename == current_file:self.show_image(i)return# 如果找不到之前的图片,显示第一张if self.filtered_images:self.show_image(0)def open_in_explorer(self):"""在文件资源管理器中打开当前文件夹"""if self.current_dir and os.path.exists(self.current_dir):if os.name == 'nt': # Windowsos.startfile(self.current_dir)elif os.name == 'posix': # macOS 或 Linuximport subprocessimport syssubprocess.call(('open' if sys.platform == 'darwin' else 'xdg-open', self.current_dir))def delete_current(self):"""删除当前显示的图片"""if not hasattr(self, 'filtered_images') or not self.filtered_images or not hasattr(self, 'current_index') or self.current_index >= len(self.filtered_images):returnfilename, _, _ = self.filtered_images[self.current_index]filepath = os.path.join(self.current_dir, filename)if os.path.exists(filepath):# 确认删除if messagebox.askyesno("确认删除", f"确定要删除这张图片吗?\n{filename}"):try:os.remove(filepath)# 从列表中移除del self.filtered_images[self.current_index]# 更新原始列表self.images = [img for img in self.images if img[0] != filename]# 更新滑动条范围self.update_slider_range()# 显示下一张图片或清空if self.filtered_images:# 如果删除的是最后一张,显示新的最后一张if self.current_index >= len(self.filtered_images):self.current_index = len(self.filtered_images) - 1self.show_image(self.current_index)else:self.clear_image()messagebox.showinfo("删除成功", "图片已删除")except Exception as e:messagebox.showerror("删除失败", f"无法删除文件: {str(e)}")# 使用方法
if __name__ == "__main__":import sysinitial_dir = Noneif len(sys.argv) > 1:initial_dir = sys.argv[1]root = tk.Tk()root.title("图片浏览器")root.geometry("1000x800")# 设置最小窗口大小root.minsize(800, 600)# 初始化应用app = ImageViewer(root, initial_dir)root.mainloop()