使用PIL的ImageDraw模块

5 投票
1 回答
13355 浏览
提问于 2025-04-16 10:56

我正在尝试使用PIL的ImageDraw模块来单独处理每个像素。下面的代码应该是创建一个Tkinter的画布组件。然后打开一张图片,把一个像素的颜色改成红色,最后把这张图片放到画布上。但是,似乎没有成功。

我的代码:

import Tkinter
from PIL import ImageTk, Image, ImageDraw


class image_manip(Tkinter.Tk):

    def __init__(self):
        Tkinter.Tk.__init__(self)

        self.configure(bg='red')

        self.ImbImage = Tkinter.Canvas(self, highlightthickness=0, bd=0, bg='blue')
        self.ImbImage.pack()

        im = Image.open(r'C:\Python26\Suite\test.png')

        print im.format, im.size, im.mode

        im = ImageDraw.Draw(im)

        im = im.point((0, 0), fill="red")

        self.i = ImageTk.PhotoImage(im)
        self.ImbImage.create_image(139, 59, image=self.i)




def run():
    image_manip().mainloop()
if __name__ == "__main__":
    run()

运行我的代码时出现了以下错误:

Exception AttributeError: "PhotoImage instance has no attribute '_PhotoImage__photo'" in <bound method PhotoImage.__del__ of <PIL.ImageTk.PhotoImage instance at 0x05DF7698>> ignored
Traceback (most recent call last):
  File "<string>", line 245, in run_nodebug
  File "C:\Python26\Suite\test_image.py", line 30, in <module>
    run()
  File "C:\Python26\Suite\test_image.py", line 28, in run
    image_manip().mainloop()
  File "C:\Python26\Suite\test_image.py", line 20, in __init__
    self.i = ImageTk.PhotoImage(im)
  File "C:\Python26\lib\site-packages\PIL\ImageTk.py", line 109, in __init__
    mode = Image.getmodebase(mode)
  File "C:\Python26\lib\site-packages\PIL\Image.py", line 245, in getmodebase
    return ImageMode.getmode(mode).basemode
  File "C:\Python26\lib\site-packages\PIL\ImageMode.py", line 50, in getmode
    return _modes[mode]
KeyError: None

1 个回答

9

你的问题在于你把 im 重新赋值给了多个东西。

im = Image.open(r'C:\Python26\Suite\test.png')
im = ImageDraw.Draw(im)
im = im.point((0, 0), fill="red")

当你调用 ImageTk.PhotoImage(im) 时,这个函数期待的是一个PIL图像对象,但你已经把 im 赋值为 point() 函数的结果,而这个函数实际上返回的是 None。这就是你问题的根源。

我觉得你对 ImageDraw 的工作原理有些误解。可以看看 这里 的例子。基本上:

  • 如果你想在你的PIL图像上画一些复杂的东西,你需要一个 ImageDraw 的实例。
  • 你仍然需要把你的PIL图像保存在某个变量里。
  • ImageDraw 会直接在你在创建时给它的图像上进行绘制。
  • 你可以在任何时候丢掉 ImageDraw 对象。它不包含任何重要的信息,因为所有内容都是直接写入图像的。

这是修正后的 __init__ 方法:

def __init__(self):
    Tkinter.Tk.__init__(self)
    self.configure(bg='red')
    im = Image.open(r'C:\Python26\Suite\test.png')
    width, height = im.size
    self.ImbImage = Tkinter.Canvas(self, highlightthickness=0, bd=0, bg='red', width=width, height=height)
    self.ImbImage.pack()
    print im.format, im.size, im.mode

    draw = ImageDraw.Draw(im)
    draw.rectangle([0, 0, 40, 40 ],  fill="green")
    del draw

    self.i = ImageTk.PhotoImage(im)
    self.ImbImage.create_image(width/2, height/2, image=self.i)

你会注意到我修正了一些地方:

  • 把画布的大小设置为图像的大小。显然,你需要在找到图像大小之前加载图像,所以我稍微调整了一下顺序。
  • ImageDraw 的实例赋值给一个单独的变量。
  • 画一个绿色的矩形而不是一个点,因为这样更显眼。注意你不需要获取 draw.rectangle 的返回值——它实际上返回的是 None,就像大多数其他绘图函数一样。
  • 在完成绘制后删除 draw 变量。
  • 在调用 create_image 时将图像居中放置在画布上。

撰写回答