Ir al contenido

IDEAS PRÁCTICAS · ASIMOV INGENIERÍA

Trabajando con imágenes en Tkinter: logos, fondos y botones

Guía completa para usar imágenes en Tkinter con PhotoImage y Pillow, añadiéndolas a Labels, Buttons y Canvas, y evitando el error clásico de que desaparezcan.

Imagen ilustrativa · Rubaitul Azad / Unsplash.

Table of contents

No titles in the blog post

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:

Computadora portátil con un editor de código
Imagen ilustrativa · Emile Perron / Unsplash.
  • Qué formatos admite PhotoImage sin 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, Button y Canvas.
  • 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_img dentro de una clase, o en una lista global.
  • Lista de referencias en el widget: label.image = img o button.image = img es una práctica muy común.
  • Uso de functools.partial o lambda con 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 Xvfb o ejecuta en modo headless con tkinter.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.