Aller au contenu

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 :

chien

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

1 items had failures:
   4 of   4 in __main__.nouvelles_dimensions
***Test Failed*** 4 failures.

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.