Python PIL 图片标签自动调整大小
Python PIL Image in Label auto resize
我正在尝试制作一个小部件来保存图像,该图像会自动调整大小以适合其容器,例如如果直接打包成 window,那么扩展 window 将扩展图像。
我有一些半功能代码,但我不得不在其中一个例程中添加几个常量,以防止自动调整大小重新触发自身(导致其大小不断增长)
我确定这是由于内部小部件引起的 padding/border,但即使尝试考虑到这一点,我也遇到了这个问题。
我在 64 位上使用 python 3.3.2 和 PIL 1.1.7 Windows 7
我的代码如下:
from tkinter import tix
from PIL import Image, ImageTk
def Resize_Image(image, maxsize):
r1 = image.size[0]/maxsize[0] # width ratio
r2 = image.size[1]/maxsize[1] # height ratio
ratio = max(r1, r2)
newsize = (int(image.size[0]/ratio), int(image.size[1]/ratio)) # keep image aspect ratio
image = image.resize(newsize, Image.ANTIALIAS)
return image
class Pict_Frame(tix.Label):
def __init__(self, parent=None, picture=None, maxupdate=None, **kwargs):
tix.Label.__init__(self, parent, **kwargs)
self.bind("<Configure>", self._resize_binding)
self.maxupdate = maxupdate
self.update_after_id = None
self.photo = None
self.image = None
if picture:
self.set_picture(picture)
def _resize_binding(self, event):
if self.photo:
if not self.maxupdate:
self.load_picture()
else:
if not self.update_after_id:
self.update_after_id = self.after(int(1000/self.maxupdate), self.load_picture)
def load_picture(self):
if self.photo:
if self.update_after_id:
self.update_after_id = None
if (self.winfo_width() > 1) and (self.winfo_height() > 1): # prevent updates before widget gets sized
self.image = ImageTk.PhotoImage(Resize_Image(self.photo, (
self.winfo_width()-int(self.cget("bd"))-1, self.winfo_height()-int(self.cget("bd"))-1)))
# here is where I added the constants ^^^
# but even using cget to get the border size I have had to add to this
# to prevent the resize loop, and when using other widget styles
#(raised etc) this problem persists
self.configure(image=self.image)
def set_picture(self, filename):
with open(filename, mode="rb") as file:
self.photo = Image.open(file)
self.photo.load() # load image into memory to allow resizing later without file access
self.load_picture()
if __name__ == "__main__":
test = Pict_Frame(bg="grey", bd=2, relief="raised",
maxupdate=2, # allows problem to be easily seen
picture="image.jpg")
test.pack(fill="both", expand=True)
test.master.mainloop()
当我应用其他样式时,例如较粗的边框 (10px),会出现此大小调整问题,表明常量并不能真正解决问题。
那么有什么方法可以只获取小部件内的 space 而不是其请求的大小?
我相信我现在已经解决了这个问题,但它确实需要使用不同的参数进行更多测试以确保准确的结果。我用来测试的代码如下:
from tkinter import tix
from PIL import Image, ImageTk
def Resize_Image(image, maxsize):
r1 = image.size[0]/maxsize[0] # width ratio
r2 = image.size[1]/maxsize[1] # height ratio
ratio = max(r1, r2)
newsize = (int(image.size[0]/ratio), int(image.size[1]/ratio)) # keep image aspect ratio
image = image.resize(newsize, Image.ANTIALIAS)
return image
class Pict_Frame(tix.Label):
def __init__(self, parent=None, picture=None, maxupdate=None, imagesize=None, **kwargs):
tix.Label.__init__(self, parent, **kwargs)
self.bind("<Configure>", self._resize_binding)
self.maxupdate = maxupdate
self.imagesize = imagesize
self.update_after_id = None # used for update rate limiting
self.photo = None # used to store raw image from file for later use
self.image = None # used for reference to the resized image
if imagesize:
self.photo=Image.new("RGB", (1,1)) # create empty image to insert
self.image=ImageTk.PhotoImage(self.photo) # create instance of image for PIL
self.configure(image=self.image)
self.configure(width=imagesize[0], height=imagesize[1]) # not label uses pixels for size, set size passed in
if picture:
self.set_picture(picture) # we have a picture so load it now
def _resize_binding(self, event):
if self.photo: # we have a picture
if not self.maxupdate: # no rate limiting
self.load_picture()
else:
if not self.update_after_id: # if we're not waiting then queue resize
self.update_after_id = self.after(int(1000/self.maxupdate), self.load_picture)
def load_picture(self):
if self.photo:
if self.update_after_id:
self.update_after_id = None
if (self.winfo_width() > 1) and (self.winfo_height() > 1): # prevent updates before widget gets sized
bd = self.cget("bd") # get the border width
if type(bd) != int: # if there was no border set we get an object back
pad = 4 # set this explicitly to avoid problems
else:
pad = int(bd*2) # we have a border both sides, so double the retrieved value
newsize = (self.winfo_width()-pad, self.winfo_height()-pad)
elif self.imagesize: # only use the passed in image size if the widget has not rendered
newsize = self.imagesize
else:
return # widget not rendered yet and no size explicitly set, so break until rendered
self.image = ImageTk.PhotoImage(Resize_Image(self.photo, newsize))
self.configure(image=self.image)
def set_picture(self, filename):
with open(filename, mode="rb") as file:
self.photo = Image.open(file)
self.photo.load() # load image into memory to allow resizing later without file access
self.load_picture()
我的测试用例是:
import os
path = "E:\imagefolder"
images = []
ind = 0
for item in os.listdir(path): # get a fully qualified list of images
if os.path.isdir(os.path.join(path, item)):
if os.path.isfile(os.path.join(path, item, "thumb.jpg")):
images.append(os.path.join(path, item, "thumb.jpg"))
def callback():
global ind
ind += 1
if ind >= len(images):
ind = 0
pict.set_picture(images[ind])
ignore_test_cases = []
if 1 not in ignore_test_cases:
print("test case 1: - no border no set size")
root = tix.Tk()
tix.Button(root, text="Next Image", command=callback).pack()
pict = Pict_Frame(parent=root, bg="grey",
maxupdate=2, # allows problem to be easily seen
picture=images[ind])
pict.pack(fill="both", expand=True)
tix.Button(root, text="Next Image", command=callback).pack()
root.mainloop()
if 2 not in ignore_test_cases:
print("test case 2: - small border no set size")
root = tix.Tk()
tix.Button(root, text="Next Image", command=callback).pack()
pict = Pict_Frame(parent=root, bg="grey", bd=2, relief="raised",
maxupdate=2,
picture=images[ind])
pict.pack(fill="both", expand=True)
tix.Button(root, text="Next Image", command=callback).pack()
root.mainloop()
if 3 not in ignore_test_cases:
print("test case 3: - large border no set size")
root = tix.Tk()
tix.Button(root, text="Next Image", command=callback).pack()
pict = Pict_Frame(parent=root, bg="grey", bd=10, relief="raised",
maxupdate=2,
picture=images[ind])
pict.pack(fill="both", expand=True)
tix.Button(root, text="Next Image", command=callback).pack()
root.mainloop()
if 4 not in ignore_test_cases:
print("test case 4: - no border with set size")
root = tix.Tk()
tix.Button(root, text="Next Image", command=callback).pack()
pict = Pict_Frame(parent=root, bg="grey",
maxupdate=2,
imagesize=(256,384),
picture=images[ind])
pict.pack(fill="both", expand=True)
tix.Button(root, text="Next Image", command=callback).pack()
root.mainloop()
if 5 not in ignore_test_cases:
print("test case 5: - small border with set size")
root = tix.Tk()
tix.Button(root, text="Next Image", command=callback).pack()
pict = Pict_Frame(parent=root, bg="grey", bd=2, relief="raised",
maxupdate=2,
imagesize=(256,384),
picture=images[ind])
pict.pack(fill="both", expand=True)
tix.Button(root, text="Next Image", command=callback).pack()
root.mainloop()
if 6 not in ignore_test_cases:
print("test case 6: - large border with set size")
root = tix.Tk()
tix.Button(root, text="Next Image", command=callback).pack()
pict = Pict_Frame(parent=root, bg="grey", bd=10, relief="raised",
maxupdate=2,
imagesize=(256,384),
picture=images[ind])
pict.pack(fill="both", expand=True)
tix.Button(root, text="Next Image", command=callback).pack()
root.mainloop()
if 10 not in ignore_test_cases:
print("test case fullscreen: - small border no set size, in fullscreen window with expansion set up")
root = tix.Tk()
root.state("zoomed")
root.grid_columnconfigure(1, weight=2)
root.grid_columnconfigure(2, weight=1)
root.grid_rowconfigure(2, weight=1)
tix.Button(root, text="Next Image", command=callback).grid(column=2, row=1, sticky="nesw")
pict = Pict_Frame(parent=root, bg="grey",# bd=10, relief="raised",
maxupdate=2,
picture=images[ind])
pict.grid(column=2, row=2, sticky="nesw")
tix.Button(root, text="Next Image", command=callback).grid(column=2, row=3, sticky="nesw")
root.mainloop()
if 11 not in ignore_test_cases:
print("test case fullscreen: - small border no set size, in fullscreen window with expansion set up")
root = tix.Tk()
root.state("zoomed")
root.grid_columnconfigure(1, weight=2)
root.grid_columnconfigure(2, weight=1)
root.grid_rowconfigure(1, weight=1)
frame = tix.Frame(root)
frame.grid(column=2, row=1, sticky="nesw")
frame.grid_columnconfigure(1, weight=1)
frame.grid_rowconfigure(2, weight=1)
tix.Button(frame, text="Next Image", command=callback).grid(column=1, row=1, sticky="nesw")
pict = Pict_Frame(parent=frame, bg="grey",# bd=10, relief="raised",
maxupdate=2,
picture=images[ind])
pict.grid(column=1, row=2, sticky="nesw")
tix.Button(frame, text="Next Image", command=callback).grid(column=1, row=3, sticky="nesw")
root.mainloop()
我在使用此代码时遇到的唯一问题是,当我在全屏应用程序中使用小部件时,在使用网格方法并设置右列为 1(使用 pict 小部件),左列(空)为 1,右列最终占据屏幕宽度的大约 2/3。
我怀疑这是由于明确设置图像的大小,然后使其变宽,这意味着几何管理器希望使其更宽(无穷大)直到达到某种平衡。但是,如果有人能对此(甚至解决方案)有所了解,我们将不胜感激。
我正在尝试制作一个小部件来保存图像,该图像会自动调整大小以适合其容器,例如如果直接打包成 window,那么扩展 window 将扩展图像。
我有一些半功能代码,但我不得不在其中一个例程中添加几个常量,以防止自动调整大小重新触发自身(导致其大小不断增长)
我确定这是由于内部小部件引起的 padding/border,但即使尝试考虑到这一点,我也遇到了这个问题。
我在 64 位上使用 python 3.3.2 和 PIL 1.1.7 Windows 7 我的代码如下:
from tkinter import tix
from PIL import Image, ImageTk
def Resize_Image(image, maxsize):
r1 = image.size[0]/maxsize[0] # width ratio
r2 = image.size[1]/maxsize[1] # height ratio
ratio = max(r1, r2)
newsize = (int(image.size[0]/ratio), int(image.size[1]/ratio)) # keep image aspect ratio
image = image.resize(newsize, Image.ANTIALIAS)
return image
class Pict_Frame(tix.Label):
def __init__(self, parent=None, picture=None, maxupdate=None, **kwargs):
tix.Label.__init__(self, parent, **kwargs)
self.bind("<Configure>", self._resize_binding)
self.maxupdate = maxupdate
self.update_after_id = None
self.photo = None
self.image = None
if picture:
self.set_picture(picture)
def _resize_binding(self, event):
if self.photo:
if not self.maxupdate:
self.load_picture()
else:
if not self.update_after_id:
self.update_after_id = self.after(int(1000/self.maxupdate), self.load_picture)
def load_picture(self):
if self.photo:
if self.update_after_id:
self.update_after_id = None
if (self.winfo_width() > 1) and (self.winfo_height() > 1): # prevent updates before widget gets sized
self.image = ImageTk.PhotoImage(Resize_Image(self.photo, (
self.winfo_width()-int(self.cget("bd"))-1, self.winfo_height()-int(self.cget("bd"))-1)))
# here is where I added the constants ^^^
# but even using cget to get the border size I have had to add to this
# to prevent the resize loop, and when using other widget styles
#(raised etc) this problem persists
self.configure(image=self.image)
def set_picture(self, filename):
with open(filename, mode="rb") as file:
self.photo = Image.open(file)
self.photo.load() # load image into memory to allow resizing later without file access
self.load_picture()
if __name__ == "__main__":
test = Pict_Frame(bg="grey", bd=2, relief="raised",
maxupdate=2, # allows problem to be easily seen
picture="image.jpg")
test.pack(fill="both", expand=True)
test.master.mainloop()
当我应用其他样式时,例如较粗的边框 (10px),会出现此大小调整问题,表明常量并不能真正解决问题。
那么有什么方法可以只获取小部件内的 space 而不是其请求的大小?
我相信我现在已经解决了这个问题,但它确实需要使用不同的参数进行更多测试以确保准确的结果。我用来测试的代码如下:
from tkinter import tix
from PIL import Image, ImageTk
def Resize_Image(image, maxsize):
r1 = image.size[0]/maxsize[0] # width ratio
r2 = image.size[1]/maxsize[1] # height ratio
ratio = max(r1, r2)
newsize = (int(image.size[0]/ratio), int(image.size[1]/ratio)) # keep image aspect ratio
image = image.resize(newsize, Image.ANTIALIAS)
return image
class Pict_Frame(tix.Label):
def __init__(self, parent=None, picture=None, maxupdate=None, imagesize=None, **kwargs):
tix.Label.__init__(self, parent, **kwargs)
self.bind("<Configure>", self._resize_binding)
self.maxupdate = maxupdate
self.imagesize = imagesize
self.update_after_id = None # used for update rate limiting
self.photo = None # used to store raw image from file for later use
self.image = None # used for reference to the resized image
if imagesize:
self.photo=Image.new("RGB", (1,1)) # create empty image to insert
self.image=ImageTk.PhotoImage(self.photo) # create instance of image for PIL
self.configure(image=self.image)
self.configure(width=imagesize[0], height=imagesize[1]) # not label uses pixels for size, set size passed in
if picture:
self.set_picture(picture) # we have a picture so load it now
def _resize_binding(self, event):
if self.photo: # we have a picture
if not self.maxupdate: # no rate limiting
self.load_picture()
else:
if not self.update_after_id: # if we're not waiting then queue resize
self.update_after_id = self.after(int(1000/self.maxupdate), self.load_picture)
def load_picture(self):
if self.photo:
if self.update_after_id:
self.update_after_id = None
if (self.winfo_width() > 1) and (self.winfo_height() > 1): # prevent updates before widget gets sized
bd = self.cget("bd") # get the border width
if type(bd) != int: # if there was no border set we get an object back
pad = 4 # set this explicitly to avoid problems
else:
pad = int(bd*2) # we have a border both sides, so double the retrieved value
newsize = (self.winfo_width()-pad, self.winfo_height()-pad)
elif self.imagesize: # only use the passed in image size if the widget has not rendered
newsize = self.imagesize
else:
return # widget not rendered yet and no size explicitly set, so break until rendered
self.image = ImageTk.PhotoImage(Resize_Image(self.photo, newsize))
self.configure(image=self.image)
def set_picture(self, filename):
with open(filename, mode="rb") as file:
self.photo = Image.open(file)
self.photo.load() # load image into memory to allow resizing later without file access
self.load_picture()
我的测试用例是:
import os
path = "E:\imagefolder"
images = []
ind = 0
for item in os.listdir(path): # get a fully qualified list of images
if os.path.isdir(os.path.join(path, item)):
if os.path.isfile(os.path.join(path, item, "thumb.jpg")):
images.append(os.path.join(path, item, "thumb.jpg"))
def callback():
global ind
ind += 1
if ind >= len(images):
ind = 0
pict.set_picture(images[ind])
ignore_test_cases = []
if 1 not in ignore_test_cases:
print("test case 1: - no border no set size")
root = tix.Tk()
tix.Button(root, text="Next Image", command=callback).pack()
pict = Pict_Frame(parent=root, bg="grey",
maxupdate=2, # allows problem to be easily seen
picture=images[ind])
pict.pack(fill="both", expand=True)
tix.Button(root, text="Next Image", command=callback).pack()
root.mainloop()
if 2 not in ignore_test_cases:
print("test case 2: - small border no set size")
root = tix.Tk()
tix.Button(root, text="Next Image", command=callback).pack()
pict = Pict_Frame(parent=root, bg="grey", bd=2, relief="raised",
maxupdate=2,
picture=images[ind])
pict.pack(fill="both", expand=True)
tix.Button(root, text="Next Image", command=callback).pack()
root.mainloop()
if 3 not in ignore_test_cases:
print("test case 3: - large border no set size")
root = tix.Tk()
tix.Button(root, text="Next Image", command=callback).pack()
pict = Pict_Frame(parent=root, bg="grey", bd=10, relief="raised",
maxupdate=2,
picture=images[ind])
pict.pack(fill="both", expand=True)
tix.Button(root, text="Next Image", command=callback).pack()
root.mainloop()
if 4 not in ignore_test_cases:
print("test case 4: - no border with set size")
root = tix.Tk()
tix.Button(root, text="Next Image", command=callback).pack()
pict = Pict_Frame(parent=root, bg="grey",
maxupdate=2,
imagesize=(256,384),
picture=images[ind])
pict.pack(fill="both", expand=True)
tix.Button(root, text="Next Image", command=callback).pack()
root.mainloop()
if 5 not in ignore_test_cases:
print("test case 5: - small border with set size")
root = tix.Tk()
tix.Button(root, text="Next Image", command=callback).pack()
pict = Pict_Frame(parent=root, bg="grey", bd=2, relief="raised",
maxupdate=2,
imagesize=(256,384),
picture=images[ind])
pict.pack(fill="both", expand=True)
tix.Button(root, text="Next Image", command=callback).pack()
root.mainloop()
if 6 not in ignore_test_cases:
print("test case 6: - large border with set size")
root = tix.Tk()
tix.Button(root, text="Next Image", command=callback).pack()
pict = Pict_Frame(parent=root, bg="grey", bd=10, relief="raised",
maxupdate=2,
imagesize=(256,384),
picture=images[ind])
pict.pack(fill="both", expand=True)
tix.Button(root, text="Next Image", command=callback).pack()
root.mainloop()
if 10 not in ignore_test_cases:
print("test case fullscreen: - small border no set size, in fullscreen window with expansion set up")
root = tix.Tk()
root.state("zoomed")
root.grid_columnconfigure(1, weight=2)
root.grid_columnconfigure(2, weight=1)
root.grid_rowconfigure(2, weight=1)
tix.Button(root, text="Next Image", command=callback).grid(column=2, row=1, sticky="nesw")
pict = Pict_Frame(parent=root, bg="grey",# bd=10, relief="raised",
maxupdate=2,
picture=images[ind])
pict.grid(column=2, row=2, sticky="nesw")
tix.Button(root, text="Next Image", command=callback).grid(column=2, row=3, sticky="nesw")
root.mainloop()
if 11 not in ignore_test_cases:
print("test case fullscreen: - small border no set size, in fullscreen window with expansion set up")
root = tix.Tk()
root.state("zoomed")
root.grid_columnconfigure(1, weight=2)
root.grid_columnconfigure(2, weight=1)
root.grid_rowconfigure(1, weight=1)
frame = tix.Frame(root)
frame.grid(column=2, row=1, sticky="nesw")
frame.grid_columnconfigure(1, weight=1)
frame.grid_rowconfigure(2, weight=1)
tix.Button(frame, text="Next Image", command=callback).grid(column=1, row=1, sticky="nesw")
pict = Pict_Frame(parent=frame, bg="grey",# bd=10, relief="raised",
maxupdate=2,
picture=images[ind])
pict.grid(column=1, row=2, sticky="nesw")
tix.Button(frame, text="Next Image", command=callback).grid(column=1, row=3, sticky="nesw")
root.mainloop()
我在使用此代码时遇到的唯一问题是,当我在全屏应用程序中使用小部件时,在使用网格方法并设置右列为 1(使用 pict 小部件),左列(空)为 1,右列最终占据屏幕宽度的大约 2/3。
我怀疑这是由于明确设置图像的大小,然后使其变宽,这意味着几何管理器希望使其更宽(无穷大)直到达到某种平衡。但是,如果有人能对此(甚至解决方案)有所了解,我们将不胜感激。