- 前言
- 说明
- 改进
- 功能
- 图片导航
- 显示上一张高清图片
- 显示下一张高清图片
- 缩略图导航
- 缩略图列表
- 缩略图上一页
- 缩略图下一页
- 缩略图指引
- 图片管理
- 高清图片
- 删除图片
- 图库管理
- 浏览图库
- 重载图库
- 调整窗口
- 窗口标题栏
- 窗口自适应布局
- 工具栏自适应
- 高清图自适应
- 导航按钮自适应
- 缩略图自适应
- GC
- 模块
- os
- tkinter
- PIL
- 自定义
- imageutil.py
- imageviewer.py
- 界面
- 附录
结合前面写的两版图片浏览器(附录),重新改写第二版。第一版可以查看缩略图和翻页,第二版结构简单,界面简洁清爽。结合两者的优点的同时,改进和增加翻页方式。在第一版中反复修改几次,一直不满意界面的风格。在写第二版时,就已经决定融合两者,最终形成当前的版本。在软件开发中,“循环翻页”一直深植人心。网站首页或栏目首页的图片轮换(轮播)就是一个很好的例子。在软件交互界面轻量化——简洁轻巧且高效的同时,尽量包含更多的功能。在有限的程序界面,精简关联密切的组件且保留其功能,是很有必要的。尤其是在值得重视的用户体验方面。融合关联密切的组件,自然而然地切换用户场景。这样可以极大释放有限的界面资源的同时,又可以显著提升用户体验。
说明 改进在图片导航中,除了高清图片导航,还加入了图片列表的导航。在翻看图片的过程中,无缝地接入翻页的功能。用户在查看上一张或下一张图片,超过图片列表界限即列表开端和末尾时,就会自动翻页更新缩略图列表。图片列表同样具有类似的功能设计。点击缩略图列表的第一张和最后一张图片,都会进行翻页并触发列表的更新。
功能 图片导航 显示上一张高清图片打开当前索引前一张图片。如果当前索引图片为第一张,则逆向翻页并显示最后一张图片。
显示下一张高清图片打开当前索引下一张图片。如果当前索引图片为图片列表最后一张,则回到图片列表的第一页。
缩略图导航 缩略图列表生成当前列表页面索引指向的5张图的缩略图列表。
缩略图上一页生成当前列表索引(包含)前面5张图的缩略图列表。
缩略图下一页生成当前列表索引(包含)后面5张图的缩略图列表。
缩略图指引在缩略图列表中指示当前高清大图是哪一张。
图片管理 高清图片打开图片列表中的任意一张的高清图片。
删除图片删除当前显示的高清图片。
图库管理 浏览图库打开文件夹浏览根目录下的所有图片。
重载图库重新显示当前图库的所有图片。
调整窗口 窗口标题栏初始化窗口标题栏为当前显示图片的文件名称。
窗口自适应布局 工具栏自适应维持工具栏在按钮画布初始化时的相对位置不变。
高清图自适应根据窗体大小变化,调节画布大小和居中显示图片。
导航按钮自适应保持上一张和下一张在按钮画布初始化时的相对位置。
缩略图自适应缩略图宽高预设不变,窗口大小变化时,调整缩略图列表居中显示。
GC在关闭窗口退出程序时,清除内存缓存。
def __del__(self):
variable = None
del variable
模块
os
tkinter
PIL
自定义
imageutil.py
"""
@author: MR.N
@created: 2021-08-22 Sun. 21:54
"""
from PIL import Image, ImageTk
S_WIDTH = 560
S_HEIGHT = 640
M_PAD_X = 1
M_PAD_Y = 20
SUB_HEIGHT = (S_HEIGHT - 2 * M_PAD_Y) // 5
SUB_WIDTH = SUB_HEIGHT
M_WIDTH = S_WIDTH + SUB_WIDTH + M_PAD_X
M_HEIGHT = S_HEIGHT
MIN_SUB_WIDTH = 16
MIN_SUB_HEIGHT = 16
I_WIDTH = S_WIDTH
I_HEIGHT = S_HEIGHT
def resize_img(path, scale=-1, screen_width=0, screen_height=0):
image = Image.open(path)
if scale == -1:
if screen_width <= 0:
screen_width = I_WIDTH
if screen_height <= 0:
screen_height = I_HEIGHT
raw_width, raw_height = image.size[0], image.size[1]
# max_width, max_height = I_WIDTH, I_HEIGHT
max_width, max_height = raw_width, screen_height
# '''
min_width = max(raw_width, max_width)
min_height = int(raw_height * min_width / raw_width)
while min_height > screen_height:
min_height = int(min_height * .9533)
while min_height < screen_height:
min_height += 1
min_width = int(raw_width * min_height / raw_height)
'''
min_height = max(raw_width, max_width)
min_width = int(raw_width * min_height / raw_height)
'''
while min_width > screen_width:
min_width -= 1
min_height = int(raw_height * min_width / raw_width)
elif scale == 1:
raw_width, raw_height = image.size[0], image.size[1]
max_width, max_height = raw_width, SUB_HEIGHT
# '''
min_width = max(raw_width, max_width)
min_height = int(raw_height * min_width / raw_width)
while min_height > SUB_HEIGHT:
min_height = int(min_height * .9533)
while min_height < SUB_HEIGHT:
min_height += 1
min_width = int(raw_width * min_height / raw_height)
while min_width > SUB_WIDTH:
min_width -= 1
min_height = int(raw_height * min_width / raw_width)
else:
raw_width, raw_height = image.size[0], image.size[1]
min_height = 18
min_width = int(raw_width * min_height / raw_height)
return image.resize((min_width, min_height))
imageviewer.py
# -*- coding: utf-8 -*-
"""
@file: imageviewer
@author: MR.N
@created: 2022/4/6 4月
@updated: 2022/4/22 4月
@version: 1.0
@blog: https://blog.csdn.net/qq_21264377
"""
import tkinter as tk
from tkinter.filedialog import askdirectory
from imageutil import *
from PIL import Image, ImageTk
import os
DEFAULT_IMAGE_DIR = 'img'
def init_dir():
img_dir = os.getcwd() + '/' + DEFAULT_IMAGE_DIR
if not os.path.exists(img_dir):
os.makedirs(img_dir)
def load_cache(media_dir=None):
if media_dir is None:
current_dir = os.path.abspath('.') + '/' + DEFAULT_IMAGE_DIR
else:
current_dir = media_dir
if not current_dir.startswith('/'):
current_dir = os.path.abspath('.') + '/' + current_dir
files = os.listdir(current_dir)
temp = []
for file in files:
if os.path.isfile(current_dir + '/' + file):
temp.append(current_dir + '/' + file)
if len(temp) < 1:
return []
files.clear()
for file in temp:
# common picture medias
if '.' in file and file.split('.')[-1].lower() in ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']:
files.append(file)
files.sort()
return files
class MyWindow:
BUTTON_SIZE_NORMAL = 32
def __init__(self):
self.x = 0
self.y = 0
self.cursor = 0
self.start_pos = 0
self.first_load = True
self.buttons = None
self.photo = None
self.sub_image = None
self.sub_image2 = None
self.sub_image3 = None
self.sub_image4 = None
self.sub_image5 = None
self.sub_queue = []
init_dir()
self.window = tk.Tk()
self.window.title('Gallery')
self.window.config(bg='#111')
self.window.overrideredirect(False)
self.frame_left_bar = tk.Frame(self.window)
self.frame_photo = tk.Frame(self.window)
self.window_width = 0
self.window_height = 0
self.window.geometry(
f'{M_WIDTH}x{M_HEIGHT}+{(self.window.winfo_screenwidth() - S_WIDTH) // 2}+{(self.window.winfo_screenheight() - S_HEIGHT) // 2 - 18}')
self.window.bind('' , self.window_resize)
# self.window.bind('', self.win_motion)
self.window.protocol('WM_DELETE_WINDOW', self.win_quit)
# self.window.bind('', self.left_down)
self.frame_left_bar.config(bg='#111')
self.frame_left_bar.config(width=SUB_WIDTH)
self.photo_can = tk.Canvas(self.frame_photo)
self.selected_dir = DEFAULT_IMAGE_DIR
self.caches = load_cache(self.selected_dir)
'''
if len(self.caches) > 0:
self.photo = ImageTk.PhotoImage(
image=resize_img(self.caches[self.cursor], screen_width=S_WIDTH, screen_height=S_HEIGHT))
self.photo_can.create_image(
((S_WIDTH - self.photo.width()) // 2 - 36, (S_HEIGHT - self.photo.height()) // 2),
anchor=tk.NW, image=self.photo)
else:
self.photo = None
'''
self.photo_can.update()
self.photo_can.config(bg='#111')
self.photo_can.config(highlightthickness=0)
self.photo_can.config(width=M_WIDTH - M_PAD_X - SUB_WIDTH)
self.photo_can.config(height=M_HEIGHT)
# previous photo
self.prev_button = tk.Button(self.window, text='‹')
self.prev_button.config(command=self.prev_photo)
self.prev_button.config(font=('', 16))
self.prev_button.place(x=0, y=(S_HEIGHT - self.BUTTON_SIZE_NORMAL) // 2, width=self.BUTTON_SIZE_NORMAL,
height=self.BUTTON_SIZE_NORMAL)
self.photo_can.pack(side='left', expand='yes', fill='both', anchor='center')
# next photo
self.next_button = tk.Button(self.window, text='›')
self.next_button.config(command=self.next_photo)
self.next_button.config(font=('', 16))
self.next_button.place(x=S_WIDTH - self.BUTTON_SIZE_NORMAL, y=(S_HEIGHT - self.BUTTON_SIZE_NORMAL) // 2,
width=self.BUTTON_SIZE_NORMAL, height=self.BUTTON_SIZE_NORMAL)
self.prev_page_button = tk.Button(self.window, text='⌃')
self.prev_page_button.config(command=self.prev_page)
self.prev_page_button.config(font=('', 10))
self.next_page_button = tk.Button(self.window, text='⌄')
self.next_page_button.config(command=self.next_page)
self.next_page_button.config(font=('', 10))
# open gallery of photo(s) in another folder
self.open_button = tk.Button(self.window, text='…')
self.open_button.config(command=self.select_dir)
self.open_button.config(font=('', 10))
self.reload_button = tk.Button(self.window, text='↺')
self.reload_button.config(command=self.reload_cache)
self.reload_button.config(font=('', 10))
self.delete_button = tk.Button(self.window, text='⤫')
self.delete_button.config(command=self.delete_cache)
self.delete_button.config(font=('', 10))
self.config_button()
self.sub_image_can = tk.Button(self.window)
self.sub_image_can.config(command=self.load_sub_1)
self.sub_image_can2 = tk.Button(self.window)
self.sub_image_can2.config(command=self.load_sub_2)
self.sub_image_can3 = tk.Button(self.window)
self.sub_image_can3.config(command=self.load_sub_3)
self.sub_image_can4 = tk.Button(self.window)
self.sub_image_can4.config(command=self.load_sub_4)
self.sub_image_can5 = tk.Button(self.window)
self.sub_image_can5.config(command=self.load_sub_5)
# load sub-images
self.load_sub()
self.sub_image_cans = [self.sub_image_can, self.sub_image_can2, self.sub_image_can3, self.sub_image_can4,
self.sub_image_can5]
self.config_can()
self.pointer = tk.Label(self.window, text='›')
self.pointer.config(height=1)
self.pointer.config(bg='#333')
self.pointer.config(fg='#eee')
self.frame_left_bar.pack(side='right', expand='yes', fill='y')
self.frame_photo.pack(side='right', expand='yes', fill='both')
self.window.mainloop()
def win_quit(self, event=None):
self.window.quit()
self.__del__()
def win_mini(self, event=None):
self.window.state('icon')
self.window.iconify()
def left_down(self, event=None):
self.x = event.x
self.y = event.y
def win_motion(self, event):
new_x = int(event.x - self.x) + self.window.winfo_x()
new_y = int(event.y - self.y) + self.window.winfo_y()
self.window.geometry(f'{self.window.winfo_width()}x{self.window.winfo_height()}+{new_x}+{new_y}')
def config_can(self):
for image_can in self.sub_image_cans:
image_can.config(relief='ridge')
image_can.config(fg='#fff')
image_can.config(activeforeground='#f5f5f5')
image_can.config(activebackground='#444')
image_can.config(bg='#222')
image_can.config(bd=0)
image_can.config(highlightthickness=0)
image_can.config(highlightcolor='#111')
image_can.config(highlightbackground='#111')
def config_button(self):
self.buttons = [self.prev_button, self.next_button, self.prev_page_button, self.next_page_button,
self.delete_button, self.open_button, self.reload_button]
for button in self.buttons:
button.config(relief='ridge')
button.config(fg='#fff')
button.config(activeforeground='#f5f5f5')
button.config(activebackground='#444')
button.config(bg='#222')
button.config(bd=0)
button.config(highlightthickness=0)
button.config(highlightcolor='#111')
button.config(highlightbackground='#111')
def get_sub_image(self, file=None):
return ImageTk.PhotoImage(image=resize_img(file, 1, screen_width=M_WIDTH, screen_height=M_HEIGHT))
def load_photo(self):
if len(self.caches) > 0:
if 0 <= self.cursor <= len(self.caches) - 1:
width = self.photo_can.winfo_width() if self.photo_can.winfo_width() > 0 else S_WIDTH
height = self.photo_can.winfo_height() if self.photo_can.winfo_height() > 0 else S_HEIGHT
image = resize_img(self.caches[self.cursor], screen_width=width, screen_height=height)
self.photo = ImageTk.PhotoImage(image=image)
self.photo_can.create_image(
((width - self.photo.width()) // 2,
(height - self.photo.height()) // 2),
anchor=tk.NW, image=self.photo)
self.window.title(f'{self.caches[self.cursor].split("/")[-1]}')
else:
self.photo = None
self.photo_can.update()
def load_sub(self):
if 0 <= self.start_pos < len(self.caches):
self.sub_image = self.get_sub_image(file=self.caches[self.start_pos])
else:
self.sub_image = self.get_sub_image(file=os.getcwd() + '/bg_empty.png')
self.sub_image_can.config(image=self.sub_image)
self.sub_image_can.update()
if 0 <= self.start_pos + 1 < len(self.caches):
self.sub_image2 = self.get_sub_image(file=self.caches[self.start_pos + 1])
else:
self.sub_image2 = self.get_sub_image(file=os.getcwd() + '/bg_empty.png')
self.sub_image_can2.config(image=self.sub_image2)
self.sub_image_can2.update()
if 0 <= self.start_pos + 2 < len(self.caches):
self.sub_image3 = self.get_sub_image(file=self.caches[self.start_pos + 2])
else:
self.sub_image3 = self.get_sub_image(file=os.getcwd() + '/bg_empty.png')
self.sub_image_can3.config(image=self.sub_image3)
self.sub_image_can3.update()
if 0 <= self.start_pos + 3 < len(self.caches):
self.sub_image4 = self.get_sub_image(file=self.caches[self.start_pos + 3])
else:
self.sub_image4 = self.get_sub_image(file=os.getcwd() + '/bg_empty.png')
self.sub_image_can4.config(image=self.sub_image4)
self.sub_image_can4.update()
if 0 <= self.start_pos + 4 < len(self.caches):
self.sub_image5 = self.get_sub_image(file=self.caches[self.start_pos + 4])
else:
self.sub_image5 = self.get_sub_image(file=os.getcwd() + '/bg_empty.png')
self.sub_image_can5.config(image=self.sub_image5)
self.sub_image_can5.update()
def load_sub_1(self):
if len(self.caches) > self.start_pos:
self.cursor = self.start_pos
self.load_photo()
self.point_to()
def load_sub_2(self):
if len(self.caches) > self.start_pos + 1:
self.cursor = self.start_pos + 1
self.load_photo()
self.point_to()
def load_sub_3(self):
if len(self.caches) > self.start_pos + 2:
self.cursor = self.start_pos + 2
self.load_photo()
self.point_to()
def load_sub_4(self):
if len(self.caches) > self.start_pos + 3:
self.cursor = self.start_pos + 3
self.load_photo()
self.point_to()
def load_sub_5(self):
if len(self.caches) > self.start_pos + 4:
self.cursor = self.start_pos + 4
self.load_photo()
self.point_to()
def point_to(self):
delta_cursor = self.cursor - self.start_pos
if 0 <= delta_cursor < len(self.sub_image_cans):
width = SUB_WIDTH // 6
height = SUB_HEIGHT
x = self.window.winfo_width() - SUB_WIDTH
y = self.sub_image_can.winfo_y() + delta_cursor * height
if self.sub_image_can.winfo_y() < M_PAD_Y:
y += M_PAD_Y
self.pointer.place(x=x, y=y, width=width, height=height)
def prev_photo(self):
if len(self.caches) > 0:
updated = False
self.cursor -= 1
if self.cursor < self.start_pos:
updated = True
if self.start_pos - 5 >= 0:
self.start_pos -= 5
else:
self.start_pos = len(self.caches) - 5
if self.cursor < 0:
updated = True
self.cursor = len(self.caches) - 1
self.load_photo()
if updated:
self.load_sub()
self.point_to()
def next_photo(self):
if len(self.caches) > 0:
updated = False
self.cursor += 1
if self.cursor > self.start_pos + 4:
updated = True
if self.start_pos + 5 < len(self.caches):
self.start_pos += 5
else:
self.start_pos = 0
if self.cursor >= len(self.caches):
updated = True
self.cursor = 0
self.start_pos = 0
self.load_photo()
if updated:
self.load_sub()
self.point_to()
def prev_page(self, event=None):
if self.start_pos - 5 >= 0:
self.start_pos -= 5
else:
self.start_pos = len(self.caches) - 5
self.cursor = self.start_pos + 4
self.load_photo()
self.load_sub()
self.point_to()
def next_page(self, event=None):
if self.start_pos + 5 < len(self.caches):
self.start_pos += 5
else:
self.start_pos = 0
self.cursor = self.start_pos
self.load_photo()
self.load_sub()
self.point_to()
def window_resize(self, event=None):
if event is not None:
# listen events of window resizing.
if self.window_width != self.photo_can.winfo_width() or self.window_height != self.photo_can.winfo_height():
if self.window_width != self.photo_can.winfo_width():
self.window_width = self.photo_can.winfo_width()
if self.window_height != self.photo_can.winfo_height():
self.window_height = self.photo_can.winfo_height()
# What happens here?
if self.first_load:
self.first_load = False
else:
self.photo_can.config(width=self.window.winfo_width() - M_PAD_X - SUB_WIDTH)
self.photo_can.config(height=self.window.winfo_height())
self.photo_can.update()
self.load_photo()
self.place_page_tool()
self.place_tool()
self.prev_button.place(
x=0,
y=(self.window_height - self.BUTTON_SIZE_NORMAL) // 2,
width=self.BUTTON_SIZE_NORMAL,
height=self.BUTTON_SIZE_NORMAL)
self.next_button.place(
x=self.window_width - self.BUTTON_SIZE_NORMAL,
y=(self.window_height - self.BUTTON_SIZE_NORMAL) // 2,
width=self.BUTTON_SIZE_NORMAL,
height=self.BUTTON_SIZE_NORMAL)
self.place_sub()
self.point_to()
def place_page_tool(self):
x = self.window.winfo_width() - SUB_WIDTH
y = 0
width = SUB_WIDTH
height = M_PAD_Y
self.prev_page_button.place(x=x, y=y, width=width, height=height)
self.next_page_button.place(x=x, y=self.window_height - M_PAD_Y, width=width, height=height)
def place_tool(self):
i = 0
for button in self.buttons[4:]:
button.place(
x=self.window.winfo_width() - SUB_WIDTH - 1 - self.BUTTON_SIZE_NORMAL * (len(self.buttons) - 2 - i),
y=0,
width=self.BUTTON_SIZE_NORMAL,
height=self.BUTTON_SIZE_NORMAL)
i += 1
def place_sub(self):
i = 0
start_y = (self.window.winfo_height() - SUB_HEIGHT * 5) // 2
# start_y += self.BUTTON_SIZE_NORMAL
for sub_can in self.sub_image_cans:
sub_can.place(x=self.window.winfo_width() - SUB_WIDTH, y=start_y + SUB_HEIGHT * i, width=SUB_WIDTH,
height=SUB_HEIGHT)
i += 1
def reload_cache(self):
caches = load_cache(self.selected_dir)
if len(caches) > 0:
self.caches = caches
self.cursor = 0
self.start_pos = 0
self.load_sub()
self.point_to()
self.load_photo()
def select_dir(self):
# Select the directory of photo(s).
selected_dir = askdirectory()
# Get () as return if selection is canceled or file dialog close.
if selected_dir is not None and selected_dir != ():
self.selected_dir = selected_dir
caches = load_cache(self.selected_dir)
if len(caches) > 0:
self.caches = caches
self.cursor = 0
self.start_pos = 0
self.load_sub()
self.point_to()
self.load_photo()
def delete_cache(self):
if 0 <= self.cursor <= len(self.caches) - 1:
cache = self.caches[self.cursor]
if os.path.exists(cache):
os.remove(cache)
self.caches.remove(cache)
if 0 <= self.cursor <= len(self.caches) - 1:
pass
else:
if len(self.caches) > 0:
self.cursor = len(self.caches) - 1
else:
self.cursor = 0
self.point_to()
self.load_photo()
def __del__(self):
self.photo = None
self.caches = None
self.window = None
del self.photo
del self.caches
del self.window
def test():
my_window = MyWindow()
if __name__ == '__main__':
test()
界面
附录
Python + Tkinter:图片浏览器(一)——最小体积
Python + Tkinter:简易图片浏览器
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)