Redimensionnement de l'image
Comme on a vu précédemment, on veut pouvoir changer la taille de l'image. Le type Image de Pillow/PIL a une méthode, resize
qui fait exactement celà.
Elle prends en paramètre les nouvelles dimensions sous forme d'un tuple de deux entiers (l, h)
, où l
est la largeur, et h
la hauteur.
Voici un exemple qui redimensionne l'image en 500 pixels de large et 1000 pixels de haut, puis l'affiche :
from PIL import Image
img = Image.open("chien.jpg")
img = img.convert("L")
img = img.resize((500, 1000))
img.show()
En executant ce code, L'image affiché doit ressembler à ça :
Rapport de forme
Elle semble... étirée. Et c'est bien normal, parce que l'image d'origine fait 1920 pixels de large pour 1279 pixels de haut. Si on regarde le ratio entre cette largeur et hauteur (largeur/hauteur
), on tombe sur environ 1.50
. Autrement dit, l'image d'origine est 1.5 fois plus large que haute.
Si on fait le même calcul avec les nouvelles dimensions (500/1000
), on tombe sur 0.5
, ce qui veut dire que la nouvelle image est deux fois moins large que haute.
Cette valeur est ce que l'on appelle le rapport de forme (aspect ratio en anglais). On veut le maintenir quand on change la largeur de l'image, pour que l'image n'apparaisse pas étirée.
On va écrire une fonction qui permet de calculer les nouvelles dimensions d'une image à partir de sa largeur et de sa hauteur.
def nouvelles_dimensions(l, d):
"""l un entier > 0, la nouvelle largeur voulue pour l'image
d un tuple d'entiers (l1, h1), respectivement la largeur et la hauteur de l'image
retourne les nouvelles dimensions pour l'image, telles que la largeur soit l, et que le rapport de forme soit préservé
"""
pass
Préparation des tests
Avant d'écrire cette fonction, on va d'abord la tester à l'aide de doctests. Modifions un peu notre code pour qu'il puisse accepter des doctests. Dans notre cas, celà consiste simplement à mettre notre code dans une fonction main
, et à ajouter un booléen en haut du fichier pour la lancer ou non.
from PIL import Image
lancer_main = True
def main():
img = Image.open("chien.jpg")
img = img.convert("L")
img = img.resize((500, 1000))
img.show()
if __name__ == "__main__" and lancer_main:
main()
Celà nous permettra de lancer nos tests à venir sans que le programme, qui prends du temps à s'exécuter, se lance.
On peut maintenant ajouter la configuration pour lancer les doctests.
from PIL import Image
lancer_main = True
def main():
img = Image.open("chien.jpg")
img = img.convert("L")
img = img.resize((500, 1000))
img.show()
# Ces deux lignes permettent de lancer les doctest
import doctest
doctest.testmod()
if __name__ == "__main__" and lancer_main:
main()
Cas de tests
Commençont à calculer à la main quelques valeurs que nous utiliseront dans les tests. C'est ce que l'on appelle des cas de tests (test case en anglais).
Activité
Dans les questions suivantes, on veut calculer la nouvelle taille qui préserve le rapport de cadre étant donné une nouvelle largeur.
Taille initiale (1000, 1000)
. Largeur cible 500
.
On calcule le ratio de cadre de l'image : 1000/1000
vaut 1
.
La nouvelle largeur est la largeur cible : 500
.
La nouvelle hauteur est la largeur cible divisée par le ratio de cadre : 500
(500/1
)
La nouvelle taille est donc (500, 500)
.
Taille initiale (500, 1000)
. Largeur cible 1000
.
On calcule le ratio de cadre de l'image : 500/1000
vaut 0.5
.
La nouvelle largeur est la largeur cible : 1000
.
La nouvelle hauteur est la largeur cible divisée par le ratio de cadre : 2000
(1000/0.5
)
La nouvelle taille est donc (1000, 2000)
.
Taille initiale (1200, 300)
. Largeur cible 400
.
On calcule le ratio de cadre de l'image : 1200/300
vaut 4
.
La nouvelle largeur est la largeur cible : 400
.
La nouvelle hauteur est la largeur cible divisée par le ratio de cadre : 100
(400/4
)
La nouvelle taille est donc (400, 100)
.
Taille initiale (1920, 1080)
. Largeur cible 1000
.
On calcule le ratio de cadre de l'image : 1920/1080
vaut environ 1.77777777778
.
La nouvelle largeur est la largeur cible : 1000
.
La nouvelle hauteur est la largeur cible divisée par le ratio de cadre : 562.499999999
(1000/1.77777777778
)
Mais comme les dimensions doivent être des entiers, on arrondis. La nouvelle hauteur est alors 562
.
La nouvelle taille est donc (1000, 562)
.
Ecriture des tests
Maintenant, on va traduire nos cas de tests en tests. Les doctests s'écrivent directement dans la docstring d'une fonction, comme si la fonction était appelée en console, suivi de son resultat, comme si affiché en console.
Pour le test correspondant à la question 1 de l'acti
def nouvelles_dimensions(l, d):
"""l un entier > 0, la nouvelle largeur voulue pour l'image
d un tuple d'entiers (l1, h1), respectivement la largeur et la hauteur de l'image
retourne les nouvelles dimensions pour l'image, telles que la largeur soit l, et que le rapport de forme soit préservé
>>> nouvelles_dimensions(500, (1000, 1000))
(500, 500)
"""
pass
La ligne >>> nouvelles_dimensions(500, (1000, 1000))
corresponds à l'appel de la fonction. La ligne (500, 500)
à son résultat. ATTENTION, le résultat est tel qu'affiché en console. Dans le cas d'un tuple comme ici, il faut bien respecter le formattage de Python, et entre autres les espaces avant chaque nouvel élément. Ici, (500, 500)
n'est pas pareil que (500,500)
!
Pour le moment, notre code complet donne. Remarquez que lancer_main
est maintenant à False
, parce que pour le moment on va seulement lancer les tests :
from PIL import Image
lancer_main = False
def nouvelles_dimensions(l, d):
"""l un entier > 0, la nouvelle largeur voulue pour l'image
d un tuple d'entiers (l1, h1), respectivement la largeur et la hauteur de l'image
retourne les nouvelles dimensions pour l'image, telles que la largeur soit l, et que le rapport de forme soit préservé
>>> nouvelles_dimensions(500, (1000, 1000))
(500, 500)
"""
pass
def main():
img = Image.open("chien.jpg")
img = img.convert("L")
img = img.resize((500, 1000))
img.show()
# Ces deux lignes permettent de lancer les doctest
import doctest
doctest.testmod()
if __name__ == "__main__" and lancer_main:
main()
Activité
Ajoutez les cas de test élaborés dans les questions 2 à 4 aux tests.
def nouvelles_dimensions(l, d):
"""l un entier > 0, la nouvelle largeur voulue pour l'image
d un tuple d'entiers (l1, h1), respectivement la largeur et la hauteur de l'image
retourne les nouvelles dimensions pour l'image, telles que la largeur soit l, et que le rapport de forme soit préservé
>>> nouvelles_dimensions(500, (1000, 1000))
(500, 500)
>>> nouvelles_dimensions(1000, (500, 1000))
(1000, 2000)
>>> nouvelles_dimensions(400, (1200, 300))
(400, 100)
>>> nouvelles_dimensions(500, (1920, 1080))
(1000, 562)
"""
pass
Executez le code. Il doit produire une erreur qui contient
C'est la preuve que vos 4 tests ont bien étés exécutés, et qu'ils ont tous échoués.
Activité
A votre avis, pourquoi est-il important que les tests échouent à cette étape ?
Conservez votre réponse dans un document ou des commentaires, il vous faudra la rendre avec le code.
Implémentation
Activité
Implémentez la fonction nouvelles_dimensions
de manière à ce que les tests passent, c'est à dire qu'ils ne génère aucune erreur (aucun affichage) quand vous executez le programme.
Les questions sur les cas de test vous donnent la formule à appliquer. N'oubliez pas de convertir des deux dimensions en entiers.
def nouvelles_dimensions(l, d):
"""l un entier > 0, la nouvelle largeur voulue pour l'image
d un tuple d'entiers (l1, h1), respectivement la largeur et la hauteur de l'image
retourne les nouvelles dimensions pour l'image, telles que la largeur soit l, et que le rapport de forme soit préservé
>>> nouvelles_dimensions(500, (1000, 1000))
(500, 500)
>>> nouvelles_dimensions(1000, (500, 1000))
(1000, 2000)
>>> nouvelles_dimensions(400, (1200, 300))
(400, 100)
>>> nouvelles_dimensions(1000, (1920, 1080))
(1000, 562)
"""
l1 = d[0]
h1 = d[1]
ratio = l1/h1
return (int(l), int(l / ratio))
utilisation
Modifions la fonction main
pour utiliser notre fonction. On peut utiliser img.size
pour récupérer les dimensions de l'image sous forme d'une tuple :
def main():
img = Image.open("chien.jpg")
img = img.convert("L")
taille = img.size
nouvelle_taille = nouvelles_dimensions(500, taille)
img = img.resize(nouvelle_taille)
img.show()
Le code complet se trouve dans le dépliant suivant. Vous remarquerez qu'on a remis lancer_main
à True
:
Code Complet
from PIL import Image
lancer_main = True
def nouvelles_dimensions(l, d):
"""l un entier > 0, la nouvelle largeur voulue pour l'image
d un tuple d'entiers (l1, h1), respectivement la largeur et la hauteur de l'image
retourne les nouvelles dimensions pour l'image, telles que la largeur soit l, et que le rapport de forme soit préservé
>>> nouvelles_dimensions(500, (1000, 1000))
(500, 500)
>>> nouvelles_dimensions(1000, (500, 1000))
(1000, 2000)
>>> nouvelles_dimensions(400, (1200, 300))
(400, 100)
>>> nouvelles_dimensions(1000, (1920, 1080))
(1000, 562)
"""
l1 = d[0]
h1 = d[1]
ratio = l1/h1
return (int(l), int(l / ratio))
def main():
img = Image.open("chien.jpg")
img = img.convert("L")
taille = img.size
nouvelle_taille = nouvelles_dimensions(500, taille)
img = img.resize(nouvelle_taille)
img.show()
# Ces deux lignes permettent de lancer les doctest
import doctest
doctest.testmod()
if __name__ == "__main__" and lancer_main:
main()
En executant ce code, l'image affichée doit avoir les bonnes proportions.