使用PIL和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=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

解决方法

不确定这是否是您想要的,但是下面我定义了一个import React from 'react'; export default function App() { const [dropdownCount,setDropdownCount] = React.useState(0); return ( <div> {Array.from({ length: dropdownCount }).map((v,idx) => { return ( <div> <p>Dropdown {idx + 1}</p> <MultiDropdown /> </div> ) })} <button onClick={() => setDropdownCount(dropdownCount + 1)}> Increase dropdown </button> </div> ); } ,该图像始终将其图像class的大小调整为950,并将高度调整为原始的width

height*delta

如果稍微修改一下并根据实际窗口值传递宽度/高度,它也可以缩小或增长。

,

去吧。 scale1.0或更低时,图像将始终适合其主图像。该答案基于@HenryYik答案,但通过添加scale参数以及考虑各个方向上的溢出的逻辑,使其更具动态性。另外,不是基于窗口屏幕空间,而是基于主屏幕空间,并且考虑是在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,**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,anchor='se')

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

#init first slide
commit_slide(slide_num,total)

root.focus_force()
root.mainloop()