Helpers - компьютеры, интернет, программирование

Автоматическое изменение размера изображения Python PIL в метке

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

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

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

Я использую 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()

когда я применяю другие стили, такие как более толстая рамка (10 пикселей), эта проблема с изменением размера возникает, показывая, что константы на самом деле не решают проблему.
так есть ли способ получить только пространство внутри виджета, а не его запрашиваемый размер?


Ответы:


1

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

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/3 ширины экрана.

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

22.07.2015
Новые материалы

Интуитивное понимание тензоров в машинном обучении
Тензор является важной концепцией во многих научных областях, таких как математика, физика, обработка сигналов и компьютерное зрение, и это лишь некоторые из них. В математике тензор — это..

Использование машинного обучения для диагностики болезни Альцгеймера, часть 4
Маркеры семантической согласованности для ранней диагностики болезни Альцгеймера (arXiv) Автор: Давиде Колла , Маттео Дельсанто , Марко Агосто , Бенедетто Витиелло , Даниэле Паоло Радичони..

Почему объяснимость так важна прямо сейчас?
По мере того, как системы искусственного интеллекта и инструменты на основе машинного обучения распространяются в нашей повседневной жизни, как практики, так и критики все чаще заявляют о..

Анимированный математический анализ
Использование Manim для создания математических анимированных визуализаций Визуализация данных помогает понять скрытые закономерности в данных, которые невозможно визуализировать..

Создание простого слайдера изображений с помощью JavaScript
Узнайте, как создать базовый слайдер изображений с помощью HTML, CSS и JavaScript. Введение В этом уроке мы создадим удобный слайдер изображений, используя JavaScript, HTML и CSS. Ползунок..

Создание базы данных с помощью супергероя «Python»
В этом посте мы узнаем, как создать «базу данных SQLite с помощью модуля python sqlite3, создав простую функцию входа и регистрации. Готовы ли вы к этому путешествию? Если да , давайте приступим..

ИИ для чайников: руководство для начинающих по пониманию будущего технологий
Вы чувствуете, что остались позади в мире ИИ? Не волнуйтесь, вы не одиноки! Со всей этой шумихой вокруг искусственного интеллекта может быть трудно понять, с чего начать. Но не позволяйте сленгу..