Afficher une image dans une interface Python semble simple, jusqu’au moment où le format n’est pas reconnu, que l’image disparaît sans raison ou qu’elle devient floue dès qu’on la redimensionne. Le sujet autour de tkinter image touche en réalité trois choses très concrètes: les formats gérés par Tkinter, la manière d’attacher l’image à un widget, et la préparation du fichier avant affichage. Je vais aller droit au but: ce qui marche nativement, quand Pillow devient nécessaire, et les pièges qui font perdre du temps même aux développeurs expérimentés.
Les points à retenir avant d’intégrer une image à votre interface
- Tkinter gère nativement surtout les formats PGM, PPM, GIF et PNG via PhotoImage.
- Pour JPEG, BMP, TIFF ou WebP, j’utilise presque toujours Pillow puis ImageTk.PhotoImage.
- Le piège numéro un reste la référence Python perdue, qui vide le widget à l’écran.
- Pour redimensionner proprement, il vaut mieux préparer l’image avec ImageOps.contain ou cover que forcer l’affichage à l’aveugle.
- Le canevas est le bon choix quand il faut placer, superposer ou remplacer une image dynamiquement.
Ce que Tkinter gère nativement avec les images
La base est plus étroite qu’on ne le croit. Tkinter sait afficher des images, mais il ne remplace pas une vraie bibliothèque de traitement d’images. En pratique, son objet central est PhotoImage, qui couvre surtout les PNG, GIF, PGM et PPM dans les versions récentes; BitmapImage reste réservé aux bitmaps XBM, donc à des cas très spécifiques. Si vous avez besoin de JPEG, BMP, TIFF ou WebP, je passe presque toujours par Pillow.
| Besoin | Outil | Quand je le choisis | Limite principale |
|---|---|---|---|
| Afficher un PNG ou un GIF simple | PhotoImage |
Icônes, logos, visuels légers | Formats supportés assez limités |
| Afficher un bitmap XBM | BitmapImage |
Cas historiques ou très ciblés | Usage rare et spécialisé |
| Ouvrir, convertir, redimensionner, recadrer | Pillow + ImageTk.PhotoImage
|
Quand l’image doit être préparée avant affichage | Dépendance externe supplémentaire |
| Placer l’image librement dans une zone | Canvas.create_image() |
Vues composites, superposition, mise à jour dynamique | Gestion plus manuelle de la position et de l’état |
La clé, ici, c’est de ne pas demander à Tkinter ce qu’il ne fait pas bien. Si votre besoin se limite à quelques vignettes légères, le natif suffit souvent. Dès qu’il faut traiter une image, la préparer pour un cadre précis ou accepter plusieurs formats, Pillow devient le choix propre. Et une fois la source choisie, la vraie difficulté devient l’affichage stable dans l’interface.

Afficher une image correctement dans un widget
Quand j’intègre une image dans une étiquette, un bouton ou un canevas, le détail qui change tout n’est pas le widget lui-même mais la durée de vie de l’objet Python. Tkinter utilise l’image tant que la référence existe côté Python; si l’objet est libéré, le widget reste vide. C’est la raison pour laquelle on voit tant de captures d’écran avec une boîte blanche à la place du visuel.
from tkinter import Tk, Label
from PIL import Image, ImageTk
root = Tk()
original = Image.open("photo.jpg")
photo = ImageTk.PhotoImage(original)
label = Label(root, image=photo)
label.image = photo # référence conservée
label.pack()
root.mainloop()Je préfère stocker l’image dans un attribut du widget, de la fenêtre ou d’un objet métier. C’est plus lisible qu’une variable globale et beaucoup plus robuste dès qu’on a plusieurs vues. Le même principe s’applique au canevas, avec create_image() et un ancrage explicite comme anchor="nw" pour partir du coin supérieur gauche.
from tkinter import Tk, Canvas
from PIL import Image, ImageTk
root = Tk()
img = Image.open("banner.png")
photo = ImageTk.PhotoImage(img)
canvas = Canvas(root, width=800, height=500)
canvas.pack()
canvas.create_image(0, 0, anchor="nw", image=photo)
canvas.photo = photo # référence conservée
root.mainloop()Si vous remplacez l’image dynamiquement, conservez aussi la nouvelle référence. Une fois ce mécanisme en place, la vraie question devient la taille: une image correcte mais trop grande reste lourde, et une image trop petite devient vite floue.
Redimensionner et recadrer sans déformer
Je conseille de préparer la taille avant conversion en image Tk, pas après. Cette étape évite les déformations, limite l’usage mémoire et permet de choisir entre un rendu qui tient dans un cadre et un rendu qui le remplit complètement. Pillow offre plusieurs stratégies, et elles ne servent pas au même besoin.
| Méthode | Effet | Quand je l’utilise | Attention |
|---|---|---|---|
resize() |
Taille exacte | Icônes, formats imposés, cas où le ratio peut changer | Peut déformer l’image |
thumbnail() |
Réduction maximale en place | Aperçus rapides, caches, lots d’images | Modifie l’objet original |
ImageOps.contain() |
Rentre dans un cadre sans crop | Vignettes où je veux garder toute l’image visible | Peut laisser des marges |
ImageOps.cover() |
Remplit le cadre en gardant le ratio | Bannières ou fonds visuels | Coupe les bords de l’image |
ImageOps.fit() |
Recadre au ratio cible | Portraits, avatars, cartes | Il faut régler le centrage |
from PIL import Image, ImageTk, ImageOps
img = Image.open("hero.webp")
img = ImageOps.contain(img, (640, 360), method=Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(img)Pour une réduction nette, j’opte souvent pour LANCZOS; c’est un peu plus coûteux, mais le rendu reste plus propre sur les images photographiques. Si la vitesse compte davantage que la finesse, BICUBIC suffit souvent. Le bon filtre dépend donc du volume d’images, de leur taille d’origine et du niveau d’exigence visuelle. Quand la taille est réglée, il devient possible d’aller plus loin que l’affichage simple, notamment en transformant l’image avant de la montrer.
Modifier une image avant de l’afficher
Pour une interface un peu vivante, je garde toujours une copie de la source et je dérive les variantes à partir d’elle. C’est la meilleure façon d’éviter les effets cumulés, comme une rotation appliquée deux fois parce qu’on a réutilisé l’image déjà transformée. Dans Tkinter, c’est aussi la bonne porte d’entrée pour un bouton “tourner”, un aperçu en niveaux de gris ou un changement d’illustration selon l’état de l’application.
Partir d’une source immuable
Je conseille de ne jamais écraser l’image originale si vous prévoyez des interactions. Une base intacte permet de revenir en arrière proprement, de tester plusieurs filtres et d’éviter que la qualité se dégrade au fil des transformations successives.
from tkinter import Tk, Label, Button
from PIL import Image, ImageTk
root = Tk()
base = Image.open("camera.png")
label = Label(root)
label.pack()
def rotate_image():
global current_photo
rotated = base.rotate(90, expand=True)
current_photo = ImageTk.PhotoImage(rotated)
label.configure(image=current_photo)
label.image = current_photo
Button(root, text="Tourner", command=rotate_image).pack()
root.mainloop()Le paramètre expand=True évite de rogner l’image après rotation. Je l’utilise presque systématiquement quand je veux une rotation visuellement propre, surtout si le fichier n’est pas carré. Si vous avez besoin de transparence ou de composition sur un fond coloré, je passe aussi volontiers en RGBA avant d’assembler les calques.
Lire aussi : Python - isnumeric() vs isdigit() vs isdecimal() - Le guide
Mettre à jour l’interface sans la bloquer
Pour une animation ou un changement périodique d’image, il faut éviter les boucles bloquantes. Tkinter réagit bien quand on utilise after() pour programmer la mise à jour suivante. C’est une méthode simple, mais elle change tout pour un affichage fluide.
from tkinter import Tk, Label
from PIL import Image, ImageTk
root = Tk()
frames = []
for path in ["frame1.png", "frame2.png", "frame3.png"]:
frames.append(ImageTk.PhotoImage(Image.open(path)))
label = Label(root)
label.pack()
index = 0
def animate():
global index
frame = frames[index]
label.configure(image=frame)
label.image = frame
index = (index + 1) % len(frames)
root.after(80, animate)
animate()
root.mainloop()Pour une séquence courte, c’est très efficace. Pour une animation plus lourde, je précharge les frames une seule fois et je surveille la mémoire, parce que le confort visuel peut vite coûter cher si l’on redimensionne ou reconvertit en permanence. Une fois ces transformations maîtrisées, les problèmes restants viennent moins de l’image elle-même que du contexte d’exécution.
Les pièges qui provoquent un écran vide
Les bugs liés aux images ont presque tous la même signature: l’interface compile, la fenêtre s’ouvre, puis le visuel manque ou s’affiche mal. Quand je vois cela, je vérifie d’abord le cycle de vie de l’objet image, puis le format du fichier, ensuite le chemin d’accès. Dans la majorité des cas, le problème est là, pas dans Tkinter lui-même.
| Symptôme | Cause probable | Correctif |
|---|---|---|
| Widget vide ou boîte blanche | Référence Python perdue | Stocker l’objet dans un attribut durable, par exemple widget.image
|
| Erreur au chargement | Format non supporté ou chemin incorrect | Vérifier le fichier, utiliser Pillow, sécuriser le chemin |
| Image étirée | Redimensionnement mal choisi | Préférer contain(), cover() ou fit() selon le besoin |
| Transparence étrange | Mode couleur inadapté | Travailler en RGBA avant l’affichage |
| Application lente | Images trop grandes ou conversions répétées | Redimensionner une seule fois, puis mettre en cache les variantes |
Dans une application distribuée, je sécurise aussi les chemins avec pathlib.Path(__file__).resolve() ou une fonction équivalente, parce qu’un chemin qui marche dans l’IDE peut casser dès qu’on lance le programme depuis un autre dossier ou qu’on l’embarque dans un exécutable. Une fois ces points nettoyés, Tkinter devient beaucoup plus prévisible, ce qui amène naturellement à la bonne stratégie globale.
Le compromis que je recommande pour un projet Tkinter en 2026
Pour un projet simple ou intermédiaire, ma règle est nette: Tkinter pour l’interface, Pillow pour le traitement. Cette séparation garde le code lisible et évite d’exiger du widget qu’il fasse un travail qu’il n’a jamais été conçu pour faire. En 2026, c’est encore le meilleur compromis pour un outil bureau sobre, rapide à maintenir et peu dépendant.
- Je charge l’image originale une seule fois.
- Je crée les variantes redimensionnées ou filtrées au moment du chargement.
- Je stocke les objets
PhotoImagedans des attributs persistants. - Je préfère un canevas pour l’affichage libre, un label pour un visuel fixe, et un bouton seulement pour une petite icône.
- Je passe à un autre outil graphique si l’application devient très riche en médias, en effets ou en animation continue.
Avec cette approche, l’affichage d’image dans Tkinter reste prévisible, léger et suffisamment souple pour la plupart des tableaux de bord, outils internes et petites interfaces métiers. Le vrai gain n’est pas seulement visuel: c’est surtout de garder un code qui ne se bat pas contre le cycle de vie des objets, les formats de fichiers et le redimensionnement.