【问题标题】:Python turtle stamp mysteriously disappears after turtle shape's image manipulation在海龟形状的图像处理后,Python 海龟印章神秘地消失了
【发布时间】:2016-04-29 19:24:54
【问题描述】:

方向:

我创建了以下函数,允许用户将海龟更改为他/她选择的图像,然后随时将其标记到画布上:

def TurtleShape():
    try:
        # Tkinter buttons related to turtle manipulation
        manipulateimage.config(state = NORMAL)
        flipButton.config(state = NORMAL)
        mirrorButton.config(state = NORMAL)
        originalButton.config(state = NORMAL)
        resetturtle.config(state = NORMAL)
        rotateButton.config(state = NORMAL)
        # Ask user for file name from tkinter file dialog, and return file name as `klob`
        global klob
        klob = filedialog.askopenfilename()
        global im
        # Open `klob` and return as `im`
        im = Image.open(klob)
        # Append `im` to pictures deque
        pictures.append(im)
        # Clear `edited` deque
        edited.clear()
        # Save `im` as an image, then register image as shape, and finally set image as turtle shape
        im.save(klob + '.gif', "GIF")
        register_shape(klob + '.gif')
        shape(klob + '.gif')
        update()
    except:
        # If user selects cancel in file dialog, then pass
        pass

def StampPic():
    stamp()
    draw_space() # Go forward 100 pixels with pen up after every stamp
    update()

图像也可以通过这些其他功能进行操作以供用户选择:

调整大小功能 - 此功能可用作第一或第二功能。第一个意思是它最初被调用,第二个意思是它编辑一个已经编辑过的图像。因此,如果仅首先调用,此函数将获取附加到 pictures 双端队列的图像,调整其大小,并将编辑后的图像输出为 .gif 图像,这将是海龟的新形状。但是,如果连续调用两次或更多次,由于多次调整同一张图片的大小会导致图像失真的问题,我不得不创建另一个双端队列 @9​​87654325@ 来保存 pictures 中的原始项目双端队列,并且每当连续多次调用此函数时,每次都会调整原始图像的大小,而不是每次都调整相同的图像。但是,如果仅作为辅助函数调用,则该函数将简单地从 edited 双端队列中获取当前图像,调整该图像的大小,然后将其设置为海龟的新形状:

def TurtleImageResize():
    if not hasattr(TurtleImageResize, "counter"):
        TurtleImageResize.counter = 0
    TurtleImageResize.counter += 1
    # width = original size of image
    width = im.size[0]
    # height = original height of image
    height = im.size[1]
    # Allow user to enter new width for image
    NewOne2 = numinput('Width of Image', 'Set the width of the image: ', minval = 1)
    # Allow user to enter new height for image
    NewOne = numinput('Height of Image', 'Set the height of your image: ', minval = 1)
    # Set width to user input if user input is NOT nothing. Otherwise, use `width` as picture width.
    Picwidth = NewOne2 if NewOne2 != None else width
    # Set height to user input if user input is NOT None. Otherwise, use `height` as picture height.
    Picheight = NewOne if NewOne != None else height
    try:
        # Secondary Step: Take ORIGINAL image appended to `jiop` (from `except:` code block succeeding `try:` code block) and resize THAT image each time this function is called twice in a row. Otherwise, if ONLY called as a secondary step, take previously edited image from `edited` deque, resize that, and append newly edited image to the `edited` deque.
        try:
            # `jiop` is a deque
            hye = jiop.pop()
            jiop.append(hye)
            print("Jiop")
        except:
            hye = edited.pop()
            jiop.append(hye)
            print("Edited")
        # Resize Image to Picwidth and Picheight
        editpic = hye.resize((int(Picwidth), int(Picheight)), Image.ANTIALIAS)
        edited.append(editpic) 
        print("Hooplah!")
    except:
        # Intial step: Take image appended to `pictures` deque from `TurtleShape` function, then edit that and append newly edited image to both `editpic` and `pictures`
        geer = pictures.pop()
        # Resize Image to Picwidth and Picheight
        editpic = geer.resize((int(Picwidth), int(Picheight)), Image.ANTIALIAS)
        jiop.append(geer)
        edited.append(editpic)
        pictures.append(editpic)
        print("Normal")
    # Save image as `.gif`
    editpic.save(klob + str(TurtleImageResize.counter) + '.gif', 'GIF')
    # Register image as a shape, and use it as shape of turtle
    register_shape(klob + str(TurtleImageResize.counter) + '.gif')
    shape(klob + str(TurtleImageResize.counter) + '.gif')
    update()

翻转、旋转和镜像功能 - 这些功能比上面的调整大小功能更简单。如果最初被调用,他们每个人都会从pictures deque 获取图像,对其进行操作,将编辑后的图像附加到edited deque,然后将海龟“形状”更改为新图像。然而,如果调用第二个,他们每个人都会从edited deque 中获取图像,对其进行操作,将操作后的图像重新附加回edited deque,然后将其设置为海龟的新“形状”。这些函数如下所示:

def flippic():
    if not hasattr(flippic, "counter"):
        flippic.counter = 0
    flippic.counter += 1
    try:
        # Secondary step: Take previously edited image from `edited` deque, manipulate that, and append newly edited image to the `edited` deque
        jiop.clear()
        ghy = edited.pop()
        # Flip image over horizontal line
        kpl = ImageOps.flip(ghy)
        edited.append(kpl)
        print("Jlop")
    except:
        # Initial step: Take image appended to `pictures` deque from `TurtleShape` function, then edit that and append newly edited image to both `editpic` and `pictures`
        neer = pictures.pop()
        # Flip image over horizontal line
        kpl = ImageOps.flip(neer)
        pictures.append(kpl)
        edited.append(kpl)
        print("Yup")
    # Save image as `.gif`
    kpl.save(klob + str(flippic.counter) + '.gif', "GIF")
    # Register image as a shape, and use it as shape of turtle
    register_shape(klob + str(flippic.counter) + '.gif')
    shape(klob + str(flippic.counter) + '.gif')
    update()

def mirror():
    if not hasattr(mirror, "counter"):
        mirror.counter = 0
    mirror.counter += 1
    try:
        jiop.clear()
        jui = edited.pop()
        # Flip image over vertical line
        fgrt = ImageOps.mirror(jui)
        edited.append(fgrt)
    except:
        bbc = pictures.pop()
        # Flip image over vertical line
        fgrt = ImageOps.mirror(bbc)
        pictures.append(fgrt)
        edited.append(fgrt)
    fgrt.save(klob + str(mirror.counter) + ".gif")
    register_shape(klob + str(mirror.counter) + ".gif")
    shape(klob + str(mirror.counter) + ".gif")
    update()

def rotatePic():
    if not hasattr(rotatePic, "counter"):
        rotatePic.counter = 0
    rotatePic.counter += 1
    try:
        jiop.clear()
        lmcb = edited.pop()
        # Rotate image 90º right
        fetch = lmcb.rotate(-90, expand = True)
        edited.append(fetch)
    except:
        bolt = pictures.pop()
        # Rotate image 90º right
        fetch = bolt.rotate(-90, expand = True)
        pictures.append(fetch)
        edited.append(fetch)
    fetch.save(klob + str(rotatePic.counter) + ".gif")
    register_shape(klob + str(rotatePic.counter) + ".gif")
    shape(klob + str(rotatePic.counter) + ".gif")
    update()

这样,所有的编辑功能在本质上相同的基本图像上协同工作。

问题:

现在,假设用户想要拍摄海龟图像,然后将其调整为 800x400 的大小,然后将其标记到画布上的特定位置。之后,用户决定将海龟图像移动到画布上的另一个位置,翻转图像,然后将图像标记在那里。现在应该有两个图像吧​​?一个盖章,另一个翻转?但是,对于我的程序,出于某种原因,情况并非如此。相反,当用户翻转海龟图像时,标记的图像消失,即使在任何地方都找不到clear() 函数(为了向您展示我的意思,请参阅编辑 下面)。 显然这个问题只发生在TurtleImageResize 函数被调用之后。

我的 TurtleImageResize 函数有什么问题导致了这个问题? 我已经完全修改了海龟形状的图像管理过程,使其成为现在的样子,希望它能解决这个问题我以前的设置也遇到过,但显然情况并非如此。因此,非常感谢您对此问题的任何帮助!

编辑:以下是重现我遇到的问题的最小、完整且可验证的方法(必须有 PIL(或 Pillow)并安装了GhostScript 以使其正常工作)

import os,shutil,subprocess, sys
her = sys.platform
if her == "win32":
    print("Windows is your Operating System")
    win_gs = ["gs","gswin32c","gswin64c"]
    if all( shutil.which(gs_version) is None for gs_version in win_gs ):
        paths = ["C:\\Program Files\\gs\\gs9.18\\bin","C:\\Program Files (x86)\\gs\\gs9.18\\bin"]
        for path in (x for x in paths if os.path.exists(x)):
            os.environ["PATH"] += ";" + path
            break
        if any( shutil.which(gs_version) for gs_version in win_gs ):
            print("GhostScript 9.18 for Windows found and utilized")
        else:
            print("You do not have GhostScript 9.18 installed for Windows. Please install it.")
            sys.exit(0)
    else:
        print("GhostScript 9.18 for Windows found and utilized")
elif her == 'darwin':
    print("Macintosh is your Operating System")
    if shutil.which("gs") is None:
        os.environ["PATH"] += ":/usr/local/bin"
        if shutil.which("gs") is None:
            print("You do not have GhostScript installed for Macintosh. Please install it.")
            sys.exit(0)
        else:
            print("GhostScript for Macintosh found and utilized")

from turtle import *
from tkinter import *
try:
    import tkinter.filedialog as filedialog
except ImportError:
    pass
import collections
from PIL import Image, ImageEnhance, ImageOps


jiop = collections.deque()
pictures = collections.deque()
edited = collections.deque()
picwidth = collections.deque()
picheight = collections.deque()

def draw_space():
    # Draw a space 200 pixels wide.
    penup()
    forward(200)
    pendown()

def TurtleShape():
   try:
       manipulateimage.config(state = NORMAL)
       flipButton.config(state = NORMAL)
       mirrorButton.config(state = NORMAL)
       rotateButton.config(state = NORMAL)
       global klob
       klob = filedialog.askopenfilename()
       global im
       im = Image.open(klob)
       pictures.append(im)
       edited.clear()
       im.save(klob + '.gif', "GIF")
       register_shape(klob + '.gif')
       shape(klob + '.gif')
       update()
   except AttributeError:
       pass

def TurtleImageResize():
    if not hasattr(TurtleImageResize, "counter"):
        TurtleImageResize.counter = 0
    TurtleImageResize.counter += 1
    width = im.size[0]
    height = im.size[1]
    NewOne2 = numinput('Width of Image', 'Set the width of the image: ', minval = 1)
    NewOne = numinput('Height of Image', 'Set the height of your image: ', minval = 1)
    Picwidth = NewOne2 if NewOne2 != None else width
    picwidth.append(Picwidth)
    Picheight = NewOne if NewOne != None else height
    picheight.append(Picheight)
    try:
        try:
            hye = jiop.pop()
            jiop.append(hye)
        except:
            hye = edited.pop()
            jiop.append(hye)
        editpic = hye.resize((int(Picwidth), int(Picheight)), Image.ANTIALIAS)
        edited.append(editpic)
        pictures.append(editpic)
    except:
        geer = pictures.pop()
        editpic = geer.resize((int(Picwidth), int(Picheight)), Image.ANTIALIAS)
        jiop.append(geer)
        edited.append(editpic)
        pictures.append(editpic)
    editpic.save(klob + str(TurtleImageResize.counter) + '.gif', 'GIF')
    register_shape(klob + str(TurtleImageResize.counter) + '.gif')
    shape(klob + str(TurtleImageResize.counter) + '.gif')
    update()

def flippic():
    if not hasattr(flippic, "counter"):
        flippic.counter = 0
    flippic.counter += 1
    try:
        jiop.clear()
        ghy = edited.pop()
        kpl = ImageOps.flip(ghy)
        edited.append(kpl)
        pictures.append(kpl)
        print("Jlop")
    except:
        neer = pictures.pop()
        kpl = ImageOps.flip(neer)
        pictures.append(kpl)
        edited.append(kpl)
        print("Yup")
    kpl.save(klob + str(flippic.counter) + '.gif', "GIF")
    register_shape(klob + str(flippic.counter) + '.gif')
    shape(klob + str(flippic.counter) + '.gif')
    update()

def mirror():
    if not hasattr(mirror, "counter"):
        mirror.counter = 0
    mirror.counter += 1
    try:
        jiop.clear()
        jui = edited.pop()
        fgrt = ImageOps.mirror(jui)
        edited.append(fgrt)
        pictures.append(fgrt)
    except:
        bbc = pictures.pop()
        fgrt = ImageOps.mirror(bbc)
        pictures.append(fgrt)
        edited.append(fgrt)
    fgrt.save(klob + str(mirror.counter) + ".gif")
    register_shape(klob + str(mirror.counter) + ".gif")
    shape(klob + str(mirror.counter) + ".gif")
    update()

def rotatePic():
    if not hasattr(rotatePic, "counter"):
        rotatePic.counter = 0
    rotatePic.counter += 1
    try:
        jiop.clear()
        lmcb = edited.pop()
        fetch = lmcb.rotate(-90, expand = True)
        edited.append(fetch)
        pictures.append(fetch)
    except:
        bolt = pictures.pop()
        fetch = bolt.rotate(-90, expand = True)
        pictures.append(fetch)
        edited.append(fetch)
    fetch.save(klob + str(rotatePic.counter) + ".gif")
    register_shape(klob + str(rotatePic.counter) + ".gif")
    shape(klob + str(rotatePic.counter) + ".gif")
    update()

def StampPic():
   stamp()
   draw_space()
   update()

def move_turtle():
    # Pick up the turtle and move it to its starting location.
    penup()
    goto(-200, 100)
    pendown()

def settings():
    #  Tkinter buttons
    turtlepic = Button(text = "Set Turtle Image", command = TurtleShape)
    turtlepic.pack(side = 'left')

    stampimage = Button(text = "Stamp", command = StampPic)
    stampimage.pack(side = 'left')

    global manipulateimage
    manipulateimage = Button(text = "Resize Turtle Image", command = TurtleImageResize, state = DISABLED)
    manipulateimage.pack(side = 'left')

    global flipButton
    flipButton = Button(text = "Flip image", command = flippic, state = DISABLED)
    flipButton.pack(side = 'left')

    global mirrorButton
    mirrorButton = Button(text = "Mirror Image", command = mirror, state = DISABLED)
    mirrorButton.pack(side = 'left')

    global rotateButton
    rotateButton = Button(text = "Rotate Image", command = rotatePic, state = DISABLED)
    rotateButton.pack(side = 'left')

def skip(x, y):
    penup()
    goto(x, y)
    pendown()
    update()

move_turtle()
settings()
speed(0)
tracer(0, 0)
onscreenclick(skip)

if sys.platform == 'win32':
   input()
else:
   pass

当/如果您的系统上同时安装了 GhostScript 和 PIL(或 Pillow),要重现我的问题,请执行以下操作(所有步骤必需 除了 第 4 步):

  1. 单击窗口底部的Set Turtle Image 按钮,选择您希望海龟成为的任何图像,然后按Open。海龟被设置为该图像。

  2. 按屏幕底部的Resize turtle Image 按钮将图像调整为 800x400(或您想要的任何其他尺寸)。会依次弹出两个对话框。在第一个对话框中输入宽度800(或您自己的宽度),然后在第二个对话框中输入高度400(或您自己的高度),完成后,图像将根据提供的尺寸改变大小(或根据您是否按下取消将图像设置回原始尺寸)。

  3. 选择窗口底部的Stamp 按钮。图像被压印在画布上,乌龟向前移动 400 像素“后”压印图像。

  4. 可选:单击画布上的任意位置以将海龟带到该位置。

  5. 翻转/镜像/旋转图像。

如您所见,在完成所有这些操作后,就像您翻转/镜像/旋转图像一样,被标记的图像就消失了我的 TurtleImageResize 函数有什么问题导致这种情况发生?

编辑#2:以防万一此信息有用,我在 OS 版本为 10.11.2 (El Capitan) 的 Macintosh 上运行 Python 3.5.1。

【问题讨论】:

  • @tobias_k 您是否完整地运行了代码?另外,如果您使用的是 Windows,您是否通过命令提示符运行它?
  • @tobias_k 你安装了GhostScript 吗?如果没有,您将需要它来重现我的问题。转至ghostscript.com/download/gsdnld.html 下载适用于您的 Windows 副本的正确版本。然后,在你这样做之后,请运行我帖子中的更新代码。
  • 我正在运行 Linux 并安装了 ghostscript。你能验证你的 MCVE 在你的机器上运行吗?也许您在将其复制到帖子时错过了一行?更新:你永远不应该这样做except: pass;原来filedialog 模块没有被导入。
  • @tobias_k 尝试从 TurtleShape 函数中注释掉(或删除)try:except: 代码,并删除之前位于(现在已注释掉/删除)中的代码块try: 代码块。按下Set Turtle Image 按钮时出现什么错误?
  • 必须添加import tkinter.filedialog as filedialog,现在它运行了,我可以重现问题。

标签: python python-3.x image-manipulation turtle-graphics python-3.5


【解决方案1】:

问题似乎在于,通过为不同的功能设置单独的计数器,您会覆盖先前操作创建的文件。假设您有一张名为test.gif 的图片并应用翻转变换。结果将保存为test.gif1.gif。如果现在应用旋转变换,旋转后的图片也会保存为test.gif1.gif覆盖现有文件,之前的图片消失。

因此,修复此错误的一种方法是对所有图片使用一个计数器,而不是每个函数一个计数器,例如使用itertools.count 或仅使用int。这也会使您的代码更短一些。


不过,还有几个问题我想指出:

  • 您有大量的代码重复,尤其是在您不同的转换函数中;您可以重构它们以将实际转换作为函数参数
  • 不要像except:那样做,更不用说except: pass;这样一来,万一出现问题,您就永远不会知道发生了什么
  • 另外,恕我直言,例外仅应用于异常行为,而您将它们用于正常行为,例如当列表仍然为空时
  • 我真的不明白所有这些不同的queues 是干什么用的;如果您将所有图片放入单个队列或根本没有队列(仅将它们保存在磁盘上),该代码也可以正常工作;但也许你需要那些不属于你的示例代码的东西

这是我的代码版本:

import turtle
import tkinter
import tkinter.filedialog as filedialog
import itertools
from PIL import Image, ImageEnhance, ImageOps

count = itertools.count()
img = None

def turtleShape():
   global img
   klob = filedialog.askopenfilename()
   img = Image.open(klob)
   saveAndUpdate(img)

def turtleImageResize():
    def resize(img):
        picwidth = turtle.numinput('Width of Image', 'Set the width of the image: ', minval=1) or img.size[0]
        picheight = turtle.numinput('Height of Image', 'Set the height of your image: ', minval=1) or img.size[1]
        return img.resize((int(picwidth), int(picheight)), Image.ANTIALIAS)
    manipulate(resize)

def manipulate(function):
    global img
    if img:
        img = function(img)
        saveAndUpdate(img)
    else:
        print("No picture selected")

def flippic():
    manipulate(ImageOps.flip)

def mirror():
    manipulate(ImageOps.mirror)

def rotatePic():
    manipulate(lambda img: img.rotate(-90, expand=True))

def saveAndUpdate(img):
    name = "pic_" + str(next(count)) + ".gif"
    img.save(name, 'GIF')
    turtle.register_shape(name)
    turtle.shape(name)
    turtle.update()

def stampPic():
    turtle.stamp()
    turtle.penup()
    turtle.forward(200)
    turtle.pendown()

def settings():
    tkinter.Button(text="Set Turtle Image", command=turtleShape).pack(side='left')
    tkinter.Button(text="Stamp", command=stampPic).pack(side = 'left')
    tkinter.Button(text="Resize Turtle Image", command=turtleImageResize).pack(side='left')
    tkinter.Button(text="Flip image", command=flippic).pack(side='left')
    tkinter.Button(text="Mirror Image", command=mirror).pack(side='left')
    tkinter.Button(text="Rotate Image", command=rotatePic).pack(side='left')

def skip(x, y):
    turtle.penup()
    turtle.goto(x, y)
    turtle.pendown()
    turtle.update()

skip(-200, 100)
settings()
turtle.speed(0)
turtle.tracer(0, 0)
turtle.onscreenclick(skip)
turtle.mainloop()

【讨论】:

  • 我注意到在你的代码中你做了很多前向声明。例如,在您的turtleshape 函数版本中,您调用了稍后在代码中定义的saveAndUpdate。程序运行时不先调用它是如何工作的?
  • 另外,是的,我确实需要那些 deques 用于 MCVE 中没有的东西。否则,我不会像以前那样设置它。但感谢所有其他提示!它真的有帮助! :)
  • @R.Kap 它正在工作,因为仅在评估调用函数时才需要调用函数,而不是在第一次解析时。如果函数没有被声明,你会得到一个 AttributeError,但是当函数被调用时它被声明了,所以一切都很好。是的,我确实从您的代码中删除了一些部分(例如,最初禁用的按钮),但您真正需要更改的唯一部分是计数器。我建议你也尝试以某种形式使用通用的manipulate 函数。
  • 您提出的解决方案世界完美!问题不再出现。谢谢! :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-08-31
  • 2014-03-18
  • 2018-05-30
  • 1970-01-01
  • 2012-05-17
  • 2020-05-11
  • 2018-03-19
相关资源
最近更新 更多