2015-07-17 2 views
12

Я пытаюсь сделать виджет для хранения изображения, которое будет автоматически изменяться в соответствии с его контейнером, например. если он упакован непосредственно в окно, то расширение этого окна расширит изображение.Python PIL Image in Label auto resize

У меня есть некоторый код, который является пол функциональным, но я должен был добавить несколько констант в одну из подпрограмм, чтобы предотвратить автоматическое изменение размеров от повторного запуска себя (в результате чего он будет продолжать расти в размерах)

Я уверен, что причина этого связана с внутренними дополнениями/границами виджетов, но даже пытаясь принять это во внимание, я получаю эту проблему.

Я использую Python 3.3.2, и PIL 1.1.7 на 64-битной ОС 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) возникает эта проблема изменения размера, показывающая, что константы действительно не решают проблему.
так есть ли какой-либо способ получить только пространство внутри виджета, а не его размер?

ответ

4

Я считаю, что сейчас я решил это, но для того, чтобы обеспечить точные результаты, на самом деле требуется гораздо больше испытаний с различными параметрами. Код я должен использовать, чтобы проверить это следующим образом:

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 (с виджетами пиктограммы), а левый столбец (пустой) равен 1, правый столбец заканчивается тем, что занимает около 2/3rds ширину экрана.

Я подозреваю, что это связано с явным определением размера изображения, что делает его более широким, что означает, что менеджер геометрии хочет сделать его еще шире (до бесконечности), пока не достигнет некоторого равновесия. Но если кто-то может пролить свет на это (или даже на решение), это будет оценено.