Python + Tkinter:图片浏览器(一)——最小体积
前言
之前写过一篇关于图片浏览器的博文:Python + Tkinter:简易图片浏览器。由于仓促间完成,没有过多的琢磨和提炼,作品有些不尽人意。在使用过程中,觉得界面还不够清爽,做过一些改进。但一直没有更新。个人觉得还是图片浏览器,在视觉感官上,简洁纯粹的画面才是所追求的体验。
说明
布局方式
一开始是采用pack布局,格局简单,但是对于图片展示来说占用有限的初始空间。尝试使用绝对定位的place布局。place布局需要预设窗体大小,能做到精准设计。
图片展示
Tkinter库用于展示高清图片的容器主要有有两个:Label和Canvas。这里使用的是画布Canvas。
自动适应窗口
为了更好地展示高清图片,可能会调整窗口大小。显示图片的同时,需要考虑图片和按钮等组件自动适应窗口大小的变化的问题。
选择图片目录
一个具有良好用户体验的软件应该具有至少一个预设的初始化应用场景。同时,提供给用户自主选择的选项。
功能
图片导航
最基本的导航功能有三个:初始化、上一张和下一张。
浏览图库
初始化图片目录和重新选择图片目录。
窗口调整
窗口调整主要需要考虑的是:窗口居中、最大化、放大和缩小。在窗口调整时,也需要调整图片的大小:是放大还是缩小?通过监听窗口大小变化事件,及时原比例缩放图片是基本的要求。
模块
tkinter
PIL
os
自定义
imageutil.py
"""
@author: MR.N
@created: 2021-08-22 Sun. 21:54
"""
from PIL import Image, ImageTk
S_WIDTH = 560
S_HEIGHT = 640
SUB_WIDTH = 166
SUB_HEIGHT = 166
MIN_SUB_WIDTH = 16
MIN_SUB_HEIGHT = 16
I_WIDTH = S_WIDTH
I_HEIGHT = S_HEIGHT
def resize(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 = 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]
min_height = max(SUB_HEIGHT, max(I_HEIGHT, screen_height - 40) // 4)
min_width = int(raw_width * min_height / raw_height)
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
"""
@file: imageviewer
@author: MR.N
@created: 2022/4/6 4月
@version: 1.0
@blog: https://blog.csdn.net/qq_21264377
"""
import tkinter as tk
from tkinter.filedialog import askdirectory
from imageutil import *
import os
def load_cache(media_dir=None):
if media_dir is None:
current_dir = os.path.abspath('.') + '/eming'
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:
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.window = tk.Tk()
self.window.title('Gallery')
self.window.config(bg='#111')
self.frame_top_bar = tk.Frame(self.window)
self.frame_photo = tk.Frame(self.window)
self.window_width = 0
self.window_height = 0
self.window.geometry(
f'{S_WIDTH}x{S_HEIGHT}+{(self.window.winfo_screenwidth() - S_WIDTH) // 2}+{(self.window.winfo_screenheight() - S_HEIGHT) // 2 - 18}')
self.window.bind('<Configure>', self.window_resize)
self.frame_top_bar.config(bg='#111111')
self.photo_can = tk.Canvas(self.frame_photo)
self.caches = load_cache('eming/img')
self.cursor = 0
if len(self.caches) > 0:
self.photo = ImageTk.PhotoImage(
resize(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.config(bg='#111')
self.photo_can.config(highlightthickness=0)
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')
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.open_button = tk.Button(self.window, text='?')
self.open_button.config(command=self.select_dir)
self.open_button.config(font=('', 12))
self.open_button.place(x=S_WIDTH - self.BUTTON_SIZE_NORMAL, y=0, width=self.BUTTON_SIZE_NORMAL,
height=self.BUTTON_SIZE_NORMAL)
for button in [self.prev_button, self.next_button, self.open_button]:
button.config(relief='ridge')
button.config(fg='#fff')
button.config(activeforeground='#f5f5f5')
button.config(activebackground='#333')
button.config(bg='#222')
button.config(bd=0)
button.config(highlightthickness=0)
button.config(highlightcolor='#111')
button.config(highlightbackground='#111')
self.frame_top_bar.pack(side='bottom', expand='no', fill='x')
self.frame_photo.pack(side='top', expand='yes', fill='both')
self.first_load = True
self.window.mainloop()
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(self.caches[self.cursor], screen_width=width, screen_height=height)
self.photo = ImageTk.PhotoImage(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 prev_photo(self):
if len(self.caches) > 0:
if self.cursor > 0:
self.cursor -= 1
self.load_photo()
def next_photo(self):
if len(self.caches) > 0:
if self.cursor < len(self.caches) - 1:
self.cursor += 1
self.load_photo()
def window_resize(self, event=None):
if event is not None:
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()
if self.first_load:
self.first_load = False
else:
self.prev_button.place(x=0, y=(self.photo_can.winfo_height() - self.BUTTON_SIZE_NORMAL) // 2,
width=self.BUTTON_SIZE_NORMAL, height=self.BUTTON_SIZE_NORMAL)
self.next_button.place(x=self.photo_can.winfo_width() - self.BUTTON_SIZE_NORMAL,
y=(self.photo_can.winfo_height() - self.BUTTON_SIZE_NORMAL) // 2,
width=self.BUTTON_SIZE_NORMAL, height=self.BUTTON_SIZE_NORMAL)
self.open_button.place(x=self.photo_can.winfo_width() - self.BUTTON_SIZE_NORMAL,
y=0,
width=self.BUTTON_SIZE_NORMAL, height=self.BUTTON_SIZE_NORMAL)
self.load_photo()
def select_dir(self):
selected_dir = askdirectory()
if selected_dir is not None and selected_dir != ():
caches = load_cache(selected_dir)
if len(caches) > 0:
self.caches = caches
self.cursor = 0
self.load_photo()
def __del__(self):
self.window = None
del self.window
def test():
my_window = MyWindow()
if __name__ == '__main__':
test()
界面
笔记
一个基本图片浏览器难点主要包含: 一)简洁合理的布局 二)图片容器、缩放、居中 三)图片导航 四)浏览图片目录 五)窗口自适应 “麻雀虽小五脏俱全”。完成以上几点基本功能,后续的扩展就更容易铺设。
|