【问题标题】:How to get Pillow images into Tkinter dynamically如何动态地将 Pillow 图像导入 Tkinter
【发布时间】:2018-06-21 01:02:42
【问题描述】:

根据我的阅读,ImageTk 应该将 Pillow 图像转换为 Tkinter 可以显示的格式。 (实际上,如果我不这样做,Tkinter 会失败并出现错误。)但是,当我在 Windows 计算机上运行此程序时,Tkinter 框说它没有响应并且不显示图像。

from PIL import Image, ImageDraw

class Block:
    def __init__(self, x=30, y=10, speed=None, size=2.5):
        self.x = x
        self.y = y
        self.size = size # radius

        if speed is None:
            speed = self.size * 2

        self.speed = speed

    def draw(self, image):
        width, height = image.size
        drawer = ImageDraw.Draw(image)
        drawer.rectangle(((self.x - self.size, height - (self.y - self.size)), (self.x + self.size, height - (self.y + self.size))), fill="white")


class BallDodge():
    def __init__(self):
        self.width = 400
        self.height = 300
        self.main_player = Block(self.width // 2)
        self.balls = []
        self.frame = 0
        self.frames_between_balls = 20
        self.frames_since_last_ball = self.frames_between_balls
        self.input_function = self.get_human_input
        self.states = []


    @property
    def state(self):
        ans = Image.new('RGBA', (self.width, self.height), "black")
        self.main_player.draw(ans)

        return ans

    def get_human_input(self, *args, **kwargs):
        try:
            ans = int(input("1, 2, 3, or 4:"))
            if ans > 4:
                raise ValueError("Can't be greater than 4")
            elif ans < 1:
                raise ValueError("Can't be less than 1")
            if ans == 1:
                return [1, 0]
            elif ans == 2:
                return [0, 0]
            elif ans == 3:
                return [0, 1]
            elif ans == 4:
                return [1, 1]
            else:
                raise ValueError("Somehow it's not one of the specified numbers. You are magical.")
        except Exception as e:
            print("Invalid input")
            print(e)
            return self.get_human_input()

    def step(self):
        inpt = self.input_function(self.state)
        directions = [-1, 1]
        for i in range(2):
            self.main_player.x += directions[i] * inpt[i]

        self.states.append(self.state)

if __name__ == "__main__":
    game = BallDodge()
    import tkinter as tk
    from PIL import ImageTk, Image
    root = tk.Tk()
    my_image = ImageTk.PhotoImage(game.state)
    game.state.show()
    while True:
        panel = tk.Label(image = ImageTk.PhotoImage(game.state), master = root)
        panel.pack()
        # panel = Label(root, image = ImageTk.PhotoImage(game.state))
        # panel.pack(side = "bottom", fill = "both", expand = "yes")
        root.update_idletasks()
        root.update()
        game.step()

我注意到的事情: 如果我这样做game.state.show(),它会很好地打开图像,所以那里肯定有图像。 如果我确实按了其中一个数字键以前进到下一帧,Tkinter 窗口的高度会增加一倍,但仍不显示任何内容。

如何获得一个循环,以便在 Tkinter 窗口中显示 Pillow 图像,获取用户输入,然后显示新图像?

【问题讨论】:

  • 窗口变得越来越高,因为你把packing_new_ tk.Labels 放在里面。您已经知道如何动态地将 Pillow 图像导入 tkinter——即使用ImageTk.PhotoImage()。您不知道的是如何显示它们,然后再更新它们。要更改标签上使用的图像,您可以使用类似于panel.config(image=new_imagetk) 的内容。
  • 但是 Tkinter 窗口甚至没有显示我放入的第一张图片。很高兴了解更改图像的配置功能,但即使我将面板线移到循环之外,我的 Tkinter 窗口也只是空白。 (这让我觉得我真的不知道如何将 Pillow 图像导入 tkinter,更不用说动态了。)
  • 另一个大问题是您没有正确编程tkinter。像您的 while True: 循环这样的东西不是它们是如何工作的。简而言之,您需要始终在某个地方调用.mainloop() 以使您的应用程序运行。从那时起,一切都必须发生在该循环内。基本上你想要做的是动画,所以这就是你需要了解如何做的(在stackoverflow上有很多与此相关的问题)。这通常使用带有通用小部件方法after() 的回调函数来完成。
  • 我担心我的所有逻辑都包含在 tkinter 函数中,因为我只想使用 tkinter 来显示游戏,而不是运行它。我看到了如何重构它,所以它确实使用主循环而不将游戏耦合到 tkinter。谢谢您的反馈!我正在使用的循环来自this question,它似乎暂时对我有用,所以这就是我现在要使用的。

标签: python image loops tkinter pillow


【解决方案1】:

使用以下循环对我有用。 (它很乱,可以改进,但它有效。)

if __name__ == "__main__":
    import tkinter as tk
    from PIL import ImageTk, Image

    game = BallDodge()
    root = tk.Tk()
    pilImage = game.state
    tkimage = ImageTk.PhotoImage(pilImage)
    width, height = pilImage.size
    root.geometry('{}x{}'.format(width+1, height+1))
    canvas = tk.Canvas(root, width=width, height=height, bg="blue")
    canvas.pack()
    imagesprite = canvas.create_image(0, 0, anchor=tk.NW, image=tkimage)
    while True:
        tkimage =  ImageTk.PhotoImage(game.state)
        imagesprite = canvas.create_image(0, 0, anchor=tk.NW, image=tkimage)
        root.update_idletasks()
        root.update()
        game.step()

【讨论】:

  • 我建议您阅读整个 linked answer。其中一位 tkinter 专家 @Bryan Oakley 的 cmets 说“没有理由同时调用 update_idletasksupdate同时”。另一件事是,它确实像我建议的那样谈论使用after(),这不应该要求将“我的所有逻辑都放在一个tkinter函数中”,因为after()回调函数本身可以做任何它想做的事情,包括调用它需要的所有非 tkinter 函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多