Expériences


Tutoriel : Présentation “PowerPoint” en ActionScript

Posté dans Programmation par Enisséo le 19 février 2008

Introduction

Contrairement à ce que le titre peut signifier, ce tutoriel va se limiter à une partie moins évidente pour l’affichage d’un diaporama (slideshow) en langage ActionScript : la présentation des diapositives (slides) sur plusieurs lignes et plusieurs colonnes de manière intelligente et dynamique. Le résultat de ce tutoriel est la disposition d’un ensemble d’images de manière à ce qu’elles occupent le maximum d’espace, en fonction de la taille de la fenêtre principale et de la taille de chaque image.

Ce tutoriel est destiné à des programmeurs ayant tout de même des notions d’ActionScript - notamment au niveau de la manipulation d’objets MovieClip, des observateurs (listeners) et du chargement de clips (typiquement MovieClipLoader) - et de programmation orientée objet. Je ne m’attarderai que sur les parties un peu plus orientées “algorithmes”.

Voici le résultat auquel nous parviendrons à la fin de ce tutoriel :

Résultat 1

Résultat 2

Il s’agit du même diaporama, mais avec une taille de fenêtre différente. Le calcul du nombre de lignes, de colonnes et de la taille de chaque slide ainsi que leur positionnement est géré dynamiquement.

Il est enfin à noter que j’ai réalisé l’application à l’aide du plugin Eclipse ASDT (ActionScript Development Tools), disponible à l’adresse http://sourceforge.net/projects/aseclipseplugin/, mais que le code est utilisable avec d’autres outils.

Préparation et configuration

Nous allons écrire une unique classe SlideShow qui contiendra toute la logique de calcul et de l’affichage des diapositives.

class SlideShow {
...
}

Le programme se déroule en 2 phases. La première consiste à récupérer les images qui vont être utilisées dans le diaporama. La deuxième est l’affichage optimal de ces images, selon la configuration de la fenêtre (largeur et hauteur), le nombre de diapositives et d’autres variables. Cette dernière phase doit intervenir à chaque modification de la configuration, en l’occurrence quand un slide est rajouté ou quand la taille de la fenêtre change.

Chacune de ces phases fait l’objet d’une méthode de la classe SlideShow :

private function loadSlides(slideDirectory: String):Void {
...
}   
 
private function updateSlides():Void {
...
}

Une variable désigne l’ensemble des diapositives. Elle est utilisée par chacune des méthodes précédentes pour, dans le cas de loadSlides(), charger les images, et dans le cas d’updateSlides(), les afficher dans la fenêtre de l’application :

// Contient les slides chargés (MovieClip)
var slides:Array;

On rajoute une méthode pour initialiser la configuration de l’environnement et un constructeur pour lancer les calculs et l’affichage :

public function SlideShow() {
	slides = new Array();   
 
	initEnv();
	// Diapositives dans le dossier "slides/"
	loadSlides("slides");
}   
 
// Initialisation de l'environnement
private function initEnv():Void {
	// Permet de gérer le redimensionnement de la fenêtre manuellement
	Stage.scaleMode = "noScale";   
 
	// Alignement des objets vis-à-vis de la fenêtre principale
	Stage.align = "TL";   
 
	// Instanciation d'un observateur de changement de taille
	// de la fenêtre principale
	var refreshStageListener:Object = new Object();
	Stage.addListener(refreshStageListener);
	// Indication de l'élément SlideShow
	refreshStageListener.slideshow = this;   
 
	// Mise à jour de l'affichage des slides en cas de redimensionnement
	refreshStageListener.onResize = function() {
		this.slideshow.updateSlides();
	};
}

Le constructeur initialise le tableau slides, appelle la méthode de configuration de l’environnement puis la méthode de chargement des images du dossier “slides/”.

Dans l’initialisation de l’environnement, on spécifie tout d’abord que l’application, désignée par la variable globale Stage ne gère pas son redimensionnement seule ("noScale"). Cela nous autorise à créer un observateur du redimensionnement, refreshStageListener, qui appelle la méthode d’actualisation de l’affichage à chaque fois que l’application est redimensionnée (refreshStageListener.onResize). Enfin, on spécifie un alignement de type "TL" (Top Left) pour les objets, ce qui nous permet de gérer manuellement le centrage des objets dans la fenêtre.

Dans ce tutoriel, seules deux variables “utilisateur” sont exploitées : la distance à respecter entre chaque diapositive (espace entre les lignes et les colonnes, et avec le bord de la fenêtre) et le ratio des diapositives, c’est-à-dire le rapport largeur/hauteur. Cette dernière variable rend possible l’affichage à taille maximale des slides sur le support désiré (écran, vidéoprojecteur…). Nous créons 2 attributs dans la classe SlideShow :

// Espace entre les slides
var SLIDE_SPACE:Number = 20;
// Ratio de taille (largeur / hauteur) des slides
// (ratio type Powerpoint par défaut)
var SLIDE_RATIO:Number = 4 / 3;

Chargement des images

Il n’existe pas à ma connaissance de manière simple pour récupérer une liste de fichiers d’un dossier local en ActionScript. À la place, nous effectuons une recherche (très sale) qui suppose l’existence d’un fichier avec un nom particulier. En l’occurrence, nous essayons de charger tous les fichiers appelés “Diapositive###.PNG” d’un dossier donné (avec ### un numéro) à l’aide d’une simple boucle sur une variable entière. Le choix de ce format de nom de fichier n’est pas anodin : en effet, l’exportation d’un diaporama Microsoft PowerPoint en format PNG a pour conséquence la création d’un ensemble d’images PNG dont le nom est “Diapositive1.PNG”, “Diapositive2.PNG”, … Nous pouvons donc passer très facilement d’un diaporama PowerPoint classique à un affichage par notre programme ActionScript, et ainsi créer le diaporama à l’aide de PowerPoint mais l’afficher grâce à notre application.

Chaque image est chargée à l’aide d’un objet MovieClipLoader et est placée à l’intérieur d’un objet de type MovieClip qui représente le slide. Il y a une différence entre le slide et l’image qu’il contient, distinction justifiée car permettant d’ajuster la taille de l’image à la taille du slide.

// Chargement de l'ensemble des slides dans un tableau
private function loadSlides(slideDirectory: String):Void {
	// "Chargeur" des images
	var imageLoader:MovieClipLoader = new MovieClipLoader();   
 
	// Instanciation d'un observateur de chargement des images des slides
	var loadImageListener:Object = new Object();
	imageLoader.addListener(loadImageListener);
	// Indication de l'élément SlideShow
	loadImageListener.slideshow = this;   
 
	// Enregistrement au chargement de l'image
	loadImageListener.onLoadInit = function(image:MovieClip) {
		// Récupération du slide contenant l'image
		var slide:MovieClip = image._parent;   
 
		// Sauvegarde du ratio initial de l'image
		image.ratio = image._width / image._height;   
 
		// Sauvegarde du slide dans le tableau, à la bonne position
		this.slideshow.slides[slide.slidePosition - 1] = slide;   
 
		// Redimensionnement de l'image pour rentrer dans le slide sans déformation
		if (image.ratio < slide._width / slide._height) {
			image._width = slide._width  * image.ratio;
			image._height = slide._width;
		} else {
			image._width = slide._height;
			image._height = slide._height / image.ratio;
		}   
 
		// Mise à jour de l'affichage
		this.slideshow.updateSlides();
	};   
 
	// Récupération des images
	for (var i:Number = 100; i > 0; i--) {
		// Url de l'image
		var urlImage:String = slideDirectory + "/Diapositive" + i + ".PNG";   
 
		// Création du slide contenant l'image
		var slide:MovieClip = _root.createEmptyMovieClip("slide" + i, _root.getNextHighestDepth());
		// Spécification de la position du slide
		slide.slidePosition = i;   
 
		// Création de l'image par chargement par un objet MovieClipLoader
		slide.image = slide.createEmptyMovieClip("slide" + i + "_image", slide.getNextHighestDepth());
		imageLoader.loadClip(urlImage, slide.image);
	}
}

On initialise une variable imageLoader qui sert à charger les images des diapositives. Sur cette variable on enregistre un observateur, loadImageListener, qui remplit le tableau slides (attribut de la classe SlideShow) à chaque image chargée (méthode onLoadInit()).

La méthode onLoadInit(), appelée dès que le chargement est terminé, s’occupe de stocker dans le tableau slides l’objet MovieClip qui représente la diapositive contenant l’image qui vient d’être chargée. L’image est ensuite redimensionnée pour rentrer dans le cadre de son slide, sans déformation. L’algorithme est relativement simple : si le ratio largeur/hauteur de l’image est inférieur au ratio de la diapositive, cela signifie que, si on ramène l’image à la taille de la diapositive, sa largeur est inférieure à celle de la diapositive. On est donc sûr qu’à même hauteur l’image rentre dans la diapositive. On redéfinit la hauteur comme étant égale à celle de la diapositive et on réajuste la largeur de l’image en conservant le ratio largeur/hauteur pour qu’il n’y ait pas de déformation. De manière opposée, si le ratio de l’image est supérieur au ratio de la diapositive, on réajuste la taille de l’image.

Pour charger les images, on utilise une boucle qui construit un objet slide pour chaque image et qui charge cette dernière grâce à l’url déduite (slideDirectory + "/Diapositive" + i + ".PNG"). La spécification de la position du slide par la propriété slidePosition permet de compléter le tableau slides en conservant le bon ordre.

Affichage des diapositives

Cette méthode a pour objectif de positionner et dimensionner les diapositives selon la configuration actuelle, soit : la taille de la fenêtre, le nombre de slides et les variables utilisateurs (SLIDE_RATIO et SLIDE_SPACE).

Pour déterminer sur combien de colonnes et de lignes doivent s’afficher les diapositives, on utilise un algorithme qui s’appuie sur la recherche de l’aire maximale d’une diapositive : on parcourt toutes les configurations (nombre de colonnes, nombre de lignes) possibles et on choisit celle qui implique des slides d’aire maximale. Pour cela, on itère sur le nombre de colonnes possible - soit entre 1 et le nombre de diapositives - et on calcule le nombre de lignes nécessaire pour afficher l’ensemble des diapositives. Ce nombre est obtenu en divisant le nombre de slides total par le nombre de colonnes, et en arrondissant à l’entier supérieur. On peut ainsi déterminer l’espace disponible pour chaque diapositive suivant la taille de la fenêtre, Stage.width et Stage.height, et l’espace entre chaque diapositive SLIDE_SPACE. On calcule la taille de chaque slide en respectant le ratio (SLIDE_RATIO) donné par l’utilisateur - on utilise pour cela l’algorithme expliqué dans la partie précédente - et on déduit donc l’aire des diapositives. Si cette aire est supérieure à l’aire maximale courante, alors les données correspondantes (taille et nombre de lignes et colonnes) sont stockées et l’aire maximale mise à jour.

// Affichage des slides selon leur taille, nombre, ...
private function updateSlides():Void {
	// Récupération de l'espace "utilisable" dans la fenêtre
	var stageWidth:Number = Stage.width - SLIDE_SPACE * 2;
	var stageHeight:Number = Stage.height - SLIDE_SPACE * 2;   
 
	// Aire maximum trouvée
	var areaMax:Number = 0;
	// Nombre de colonnes de slides correspondant à l'aire maximum
	var nbCols:Number = slides.length;
	// Nombre de lignes de slides pour l'aire maximum
	var nbRows:Number = 1;
	// Taille correspondante pour les slides
	var slideWidth:Number = 0;
	var slideHeight:Number = 0;   
 
	// Boucle de "contrainte" :
	// On doit trouver quelle est la configuration en termes
	// de lignes et de colonnes pour laquelle l'aire des slides
	// est maximum (plus "grand" possible).
	// Pour tous les nombres de slides possibles dans une colonne [1 .. nombre de slides]
	for (var col:Number = this.slides.length; col > 0; col--) {
		// Détermination du nombre de lignes correspondant
		var row:Number = Math.ceil(slides.length / col);   
 
		// Calcul de la taille d'un slide sous cette configuration
		var sWidth:Number = Math.max(0, (stageWidth - ((col - 1) * SLIDE_SPACE)) / col);
		var sHeight:Number = Math.max(0, (stageHeight - ((row - 1) * SLIDE_SPACE)) / row);
		// Réajustement de la taille selon le ratio de taille des slides
		if (sWidth / sHeight > SLIDE_RATIO) {
			sWidth = sHeight * SLIDE_RATIO;
			sHeight = sHeight;
		} else {
			sWidth = sWidth;
			sHeight = sWidth / SLIDE_RATIO;
		}   
 
		// Calcul de l'aire correspondante
		var area:Number = sWidth * sHeight;
		// Détermination de l'aire maximum et sauvegarde de
		// la configuration (lignes, colonnes, taille) associée
		if (area > areaMax)
		{
			areaMax = area;
			nbCols = col;
			nbRows = row;
			slideWidth = sWidth;
			slideHeight = sHeight;
		}
	}

Une fois ces données disponibles, on peut parcourir le tableau des slides en redimensionnant et positionnant correctement chaque diapositive selon la ligne et la colonne dans laquelle elle doit se situer. On introduit en plus un décalage en X et Y (offsetX et offsetY) qui correspond au recentrage de l’ensemble des diapositives dans la fenêtre de l’application :

	// Décalages à effectuer à chaque slide pour obtenir un ensemble centré
	// dans la fenêtre principale
	var offsetX:Number = SLIDE_SPACE + (Stage.width - nbCols * (slideWidth + SLIDE_SPACE) - SLIDE_SPACE) / 2;
	var offsetY:Number = SLIDE_SPACE + (Stage.height - nbRows * (slideHeight + SLIDE_SPACE) - SLIDE_SPACE) / 2;   
 
	// Affichage des slides selon le nombre de lignes et colonnes
	for (var i:Number = slides.length - 1; i >= 0; i--) {
		// Récupération du slide à afficher
		var slide:MovieClip = slides[i];   
 
		// Calcul de la ligne et colonne du slide selon sa position dans la liste
		var currCol:Number = i % nbCols;
		var currRow:Number = Math.floor(i / nbCols);   
 
		// Spécification de la taille
		slide._width = slideWidth;
		slide._height = slideHeight;   
 
		// Spécification de la position
		slide._x = offsetX + currCol * (slideWidth + SLIDE_SPACE);
		slide._y = offsetY + currRow * (slideHeight + SLIDE_SPACE);
	}
}

Conclusion

Pour un bel effet graphique, il est relativement aisé d’ajouter une légère ombre autour des diapositives ainsi que des animations de repositionnement et redimensionnement. De plus, il est évidemment indispensable de rajouter un mode “plein écran” où les diapositives sont affichées sur l’ensemble de la fenêtre et où il est possible de naviguer de l’une à l’autre, ce qui est l’intérêt d’un vrai diaporama.

Le fichier du tutoriel peut être téléchargé ici. Une version animée est visible à cette adresse, et le fichier source correspondant est téléchargeable .

S’il vous prend l’envie d’essayer ce tutoriel et si vous ajoutez d’autres fonctionnalités, n’hésitez pas à poster dans les commentaires des liens vers vos applications de diaporamas.

Cet article peut être modifié ou approfondi ultérieurement. Il ne se veut pas complet et n’est pas exempt d’erreurs. N’hésitez pas à les signaler.

Vous pouvez m’adresser vos remarques ou suggestions à enisseo@hotmail.com. Je ne manquerai pas de vous répondre.

Laisser une réponse