Python + Tkinter:图片浏览器(一)——最小体积

Python + Tkinter:图片浏览器(一)——最小体积,第1张

Python + Tkinter:图片浏览器(一)——最小体积
  • 前言
  • 说明
    • 布局方式
    • 图片展示
    • 自动适应窗口
    • 选择图片目录
  • 功能
    • 图片导航
    • 浏览图库
    • 窗口调整
  • 模块
    • tkinter
    • PIL
    • os
    • 自定义
      • imageutil.py
      • imageviewer.py
  • 界面
  • 笔记

前言

之前写过一篇关于图片浏览器的博文: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 = 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]
        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
# -*- coding: utf-8 -*-
"""
@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:
        # 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.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('', 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)
        # 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)
        # 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=('', 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:
            # 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.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):
        # 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 != ():
            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()


界面

笔记

一个基本图片浏览器难点主要包含:
一)简洁合理的布局
二)图片容器、缩放、居中
三)图片导航
四)浏览图片目录
五)窗口自适应
“麻雀虽小五脏俱全”。


完成以上几点基本功能,后续的扩展就更容易铺设。


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

原文地址: http://outofmemory.cn/langs/568670.html

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

发表评论

登录后才能评论

评论列表(0条)

保存