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, en plus de quelques manipulations utiles. Toutefois, nous n'aborderons pas le chargement dynamique d'images dans ce chapitre.
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.
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 image dans une classe qui représentera notre bibliothèque d'images.
Créez une nouvelle classe ImageLibrary
à côté de notre classe Main
:
1 2 3 4 5 6 7 8 9 10 11 12 | 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
:
1 | [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, faite un clic droit sur l'image à incorporer, puis sélectionnez Generate Embed Code
, comme à la figure suivante.
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 :
1 | public static var Zozor:Class; |
Voici le code complet de la classe :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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).
Si nous utilisons maintenant le constructeur de la classe Bitmap
, nous obtiendrons une image vide :
1 2 | // Image vide var zozor:Bitmap = new Bitmap(); |
À la place, nous allons utiliser la classe contenu dans l'attribut statique que nous avons déclaré dans notre classe ImageLibrary
:
1 | var zozor:Bitmap = new ImageLibrary.Zozor(); |
Voici le code complet pour afficher notre image de Zozor :
1 2 3 4 5 6 7 | // 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.
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.
Voici une solution possible :
1 2 3 4 5 6 7 8 9 10 11 | // 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… Peux-on y remédier ?
Plus ou moins.
En vérité, il n'y a pas de formule magique pour rendre l'image aussi nette qu'elle é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 :
1 2 | // Lissage des pixels zozor.smoothing = true; |
Ce qui nous donne à l'écran la figure suivante.
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 Zozo, mettons-la à 70%, sans utiliser le lissage :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 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.
Comme vous pouvez le voir, le rendu que nous obtenu est loin d'être agréable. Pour remédier à cela, réactivons le lissage :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 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.
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.
N'abusez pas du lissage si vous affichez beaucoup d'images : sinon, cela dégradera les performances de votre application.
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.
Il est possible d'utiliser un seul objet BitmapData
pour plusieurs images Bitmap
. N'hésitez pas à le faire si vous devez afficher plusieurs fois la même image, car cette astuce améliore grandement les performances d'affichage des images concernées.
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 :
1 2 3 | // 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 :
1 | trace("taille de l'image : " + image.width + " x " + image.height); |
Nous obtenons ceci dans la console :
1 | 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
:
1 2 | // 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), puis le deuxième est sa hauteur. Ensuite, un booléen nous permet d'indiquer si l'image vide est transparente ou non ; enfin, nous passons 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
:
1 2 | // Données pour l'image image.bitmapData = imageData; |
Nous avons désormais la figure suivante à l'écran.
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
).
1 2 3 | // 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)
.
1 2 3 | // 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 do colorier un pixel aux coordonnées que nous passons en paramètre, avec une certaine couleur (sans gestion de l'opacité) :
1 2 3 | // 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).
Comme tout à l'heure, 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 de0
à1
(que vous pouvez multiplier par0xffffff
pour avoir une couleur aléatoire), - la classe
BitmapData
dispose des attributswidth
etheight
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.
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, le 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) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // 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.
Solution :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | ///// 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.
1 2 | // Traçons un rectangle rouge imageData.fillRect(new Rectangle(0, 0, 150, 100), 0xffff0000); |
Notre image ressemble désormais à la figure suivante.
Exercice : deuxième variante
Hop ! 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, - et bien entendu, utilisez la méthode que nous venons de voir,
fillRect()
!
J'obtiens la figure suivante.
Et voici ce que j'ai écrit :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | ///// 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 typeClass
, 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éebitmapData
de typeBitmapData
, dont l'édition est possible.