Programming Resources
For Fun and Learning
Charles Cusack
Computer Science
Hope College
main


Python


C++
JAVA
PHP
SQL
Assignments

Turtle


util.py

from tkinter import Canvas, Tk
from turtle import TurtleScreen, RawTurtle
from functools import partial
import ctypes
from PIL import ImageGrab

# Utility methods for working with turtle.
# The first method is the only one that is important.
# Feel free to ignore the rest.
# CAC, 2024

def getTurtleAndScreen(title, width, height, filename, moveWorld=True):
    '''
    Create a screen and a turtle to draw on that screen
    :param title: The title you want on the window.
    :param width: The width of the window/canvas
    :param height: The height of the window/canvas
    :param filename: the name of the file that called this. Used to name the image file
    if a screenshot is taken.
    :param moveWorld: If set to true, the lower left corner will be (0,0) and
    the upper right will be (width,height)
    Otherwise, the lower left corner will be (-width/2, -height/2), the
    center of the screen will be (0,0), and the upper right will be (width/2,height/2)
    :return: a turtle and a screen
    '''

    #----------------------------------------------
    # From https://stackoverflow.com/questions/44398075/can-dpi-scaling-be-enabled-disabled-programmatically-on-a-per-session-basis
    # to make it not scale if scaling is turned on.
    # Set DPI Awareness  (Windows 10 and 8)
    errorCode = ctypes.windll.shcore.SetProcessDpiAwareness(2)
    # Set DPI Awareness  (Windows 7 and Vista)
    success = ctypes.windll.user32.SetProcessDPIAware()
    #----------------------------------------------

    root = Tk()
    ws = root.winfo_screenwidth()
    hs = root.winfo_screenheight()
    x = (ws / 2) - (width / 2)
    y = (hs / 2) - (height / 2)
    root.geometry('%dx%d+%d+%d' % (width, height, x, y))
    root.lift()
    root.wm_title(title)
    canvas = ResizingCanvas(root, desired_width=width, desired_height=height, width=width,
                            height=height, highlightthickness=0)
    canvas.pack(fill="both", expand=True)
    screen = TurtleScreen(canvas)
    if moveWorld:
        screen.setworldcoordinates(0, -1, width, height - 1)
    # So you can specify colors using rgb integer values.
    screen.colormode(255)
    # Do not show the turtle drawing.
    screen.tracer(0)

    # Get an invisible turtle.
    turtle = RawTurtle(screen, visible=False)
    params = {}
    params['root']=root
    params['width']=width
    params['height']=height
    params['swidth']=ws
    params['sheight']=hs
    params['title']=title
    params['filename']=filename
    screen.onscreenclick(partial(myClickHandler, params))

    return turtle, screen

def myClickHandler(params,x,y):
    params['x']=x
    params['y']=y
    image = screenshot(params)
    filename = params['filename'] if 'filename' in params else "unknown"
    filename += ".png"
    image.save(filename)

def screenshot(params):
    width = int(params['width'])
    height = int(params['height'])
    root = params['root']

    # To take screenshot properly when scaling is happening, I need to use
    # this. I have instead added code above that "turns off" scaling.
    # so I set the scale_factor to 1. Leaving this in case some machines
    # do not do the above correctly.
    # sf = ctypes.windll.shcore.GetScaleFactorForDevice(0)/100
    sf=1

    x0 = root.winfo_rootx()
    y0 = root.winfo_rooty()
    x1 = x0 + width
    y1 = y0 + height
    image = ImageGrab.grab().crop((x0*sf, y0*sf, x1*sf, y1*sf))
    image = image.resize((width,height))
    return image

class ResizingCanvas(Canvas):
    def __init__(self, parent, desired_width, desired_height, **kwargs):
        Canvas.__init__(self, parent, **kwargs)
        self.bind("<Configure>", self.on_resize)
        self.height = self.winfo_reqheight()
        self.width = self.winfo_reqwidth()
        self.origHeight = self.height  # save original height
        self.origWidth = self.width  # save original height
        # Need to negate this one. Not sure why.
        self.x_shift = -(self.width - desired_width) // 2
        # Don't need to negate this one. Not sure why.
        self.y_shift = (self.height - desired_height) // 2

    def on_resize(self, event):
        # determine the ratio of old width/height to new width/height
        wscale = float(event.width) / self.width
        hscale = float(event.height) / self.height
        self.width = event.width
        self.height = event.height
        # resize the canvas
        self.config(width=self.width, height=self.height)
        # rescale all the objects tagged with the "all" tag
        self.scale("all", self.x_shift, self.y_shift - self.origHeight, wscale, hscale)