【问题标题】:Resizing and Redrawing Canvas in tkinter在 tkinter 中调整画布大小和重绘画布
【发布时间】:2021-04-28 03:45:23
【问题描述】:

我目前正在尝试在画布上显示图像。更具体地说,我想让画布上绘制的图像根据窗口的大小调整大小(这样图像总是适合画布)。

我从一个填满整个屏幕的简单画布开始。

import tkinter as tk
from PIL import Image, ImageTk

root = tk.Tk()
WIDTH, HEIGHT = root.winfo_screenwidth(), root.winfo_screenheight()
root.geometry('%dx%d+0+0' % (WIDTH, HEIGHT))

canvas = tk.Canvas(root)
canvas.pack(fill="both", expand=True)

然后我会设置我的背景图像和能够在背景上调整大小的图像。

backgroundImage = ImageTk.PhotoImage(Image.open("filepath"))
image1 = Image.open("filepath")
image2 = ...
....
....

然后我创建了一个调整图像大小的方法。

"""
This method resizes a image so that all the images fits on the GUI. This first creates an Image object,
but since the Image object does not allow access to the width and height of the Image object, a ImageTk
object needs to be created from the Image object. The ImageTk object cannot resize, but the Image object
can. So using ImageTk object to get the height and width and the Image object to resize, a new Image object
that is resized to fit the GUI is created.
@imageFile- the image file that is being resized
@windowMeasure- the measurement of the window to proportionally resize the image
@useHeight- determine the measurement being proportioned
"""
def resizedImageTk(image, windowMeasure, useHeight):
    imageTk = ImageTk.PhotoImage(image)
    area = windowMeasure * 4/5
    tileSize = area / 4
    if useHeight:
        proportion = tileSize / imageTk.height()
    else:
        proportion = tileSize / imageTk.width()
    resizedImage = image.resize((int(imageTk.width()*proportion), int(imageTk.height()*proportion)))
    resizedImageTk = ImageTk.PhotoImage(resizedImage)
    return resizedImageTk

然后我使用一种方法在窗口大小发生变化时重新绘制调整大小的图像并将其绑定到根。注意:我知道这可能是非常计算的,所以我减少了这种情况发生的次数

numResizes = 0
def handle_configure(event):
    if numResizes % 5 == 0:
        geometry = root.geometry()
        width = int(geometry[0:geometry.index("x")])
        height = int(geometry[geometry.index("x")+1:geometry.index("+")])
        canvas.create_image((0,0), image=backgroundImageTk, anchor="nw")

        if height < width:
            measurement = height
        else:
            measurement = width

        resizedImage1 = resizedImageTk(image1, measurement, height < width)
        resizedImage2 = ....
        ....
        ....

   
        images = [resizedImage1, resizedImage2, ...]
    
        imageWidth = resizedImage1.width()
        imageHeight = resizedImage1.height()
    
        i = 0
        for row in range(0, int(len(images) / 4)):
            for column in range(0, int(len(images) / 5):
                x = imageWidth*column + int(width/2) - imageWidth * 2
                y = imageHeight*row + int(height/2) - int(imageHeight*2.5)
                canvas.create_image((x, y), image=images[i])
                i=i+1
    numResizes = numResizes + 1  
root.bind("<Configure>", handle_configure)
root.mainloop()

我已经用我的图像运行了这个并取得了一些成功,但是它并不能完全工作。我有我的背景图像显示,但我的其他图像没有。我不知道为什么,因为当我在嵌套 for 循环(没有显示图像的地方)中为画布使用 create_line 函数时,我确实得到了显示线。

如果有人可以就此提供一些建议,我将不胜感激!

谢谢

更新:

这是我正在尝试做的一个简单示例。您可以使用任何示例图像对此进行测试。

import tkinter as tk
from PIL import Image, ImageTk

root = tk.Tk()
WIDTH, HEIGHT = int(root.winfo_screenwidth() * 103/104), int(root.winfo_screenheight() * 11/12)
root.geometry('%dx%d+0+0' % (WIDTH, HEIGHT))

canvas = tk.Canvas(root)
canvas.pack(fill="both", expand=True)

testImage = Image.open("enter file path here!")
testImageTk = ImageTk.PhotoImage(testImage)

resizedTestImage = None
resizedTestImageTk = None

def handle_configure(event):
    geometry = root.geometry()
    width = int(geometry[0:geometry.index("x")])
    height = int(geometry[geometry.index("x")+1:geometry.index("+")])
    
    useHeight = height < width
    if useHeight:
        measurement = height
    else:
        measurement = width

    if useHeight:
        proportion = measurement / testImageTk.height()
    else:
        proportion = measurement / testImageTk.width()
    
    resizedTestImage = testImage.resize((int(testImageTk.width()*proportion), int(testImageTk.height()*proportion)))
    resizedTestImageTk = ImageTk.PhotoImage(resizedTestImage)
    canvas.create_image((0,0), image=resizedTestImageTk, anchor="nw")
    print("(image width, image height): (" + str(resizedTestImageTk.width()) + " " + str(resizedTestImageTk.height()) + ")")

root.bind("<Configure>", handle_configure)

root.mainloop()

【问题讨论】:

  • &lt;Configure&gt; 即使您只移动窗口也可以执行 - 您可以使用 event.width, event.height 并与之前的值 event.width, event.height 进行比较来识别窗口是否已调整大小。
  • 也许您在 PhotoImage 中遇到错误,当图像被分配给局部变量时,它会从内存中删除图像 - 您可能必须将图像分配给全局变量或某个类中的变量。请参阅页面末尾的注意PhotoImage
  • 问题是图像的信息(宽度和高度)仍然存在(我已经测试了打印使用图像宽度和高度的坐标)。可能是图像中像素(颜色)的信息被清除了。我想如果那是问题,我可以取消我的代码。我会试试这个。谢谢!
  • 正如我所说的PhotoImage 有bug,它会从内存中删除图像,你可能会得到空图像。
  • 好吧,我几乎尝试了所有方法,但仍然没有运气。我首先在 handle_configure 方法中做了所有事情,但没有奏效。然后,我将所有信息(ImageTk 对象、调整大小的 Image 对象和调整大小的 ImageTk 对象)存储为全局变量,但仍然无法正常工作。

标签: python user-interface tkinter python-imaging-library tkinter-canvas


【解决方案1】:

您的新代码存在PhotoImage 中的错误问题。

在 handle_configure 内部,您分配给局部变量 resizedTestImageTk,即使您已经创建了外部/全局变量 resizedTestImageTk

如果我通知函数它必须使用global 通知函数它必须将其分配给外部变量而不是本地变量,它对我有用

def handle_configure(event):
    global resizedTestImageTk   # <-- inform function to assign image to global variable instead of using local one

或者我将本地resizedTestImageTk 分配给全局类。

canvas.resizedTestImageTk = resizedTestImageTk   # <-- assign local `resizedTestImageTk` to global class.

最少的工作代码。

我更改了一些计算以使其更具可读性。

import tkinter as tk
from PIL import Image, ImageTk

# --- functions ---

def handle_configure(event):
    global resizedTestImageTk   # <-- inform function to assign image to global variable instead of using local one
    
    geometry = root.geometry()    
    window_width  = int(geometry[0:geometry.index("x")])
    window_height = int(geometry[geometry.index("x")+1:geometry.index("+")])
    
    image_width  = testImageTk.height()
    image_height = testImageTk.width()
    
    if window_height < window_width:
        proportion = window_height / image_height
    else:
        proportion = window_width / image_width
     
    image_width  = int(image_width  * proportion)
    image_height = int(image_height * proportion)
    
    resizedTestImage   = testImage.resize((image_width, image_height))
    resizedTestImageTk = ImageTk.PhotoImage(resizedTestImage)
    canvas.create_image((0,0), image=resizedTestImageTk, anchor="nw")
    #canvas.resizedTestImageTk = resizedTestImageTk   # <-- assign local `resizedTestImageTk` to global class.

    print(f"(image width, image height): ({image_width} {image_height})")

# --- main ---

root = tk.Tk()
WIDTH =  int(root.winfo_screenwidth() * 103/104)
HEIGHT = int(root.winfo_screenheight() * 11/12)

root.geometry('%dx%d+0+0' % (WIDTH, HEIGHT))

canvas = tk.Canvas(root)
canvas.pack(fill="both", expand=True)

testImage = Image.open("lenna.png")
testImageTk = ImageTk.PhotoImage(testImage)

resizedTestImage = None
resizedTestImageTk = None

root.bind("<Configure>", handle_configure)

root.mainloop()

测试用图片

维基百科:Lenna


编辑:

我看到其他问题 - 不那么明显。您总是放上新图像,但不会删除旧图像 - 所以最终它可能会使用更多内存。

您可以在开始时将图像放在画布上 - 并获取其 ID

image_id = canvas.create_image((0,0), image=testImageTk, anchor="nw")

之后使用 ID 替换图片

canvas.itemconfig(image_id, image=resizedTestImageTk)

最小的工作代码:

import tkinter as tk
from PIL import Image, ImageTk

# --- functions ---

def handle_configure(event):
    global resizedTestImageTk   # <-- inform function to assign image to global variable instead of using local one
    
    geometry = root.geometry()    
    window_width  = int(geometry[0:geometry.index("x")])
    window_height = int(geometry[geometry.index("x")+1:geometry.index("+")])
    
    image_width = testImageTk.width()
    image_height = testImageTk.height()
    
    if window_height < window_width:
        proportion = window_height / image_width
    else:
        proportion = window_width / image_height

    image_width  = int(image_width  * proportion)
    image_height = int(image_height * proportion)
    
    resizedTestImage = testImage.resize((image_width, image_height))
    resizedTestImageTk = ImageTk.PhotoImage(resizedTestImage)
    
    canvas.itemconfig(image_id, image=resizedTestImageTk)

    #canvas.resizedTestImageTk = resizedTestImageTk   # <-- assign local `resizedTestImageTk` to global class.


    print(f"(image width, image height): ({image_width} {image_height})")

# --- main ---

image_id = None # default value at start

root = tk.Tk()

WIDTH =  int(root.winfo_screenwidth() * 103/104)
HEIGHT = int(root.winfo_screenheight() * 11/12)

root.geometry('%dx%d+0+0' % (WIDTH, HEIGHT))

canvas = tk.Canvas(root)
canvas.pack(fill="both", expand=True)

testImage = Image.open("lenna.png")
testImageTk = ImageTk.PhotoImage(testImage)

resizedTestImage = None
resizedTestImageTk = None

image_id = canvas.create_image((0,0), image=testImageTk, anchor="nw")

root.bind("<Configure>", handle_configure)

root.mainloop()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-24
    • 2014-05-15
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    • 1970-01-01
    相关资源
    最近更新 更多