使用 PIL 和 tkinter 动态调整图像大小

Dynamic resizing of image using PIL and tkinter

我想知道是否可以动态调整图像大小(保持其纵横比)。我制作了一个图像查看器应用程序,但是垂直长的图像溢出了屏幕,所以我想知道一种调整图像大小的方法,我尝试了一种方法,它包含在下面。但我仍然得到溢出屏幕的相同输出。

from win32api import GetSystemMetrics
from tkinter import *

screen_width, screen_height = GetSystemMetrics(0), GetSystemMetrics(1)

root = Tk() # this is your window
root.geometry("{}x{}".format(screen_width//2, screen_height//2)) # set size of you window here is example for 1/2 screen height and width

img = Image.open("picture_name.png")
width, height = screen_width//4, screen_height//4 

img.resize((width, height), Image.ANTIALIAS) 

l = Label(root,image=img)
l.pack()

root.mainloop()

我仍然收到未调整大小的图像,不知道为什么。

然后我尝试了这个方法,我在其中设置了一个分辨率,它适用于我的屏幕。但是如果我要发送给其他人,它不会动态调整。

desired_size = 950

im = Image.open('img.png')
old_size = im.size

ratio = float(desired_size)/max(old_size)
new_size = tuple([int(x*ratio) for x in old_size])

im = im.resize(new_size, Image.ANTIALIAS)
img = ImageTk.PhotoImage(im)
l = Label(root, image=img)
l.image = img
l.pack()

我想知道一种动态调整图像大小的方法,同时保持其纵横比,这样就不会发生失真,就像 Windows 10 中的照片应用程序一样。

完整代码:

from tkinter import *
from tkinter import messagebox
from glob import glob
from tkinter import filedialog
from PIL import Image, ImageTk

root = Tk()
root.config(bg='white')
root.title('Image Viewer App')

def forward_image(event=None):
    global n
    n += 1
    if n > len(main_img)-2:
        forward['state'] = DISABLED
        root.unbind('<Key-Right>')

    else:
        backward['state'] = NORMAL
        root.bind('<Key-Left>', backward_image)

    im = Image.open(main_img[n])
    old_size = im.size

    ratio = float(desired_size)/max(old_size)
    new_size = tuple([int(x*ratio) for x in old_size])

    im = im.resize(new_size, Image.ANTIALIAS)
    img = ImageTk.PhotoImage(im)
    l.image = img
    l.config(image=img)
    status.config(text=f'{n+1} of {total} images')

def backward_image(event=None):
    global n
    n -= 1
    if n <= 0:
        backward['state'] = DISABLED
        root.unbind('<Key-Left>')

    else:
        forward['state'] = NORMAL
        root.bind('<Key-Right>', forward_image)

    im = Image.open(main_img[n])
    old_size = im.size

    ratio = float(desired_size)/max(old_size)
    new_size = tuple([int(x*ratio) for x in old_size])

    im = im.resize(new_size, Image.ANTIALIAS)
    img = ImageTk.PhotoImage(im)
    l.image = img
    l.config(image=img)
    status.config(text=f'{n+1} of {total} images')

def path():
    global main_img
    path = filedialog.askdirectory(
        initialdir='c:/', title='Select a folder with images')
    img_png = glob(path+'/*.png')
    img_jpg = glob(path+'/*.jpg')
    main_img = img_jpg + img_png


path()

n = 0
desired_size = 950

im = Image.open(main_img[n])
old_size = im.size

ratio = float(desired_size)/max(old_size)
new_size = tuple([int(x*ratio) for x in old_size])

im = im.resize(new_size, Image.ANTIALIAS)
img = ImageTk.PhotoImage(im)
l = Label(root, image=img)
l.image = img
l.pack()


forward = Button(root, text='Forward', command=forward_image)
forward.pack(side=RIGHT)

backward = Button(root, text='Backward', command=backward_image)
backward.pack(side=LEFT)
backward['state'] = DISABLED

total = len(main_img)
status = Label(root,text=f'{n+1} of {total} images',bg='white',font=('helvetica',10))
status.pack(side=BOTTOM)

root.focus_force()

root.bind('<Key-Left>', backward_image)
root.bind('<Key-Right>', forward_image)
root.bind('<Escape>', lambda event: root.state('normal'))
root.bind('<F11>', lambda event: root.state('zoomed'))

if total <= 1:
    backward['state'] = DISABLED
    forward['state'] = DISABLED
    root.unbind('<Key-Right>')
    root.unbind('<Key-Left>')

if total == 0:
    messagebox.showerror('No image','Choose a directory with images.')
root.mainloop()

提前致谢:D

不确定这是否是您想要的,但我在下面定义了一个 class,它始终将其图像 width 调整为 950,并将高度调整为原始 height*delta:

import tkinter as tk
from PIL import Image, ImageTk

root = tk.Tk()

class DynamicImage(tk.Label):
    def __init__(self, master=None, image_path="", *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.width = master.winfo_screenwidth()//2
        self.height = master.winfo_screenheight()//2
        self.img = Image.open(image_path)
        self.p_img = None
        self.bind("<Configure>", self.resizing)

    def resizing(self, event=None):
        w, h = self.img.width, self.img.height
        if w>h:
            delta = self.width/w
            new_width, new_height = self.width, int(h*delta)
        else:
            delta = self.height/h
            new_width, new_height = int(w*delta), self.height
        self.p_img = ImageTk.PhotoImage(self.img.resize((new_width, new_height)))
        self.config(image=self.p_img)
        s.config(text=f"Dimension: {self.p_img.width()}x{self.p_img.height()}")

s = tk.Label(text="")
s.pack()

DynamicImage(root, image_path="your_path").pack(fill="both",expand=True)

root.mainloop()

如果稍微修改一下并根据实际 window 值传递 width/height,它也可以缩小或增大。

给你。如果 scale1.0 或更低,图像将 总是 适合它的母版。这个答案基于@HenryYik 的答案,但通过添加 scale 参数和考虑各个方向溢出的逻辑变得更加动态。此外,它不是基于 window 屏幕空间,而是基于主屏幕空间,并且在 resizing 中进行了考虑,而不是在 __init__.

其他变化:

  • 使用 super()__init__ 超类并不理想,因此该部分已更改为更严格的语法。
  • 除非你的头上有一个运行列表,所有kwargs的确切顺序,对于每个小部件,你永远不会使用*args,所以它被省略了。

import tkinter as tk
from tkinter import messagebox, filedialog
from glob import glob
from PIL import Image, ImageTk

#configure root
root = tk.Tk()
root.title('Image Viewer App')
root.geometry('800x600')
root.config(bg='#222222',bd=0,padx=0,pady=0,highlightthickness=0)
root.bind('<Escape>', lambda event: root.state('normal'))
root.bind('<F11>', lambda event: root.state('zoomed'))
    

class Slide(tk.Label):
    def __init__(self, master, image_path:str='', scale:float=1.0, **kwargs):
        tk.Label.__init__(self, master, **kwargs)
        self.configure(bg=master['bg'])
        self.img   = None if not image_path else Image.open(image_path)
        self.p_img = None
        self.scale = scale
                
        self.bind("<Configure>", self.resizing)
        
    def set_image(self, image_path:str):
        self.img   = Image.open(image_path)
        self.resizing()

    def resizing(self, event=None):
        if self.img:
            iw, ih  = self.img.width, self.img.height
            mw, mh  = self.master.winfo_width(), self.master.winfo_height()
            
            if iw>ih:
                ih = ih*(mw/iw)
                r = mh/ih if (ih/mh) > 1 else 1
                iw, ih = mw*r, ih*r
            else:
                iw = iw*(mh/ih)
                r = mw/iw if (iw/mw) > 1 else 1
                iw, ih = iw*r, mh*r
                
            self.p_img = ImageTk.PhotoImage(self.img.resize((int(iw*self.scale), int(ih*self.scale))))
            self.config(image=self.p_img)



total     = 0
slide_num = 0

def get_slides():
    global total
    path  = filedialog.askdirectory(initialdir='c:/', title='Select a folder with images')
    cache = glob(path+'/*.png') + glob(path+'/*.jpg')
    
    total = len(cache)
    if not total:
        m = messagebox.askyesno('No Images','The directory you have chosen does not contain any images. Try Again?')
        if m:
            return get_slides()
        else:
            root.quit()
            exit(0)
        
    return cache


image_cache = get_slides()


def commit_slide(n, t):
    slide.set_image(image_cache[n])
    status.config(text=f'{n+1} of {t} images')

    
def next_slide(event=None):
    global slide_num, total
    slide_num = (slide_num+1)%len(image_cache)       #wrap
    commit_slide(slide_num, total)
    
root.bind('<Key-Right>', next_slide)


def previous_slide(event=None):
    global slide_num, total
    slide_num = range(len(image_cache))[slide_num-1] #wrap
    commit_slide(slide_num, total)
    
root.bind('<Key-Left>', previous_slide)


#init display widgets
slide = Slide(root)
slide.pack()

tk.Button(root, text='prev', command=previous_slide).place(relx=.02, rely=.99, anchor='sw')
tk.Button(root, text='next', command=next_slide).place(relx=.98, rely=.99, anchor='se')

status = tk.Label(root, bg='white', font=('helvetica',10))
status.place(relx=.5, rely=.99, anchor='s')

#init first slide
commit_slide(slide_num, total)

root.focus_force()
root.mainloop()