无论是在工作中,还是在日常生活中,我们都经常需要快速查看电脑上的图片,无论是浏览照片集,还是查看工作中的图像资料,选择一款好的图片浏览器能大幅提升效率。本节教程将介绍如何使用Python的Tkinter库构建一个功能完善的图片浏览器,包括图片翻页浏览、缩放、旋转、保存和删除等功能。
1、图片浏览器介绍
Python作为一种简单而强大的编程语言,提供了丰富的图像处理库和工具。通过Python自带的Tkinter库创建GUI图形操作界面,并借助Python的第三方库PIL(Pillow)图像处理库,我们可以轻松地创建一个功能强大的图片浏览器。
该程序包括初始界面、图片浏览界面和具体功能实现,如打开图片文件、保存图片、删除图片等操作,允许用户浏览和操作本地计算机上的图片,包括上一张、下一张、放大和缩小等功能。通过os模块进行文件操作,PIL库处理图像显示,它涉及到文件操作、图像处理和用户界面设计等方面的技术。
打开图片:浏览图片前,须点击“选择需要查看的图片”按钮选择指定图片,支持浏览多种常见的图片格式包括JPG、JPEG、PNG、GIF、BMP。
保存图片:支持将图片保存为.jpg或.png后缀名的图像文件,这里需要注意的是,如果用户输入保存图片的扩展名是png,则将保留图片的透明背景;如果用户输入保存图片的扩展名是jpg,则程序会自动将透明背景转换成白色背景,这是因为jpg采用有损压缩格式,设计初衷用于存储照片,不支持Alpha通道,因此无法保留透明背景。如果用户输入保存图片的扩展名不包含.jpg或.png后缀名,则程序会提示错误,无法保存。
编辑和删除图片:支持编辑图片包括放大、缩小、左旋转、右旋转,并支持将编辑后的图片保存到本地电脑上。这里需要注意的是,应该谨慎使用“删除图片”功能,删除后不可恢复,注意备份。
菜单栏功能:程序顶部的菜单栏支持打开图片、另存为、删除图片功能。
快捷键操作:支持鼠标右键弹出菜单栏,并支持键盘快捷键,点击键盘左、右箭头键可以在图片之间前后切换。
图片信息显示:显示已打开的图片信息,包括图片路径和图片尺寸,并统计文件夹中的图片数量。
容错处理:程序能够处理文件夹内的异常图像文件。如果打开的图片异常,无法读取图片数据,则程序会自动创建一张提示图片,并在提示图片上显示“不支持这种文件格式”,确保用户体验的连贯性和程序的稳定性。
2、图片浏览器界面演示
3、代码实现
(1)下载图标文件
程序中Button按钮控件的图标是通过加载本地图片文件,须将这些图标文件下载到本地电脑内,且须存放在Python程序脚本文件所在的同一目录内。
程序中用到的图标文件包括:
choose.png
open.png
save.png
delete.png
previous.png
left.png
big.png
small.png
right.png
next.png
注意:以上图标文件下载后,必须重新命名,比如:next_20251006120832573889.png下载后重新命名为next.png,right_20251006120804645880.png下载后重新命名为right.png,其它图片以此类推。
(2)安装PIL库
首先,确保你已经安装了PIL库(Pillow)。如果还没有安装,可以通过pip安装:
pip install Pillow
(3)图片浏览器,完整代码如下所示:
动手练一练:
import tkinter as tk
from tkinter import filedialog, messagebox
import webbrowser
from os import listdir, remove, path, chdir
from PIL import ImageTk, Image, UnidentifiedImageError, ImageDraw, ImageFont
# 获取当前脚本文件所在的目录
default_directory = path.dirname(path.abspath(__file__))
# 将当前脚本文件所在的目录设置为工作目录
chdir(default_directory)
# 创建一个白色背景图像,写入内容
def create_image(width, height, color=(255, 255, 255)):
# 创建新图像
image = Image.new("RGB", (width, height), color)
# 创建一个可以在给定图像上绘图的对象
draw = ImageDraw.Draw(image)
# 设置文字内容、位置和字体大小
text = "不支持这种文件格式"
position = (10, 5) # 文本的位置,以像素为单位
font_size = 20 # 字体大小
# 尝试使用Windows系统预装的黑体字体
try:
font = ImageFont.truetype("simhei.ttf", font_size)
except OSError:
try:
# 如果Windows系统不存在“simhei.ttf”字体,则尝试使用系统默认字体
font = ImageFont.load_default()
except Exception as e:
# 如果字体加载失败,则抛出错误提示
messagebox.showerror("错误", f"无法加载字体: {str(e)}")
return
# 在图像上写入文字,fill=(0, 0, 0) 表示黑色文字
draw.text(position, text, fill=(0, 0, 0), font=font)
return image
# 创建一个200x30的图像,当图片打开异常时显示该图像
prompt_image = create_image(200, 30)
# 当鼠标悬停在按钮上
def mouse_hover(button, color=None):
if not color:
color = '#b3f7fd'
button.configure(bg=color)
# 当鼠标未悬停在按钮上
def mouse_not_hover(button, color=None):
if not color:
color = 'white'
button.configure(bg=color)
# 定义ImageBrowse类,用于封装图片浏览程序
class ImageBrowse:
# 初始化参数
def __init__(self):
# 初始化图片路径
self.path = None
self.dir = None
# 初始化图片地址列表
self.result_images = None
# 初始化当前文件夹内图片总数量
self.all_images = 0
# 初始化源图像
self.source_img = None
# 初始化源图像尺寸
self.size = None
# 初始化图片缩放比例
self.upper = 1
self.root = tk.Tk()
self.root.title('图片浏览器')
# 设置窗口居中
window_width = 1000
window_height = 700
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
self.root.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
# 加载按钮背景图片
self.choose_icon = Image.open('choose.png')
self.choose_icon = ImageTk.PhotoImage(self.choose_icon)
self.open_icon = Image.open('open.png')
self.open_icon = self.open_icon.resize((50, 40), Image.Resampling.LANCZOS)
self.open_icon = ImageTk.PhotoImage(self.open_icon)
self.save_icon = Image.open('save.png')
self.save_icon = self.save_icon.resize((40, 40), Image.Resampling.LANCZOS)
self.save_icon = ImageTk.PhotoImage(self.save_icon)
self.delete_icon = Image.open('delete.png')
self.delete_icon = self.delete_icon.resize((40, 40), Image.Resampling.LANCZOS)
self.delete_icon = ImageTk.PhotoImage(self.delete_icon)
self.previous_icon = Image.open('previous.png')
self.previous_icon = ImageTk.PhotoImage(self.previous_icon)
self.left_icon = Image.open('left.png')
self.left_icon = ImageTk.PhotoImage(self.left_icon)
self.big_icon = Image.open('big.png')
self.big_icon = ImageTk.PhotoImage(self.big_icon)
self.small_icon = Image.open('small.png')
self.small_icon = ImageTk.PhotoImage(self.small_icon)
self.right_icon = Image.open('right.png')
self.right_icon = ImageTk.PhotoImage(self.right_icon)
self.next_icon = Image.open('next.png')
self.next_icon = ImageTk.PhotoImage(self.next_icon)
# 设置按钮,浏览图片前用于选择需要打开的图片
self.choose = tk.Button(self.root,
image=self.choose_icon,
relief='flat',
background='#f0f0f0',
cursor="hand2",
command=self.choose_file,
)
self.choose.pack(expand='yes', anchor='center')
self.choose .bind("<Enter>", lambda e: mouse_hover(self.choose, color='#bbfa68'))
self.choose .bind("<Leave>", lambda e: mouse_not_hover(self.choose, color="#f0f0f0"))
# 显示图片信息,包括图片路径和图片尺寸
self.info_frame = tk.Frame(self.root)
self.info_frame.pack(fill="x")
self.info_bar = tk.Label(self.info_frame, font=('宋体', 13))
self.info_bar.pack()
# 显示统计信息
self.status_frame = tk.Frame(self.root, background='#BCBCBC')
self.status_bar = tk.Label(self.status_frame, font=('黑体', 14), background='#BCBCBC')
self.status_bar.pack(side='left', padx=10, pady=10)
# 删除图片按键布置
delete_button = tk.Button(self.status_frame,
image=self.delete_icon,
compound="left",
text="删除图片",
cursor="hand2",
padx=10,
borderwidth=3,
background="#f0f0f0",
command=self.delete_image)
delete_button.pack(side='right', padx=10, pady=10)
delete_button.bind("<Enter>", lambda e: mouse_hover(delete_button, color='#ff0000'))
delete_button.bind("<Leave>", lambda e: mouse_not_hover(delete_button, color='#f0f0f0'))
# 保存图像按键布置
save_button = tk.Button(self.status_frame,
image=self.save_icon,
compound="left",
text="保存图片",
cursor="hand2",
padx=10,
borderwidth=3,
background="#f0f0f0",
command=self.save_file)
save_button.pack(side='right', padx=10, pady=10)
save_button.bind("<Enter>", lambda e: mouse_hover(save_button, color='#b3f7fd'))
save_button.bind("<Leave>", lambda e: mouse_not_hover(save_button, color='#f0f0f0'))
# 打开图像按键布置
open_button = tk.Button(self.status_frame,
image=self.open_icon,
compound="left",
text="打开图片",
cursor="hand2",
padx=10,
borderwidth=3,
background="#f0f0f0",
command=self.openfile)
open_button.pack(side='right', padx=10, pady=10)
open_button.bind("<Enter>", lambda e: mouse_hover(open_button, color='#b3f7fd'))
open_button.bind("<Leave>", lambda e: mouse_not_hover(open_button, color='#f0f0f0'))
# 将窗口关闭事件与window_close函数关联
self.root.protocol("WM_DELETE_WINDOW", self.window_close)
self.root.mainloop()
# 定义函数,用于打开文件选择对话框
def choose_file(self):
img_path = filedialog.askopenfilename(
title='请选择图片文件',
# 筛选常见图片文件
filetypes=[('图片', '.jpg .png .gif .bmp .jpeg')],
)
if img_path and img_path.endswith(('.jpg', '.png', '.gif', '.jpeg', '.bmp')):
self.path = img_path
self.dir = path.split(self.path)[0]
self.images_get()
self.choose.destroy()
self.main_window()
# 图片路径获取后,将图片路径存放在self.result_images列表中
def images_get(self):
# 定义一个小函数,判断是否是图片
def is_image(path):
if path.endswith(('.gif', '.jpg', '.jpeg', '.png', '.bmp')):
return True
else:
return False
# listdir()函数用于列出指定目录下所有的文件和子目录
files = [self.dir + f'/{name}' for name in listdir(self.dir)]
self.result_images = list(filter(is_image, files))
self.all_images = len(self.result_images)
# 创建主界面组件
def main_window(self):
# 进入主界面之前先打开源图像
self.open_source_image()
# 创建顶部的菜单栏
self.mnu = tk.Menu(self.root)
filemenu = tk.Menu(self.mnu, tearoff=0)
self.mnu.add_cascade(label='文件', menu=filemenu)
filemenu.add_command(label='打开', command=self.openfile)
filemenu.add_command(label='另存为', command=self.save_file)
filemenu.add_command(label='删除文件', command=self.delete_image)
help_menu = tk.Menu(self.mnu, tearoff=0)
help_menu.add_command(label="关于程序", command=self.about_image_browse)
help_menu.add_command(label="程序讲解", command=self.image_browse_help)
help_menu.add_separator()
help_menu.add_command(label="退出", command=self.window_close)
self.mnu.add_cascade(label="帮助", menu=help_menu)
# 放置菜单栏
self.root.config(menu=self.mnu)
# 限制窗口显示最小尺寸
self.root.minsize(1000, int(self.size[1])+100)
# 调用show函数显示图像
self.show()
# 操作按钮布置
# “in_”参数用于指定组件的父容器
button_frame = tk.Frame(self.root)
button_frame.pack(in_=self.root, side='bottom')
# 放大按键布置
bigger = tk.Button(button_frame,
image=self.big_icon,
relief='flat',
background='#f0f0f0',
cursor="hand2",
command=lambda: self.image_size_change(1))
bigger.grid(row=0, column=4)
bigger.bind("<Enter>", lambda e: mouse_hover(bigger, color='#ffffff'))
bigger.bind("<Leave>", lambda e: mouse_not_hover(bigger, color="#f0f0f0"))
# 缩小按键布置
smaller = tk.Button(button_frame,
image=self.small_icon,
relief='flat',
background='#f0f0f0',
cursor="hand2",
command=lambda: self.image_size_change(0))
smaller.grid(row=0, column=6)
smaller.bind("<Enter>", lambda e: mouse_hover(smaller, color='#ffffff'))
smaller.bind("<Leave>", lambda e: mouse_not_hover(smaller, color="#f0f0f0"))
# 逆时针旋转按键布置
reverse_rotate = tk.Button(button_frame,
image=self.left_icon,
relief='flat',
background='#f0f0f0',
cursor="hand2",
command=lambda: self.rotate_image(0))
reverse_rotate.grid(row=0, column=2)
reverse_rotate.bind("<Enter>", lambda e: mouse_hover(reverse_rotate, color='#ffffff'))
reverse_rotate.bind("<Leave>", lambda e: mouse_not_hover(reverse_rotate, color="#f0f0f0"))
# 顺时针旋转按键布置
correct_rotate = tk.Button(button_frame,
image=self.right_icon,
relief='flat',
background='#f0f0f0',
cursor="hand2",
command=lambda: self.rotate_image(1))
correct_rotate.grid(row=0, column=8)
correct_rotate.bind("<Enter>", lambda e: mouse_hover(correct_rotate, color='#ffffff'))
correct_rotate.bind("<Leave>", lambda e: mouse_not_hover(correct_rotate, color="#f0f0f0"))
# 切换上一张按键布置
previous = tk.Button(button_frame,
image=self.previous_icon,
relief='flat',
background='#f0f0f0',
cursor="hand2",
command=lambda: self.turn_page(0))
previous.grid(row=0, column=0)
previous.bind("<Enter>", lambda e: mouse_hover(previous, color='#ffffff'))
previous.bind("<Leave>", lambda e: mouse_not_hover(previous, color="#f0f0f0"))
# 切换下一张按键布置
next = tk.Button(button_frame,
image=self.next_icon,
relief='flat',
background='#f0f0f0',
cursor="hand2",
command=lambda: self.turn_page(1))
next.grid(row=0, column=10)
next.bind("<Enter>", lambda e: mouse_hover(next, color='#ffffff'))
next.bind("<Leave>", lambda e: mouse_not_hover(next, color="#f0f0f0"))
self.status_frame.pack(fill="x", side='bottom')
# 定义打开图像方法
def open_image(self, file_path):
try:
self.source_img = Image.open(file_path)
return self.source_img
except UnidentifiedImageError as e:
return None
# 打开源图像,获取图像数据,根据self.path来更新当前图像参数
def open_source_image(self):
self.source_img = self.open_image(self.path)
if self.source_img:
self.size = self.source_img.size
self.default_image = self.source_img
try:
self.result_image = ImageTk.PhotoImage(self.source_img)
except:
return None
else:
# 如果源图像打开异常,则显示提示图像信息
self.source_img = prompt_image
self.size = self.source_img.size
self.default_image = self.source_img
try:
self.result_image = ImageTk.PhotoImage(self.source_img)
except:
return None
# 利用tkinter组件显示图像
def show(self):
# 计算图像在文件夹中的位置
index = self.result_images.index(self.path)
self.status_bar.configure(text=f"显示:第{index + 1}张 / 共{len(self.result_images)}张")
self.info_bar.configure(text=f'图片信息 | {self.path}【尺寸:{self.size[0]}x{self.size[1]}】')
# tkinter的Label组件中显示图像
self.lab = tk.Label(self.root, bg='#BCBCBC')
self.lab['image'] = self.result_image
# bind函数绑定键盘左右键,对图像前后翻页
self.lab.bind('<Left>', lambda event: self.turn_page(0))
self.lab.bind('<Right>', lambda ev: self.turn_page(1))
self.lab.focus_set()
# 右击鼠标显示菜单栏
self.lab.bind('<Button-3>', self.right_mouse)
self.lab.pack(anchor='center', expand='yes', fill='both')
# 下一个或上一个图片翻页
def turn_page(self, norp):
index = self.result_images.index(self.path)
# 默认下一张
new_index = (index + 1) % self.all_images
if not norp:
if not index:
new_index = self.all_images - 1
else:
new_index = (index - 1) % self.all_images
self.path = self.result_images[new_index]
self.open_source_image()
self.lab.destroy()
self.show()
# 顺时针或者逆时针旋转图片
def rotate_image(self, direction):
# 设置旋转角度为90度
angle = 90
# direction=1表示顺时针,否则表示逆时针
if direction:
angle = -90
self.source_img = self.source_img.rotate(angle, expand=True)
self.size = self.source_img.size
self.default_image = self.default_image.rotate(angle, expand=True)
self.result_image = ImageTk.PhotoImage(self.default_image)
self.lab.destroy()
self.show()
# 更改图像大小
# 需要改变和保留self.default_image,旋转时需要用到
def image_size_change(self, bors):
# 变大或者变小
if_change = 0
if (self.upper > .1) and (not bors):
if_change = 1
self.upper -= .1
elif (self.upper < 8) and bors:
if_change = 1
self.upper += .15
if if_change:
new_size = (int(self.size[0]*self.upper), int(self.size[1]*self.upper))
self.default_image = self.source_img.resize(new_size)
self.result_image = ImageTk.PhotoImage(self.default_image)
self.lab.destroy()
self.show()
# 点击鼠标右键显示菜单栏
def right_mouse(self, event):
self.mmnu = tk.Menu(self.root, tearoff=0)
self.mmnu.add_command(label='打开', command=self.openfile)
self.mmnu.add_command(label='另存为', command=self.save_file)
self.mmnu.add_command(label='删除文件', command=self.delete_image)
self.mmnu.post(x=event.x_root, y=event.y_root)
# 定义函数,用于打开文件选择对话框
def openfile(self):
img_path = filedialog.askopenfilename(
title='请选择图片文件',
# 筛选常见图片文件
filetypes=[('图片', '.jpg .png .gif .bmp .jpeg')],
)
if img_path and img_path.endswith(('.jpg', '.png', '.gif', '.jpeg', '.bmp')):
self.path = img_path
self.dir = path.split(self.path)[0]
self.images_get()
self.lab.destroy()
self.open_source_image()
self.show()
# 删除已打开的图像,删除后不可恢复
def delete_image(self):
if messagebox.askyesno('确认', '是否彻底删除该图片?删除后不可恢复'):
out_path = self.path
self.turn_page(1)
self.result_images.pop(self.result_images.index(out_path))
self.all_images -= 1
remove(out_path)
messagebox.showinfo('完毕', '图片已彻底删除')
self.images_get()
# 重新计算图像在文件夹中的位置
index = self.result_images.index(self.path)
self.status_bar.configure(text=f"显示:第{index + 1}张 / 共{len(self.result_images)}张")
# 保存图像
def save_file(self):
try:
# 弹出保存文件对话框
file_path = filedialog.asksaveasfilename(
defaultextension='.jpg',
filetypes=[
("JPG文件", "*.jpg"),
("PNG文件", "*.png"),
],
)
# 获取用户输入的扩展名
image_name = file_path.split('.')[-1]
# 检查用户是否取消了保存操作
if not file_path:
return # 用户点击了取消按钮
# 如果用户输入的扩展名是png,则保留透明背景
if image_name == "png":
try:
# 将PhotoImage转换为PIL的Image对象
# convert()方法将图片转换为RGBA模式(一种包含红色、绿色、蓝色和透明度四个通道的颜色模式)
pil_image = ImageTk.getimage(self.result_image).convert("RGBA")
# 保存为png格式,压缩质量为95%最高质量
pil_image.save(file_path, 'PNG', quality=95)
messagebox.showinfo('保存成功', f'图像已成功保存为“{file_path}”')
self.images_get()
# 重新计算图像在文件夹中的位置
index = self.result_images.index(self.path)
self.status_bar.configure(text=f"显示:第{index + 1}张 / 共{len(self.result_images)}张")
except Exception as e:
messagebox.showinfo('失败', f'图像保存失败:“{e}”')
# 如果用户输入的扩展名是jpg,则将透明背景转换成白色背景
elif image_name == "jpg":
try:
# 将PhotoImage转换为PIL的Image对象
# convert()方法将图片转换为RGB模式(一种包含红色、绿色、蓝色三个通道的颜色模式)
pil_image = ImageTk.getimage(self.result_image)
# 转换图像模式为RGB,并添加白色背景
background = Image.new('RGB', pil_image.size, (255, 255, 255))
background.paste(pil_image, mask=pil_image.split()[-1]) # 使用透明度通道作为蒙版
original_image = background
# 保存为jpg格式,压缩质量为95%最高质量
original_image.save(file_path, 'JPEG', quality=95)
messagebox.showinfo('保存成功', f'图像已成功保存为“{file_path}”')
self.images_get()
# 重新计算图像在文件夹中的位置
index = self.result_images.index(self.path)
self.status_bar.configure(text=f"显示:第{index + 1}张 / 共{len(self.result_images)}张")
except Exception as e:
messagebox.showinfo('失败', f'图像保存失败:“{e}”')
# 如果用户输入其它的扩展名则提示错误
else:
messagebox.showerror("错误", "请确保文件名以.jpg或.png结尾")
return # 用户输入了错误的文件扩展名,返回重新选择
except Exception as e:
messagebox.showerror("错误", f"保存文件时发生错误:{e}")
# 关于图片浏览程序
def about_image_browse(self):
about = tk.Tk()
about.title('关于图片浏览程序')
# 设置窗口居中
window_width = 630
window_height = 460
screen_width = about.winfo_screenwidth()
screen_height = about.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
about.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
about.resizable(width=False, height=False)
about_frame = tk.Frame(about, width=630, height=460)
about_frame.pack()
tk.Label(about_frame, text='图片浏览程序', font=("宋体", 16)).place(x=220, y=20)
tk.Label(about_frame, text='使用编程语言:Python', font=("宋体", 14)).place(x=50, y=90)
tk.Label(about_frame, text='打开图像:支持打开后缀名为jpg、jpeg、png、gif、bmp的图像', font=("宋体", 14)).place(x=50, y=150)
tk.Label(about_frame, text='保存图像:可以将图像保存为.jpg或.png后缀名的图像文件', font=("宋体", 14)).place(x=50, y=210)
tk.Label(about_frame, text='编辑图像:支持旋转图像、放大和缩小图像', font=("宋体", 14)).place(x=50, y=270)
tk.Label(about_frame, text='图像翻页:点击键盘左、右键,支持对图像前后翻页', font=("宋体", 14)).place(x=50, y=330)
tk.Label(about_frame, text='创作者:www.pyhint.com', font=("宋体", 14)).place(x=50, y=390)
about.mainloop()
# 图片浏览程序讲解页面
def image_browse_help(self):
webbrowser.open("https://www.pyhint.com/article/164.html")
# 点击关闭窗口触发函数
def window_close(self):
# 弹出确认对话框
if messagebox.askokcancel("退出", "你确定要退出吗?"):
# 点击确定后关闭窗口
self.root.destroy()
# 当前模块直接被执行
if __name__ == "__main__":
# 创建主窗口
app = ImageBrowse()