Pour le dernier chapitre théorique de cette partie sur l'affichage, je vous propose de découvrir les masques ! Ce concept qui permet de cacher certaines zones d'un objet affichage, est spécifique à l'Actionscript et à la technologie Flash. Pour ceux qui utilisent des logiciels de graphisme comme Gimp ou Photoshop, vous devriez vite cerner la chose. En revanche pour les autres, suivez attentivement ce que va être dit ici car cette technique est vraiment très pratique et très utilisée.
Un masque… qui ne masque pas
Le principe des masques
Lorsque vous créez des éléments graphiques, il est possible que vous ayez besoin d'isoler une certaine partie de l'objet d'affichage en question. C'est pourquoi vous aurez besoin de séparer la zone visible de la zone à cacher. Pour cela, nous utilisons ce qu'on appelle des masques !
Mais qu'est-ce qu'un masque ?
Un masque est un élément graphique comme un autre ; on retrouve alors des masques de type Shape
, Sprite
ou encore Bitmap
. Celui-ci sert alors à délimiter la zone visible d'un autre objet d'affichage, définie par l'opacité du masque.
Pour bien comprendre, je vous propose à la figure suivante un petit schéma.
Comme vous pouvez le voir, nous avons donc défini un masque circulaire, qui laisse apparaître la photo uniquement à l'intérieur de celui-ci. Bien évidemment, un masque peut être extrêmement complexe et composé de différentes formes. Par exemple, nous aurions pu suivre les courbes du serpent pour pouvoir l'isoler du reste de l'image.
Dans l'exemple précédent comme dans la suite du chapitre, j'utiliserai exclusivement des masques de couleur verte. Cela vous permettra de bien discerner les masques des autres objets d'affichage « classiques ».
Les masques en Flash
Définition du masque
Avant d'appliquer un masque à un objet visuel quelconque, il est nécessaire de définir ces deux éléments.
Pour commencer, je vous suggère un exemple relativement simple. Nous allons donc démarrer par deux objets Shape
où nous dessinerons des formes basiques.
Dans ce chapitre, nous nous contenterons de définir des masques dessinés grâce aux méthodes de la classe Graphics
. Toutefois, le principe avec des images « bitmap » est strictement identique. Il faudra néanmoins prendre en compte le canal alpha, comme nous le verrons dans la suite du chapitre.
Voici donc nos deux éléments monObjet
et masque
, contenant respectivement un rectangle orange et un cercle vert :
1 2 3 4 5 6 7 8 9 10 11 12 | var monObjet:Shape = new Shape(); var masque:Shape = new Shape(); this.addChild(monObjet); this.addChild(masque); monObjet.graphics.beginFill(0xFF8800); monObjet.graphics.drawRect(0, 0, 100, 100); monObjet.graphics.endFill(); masque.graphics.beginFill(0x00FF00); masque.graphics.drawCircle(50, 50, 50); masque.graphics.endFill(); |
Nous avons ici placé l'objet masque
au premier plan, ce qui donne la figure suivante.
Lorsque vous définissez un masque avec les méthodes de la classe Graphics
, seuls les remplissages sont utilisés pour définir un masque. Les lignes seront alors tout simplement ignorées.
L'objectif consiste donc maintenant à définir l'objet de couleur verte comme masque pour l'élément monObjet
.
Une propriété mask
En fait la notion de masques est liée à tout objet visuel, c'est-à-dire tout objet de la classe DisplayObject
ou plutôt d'une de ses sous-classes.
Nous avons déjà vu qu'une classe peut posséder un attribut d'elle-même ; la classe DisplayObject
est justement l'une d'elles. En effet, celle-ci possède une propriété nommée mask
de type DisplayObject
!
Ainsi avec les objets de type Shape
déclarés précédemment, il est possible de faire ceci :
1 | monObjet.mask = masque; |
Dans cette instruction nous passons bien l'objet masque
à la propriété mask
de l'élément monObjet
. Si vous relancez alors l'exécution du projet, vous verrez la magie s'opérer (voir figure suivante).
Comme vous pouvez le constater, le masque n'est maintenant plus visible. C'est pourquoi, l'instruction this.addChild(masque)
est en réalité facultative. Toutefois si vous n'ajoutez pas le masque à la liste d'affichage, celui-ci ne sera alors pas affecté par les éventuelles transformations du conteneur. Pour éviter tout problème, je vous conseille donc de l'ajouter à chaque fois à la liste d'affichage.
Pour finir, veuillez noter qu'il est possible de supprimer le masque d'un objet simplement en définissant sa propriété mask
à null
.
Niveaux de transparence multiples
Présentation du concept
Comme nous avons vu précédemment, nous pouvons définir la forme d'un masque pour isoler certaines zones d'un objet d'affichage. Toutefois, il est également possible de définir la transparence de celui-ci en gérant l'opacité du masque. Rien ne nous empêche alors d'utiliser des niveaux de transparence multiples. Pour illustrer ce qui vient d'être dit, je vous propose à la figure suivante un exemple de masque à plusieurs niveaux de transparence.
Comme le montre l'illustration précédente, l'objet d'affichage adapte donc sa transparence à l'opacité du masque qui lui est associé. Nous verrons alors qu'il est possible d'utiliser des dégradés pour définir un masque, ou bien utiliser directement une image « bitmap » dont le format supporte la transparence.
Place au code
Création du masque
Pour définir différents niveaux de transparence, il est nécessaire de « jouer » avec l'opacité des remplissages. Je suggère alors que nous réalisions un masque composé de trois disques qui se superposent partiellement. Voici le code que je vous propose pour faire cela :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Objet à masquer monObjet.graphics.beginFill(0xFF00FF); monObjet.graphics.drawRect(0, 0, 160, 150); monObjet.graphics.endFill(); // Masque masque.graphics.beginFill(0x00FF00,0.5); masque.graphics.drawCircle(80, 50, 50); masque.graphics.endFill(); masque.graphics.beginFill(0x00FF00,0.5); masque.graphics.drawCircle(50, 100, 50); masque.graphics.endFill(); masque.graphics.beginFill(0x00FF00,0.5); masque.graphics.drawCircle(110, 100, 50); masque.graphics.endFill(); |
Nous avons donc ici réalisé un masque composé de trois niveaux de transparence différents, comme vous pouvez le voir à la figure suivante.
Mise en place du masque
Redéfinissons maintenant la propriété mask
de monObjet
comme nous avons appris à le faire :
1 | monObjet.mask = masque; |
Vous serez alors surpris de découvrir que ce masque ne fonctionne pas comme nous l'avions prévu, mais réagit comme si nous avions laissé une opacité maximale pour nos remplissages. Regardez plutôt la figure suivante.
En réalité pour pouvoir gérer la transparence d'un masque, nous devons utiliser une mise en cache sous forme de bitmap. L'élément graphique est alors géré comme s'il s'agissait d'une image « bitmap » en tenant compte du niveau de transparence de chacun de ses pixels.
Le cache bitmap
Tout objet de type DisplayObject
possède une propriété nommée cacheAsBitmap
. Celle-ci permet d'activer la « mise en cache sous forme de bitmap », en la définissant tout simplement à true
. Habituellement, les objets d'affichages sont manipulés par le lecteur Flash comme s'il s'agissait d'objets vectoriels, ce qui donne de bonnes performances la plupart du temps, mais implique quelques limitations, comme celle que nous venons de rencontrer. Lorsqu'un objet d'affichage est mis en cache sous forme de bitmap, il est d'abord rendu sous forme d'image contenant des pixels, puis manipulé par le lecteur Flash. Cela nous permet donc d'effectuer des opérations plus complexes (comme les masques à plusieurs niveaux de transparence). Ainsi, il nous faut activer ce cache sur le masque, mais également sur l'objet auquel il est appliqué.
Ainsi, voici comment procéder pour appliquer le masque à l'objet :
1 2 3 | monObjet.cacheAsBitmap = true; masque.cacheAsBitmap = true; monObjet.mask = masque; |
Cette fois, vous verrez apparaître les différents niveaux de transparence, tels qu'ils ont été définis durant la création du masque. Nous obtenons la figure suivante.
Cette mise cache est également nécessaire lorsqu'on travaille avec des objets de type Bitmap
. Le format d'image choisi doit néanmoins utiliser un canal alpha, comme c'est le cas des formats GIF ou PNG. La transparence est alors utilisée pour définir le masque à partir de cette image.
Mettre un objet d'affichage en cache bitmap a un impact sur les performances : ainsi, il est plus rapide de déplacer l'objet en cache bitmap, mais les transformations comme la rotation ou le redimensionnement ralentissent l'application de façon significative. Pour résumer, s'il vous voulez afficher un grand nombre d'objets qui se déplacent, vous pouvez améliorer les performances de votre application en activant la mise en cache bitmap, à condition de ne pas leur appliquer de transformations.
Exercice : une lampe torche
Pour terminer ce chapitre, nous allons réaliser un petit exercice ensemble. Il s'agit d'un effet de lampe torche que nous créerons à partir de deux images et d'un masque. Cet exercice ne comporte aucune difficulté, contentez-vous uniquement de suivre ces différentes étapes avec moi.
Préparation des images
Rue de nuit
Pour commencer, je vous invite à créer un dossier nommé images
où nous placerons nos deux images.
Dans un premier temps, nous aurons besoin d'une image d'une rue sombre qui servira de fond pour notre projet. Voici à la figure suivante l'image que j'ai nommée « RueSombre.png
».
Cette image sera ainsi notre rue lorsqu'elle n'est pas éclairée par la lampe torche. Nous viendrons donc ajouter une partie lumineuse par dessus pour simuler l'effet d'une lampe torche.
Rue éclairée par la lampe
Lorsque notre lampe torche passera sur une zone, celle-ci sera éclairée presque comme en plein jour. Nous aurons alors besoin d'une seconde image de cette même rue, mais cette fois beaucoup plus lumineuse. Voici donc notre seconde image « RueEclairee.png
» (figure suivante).
La lampe torche n'agissant que sur une certaine partie de la rue, c'est cette image qui subira l'effet du masque que nous créerons juste après.
Chargement des images
En premier lieu, nous allons importer nos images dans notre animation Flash. Pour cela, Nous créerons deux variables de type Class
que nous appellerons RueSombre
et RueEclairee
, comme vous avez appris à le faire.
Voici toutefois le code correspondant pour ceux qui auraient déjà oublié :
1 2 3 4 5 | // Chargement des images [Embed(source = 'images/RueSombre.png')] private var RueSombre:Class; [Embed(source = 'images/RueEclairee.png')] private var RueEclairee:Class; |
N'oubliez pas d'ajouter les images dans le dossier de votre projet pour que celui-ci puisse compiler. Si jamais vous n'avez pas placé les images dans un dossier images
, pensez alors à mettre à jour les liens ci-dessous.
Mise en place des images
Là encore, il n'y a aucune difficulté pour la mise en place des images. La seule chose à vérifier est de placer l'instance de la classe RueEclairee
par dessus celle de la classe RueSombre
. Sinon vous ne verrez absolument rien dans le rendu final de l'animation.
Voici ce que je vous propose de faire :
1 2 3 4 5 | // Mise en place des images var maRueSombre:Bitmap = new RueSombre(); var maRueEclairee:Bitmap = new RueEclairee(); this.addChild(maRueSombre); this.addChild(maRueEclairee); |
Mise en place du masque
À présent, nous allons nous occuper du masque que nous appliquerons à l'image de la rue éclairée. Cela devrait donc donner au final un effet de lampe torche, que vous pourrez déplacer pour éclairer la zone de la rue que vous souhaiterez.
Préparation du dégradé
La première étape est la préparation du dégradé qui nous servira à tracer notre masque. Voici donc le code suggéré :
1 2 3 4 5 6 | // Préparation du dégradé var couleurs:Array = [0x00FF00, 0x00FF00]; var alphas:Array = [1, 0]; var ratios:Array = [192, 255]; var matrix:Matrix = new Matrix(); matrix.createGradientBox(200, 200, 0, -100, -100); |
Ce qu'il est important de noter, c'est que nous utiliserons une seule couleur où l'opacité diminuera sur les bords pour donner un effet d'atténuation de la lumière. Également nous voulons un dégradé rapide sur l'extérieur de la forme, c'est pourquoi nous avons augmenter la première valeur du tableau ratios
.
Création de la lampe
Nous pouvons maintenant dessiner la forme du masque dans un objet de type Sprite
. Nous allons donc définir un remplissage dégradé, puis nous tracerons un cercle pour représenter la forme du faisceau de lumière sortant de la lampe.
Voici comment réaliser ceci :
1 2 3 4 5 6 | // Création de la lampe var lampe:Sprite = new Sprite(); lampe.graphics.beginGradientFill(GradientType.RADIAL, couleurs, alphas, ratios, matrix); lampe.graphics.drawCircle(0, 0, 100); lampe.graphics.endFill(); this.addChild(lampe); |
La figure suivante vous montre à quoi ressemble le masque nous allons utiliser.
Application du masque
Le masque réalisé précédemment va nous permettre d'afficher l'image de la rue éclairée uniquement dans la zone où la lampe pointe.
Pour faire cela, nous allons définir l'instance lampe
comme masque pour l'objet maRueEclairee
. Nous allons donc procéder comme nous avons appris à le faire :
1 2 3 4 | // Mise en place du masque maRueEclairee.cacheAsBitmap = true; lampe.cacheAsBitmap = true; maRueEclairee.mask = lampe; |
Nous avons à présent réaliser l'effet souhaité. Ceci dit, pour donner un peu plus d'intérêt à ce projet, nous allons animer celui-ci.
Animation de la lampe
Pour animer le masque, nous allons utiliser la méthode startDrag()
de la classe Sprite
. En indiquant son paramètre à true
, on impose à l'objet en question de « coller » à la position de la souris. Ainsi, notre lampe va suivre les mouvements de la souris, pour imiter les gestes du poignet.
Voici donc comment procéder :
1 2 | // Animation de la lampe lampe.startDrag(true); |
Projet final
Le rendu
Nous avons maintenant terminé cet exercice sur les masques. Je vous propose donc à la figure suivante un aperçu de l'animation finale.
Le code complet
Enfin pour finir, je vous ai ici recopier l'intégralité du code de ce projet :
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | package { import flash.display.Sprite; import flash.display.Shape; import flash.display.Bitmap; import flash.events.Event; import flash.geom.Matrix; import flash.display.GradientType; /** * ... * @author Guillaume */ public class Main extends Sprite { // Chargement des images [Embed(source = 'images/RueSombre.png')] private var RueSombre:Class; [Embed(source = 'images/RueEclairee.png')] private var RueEclairee:Class; public function Main():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); // Mise en place des images var maRueSombre:Bitmap = new RueSombre(); var maRueEclairee:Bitmap = new RueEclairee(); this.addChild(maRueSombre); this.addChild(maRueEclairee); // Préparation du dégradé var couleurs:Array = [0x00FF00, 0x00FF00]; var alphas:Array = [1, 0]; var ratios:Array = [192, 255]; var matrix:Matrix = new Matrix(); matrix.createGradientBox(200, 200, 0, -100, -100); // Création de la lampe var lampe:Sprite = new Sprite(); lampe.graphics.beginGradientFill(GradientType.RADIAL, couleurs, alphas, ratios, matrix); lampe.graphics.drawCircle(0, 0, 100); lampe.graphics.endFill(); this.addChild(lampe); // Mise en place du masque maRueEclairee.cacheAsBitmap = true; lampe.cacheAsBitmap = true; maRueEclairee.mask = lampe; // Animation de la lampe lampe.startDrag(true); } } } |
En résumé
- Un masque est un objet d'affichage qui sert à délimiter une zone visible d'un autre objet.
- Lorsqu'on utilise la classe
Graphics
, seuls les remplissages sont pris en compte dans les masques. - Le masque d'un objet se définit par sa propriété
mask
. - Pour gérer les niveaux de transparence, la propriété
cacheAsBitmap
du masque et de l'objet doit être passée àtrue
. - Pour supprimer le masque d'un objet, sa propriété
mask
doit être redéfinie à la valeurnull
.