• 12 heures
  • {0} Facile|{1} Moyenne|{2} Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 30/04/2014

Manipuler des images

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Il est temps d'embellir notre application à l'aide d'images. Dans ce chapitre, nous verrons comment incorporer des images à notre application, comment les afficher sur la scène, nous aborderons également quelques manipulations utiles. Toutefois, nous n'évoquerons pas ici le chargement dynamique d'images.

Embarquer des images

Préparation de l'image

Dans un premier temps, il faut préparer l'image que nous voulons incorporer dans notre application. Reprenons l'image de Zozor (voir figure suivante) que nous avons utilisée pour afficher une image dans un champ de texte.

Zozor, la mascotte du Site du Zéro
Zozor, la mascotte du Site du Zéro

Créez un dossier lib s'il n'existe pas déjà dans le répertoire de votre projet, puis créez-y un dossier img. Enfin, copiez dans ce dossier img l'image de Zozor. Voilà ! Notre image de Zozor est prête à être incorporée dans notre application ! :)

Librairie d'images

Une nouvelle classe

Pour que notre projet soit un peu organisé, nous allons embarquer nos images dans une classe qui représentera notre bibliothèque d'images.
Créez une nouvelle classe ImageLibrary à côté de notre classe Main :

package  
{
    /**
     * Librairie contenant nos images embarquées.
     * @author Guillaume CHAU
     */
    public class ImageLibrary 
    {
        
    }

}
Incorporer une image

Pour embarquer une image, nous allons utiliser une instruction spéciale pour le compilateur, de la même manière que nous avons embarqué une police de caractère, grâce au mot-clé Embed :

[Embed(source="../lib/img/zozor.png")]

Le code est plus simple : il suffit d'indiquer le chemin de l'image dans le paramètre source.
Petite astuce si vous utilisez FlashDevelop : dans l'arborescence du projet, faites un clic droit sur l'image à incorporer, puis sélectionnez Generate Embed Code, comme à la figure suivante.

FlashDevelop facilite l'incorporation d'images
FlashDevelop facilite l'incorporation d'images

FlashDevelop insère alors l'instruction spéciale automatiquement, à l'endroit où votre curseur d'insertion se trouve dans la classe.

Puis, sur la ligne suivante, il faut déclarer un attribut statique de type Class, comme pour les polices de caractères embarquées :

public static var Zozor:Class;

Voici le code complet de la classe :

package  
{
    /**
     * Librairie contenant nos images embarquées.
     * @author Guillaume CHAU
     */
    public class ImageLibrary 
    {
        
        [Embed(source="../lib/img/zozor.png")]
        public static var Zozor:Class;
        
    }

}

Notre librairie d'images embarquées est maintenant prête à l'emploi ! :)

Afficher des images

La classe Bitmap

Pour afficher une image, nous allons utiliser la classe Bitmap (voir figure suivante).

La classe Bitmap dans l'arbre des classes d'affichage
La classe Bitmap dans l'arbre des classes d'affichage

Si nous utilisons maintenant le constructeur de la classe Bitmap, nous obtiendrons une image vide :

// Image vide
var zozor:Bitmap = new Bitmap();

À la place, nous allons utiliser la classe contenue dans l'attribut statique que nous avons déclaré dans notre classe ImageLibrary :

var zozor:Bitmap = new ImageLibrary.Zozor();

Voici le code complet pour afficher notre image de Zozor :

// Création de l'image
var zozor:Bitmap = new ImageLibrary.Zozor();
addChild(zozor);

// Alignement au centre
zozor.x = (stage.stageWidth - zozor.width) / 2;
zozor.y = (stage.stageHeight - zozor.height) / 2;

Ce qui nous donne à l'écran la figure suivante.

Zozor !
Zozor !

Redimensionnement

Je vous propose d'essayer de redimensionner notre image de Zozor : doublez sa taille sans regarder la solution pour vérifier que vous vous souvenez bien des attributs nécessaires.

Vous devriez obtenir la figure suivante.

Zozor a pris du poids.
Zozor a pris du poids.

Voici une solution possible :

// Création de l'image
var zozor:Bitmap = new ImageLibrary.Zozor();
addChild(zozor);

// Redimensionnement
zozor.scaleX = 2;
zozor.scaleY = zozor.scaleX;

// Alignement au centre
zozor.x = (stage.stageWidth - zozor.width) / 2;
zozor.y = (stage.stageHeight - zozor.height) / 2;

À l'aide des attributs scaleX et scaleY de la classe DisplayObject dont hérite la classe Bitmap, nous pouvons très facilement appliquer un coefficient multipliant la taille de notre objet d'affichage.

L'image est devenue affreuse avec ces gros pixels... Peut-on y remédier ?

Plus ou moins. :D
En vérité, il n'y a pas de formule magique pour rendre l'image aussi nette qu'elle n'était à sa taille d'origine, mais il est néanmoins possible d'améliorer le rendu obtenu lors de tels redimensionnements jusqu'à une certaine mesure. Pour cet effet, la classe Bitmap dispose d'un attribut smoothing qui indique si les pixels de l'image doivent être lissés si elle est redimensionnée :

// Lissage des pixels
zozor.smoothing = true;

Ce qui nous donne à l'écran la figure suivante.

Les pixels sont lissés.
Les pixels sont lissés.

C'est déjà un peu mieux ! :)
Mais l'effet de cet attribut se fait surtout ressentir lorsque l'on diminue la taille des images. Au lieu de doubler la taille de Zozor, mettons-la à 70%, sans utiliser le lissage :

// Création de l'image
var zozor:Bitmap = new ImageLibrary.Zozor();
addChild(zozor);

// Lissage des pixels
//zozor.smoothing = true;

// Redimensionnement
zozor.scaleX = 0.7;
zozor.scaleY = zozor.scaleX;

// Alignement au centre
zozor.x = (stage.stageWidth - zozor.width) / 2;
zozor.y = (stage.stageHeight - zozor.height) / 2;

Ce qui nous donne la figure suivante.

Taille à 70% de l'original
Taille à 70% de l'original

Comme vous pouvez le constater, le rendu que nous avons obtenu est loin d'être agréable. Pour remédier à cela, réactivons le lissage :

// Création de l'image
var zozor:Bitmap = new ImageLibrary.Zozor();
addChild(zozor);

// Lissage des pixels
zozor.smoothing = true;

// Redimensionnement
zozor.scaleX = 0.7;
zozor.scaleY = zozor.scaleX;

// Alignement au centre
zozor.x = (stage.stageWidth - zozor.width) / 2;
zozor.y = (stage.stageHeight - zozor.height) / 2;

Ce qui nous donne à l'écran la figure suivante.

Taille à 70% avec lissage
Taille à 70% avec lissage

C'est tout de même beaucoup mieux ! :)
Pour mieux voir les différences entre les deux rendus, voici à la figure suivante une comparaison avec zoom.

Comparaison avec et sans lissage
Comparaison avec et sans lissage

Opérations sur les images

La classe BitmapData

Tout objet de la classe Bitmap dispose d'un attribut bitmapData de la classe BitmapData (voir figure suivante). Cet objet représente les pixels de l'image stockés en mémoire. L'instance de la classe Bitmap ne contient pas de pixels, elle n'est là que pour les afficher.

Tout objet de la classe Bitmap dispose d'un attribut bitmapData de la classe BitmapData
Tout objet de la classe Bitmap dispose d'un attribut bitmapData de la classe BitmapData

Créer notre première image

Pour créer une image vide, nous pouvons créer une instance de la classe Bitmap sans lui passer de paramètre :

// Création de l'image
var image:Bitmap = new Bitmap();
addChild(image);

Notre objet image n'a aucun pixel à sa disposition, donc il n'affiche rien et a une taille nulle :

trace("taille de l'image : " + image.width + " x " + image.height);

Nous obtenons ceci dans la console :

taille de l'image : 0 x 0

Ne nous arrêtons pas là et créons notre première image grâce à la classe BitmapData :

// Création des données de l'image (les pixels)
var imageData:BitmapData = new BitmapData(300, 300, false, 0x0000ff);

Le premier paramètre est la largeur du tableau de pixel (donc de l'image), le deuxième est sa hauteur. Ensuite, un booléen nous permet d'indiquer si l'image vide est transparente ou non. Pour finir, nous devons passer la couleur de remplissage initiale.
Ici, notre image sera donc un carré de 300 pixels de côté, rempli de couleur bleue.

Ensuite, il faut dire à notre objet image d'utiliser les données contenues dans notre objet imageData :

// Données pour l'image
image.bitmapData = imageData;

Nous avons désormais la figure suivante à l'écran.

Un carré bleu
Un carré bleu

Nous avons créé une image à partir de rien ! :)

Dessiner sur des images

Nous allons maintenant nous atteler à rendre ce morne carré bleu un peu plus intéressant en manipulant les pixels qui le composent.

Pipette

Nous avons à disposition d'une sorte de pipette, comme dans les logiciels de dessin, qui nous permet de connaître la couleur d'un pixel. Cette pipette est la méthode getPixel(x, y) de la classe BitmapData. En paramètre, nous lui fournissons les coordonnées du pixel dont nous voulons la couleur et elle nous la renvoie (si nous donnons des coordonnées qui sortent de l'image, elle nous renvoie 0).

// Prenons la couleur d'un de nos pixels (celui du centre)
var couleur:uint = imageData.getPixel(150, 150);
trace("couleur du pixel : " + couleur.toString(16)); // Affiche: ff (équivalent de 0000ff)

Il existe une deuxième pipette qui nous renvoie la couleur d'un pixel avec son opacité : getPixel32(x, y).

// Avec l'opacité
couleur = imageData.getPixel32(150, 150);
trace("couleur ARVB du pixel : " + couleur.toString(16)); // Affiche: ff0000ff

Comme vous pouvez le remarquer, il y a deux caractères supplémentaires par rapport à une couleur classique : ce sont les deux premiers. Ils représentent l'opacité de la couleur en quelque sorte, notée en hexadécimal de 00 à ff, comme pour le rouge, le vert et le bleu. Ainsi, 0x00000000 est une couleur totalement transparente, 0x880000ff représente du bleu à moitié transparent et 0xff0000ff du bleu totalement opaque.

Colorier un pixel

Il est grand temps de barbouiller notre carré de couleur ! :) Commençons avec la méthode setPixel(x, y, couleur), qui permet de colorier un pixel aux coordonnées que nous passons en paramètre, avec une certaine couleur (sans gestion de l'opacité) :

// Colorions le pixel du centre en rouge
couleur = 0xff0000;
imageData.setPixel(150, 150, couleur);

Nous obtenons la figure suivante (j'ai agrandi l'image pour que l'on puisse mieux voir).

Pixel rouge au milieu de pixels bleus.
Pixel rouge au milieu de pixels bleus.

Comme nous l'avons vu précédemment, il existe également la méthode setPixel32(x, y, couleur) qui permet de colorier un pixel avec une couleur contenant une valeur de transparence.

Exercice

Essayons-nous à plus de fantaisies ! Je vous propose un petit exercice : coloriez chaque pixel de notre carré avec une couleur aléatoire opaque. Essayez de ne pas regarder la solution trop vite ! ;)

Quelques conseils :

  • Utilisez deux boucles imbriquées pour "parcourir" les pixels de l'image.

  • Utilisez la méthode Math.random() pour générer un nombre aléatoire de 0 à 1 (que vous pouvez multiplier par 0xffffff pour avoir une couleur aléatoire).

  • La classe BitmapData dispose des attributs width et height permettant d'obtenir la taille de l'image stockée dans l'objet, un peu comme pour les objets d'affichage.

J'obtiens la figure suivante.

Un beau tas de pixels.
Un beau tas de pixels.

Ne regardez pas tout de suite la solution que je vous propose si vous n'avez pas réfléchi au code qu'il faut écrire. Essayez d'obtenir un bon résultat et, même si vous n'y arrivez pas, rassurez-vous, le simple fait d'avoir essayé est déjà un grand pas vers la maîtrise du langage. ;)

Si vous avez réussi (ou que vous êtes sur le point de vous jeter par la fenêtre !), vous pouvez comparer votre code avec le mien (je n'ai vraiment pas envie d'avoir votre mort sur la conscience) :

// Création de l'image
var image:Bitmap = new Bitmap();
addChild(image);

// Création des données de l'image (les pixels)
var imageData:BitmapData = new BitmapData(300, 300, false, 0x0000ff);

// Données pour l'image
image.bitmapData = imageData;

///// Barbouillage !
// On parcourt chaque pixel de l'image
// Lignes
for (var x:int = 0; x < imageData.width; x ++)
{
	// Colonnes
	for (var y:int = 0; y < imageData.height; y ++)
	{
		// Couleur aléatoire
		couleur = Math.random() * 0xffffff;
		
		// Colorions le pixel actuel
		imageData.setPixel32(x, y, couleur);
	}
}

Et c'est tout ! Finalement, c'est plutôt simple non ?

Exercice : variante

Et si nous corsions un peu les choses ? Modifions l'exercice : il faut désormais que tous les pixels de l'image soient remplis avec une couleur choisie aléatoirement parmi un choix limité : rouge, vert, bleu.

Quelques conseils :

  • Utilisez un tableau pour stocker les couleurs autorisées.

  • Réutilisez la méthode Math.random() et la propriété length de votre tableau pour choisir aléatoirement une couleur parmi celle du tableau.

J'obtiens cette fois-ci la figure suivante.

Pixels de trois couleurs
Pixels de trois couleurs

Solution :

///// Barbouillage v 2.0
// On définit d'abord les couleurs possibles
var couleurs:Array = [0xff0000, 0x00ff00, 0x0000ff];

// On parcourt chaque pixel de l'image
// Lignes
for (var x:int = 0; x < imageData.width; x ++)
{
	// Colonnes
	for (var y:int = 0; y < imageData.height; y ++)
	{
		// Choix de la couleur
		var choix:int = Math.random() * couleurs.length;
		
		// Couleur choisie
		couleur = couleurs[choix];
		
		// Colorions le pixel actuel
		imageData.setPixel32(x, y, couleur);
	}
}
Remplissage un rectangle de couleur

Une autre méthode bien utile permet de remplir une zone rectangulaire avec une couleur (avec transparence), ce qui nous simplifie tout de même la vie. Il s'agit de fillRect(rectangle, couleur) de la classe BitmapData, qui prend en paramètre un objet de la classe Rectangle et une couleur avec transparence.

// Traçons un rectangle rouge
imageData.fillRect(new Rectangle(0, 0, 150, 100), 0xffff0000);

Notre image ressemble désormais à la figure suivante.

Un rectangle rouge sur fond bleu
Un rectangle rouge sur fond bleu
Exercice : deuxième variante

Encore le même exercice ! Mais cette fois-ci, remplissons notre image avec de gros carrés de 25 pixels de côté d'une couleur choisie aléatoirement parmi le même choix limité : rouge, vert, bleu.

Quelques conseils :

  • Utilisez un objet de la classe Rectangle pour stocker les coordonnées et la taille des carrés.

  • Bien entendu, utilisez la méthode que nous venons de voir, fillRect() !

J'obtiens la figure suivante.

Le rendu
Le rendu

Et voici ce que j'ai écrit :

///// Barbouillage v 3.0
// On définit d'abord les couleurs possibles
var couleur:uint;
var couleurs:Array = [0xff0000, 0x00ff00, 0x0000ff];

// On définit la taille des rectangles
var rectangle:Rectangle = new Rectangle(0, 0, 25, 25);

// On parcourt l'image pour la remplir de rectangles
// Lignes
for (rectangle.x = 0; rectangle.x < imageData.width; rectangle.x += rectangle.width)
{
    // Colonnes
    for (rectangle.y = 0; rectangle.y < imageData.height; rectangle.y += rectangle.height)
    {
        // Choix de la couleur
        var choix:int = Math.random() * couleurs.length;

        // Couleur choisie
        couleur = couleurs[choix];
		
        // Colorions le rectangle actuel
        imageData.fillRect(rectangle, couleur);
    }
}

Comme vous pouvez le remarquer, j'ai utilisé l'objet rectangle directement dans les boucles for : le code en est d'autant plus logique et lisible. ;)

En résumé
  • Pour embarquer une image, il suffit d'utiliser l'instruction spéciale Embed et définir un attribut de type Class, comme pour les polices embarquées.

  • L'affichage d'une image se fait via la classe Bitmap dont la définition peut se faire grâce aux images embarquées.

  • La classe Bitmap dispose d'une propriété smoothing qui permet de lisser les pixels d'une image, principalement utile après une transformation.

  • L'ensemble des pixels d'une image sont stockés en mémoire à l'aide une instance de la classe BitmapData.

  • La classe Bitmap possède donc une propriété nommée bitmapData de type BitmapData dont l'édition est possible.

Exemple de certificat de réussite
Exemple de certificat de réussite