Exemple d’objet Bitmap : lune en rotation animéeFlash Player 9 et ultérieur, Adobe AIR 1.0 et ultérieur L’exemple de lune en rotation animée illustre les techniques d’utilisation des objets Bitmap et des données d’image bitmap (objets BitmapData). L’exemple crée une animation d’une lune sphérique en rotation et utilise comme données d’image brutes une image plane de la surface de la lune. Les techniques suivantes sont illustrées :
Pour obtenir les fichiers d’application de cet exemple, voir www.adobe.com/go/learn_programmingAS3samples_flash_fr. Les fichiers d’application de la lune en rotation animée résident dans le dossier Samples/SpinningMoon. L’application se compose des fichiers suivants :
Chargement d’une image externe comme données bitmapLa première tâche à exécuter dans cet exemple consiste à charger une image externe, une photographie de la surface de la lune. Le chargement est géré par deux méthodes de la classe MoonSphere : le constructeur MoonSphere(), qui lance le processus de chargement, et la méthode imageLoadComplete(), qui est appelée au terme du chargement de l’image externe. Le chargement d’une image externe est similaire à celui d’un fichier SWF externe : il est réalisé par une occurrence de la classe flash.display.Loader. Le code ci-après de la méthode MoonSphere() commence à charger l’image : var imageLoader:Loader = new Loader(); imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoadComplete); imageLoader.load(new URLRequest("moonMap.png")); La première ligne déclare l’occurrence de Loader appelée imageLoader. La troisième ligne commence le processus de chargement à proprement parler en appelant la méthode load() de l’objet Loader. Cette méthode transmet une occurrence URLRequest représentant l’URL de l’image à charger. La deuxième ligne définit l’écouteur d’événement qui se déclenchera à l’issue du chargement de l’image. Observez que la méthode addEventListener() n’est pas appelée sur l’occurrence de Loader elle-même, mais sur la propriété contentLoaderInfo de l’objet Loader. L’occurrence de Loader n’envoie pas d’événements en rapport avec le contenu chargé. En revanche, sa propriété contentLoaderInfo contient une référence à l’objet LoaderInfo qui est associé au contenu chargé dans l’objet Loader (en l’occurrence, l’image externe). L’objet LoaderInfo génère des événements en rapport avec le déroulement et la fin du chargement du contenu externe, notamment l’événement complete (Event.COMPLETE) qui déclenche un appel à la méthode imageLoadComplete() au terme du chargement de l’image. S’il est essentiel de lancer le chargement de l’image externe, il est tout aussi important de savoir comment procéder au terme de cette opération. Comme l’illustre le code ci-dessus, la fonction imageLoadComplete() est appelée une fois l’image chargée. Cette fonction exécute diverses opérations sur les données chargées, comme indiqué ultérieurement. Cependant, pour utiliser les données d’image, elle doit pouvoir y accéder. Une image externe chargée par le biais d’un objet Loader devient une image Bitmap jointe en tant qu’objet d’affichage enfant de l’objet Loader. Dans ce cas, la méthode écouteur d’événement a accès à l’occurrence de Loader dans l’objet événement transmis en tant que paramètre à la méthode. Les premières lignes de la méthode imageLoadComplete() sont les suivantes : private function imageLoadComplete(event:Event):void { textureMap = event.target.content.bitmapData; ... } Notez que le paramètre de l’objet événement s’appelle event et que c’est une occurrence de la classe Event. Chaque occurrence de la classe Event possède une propriété target, qui fait référence à l’objet déclenchant l’événement (en l’occurrence, l’occurrence de LoaderInfo sur laquelle la méthode addEventListener() a été appelée, comme indiqué plus haut). De même, l’objet LoaderInfo possède une propriété content qui, à l’issue du chargement, contient une occurrence de Bitmap comportant l’image bitmap chargée. Pour afficher l’image directement à l’écran, vous pouvez joindre cette occurrence de Bitmap (event.target.content) à un conteneur d’objet d’affichage (vous pouvez aussi joindre l’objet Loader à un conteneur d’objet d’affichage). Toutefois, dans cet exemple, le contenu chargé n’est pas affiché à l’écran, il est utilisé en tant que données d’image brutes. Par conséquent, la première ligne de la méthode imageLoadComplete() lit la propriété bitmapData de l’occurrence de Bitmap chargée (event.target.content.bitmapData) et la stocke dans la variable d’occurrence de textureMap, qui est utilisée en tant que source des données d’image pour créer l’animation de la lune en rotation. Ce processus est décrit ci-après. Création d’une animation par copie de pixelsUne animation, dans sa définition la plus simple, est l’illusion d’un mouvement ou d’un changement, créée par la modification graduelle d’une image. Cet exemple a pour but de créer l’illusion d’une lune sphérique tournant sur son axe vertical. Cependant, pour les besoins de l’animation, vous pouvez ne pas tenir compte de l’aspect de distorsion sphérique de l’exemple. Examinez l’image chargée et utilisée comme source des données d’image de la lune : Comme vous pouvez le constater, l’image ne représente pas une ou plusieurs sphères ; c’est une photographie rectangulaire de la surface de la lune. La photographie ayant été prise à l’emplacement exact de l’équateur de la lune, les parties supérieures et inférieures de l’image sont donc étirées et déformées. Pour supprimer la distorsion de l’image et lui redonner son aspect sphérique, nous utiliserons un filtre Mappage de déplacement (voir plus bas). Toutefois, l’image source étant un rectangle, il suffit que le code fasse glisser horizontalement la photographie de la surface de la lune pour créer l’illusion d’une sphère en rotation. Observez que l’image contient en fait deux copies juxtaposées de la photographie de la surface de la lune. Cette image représente l’image source dans laquelle des données ont été copiées plusieurs fois pour créer un effet de mouvement. La juxtaposition de deux copies de l’image facilite la création d’un effet de défilement continu. Examinons en détail le processus d’animation afin de mieux le comprendre. Le processus s’applique à deux objets ActionScript distincts. Le premier de ces objets est l’image source chargée qui, dans le code, est représentée par l’occurrence de BitmapData textureMap Comme nous l’avons vu, les données d’image sont insérées dans textureMap dès le chargement de l’image externe à l’aide de ce code : textureMap = event.target.content.bitmapData; Le contenu de textureMap correspond à l’image de la lune rectangulaire. En outre, pour créer la rotation animée, le code utilise l’occurrence de Bitmap sphere, qui représente l’objet d’affichage qui affiche l’image de la lune à l’écran. A l’instar de textureMap, l’objet sphere contient les données d’image initiales de la méthode imageLoadComplete(), ainsi que le stipule le code suivant : sphere = new Bitmap(); sphere.bitmapData = new BitmapData(textureMap.width / 2, textureMap.height); sphere.bitmapData.copyPixels(textureMap, new Rectangle(0, 0, sphere.width, sphere.height), new Point(0, 0)); Comme vous pouvez le constater, sphere est instancié. La hauteur et la largeur de sa propriété bitmapData (les données d’image brutes qui sont affichées par sphere) sont identiques à celles de textureMap. Autrement dit, le contenu de sphere a la même taille qu’une seule photographie de la lune (puisque l’image textureMap contient deux photographies juxtaposées). Des données d’image sont ensuite insérées dans la propriété bitmapData à l’aide de sa méthode copyPixels(). Les paramètres de l’appel de la méthode copyPixels() donnent plusieurs indications :
Représenté visuellement, le code copie les pixels de textureMap mis en évidence ci-dessous et les colle sur sphere. Autrement dit le contenu BitmapData de sphere correspond à la partie de textureMap mise en évidence : Pour rappel, il s’agit seulement de l’état initial de sphere, le contenu de la première image copiée sur sphere. Une fois l’image source chargée et sphere créé, il ne reste plus à la méthode imageLoadComplete() qu’à définir l’animation. L’animation est pilotée par une occurrence de Timer, rotationTimer, créée et lancée par le code suivant : var rotationTimer:Timer = new Timer(15); rotationTimer.addEventListener(TimerEvent.TIMER, rotateMoon); rotationTimer.start(); Le code commence par créer l’occurrence de Timer rotationTimer. Le paramètre passé au constructeur Timer() indique que rotationTimer doit déclencher son événement timer toutes les 15 millisecondes. La méthode addEventListener() qui est appelée ensuite stipule que le déclenchement de l’événement timer (TimerEvent.TIMER) entraîne l’appel de la méthode rotateMoon(). Enfin, l’appel de la méthode start() du timer entraîne le démarrage de celui-ci. De par la définition de rotationTimer, Flash Player appelle la méthode rotateMoon() dans la classe MoonSphere environ toutes les 15 millisecondes, ce qui se traduit par l’animation de la lune. Le code source de la méthode rotateMoon() est le suivant : private function rotateMoon(event:TimerEvent):void { sourceX += 1; if (sourceX > textureMap.width / 2) { sourceX = 0; } sphere.Data.copyPixels(textureMap, new Rectangle(sourceX, 0, sphere.width, sphere.height), new Point(0, 0)); event.updateAfterEvent(); } Ce code effectue trois opérations :
Pour rappel, ce code est appelé toutes les 15 millisecondes. Comment l’emplacement du rectangle source change constamment et que les pixels sont copiés sur sphere, à l’écran, la photographie de la lune représentée par sphere semble glisser continuellement. En d’autres termes, la lune semble tourner sur elle-même continuellement. Définition de l’aspect sphériqueLa lune est bien entendu sphérique , ce n’est pas un rectangle. La photographie rectangulaire de la surface lunaire, qui fait l’objet d’une animation constante, doit donc être convertie en sphère. Cette opération comprend deux étapes : un masque cache tout le contenu excepté une partie circulaire de la photographie et un filtre Mappage de déplacement déforme l’apparence de la photographie, lui donnant un aspect tridimensionnel. Dans un premier temps, un masque circulaire cache entièrement le contenu de l’objet MoonSphere excepté la sphère créée par le filtre. Le code suivant crée le masque, une occurrence de Shape, et l’applique à l’occurrence de MoonSphere : moonMask = new Shape(); moonMask.graphics.beginFill(0); moonMask.graphics.drawCircle(0, 0, radius); this.addChild(moonMask); this.mask = moonMask; Comme MoonSphere est un objet d’affichage (fondé sur la classe Sprite), il est possible d’appliquer directement le masque à l’occurrence de MoonSphere à l’aide de sa propriété mask héritée. ![]() Il ne suffit pas d’occulter des parties de la photographie à l’aide d’un masque circulaire pour créer un effet réaliste de sphère en rotation. En raison de la façon dont la photographie de la surface lunaire a été prise, ses dimensions ne sont pas proportionnelles . les parties supérieures et inférieures de l’image sont déformées et étirées par rapport aux zones équatoriales. Pour déformer l’apparence de la photographie et lui donner un aspect tridimensionnel, nous allons utiliser un filtre Mappage de déplacement. Ce type de filtre permet de déformer une image. En l’occurrence, nous allons déformer la photographie de la lune pour lui donner un aspect plus réaliste, en compressant horizontalement les parties supérieures et inférieures de l’image sans toucher à son milieu. En supposant que le filtre intervienne sur une partie carrée de la photographie, la compression du haut et du bas mais pas du milieu aura pour effet de convertir le carré en cercle. L’animation de cette image déformée a un effet secondaire : la distance en pixels parcourue par le milieu de l’image semble supérieure à celle couverte par les parties supérieure et inférieure, d’où l’impression que le cercle est en fait un objet tridimensionnel (une sphère). Le code suivant permet de créer un filtre Mappage de déplacement appelé displaceFilter : var displaceFilter:DisplacementMapFilter; displaceFilter = new DisplacementMapFilter(fisheyeLens, new Point(radius, 0), BitmapDataChannel.RED, BitmapDataChannel.GREEN, radius, 0); Le premier paramètre, fisheyeLens, est l’image de mappage ; en l’occurrence, un objet BitmapData créé par programmation. La création de cette image est décrite dans Création d’une image bitmap par définition de la valeur des pixels. Les autres paramètres décrivent l’emplacement d’application du filtre au sein de l’image filtrée, les canaux colorimétriques utilisés pour régir l’effet de déplacement et leur impact sur celui-ci. Une fois le filtre Mappage de déplacement créé, il est appliqué à sphere, toujours dans la méthode imageLoadComplete() : sphere.filters = [displaceFilter]; L’image finale, une fois le masque et le filtre appliqués, se présente comme suit : ![]() A chaque cycle du processus d’animation de la lune en rotation, le contenu BitmapData de sphere est remplacé par un nouveau cliché des données d’image source. Il est cependant inutile de réappliquer le filtre à chaque fois car il est appliqué à l’occurrence de Bitmap (l’objet d’affichage) plutôt qu’aux données bitmap (données de pixel brutes). Pour rappel, l’occurrence de Bitmap ne correspond pas aux données bitmap. C’est un objet d’affichage qui affiche ces données à l’écran. Une occurrence de Bitmap peut être assimilée à un projecteur de diapositives, tandis qu’un objet BitmapData serait une diapositive présentée par le biais du projecteur. Il est possible d’appliquer un filtre directement à un objet BitmapData, ce qui reviendrait à dessiner sur une diapositive pour modifier l’image. Vous pouvez aussi appliquer un filtre à tout objet d’affichage, y compris une occurrence de Bitmap, ce qui équivaudrait à placer un filtre devant l’objectif du projecteur pour déformer l’image à l’écran sans modifier la diapositive d’origine. Comme les données bitmap brutes sont accessibles par le biais de la propriété bitmapData d’une occurrence de Bitmap, rien n’empêche de leur appliquer directement le filtre. Dans ce cas, cependant, il est préférable d’appliquer directement le filtre à l’objet d’affichage Bitmap plutôt qu’aux données bitmap. Pour plus d’informations sur l’utilisation du filtre Mappage de déplacement en ActionScript, voir Filtrage des objets d’affichage. Création d’une image bitmap par définition de la valeur des pixelsLe fait qu’un filtre Mappage de déplacement implique en réalité deux images est un facteur important. L’image source est modifiée par le filtre. Dans cet exemple, il s’agit de l’occurrence de Bitmap sphere. L’autre image utilisée par le filtre est appelée l’image de mappage. Elle n’apparaît pas à l’écran. En revanche, la couleur de ses pixels est utilisée en entrée par la fonction de déplacement : la couleur d’un pixel se trouvant à des coordonnées x, y spécifiques détermine le déplacement (changement physique de position) à appliquer au pixel à ces coordonnées x, y dans l’image source. Dans cet exemple, pour utiliser le filtre Mappage de déplacement en vue de créer un effet sphérique, il est donc nécessaire d’utiliser l’image de mappage appropriée, c’est-à-dire une image au fond gris comportant un cercle rempli d’un dégradé d’une seule couleur (rouge) qui passe, horizontalement, du foncé au clair, comme illustré ci-dessous : ![]() Comme une image de mappage et un filtre uniques sont utilisés dans cet exemple, l’image de mappage est créée une seule fois, dans la méthode imageLoadComplete() (autrement dit, à l’issue du chargement de l’image externe). L’image de mappage, fisheyeLens, est créée par appel de la méthode createFisheyeMap() de la classe MoonSphere : var fisheyeLens:BitmapData = createFisheyeMap(radius); Au sein de la méthode createFisheyeMap(), l’image de mappage est dessinée pixel par pixel à l’aide de la méthode setPixel() de la classe BitmapData. Vous trouverez le code complet de la méthode createFisheyeMap() ci-dessous, suivi d’une présentation détaillée de son fonctionnement : private function createFisheyeMap(radius:int):BitmapData { var diameter:int = 2 * radius; var result:BitmapData = new BitmapData(diameter, diameter, false, 0x808080); // Loop through the pixels in the image one by one for (var i:int = 0; i < diameter; i++) { for (var j:int = 0; j < diameter; j++) { // Calculate the x and y distances of this pixel from // the center of the circle (as a percentage of the radius). var pctX:Number = (i - radius) / radius; var pctY:Number = (j - radius) / radius; // Calculate the linear distance of this pixel from // the center of the circle (as a percentage of the radius). var pctDistance:Number = Math.sqrt(pctX * pctX + pctY * pctY); // If the current pixel is inside the circle, // set its color. if (pctDistance < 1) { // Calculate the appropriate color depending on the // distance of this pixel from the center of the circle. var red:int; var green:int; var blue:int; var rgb:uint; red = 128 * (1 + 0.75 * pctX * pctX * pctX / (1 - pctY * pctY)); green = 0; blue = 0; rgb = (red << 16 | green << 8 | blue); // Set the pixel to the calculated color. result.setPixel(i, j, rgb); } } } return result; } En premier lieu, la méthode reçoit un paramètre, radius, qui indique le rayon de l’image circulaire à créer. Le code crée ensuite l’objet BitmapData sur lequel sera tracé le cercle. Cet objet, appelé result, est renvoyé comme valeur résultante de la méthode. Comme illustré par l’extrait de code ci-dessous, la largeur et la hauteur de l’occurrence de BitmapData result créée sont égales au diamètre du cercle. En outre, cette occurrence n’a pas de transparence (le troisième paramètre correspond à false) et elle est pré-remplie par la couleur 0x808080 (gris moyen) : var result:BitmapData = new BitmapData(diameter, diameter, false, 0x808080); Le code utilise ensuite deux boucles pour itérer par-dessus chaque pixel de l’image. La boucle extérieure parcourt de droite à gauche chaque colonne de l’image (la variable i représentant la position horizontale du pixel manipulé), alors que la boucle intérieure intervient sur chaque pixel de la colonne actuelle, de bas en haut (la variable j représentant la position verticale du pixel actuel). Le code des boucles (le contenu de la boucle intérieure étant omis) est illustré ci-dessous : for (var i:int = 0; i < diameter; i++) { for (var j:int = 0; j < diameter; j++) { ... } } Au fur et à mesure de la manipulation des pixels par les boucles, une valeur est calculée à chacun d’eux (la valeur colorimétrique de ce pixel dans l’image de mappage). Ce processus comporte quatre étapes :
|
![]() |