Python + Tkinter:图片浏览器(二)

Python + Tkinter:图片浏览器(二),第1张

Python + Tkinter:图片浏览器(二)
  • 前言
  • 说明
    • 改进
  • 功能
    • 图片导航
      • 显示上一张高清图片
      • 显示下一张高清图片
    • 缩略图导航
      • 缩略图列表
      • 缩略图上一页
      • 缩略图下一页
      • 缩略图指引
    • 图片管理
      • 高清图片
      • 删除图片
    • 图库管理
      • 浏览图库
      • 重载图库
    • 调整窗口
      • 窗口标题栏
      • 窗口自适应布局
        • 工具栏自适应
        • 高清图自适应
        • 导航按钮自适应
        • 缩略图自适应
      • 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:简易图片浏览器

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/langs/718859.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存