2015-07-17 15 views
12

Ich versuche, ein Widget zu machen, um ein Bild zu halten, das automatisch die Größe zu seinem Behälter, z. Wenn das Fenster direkt in ein Fenster gepackt wird, wird das Bild beim Erweitern dieses Fensters vergrößert.Python PIL Bild in Label auto resize

Ich habe einige Code, halb funktionsfähig ist, aber ich habe ein paar Konstanten in einer der Routinen der Auto von Re die Größe ergänzen hatte zu verhindern selbst auslösenden (was sie wachsen in der Größe zu halten)

Ich bin mir sicher, dass der Grund dafür der interne Padding/Rahmen der Widgets ist, aber selbst wenn ich versuche, dies zu berücksichtigen, bekomme ich dieses Problem.

Ich verwende Python 3.3.2 und 1.1.7 PIL auf 64-Bit-Windows 7 mein Code ist folgende:

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() 

wenn ich andere Stile anwenden, wie ein dicker Rand (10px) Dieses Größenänderungsproblem tritt auf und zeigt, dass die Konstanten das Problem nicht wirklich lösen.
Gibt es also irgendeine Methode, um nur den Platz innerhalb des Widgets zu erhalten, anstatt die angeforderte Größe?

Antwort

4

Ich glaube, ich habe das jetzt gelöst, aber es braucht wirklich viel mehr Tests mit verschiedenen Parametern, um genaue Ergebnisse zu gewährleisten. Der Code, den ich verwenden zu testen ist dies wie folgt:

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() 

und meine Testfälle waren:

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() 

Das einzige Problem, das ich mit diesem Code gehabt haben, ist, dass, wenn ich bin mit dem Widget in Bei einer Vollbildanwendung funktioniert die Größenanpassung nicht wie vorgesehen, wenn Sie die Rastermethode verwenden und die Gewichtung der rechten Spalte auf 1 (mit dem Bild-Widget) und die linke Spalte (leer) auf 1, die rechte Spalte, setzen endet mit ca. 2/3 der Breite des Bildschirms.

Ich vermute, dies liegt an der expliziten Einstellung der Größe des Bildes, was es dann breiter macht, was bedeutet, dass der Geometrie-Manager es immer noch erweitern möchte (ad infinitum), bis es ein Gleichgewicht erreicht. Aber wenn jemand dieses Licht (oder auch nur eine Lösung) beleuchten kann, würde es geschätzt werden.