2025, Oct 24 11:00

Why Your Python turtle Image Doesn't Appear and How to Fix It with Tkinter PhotoImage file=

Custom turtle.Turtle sprite not showing? Fix the Tkinter PhotoImage issue: use file= to load GIF/PNG/JPG, register_shape, and set .shape to display your image.

When you subclass turtle.Turtle to draw a custom sprite from an image, it’s easy to trip over a subtle Tkinter API detail. The symptom is familiar: the turtle never shows up, even though you register a shape and set it on the instance. The root cause is a misused parameter in tkinter.PhotoImage.

Goal: a turtle with an image-based shape

The task is to build a class inheriting from turtle.Turtle and give it a shape defined by an image file, e.g., a meteor or asteroid graphic.

The buggy implementation

Below is a minimal version that looks reasonable at first glance but fails to display anything. The image never renders, and the turtle doesn’t appear on the canvas.

from turtle import Turtle, Screen, Shape
import tkinter
class Asteroid(Turtle):
    def __init__(self, hp):
        Turtle.__init__(self)
        self.hp = hp
        self.img_ref = tkinter.PhotoImage('Spinning-asteroid-6.gif')
        Screen().register_shape('meteor', Shape('image', self.img_ref))
        self.shape('meteor')

What’s actually going wrong

In tkinter.PhotoImage, the first positional argument is not the filename. It maps to master=, i.e., the parent widget/owner. To load from disk you must pass the path via the file= keyword. Without file=, the image is never loaded from your GIF, so the registered shape has nothing to show and your turtle remains invisible.

The fix

Use the file= keyword when creating the PhotoImage. With that one change, the shape registration and assignment work as intended.

from turtle import Turtle, Screen, Shape
import tkinter
class Asteroid(Turtle):
    def __init__(self, hp):
        Turtle.__init__(self)
        self.hp = hp
        self.img_ref = tkinter.PhotoImage(file='Spinning-asteroid-6.gif')
        Screen().register_shape('meteor', Shape('image', self.img_ref))
        self.shape('meteor')

Using different image formats

It’s possible to use .png with current tkinter via tkinter.PhotoImage. If your asset is a PNG, load it the same way with file= and register it as an image shape.

self.img_ref = tkinter.PhotoImage(file='my_image.png')
Screen().register_shape('meteor', Shape('image', self.img_ref))

With PIL, you can load .jpg as well by importing the PhotoImage implementation from PIL.ImageTk.

from PIL.ImageTk import PhotoImage as PILPhotoImage
self.img_ref = PILPhotoImage(file='my_image.jpg')
Screen().register_shape('meteor', Shape('image', self.img_ref))

An alternative naming approach

Another working pattern is to register the shape using the image filename itself and then select it by the same name. If you prefer that style, you can use the filename string consistently when registering and setting the shape.

It works to use register_shape('image.gif') and then self.shape('image.gif').

Inheritance or composition?

While subclassing turtle.Turtle is a common first step, there’s a pragmatic viewpoint that favors composition over inheritance for turtle-based apps. turtle isn’t designed with subclassing as a primary use case, and mixing your game logic directly into the library class can get confusing. This note doesn’t affect the image issue above, but it’s worth keeping in mind for maintainability.

Why this detail matters

The wrong parameter in PhotoImage silently prevents resource loading, which looks like a rendering or turtle problem but is actually a file I/O issue. Understanding that PhotoImage expects file= for disk paths saves time and prevents chasing the wrong layer of the stack. It also sets you up to swap formats (GIF, PNG, JPG via PIL) without surprises.

Wrap-up

If your turtle with a custom image shape doesn’t appear, check how you construct tkinter.PhotoImage. Always pass the filename using file=. After that, register the image with Screen().register_shape and apply it via .shape on your turtle instance. If you want PNG, load it with tkinter.PhotoImage; for JPEG, use PIL’s PhotoImage. And if your project grows, consider keeping turtle as a dependency you compose with, rather than a class you inherit from.

The article is based on a question from StackOverflow by Aadvik and an answer by furas.