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):
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.
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).
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.
OPTIONAL: Click anywhere on the canvas to take the turtle to that spot.
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).
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:
except:
as much, much less except: pass
; this way you never know that happened in case something goes wrongqueue
s 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 codeHere'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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With