Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python turtle stamp mysteriously disappears after turtle shape's image manipulation

Orientation:

I have created the following functions to allow the user to change the turtle to an image of the his/her choosing and then stamp it to the canvas at any point:

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()

The image can also be manipulated to the user's choosing by these other functions:

Resize function – This function works either as a first or secondary function. First meaning that it is called initially, and secondary meaning it edits an already edited image. So, if ONLY called first, this function will take the image appended to the pictures deque, resize that, and output the edited image as a .gif image, which will be the new shape of the turtle. However, if called two times or more in a row, because of an issue where resizing the same picture more than once will result in a distorted image, I had to create another deque jiop which saves the original item from the pictures deque, and whenever this function is called more than once in a row, that original image is resized every time, instead of the same image each time. But, if ONLY called as a secondary function, then the function will simply take the current image from the edited deque, resize that image, and then set that as the turtle's new shape:

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()

Flip, Rotate, and Mirror functions - These work rather simpler than the resize function above. If called initially, they each will take the image from the pictures deque, manipulate it, append that edited image to the edited deque, then change the turtle "shape" to that new image. However, if called second, they each will take the image from the edited deque, manipulate that, re-append the manipulated image back to the edited deque, then set that as the turtle's new "shape". These functions are shown below:

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()

This way ALL the editing functions work together on essentially the same fundamental image.

The Issue:

Now, consider that the user wants to take the turtle image and then resize it to the size, for instance, 800x400, and stamp it to a specific spot on the canvas. After that, the user decides to move the turtle image to another spot on the canvas, flip the image, and then stamp the image there. There should now be two images right? One stamped, and the other flipped? However, with my program, for some reason, that is not the case. Instead, the stamped image disappears the moment the user flips the turtle image, even though there is no clear() function to be found anywhere (to show you what I mean, refer to the edit below). Apparently this issue ONLY occurs after the TurtleImageResize function is called.

What is wrong in my TurtleImageResize function that is leading to this issue? I had completely revamped the turtle shape's image management process to what it is right now in hopes that it will fix this issue that I was also experiencing with my previous setup, but apparently, that is STILL not the case. Therefore, any help with this issue is greatly appreciated!

EDIT: Below is a minimal, complete, and verifiable way to reproduce the issue I am having (MUST have PIL (or Pillow) and GhostScript installed in order for this to work):

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

When/if you have both GhostScript and PIL (or Pillow) installed on your system, to reproduce my issue, please do the following (All steps required except step # 4):

  1. Click the Set Turtle Image button at bottom of window, select any image you want the turtle to be, then press Open. The turtle gets set to that image.

  2. Resize the Image to 800x400 (or any other size you want) by pressing the Resize turtle Image button at the bottom of the screen. Two dialogs will pop up in succession. Enter the width of 800 (or your own width) in the first dialog, and then enter the height of 400 (or your own height) in the second dialog, and after you finish, the image will change size according to the dimensions provided (or set image back to the original dimension(s) depending on whether or not you pressed cancel).

  3. Select the Stamp button at the bottom of the window. The image is stamped onto the canvas, and the turtle moves forward 400 pixels "behind" the stamped image.

  4. OPTIONAL: Click anywhere on the canvas to take the turtle to that spot.

  5. Flip/mirror/rotate the image.

As you can see, after doing all this, just as you flip/mirror/rotate the image, the stamped image just disappears. What is wrong with my TurtleImageResize function that is causing this to occur?

EDIT # 2: Just in case this information is useful, I am running Python 3.5.1 on a Macintosh with OS version 10.11.2 (El Capitan).

like image 260
R. Kap Avatar asked Sep 26 '22 14:09

R. Kap


1 Answers

The problem seems to be that by having individual counters for the different functions, you overwrite the files created by previous operations. Let's say you have a picture named test.gif and apply the flip transformation. The result will be saved as test.gif1.gif. If you now apply a rotate transformation, the rotated picture is also saved as test.gif1.gif, overwriting the existing file, and the previous picture disappears.

So one way to fix this bug is to use a single counter for all the pictures, instead of one per function, e.g. using itertools.count or just an int. This will also make your code somewhat shorter.


However, there are a few more issues that I'd like to point out:

  • you have quite a high amount of code duplication, particularly in your different transformation functions; you can refactor those to take the actual transformation as a function parameter
  • don't do except: as much, much less except: pass; this way you never know that happened in case something goes wrong
  • also, IMHO exceptions should only be used for exceptional behaviour, while you use them for normal behaviour, such as when the lists are still empty
  • I don't really see what all those different queues are for; the code works just as well if you put all the pictures into a single queue, or no queues at all (just saving them on disk); but maybe you need those for something that was not part of your example code

Here's my version of the code:

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()
like image 120
tobias_k Avatar answered Oct 03 '22 08:10

tobias_k