Trabajando con imágenes en Tkinter: logos, fondos y botones
1. Introducción
Tkinter es la biblioteca estándar de Python para crear interfaces gráficas. Aunque su API es sencilla, manejar imágenes puede resultar confuso para los principiantes, sobre todo cuando la imagen desaparece inesperadamente. En este artículo veremos:
- Qué formatos admite
PhotoImagesin dependencias externas. - Cómo integrar Pillow (PIL) para trabajar con PNG, JPG y más formatos.
- Ejemplos prácticos de añadir imágenes a
Label,ButtonyCanvas. - El típico problema de la pérdida de referencia y cómo solucionarlo.
- Buenas prácticas de rendimiento, escalabilidad y depuración.
2. PhotoImage: formatos soportados por defecto
El objeto tkinter.PhotoImage está incluido en la instalación estándar de Tkinter. Sólo reconoce los siguientes tipos de archivo:
- GIF (incluido el soporte a animación)
- PPM/PGM (formato de mapa de bits de Netpbm)
- PNG (a partir de Tk 8.6, que llega con Python 3.7+)
Si tu aplicación necesita JPG, BMP, WebP o SVG, deberás recurrir a una librería externa, siendo Pillow la opción más popular.
3. Pillow (PIL) como puente para formatos avanzados
Pillow es un fork de la histórica Python Imaging Library. Proporciona una interfaz ImageTk.PhotoImage que convierte cualquier objeto PIL.Image en una imagen compatible con Tkinter.
Instalación
pip install --upgrade pillow
Ventajas de usar Pillow
Compatibilidad
- JPG, JPEG, PNG, BMP, GIF, TIFF, WebP, ICO, HEIF, etc.
- Soporte multiplataforma (Windows, macOS, Linux)
Funcionalidades extra
- Redimensionado, rotación, filtros, recorte.
- Gestión de transparencias y canales alfa.
- Conversión entre modos (RGB, RGBA, L, P).
4. Añadir una imagen a los widgets más habituales
4.1. A un Label
import tkinter as tk
from PIL import Image, ImageTk # solo si usamos Pillow
root = tk.Tk()
root.title("Label con imagen")
# ---- Usando PhotoImage (solo PNG/GIF) ----
logo = tk.PhotoImage(file="logo.png")
label1 = tk.Label(root, image=logo, text="Logo", compound="top")
label1.pack(pady=10)
# ---- Usando Pillow para JPG ----
img = Image.open("foto.jpg")
img = img.resize((200, 150), Image.LANCZOS) # redimensionado opcional
photo = ImageTk.PhotoImage(img)
label2 = tk.Label(root, image=photo, text="Foto JPG", compound="left")
label2.pack(pady=10)
# Mantener referencias (ver sección 6)
root.mainloop()
4.2. A un Button
# Continuando en el mismo script
# Botón con icono PNG
icon = tk.PhotoImage(file="icono.png")
btn1 = tk.Button(root, image=icon, text="Aceptar", compound="left", command=lambda: print("OK"))
btn1.pack(pady=5)
# Botón con JPG usando Pillow
img_btn = Image.open("boton.jpg")
img_btn = img_btn.resize((24, 24), Image.LANCZOS)
photo_btn = ImageTk.PhotoImage(img_btn)
btn2 = tk.Button(root, image=photo_btn, text="Descargar", compound="right", command=lambda: print("Descarga"))
btn2.pack(pady=5)
4.3. A un Canvas
# Canvas como fondo o sprite
canvas = tk.Canvas(root, width=400, height=300, bg="white")
canvas.pack(pady=10)
# Imagen de fondo (PNG)
bg_img = tk.PhotoImage(file="fondo.png")
canvas.create_image(0, 0, anchor="nw", image=bg_img)
# Imagen con Pillow (JPG) como sprite
sprite = Image.open("sprite.jpg").resize((80, 80), Image.LANCZOS)
sprite_tk = ImageTk.PhotoImage(sprite)
# Guardar referencia en una lista para evitar GC
canvas.sprites = []
canvas.sprites.append(sprite_tk)
canvas.create_image(200, 150, image=sprite_tk)
5. El problema de la “imagen desaparecida”
Tkinter mantiene una referencia interna a la imagen mientras el widget la necesite. Sin embargo, si la variable que contiene el objeto PhotoImage o ImageTk.PhotoImage se elimina (por ejemplo, al quedar fuera del alcance de la función), el recolector de basura (GC) lo destruye y el widget queda sin imagen, mostrando un cuadro vacío.
Ejemplo típico
def crear_ventana():
win = tk.Toplevel()
img = tk.PhotoImage(file="logo.png")
lbl = tk.Label(win, image=img)
lbl.pack()
#
Al cerrar la función, img se pierde y el Label muestra nada.
Soluciones recomendadas
- Variable global o atributo de clase: almacenar la referencia en
self.logo_imgdentro de una clase, o en una lista global. - Lista de referencias en el widget:
label.image = imgobutton.image = imges una práctica muy común. - Uso de
functools.partialolambdacon cierre que capture la variable.
# Solución rápida usando atributo del widget
lbl = tk.Label(root, image=logo)
lbl.image = logo # evita que el GC lo elimine
lbl.pack()
6. Buenas prácticas, rendimiento y escalabilidad
6.1. Redimensionado antes de cargar
Redimensionar la imagen con Pillow antes de pasarla a Tkinter reduce el consumo de memoria y acelera la renderización.
img = Image.open("gran_foto.jpg")
max_w, max_h = 800, 600
img.thumbnail((max_w, max_h), Image.LANCZOS)
photo = ImageTk.PhotoImage(img)
6.2. Cache de imágenes
En aplicaciones con muchos iconos (por ejemplo, una barra de herramientas) es aconsejable crear un singleton cache para reutilizar objetos PhotoImage en vez de volver a leer el archivo.
class ImageCache:
_cache = {}
@staticmethod
def get(path, size=None):
key = (path, size)
if key not in ImageCache._cache:
img = Image.open(path)
if size:
img = img.resize(size, Image.LANCZOS)
ImageCache._cache[key] = ImageTk.PhotoImage(img)
return ImageCache._cache[key]
icon_save = ImageCache.get("icons/save.png", (24,24))
button = tk.Button(root, image=icon_save, text="Guardar", compound="left")
button.image = icon_save
6.3. Seguridad y validación de archivos
Si tu aplicación permite al usuario cargar imágenes externas, valida la extensión y, de ser posible, abre el archivo con Pillow en modo Image.open() dentro de un bloque try/except para capturar errores de formato o archivos corruptos.
def cargar_imagen(ruta):
try:
img = Image.open(ruta)
img.verify() # detecta corrupciones
except (IOError, SyntaxError) as e:
tk.messagebox.showerror("Error", f"Archivo no válido: {e}")
return None
return ImageTk.PhotoImage(Image.open(ruta))
6.4. Compatibilidad con versiones de Tk
Python 3.7+ incluye Tk 8.6, que ya soporta PNG nativo. En sistemas donde la versión de Tk sea anterior (por ejemplo, en algunas distribuciones Linux antiguas), se requerirá Pillow incluso para PNG. Verifica la versión con:
import tkinter as tk
print(tk.TkVersion) # 8.6 o 8.5
7. Depuración y troubleshooting
- Imagen no se muestra: comprueba que la variable de la imagen no se haya perdido (usa
widget.image = img). - Mensaje "could not open display" al lanzar la GUI en servidores sin X11: usa
Xvfbo ejecuta en modo headless contkinter.Tk().withdraw()si solo necesitas procesar la imagen. - Errores al cargar JPG con PhotoImage: indica que PhotoImage no los soporta; instala Pillow y usa
ImageTk.PhotoImage. - Imagen demasiado grande y la ventana se vuelve lenta: redimensiona antes de crear el objeto PhotoImage, o usa
thumbnail()como se mostró.
8. Conclusiones
Dominar el manejo de imágenes en Tkinter implica comprender las limitaciones de PhotoImage, aprovechar la potencia de Pillow y, sobre todo, mantener referencias explícitas a los objetos de imagen. Siguiendo las prácticas mostradas podrás crear interfaces profesionales con logos, fondos atractivos y botones iconográficos sin sorpresas inesperadas.