Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Créer un diaporama pas à pas - 10 - Comment créer une barre présentant des images miniatures

Compatible ActionScript 2. Cliquer pour en savoir plus sur les compatibilités.Par lilive (Olivier Tarasse), le 19 septembre 2008

Objectif

Cette série de tutoriaux, à l'usage des débutants en ActionScript 2, présente des notions fondamentales de programmation par le biais de la réalisation d'un diaporama en ligne.

Un diaporama intègre souvent une barre de miniatures, c'est-à-dire affiche en petit les images de l'album, pour permettre à l'utilisateur d'y choisir d'un clic l'image qu'il voudrait voir s'afficher en grand.

Une telle barre de miniatures peut également exister dans d'autres programmes qu'un diaporama. Cet article se penche sur la réalisation d'une barre de miniatures, indépendamment du programme du diaporama. Aussi, même un lecteur qui ne suivrait pas cette série de tutoriaux peut sans problème l'aborder. Pour le diaporama, nous verrons par la suite comment l'y intégrer.

Voici la barre de miniatures que nous allons construire au long de cet article:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.


I - Fabriquer les fichiers miniatures

Pour afficher des miniatures nous avons le choix: Nous pouvons demander au programme de charger les images de grandes tailles, de les copier et de les réduire, ou bien nous pouvons avoir des fichiers d'images qui sont déjà miniaturisés et charger ces fichiers.

La première de ces méthode a pour avantage de ne pas demander à FlashPlayer de charger des fichiers supplémentaires, et génère les miniatures à partir des images grands formats qui sont chargées. L'inconvénient de ceci et qu'il faudra attendre que toutes les images soient chargées pour disposer d'une barre de miniatures complète.

C'est pourquoi nous allons utiliser la seconde méthode, qui a pour avantage que nous pourrons charger et afficher rapidement toutes les miniatures, pour pouvoir proposer le plus vite possible à l'utilisateur les miniatures de toutes les images de l'album. En effet il sera bien plus rapide de charger de petits fichiers d'images de quelques kilo-octets, plutôt que de charger les images grands formats et de les réduire ensuite à la bonne taille.

Néanmoins, comme la question de savoir copier et réduire des images sans les recharger est intéressante, elle fera l'objet du chapitre VI de cet article, dont la lecture est facultative pour ceux qui veulent s'en tenir à la réalisation de notre diaporama.

La première chose à faire est donc de fabriquer les fichiers d'images des miniatures. Cela vous savez déjà le faire à l'aide d'un logiciel ou d'un autre, puisque lors de la première partie de ce tutoriel nous avons eu besoin de générer des fichiers d'images de 400×300 pixels. Cette fois, créons des fichiers plus petits, d'une taille disons de 60×45 pixels. Cette taille correspondra aux images de l'album qui sont les plus grandes. Si vous avez des images de différents formats, comme nous pouvons le faire depuis l'article précédent, faites en sorte que les images miniatures aient soit une largeur de 60 pixels, soit une hauteur de 45 pixels, soit les deux.

Voici par exemple les miniatures que j'utiliserais:

Mettez ces images dans un répertoire nommé miniatures, placé à côté du répertoire images. Les noms des fichiers miniatures doivent être les mêmes que ceux des grandes images, c'est-à-dire image0.jpg, image1.jpg, image2.jpg, etc.

Si vous le désirez, vous pouvez choisir une autre taille pour les miniatures, il vous sera facile d'adapter ce tutoriel.


II - Charger les miniatures

Le programme du diaporama tel que nous l'avons obtenu à la fin de la partie précédente 1) commence à être assez volumineux. Nous allons, pour simplifier les choses, travailler sur un nouveau document vierge, pour nous exercer à créer une barre de miniatures et à la manipuler. Nous ne nous occuperons d'intégrer les miniatures au diaporama que quand nous serons au point.

Le chargement des miniatures ne va pas nous poser de grands problèmes, car cela va ressembler de près au chargement des images dans un tableau de MovieClips, comme nous l'avons déjà fait.

Entrez donc ce code sur la première image de votre nouveau document:

var nbrImages:Number = 16; // Ici le nombre d'images que vous désirez
 
// Création d'un MovieClip regroupant les miniatures:
var miniaturesMC:MovieClip = this.createEmptyMovieClip("miniatures", this.getNextHighestDepth());
// Tableau de MovieClip pour recevoir chaque miniature:
var minisMC:Array = new Array();
// Création des MovieClips pour chaque miniature:
for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
	minisMC[iMini] = miniaturesMC.createEmptyMovieClip("mini" + iMini, iMini);
}
 
// Mise en place du MovieClipLoader et de son écouteur
var mcl:MovieClipLoader = new MovieClipLoader();
var mclListener:Object = new Object();
mclListener.onLoadInit = onMiniLoadInit;
mcl.addListener(mclListener);
 
function chargerMini() {
	// Lance le chargement de la miniature numéro miniToLoad dans le bon MovieClip
	mcl.loadClip("miniatures/image" + miniToLoad + ".jpg", minisMC[miniToLoad]);
}
function onMiniLoadInit(mc:MovieClip) {
	// A la fin du chargement d'une miniature, lancer le chargement de la suivante
	miniToLoad++;
	if (miniToLoad < nbrImages) chargerMini();
}
 
// Lancement du chargement:
var miniToLoad:Number = 0;  // Index de la miniature à charger
chargerMini();

Un MovieClip nommé miniatures est créé. Ce MovieClip va contenir toutes les miniatures. La boucle for crée autant de MovieClips qu'il y a d'images, à l'intérieur de ce MovieClip, et les références à ces MovieClips sont stockées dans le tableau minisMC. Puis les images sont successivement chargées dans ces MovieClips à l'aide d'un MovieClipLoader.

Tout ceci reprend quasiment à l'identique la procédure de chargement des images vue ici. Vous pouvez exécuter ce programme, qui va charger et afficher toutes les miniatures les unes sur les autres dans le coin supérieur gauche de la scène.


III - Placer les miniatures

Voyons maintenant comment nous y prendre pour placer les MovieClips des miniatures les uns à la suite des autres, de gauche à droite, pour obtenir une “barre” faite avec les miniatures.

Donnez à votre scène une taille suffisamment grande pour pouvoir afficher la barre de miniatures. Dans les exemples affichés sur la page, j'utilise une largeur de 400 pixels et une hauteur de 45. Il n'est pas nécessaire que toutes les miniatures puissent “rentrer” en largeur dans la scène, car nous nous occuperons plus tard de voir comment il est possible de les faire défiler horizontalement.

1- Un premier placement peu convaincant

function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
		minisMC[iMini]._x = xMiniature;
		xMiniature += minisMC[iMini]._width;
	}
}

Cette fonction parcours le tableau des miniatures. La variable xMiniature indique l'abscisse (la coordonnée x) où placer la prochaine miniature. Au début de la boucle xMiniature vaut 0 et la première miniature sera placée au point (0,0) dans le MovieClip miniaturesMC. Puis xMiniature augmente de la largeur de la miniature qui vient d'être placée (60 dans l'exemple), et la boucle reprend pour placer la seconde miniature au point (60,0). xMiniature est de nouveau augmentée de la largeur de cette miniature (29) et prend la valeur 89. La boucle revient au début et entreprend de placer la troisième miniature, etc.

Il suffit d'appeler cette fonction une fois que toutes les miniatures sont chargées pour les placer correctement. Ajoutez la fonction initialiserBarreMiniatures à votre code, puis modifiez la fonction onMiniLoadInit:

function onMiniLoadInit(mc:MovieClip) {
	miniToLoad++;
	if (miniToLoad < nbrImages) chargerMini();
	else initialiserBarreMiniatures();
}

Et le tour et joué (lisez cette note 2) ):

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

S'il y a suffisamment de miniatures, la barre pourra avoir une largeur supérieure à celle de la scène, et les miniatures les plus à droite ne seront pas visibles.

2- Ecarter et centrer les miniatures

Le premier résultat obtenu laisse un peu à désirer. Pour rendre plus lisible la barre de miniatures, nous allons les espacer légèrement, et centrer verticalement celles qui sont moins hautes que les autres.

Pour le centrage vertical, nous aurons besoin de calculer l'ordonnée (la coordonnée y) de chaque miniature, à partir de la hauteur prévue pour la barre. Tant qu'à y être, prévoyons l'avenir, et déclarons un nouvel objet Rectangle pour y stocker toutes les coordonnées et dimensions que nous voulons donner à la barre:.

import flash.geom.Rectangle;
var zoneMinis:Rectangle = new Rectangle(0, 0, 400, 45);

Puis modifions le code de initialiserBarreMiniatures:

function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
		minisMC[iMini]._x = xMiniature;
		// Ajouter 5 pour espacer les miniatures:
		xMiniature += minisMC[iMini]._width + 5;
		// Centrer verticalement la miniature si besoin:
		if (minisMC[iMini]._height < zoneMinis.height)
			minisMC[iMini]._y = zoneMinis.height / 2 - minisMC[iMini]._height / 2; 
	}
}

Le procédé de calcul pour centrer verticalement les vignettes est le même que celui utilisé quand nous avons centré les images à la partie précédente.

Ce qui nous donne 3) :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

C'est mieux!

3- Ajouter un fond aux miniatures

Une autre amélioration visuelle peut être d'ajouter un fond aux miniatures qui sont moins hautes que les autres, pour rendre l'ensemble un peu plus homogène, ce qui donnera ce résultat:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Pour cela, nous allons dessiner un rectangle derrière les miniatures les moins hautes. Pour pouvoir dessiner ce rectangle en arrière plan, nous avons besoin d'un MovieClip qui soit placé derrière le MovieClip de la miniature. Nous allons modifier le code pour faire en sorte que chaque MovieClip de miniature minisMC[…] contienne lui-même deux MovieClips: un nommé fond qui recevra le rectangle de fond, et un nommé image qui contiendra l'image:

// Création des MovieClips pour chaque miniature:
for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
	minisMC[iMini] = miniaturesMC.createEmptyMovieClip("mini" + iMini, iMini);
	minisMC[iMini].createEmptyMovieClip("fond", 0);
	minisMC[iMini].createEmptyMovieClip("image", 1);
}

Si nous résumons:

  • Toutes les miniatures sont dans un MovieClip miniaturesMC créé sur la scène
  • Chaque miniature d'index iMini est un MovieClip minisMC[iMini] créé dans miniaturesMC
  • Chaque MovieClip minisMC[iMini] contient deux MovieClips: un MovieClip fond en profondeur 0 et une MovieClip image en profondeur 1

En image cela donne:

Pour faire référence au MovieClip fond de minisMC[0] nous pouvons noter minisMC[0].fond, et pour le MovieClip image cela donne minisMC[0].image. Remarquez que cette fois, nous n'introduirons pas de nouveaux tableaux de MovieClips pour y stocker ces référence, pour ne pas trop alourdir le code. Cela reste un choix personnel à chaque programmeur.

Nous devons maintenant modifier la fonction chargerMini pour charger l'image de la miniature dans minisMC[miniToLoad].image et non plus dans minisMC[miniToLoad]:

function chargerMini() {
	// Lance le chargement de la miniature numéro miniToLoad dans le bon MovieClip
	mcl.loadClip("miniatures/image" + miniToLoad + ".jpg", minisMC[miniToLoad].image);
}

Puis nous dessinons un rectangle derrière les vignettes qui ne prennent pas toute la hauteur de la barre en utilisant la fonction drawFilledRectangle :

function drawFilledRectangle(
	mc:MovieClip,
	x:Number, y:Number, width:Number, height:Number,
	rgb:Number, alpha:Number
) {
	mc.beginFill(rgb, alpha);
	mc.moveTo(x, y);
	mc.lineTo(x + width, y);
	mc.lineTo(x + width, y + height);
	mc.lineTo(x, y + height);
	mc.endFill();
}
function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
		// Toute la miniature est placée horizontalement:
		minisMC[iMini]._x = xMiniature;
		xMiniature += minisMC[iMini]._width + 5;
		if (minisMC[iMini]._height < zoneMinis.height) {
			// Mais seul le MovieClip "image" est centré verticalement:
			minisMC[iMini].image._y = zoneMinis.height / 2 - minisMC[iMini]._height / 2;
			// Ce qui permet que le rectangle de fond soit bien placé:
			drawFilledRectangle(minisMC[iMini].fond, 0, 0, minisMC[iMini]._width,
			zoneMinis.height, 0, 50);
		}
	}
}

Et voilà, ce nouveau programme donne le résultat attendu:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Si vous êtes attentif à l'optimisation du code, vous aurez peut-être remarqué qu'il n'est pas utile de créer un MovieClip fond pour chaque miniature, puisque seules les miniatures moins hautes en ont besoin. Vous avez raison. Maintenant que le principe a été présenté, nous pouvons optimiser la chose:

for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
	minisMC[iMini] = miniaturesMC.createEmptyMovieClip("mini" + iMini, iMini);
	// Le MovieClip "fond" n'est plus créé ici
	minisMC[iMini].createEmptyMovieClip("image", 1);
}
function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
		minisMC[iMini]._x = xMiniature;
		xMiniature += minisMC[iMini]._width + 5;
		if (minisMC[iMini]._height < zoneMinis.height) {
			minisMC[iMini].image._y = zoneMinis.height / 2 - minisMC[iMini]._height / 2;
			// Mais ici:
			minisMC[iMini].createEmptyMovieClip("fond", 0);
			drawFilledRectangle(minisMC[iMini].fond, 0, 0, minisMC[iMini]._width, zoneMinis.height, 0, 50);
		}
	}
}

Le résultat est le même, mais en épargnant la création de MovieClips qui resteraient vierges et ne serviraient à rien.

Les miniatures sont donc mises en place dans la barre. Nous allons maintenant nous occuper de la rendre interactive.


IV - Rendre les miniatures interactives

Première chose à faire, permettre aux miniatures de prendre en compte les clics de l'utilisateur. Ceci nous permettra de demander au diaporama d'afficher en grand l'image correspondante.

1- Restons classiques

Comment faire?

function onMiniPress() {
	trace("La miniature est cliquée");
}
function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
		// Placer la miniature et dessiner le fond
		minisMC[iMini]._x = xMiniature;
		xMiniature += minisMC[iMini]._width + 5;
		if (minisMC[iMini]._height < zoneMinis.height) {
			minisMC[iMini].image._y = zoneMinis.height / 2 - minisMC[iMini]._height / 2;
			minisMC[iMini].createEmptyMovieClip("fond", 0);
			drawFilledRectangle(minisMC[iMini].fond, 0, 0, minisMC[iMini]._width,
			zoneMinis.height, 0, 50);
		}
 
		// Rendre la miniature cliquable
		minisMC[iMini].onPress = onMiniPress;
	}
}

C'est très simple, il suffit, comme nous l'avons déjà fait plusieurs fois dans cette série de tutoriaux, d'affecter une fonction au gestionnaire d'évènement onPress de chaque miniature. Maintenant, quand le curseur de la souris survole une miniature, FlashPlayer affiche une petite main pour indiquer que cet élément est cliquable. Le clic déclenche l'exécution de la function onMiniPress, et “La miniature est cliquée” s'affiche dans la fenêtre de sortie.

2- Récupérer l'index de la miniature

Quand nous intégrerons la barre de miniatures dans un projet plus vaste, comme celui du diaporama, nous aurons vraisemblablement besoin de pouvoir récupérer, dans onMiniPress, l'index de la miniature qui a été cliquée. Par exemple pour pouvoir demander l'affichage de l'image grand format correspondante.

Ce que nous pouvons connaitre facilement dans onMiniPress, c'est le MovieClip qui a été cliqué. En effet, à l'intérieur de la fonction onMiniPress, le mot clé this fera référence à ce MovieClip, comme nous l'avons vu ici.

L'idéal serait que ce MovieClip possède une propriété index, comme cela this.index nous permettrait de connaître le numéro de la miniature. Or nous savons comment ajouter une propriété à un MovieClip. Il ne reste plus qu'à le faire 4) :

function onMiniPress() {
	trace("La miniature " + this.index + " est cliquée");
}
function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
		// Placer la miniature et dessiner le fond:
		(pas de changement)
		// Rendre la miniature cliquable:
		minisMC[iMini].index = iMini; // Ajouter une propriété index
		minisMC[iMini].onPress = onMiniPress;
	}
}

3- Encadrer la miniature active

Maintenant, voyons comment nous y prendre pour encadrer la dernière miniature qui a été cliquée, pour la mettre en évidence.

Pour cela nous allons doter chaque miniature minisMC[…] d'un nouveau MovieClip, au-dessus de l'image, où nous dessinerons un cadre à l'aide de la fonction drawRectangle. Le cadre de chaque miniature sera rendu invisible aussitôt dessiné 5) :

// Ajout de la fonction de dessin du pourtour d'un rectangle
function drawRectangle(
	mc:MovieClip,
	x:Number, y:Number, width:Number, height:Number,
	thickness:Number, rgb:Number, alpha:Number
) {
	mc.lineStyle(thickness, rgb, alpha, true, "normal", "none", "miter");
	mc.moveTo(x, y);
	mc.lineTo(x + width, y);
	mc.lineTo(x + width, y + height);
	mc.lineTo(x, y + height);
	mc.lineTo(x, y);
}
// Création des MovieClips pour chaque miniature:
for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
	minisMC[iMini] = miniaturesMC.createEmptyMovieClip("mini" + iMini, iMini);
	minisMC[iMini].createEmptyMovieClip("image", 1);
	// Création d'un nouveau MovieClip pour le dessin du cadre:
	minisMC[iMini].createEmptyMovieClip("cadre", 2);
}
function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
		// Placer la miniature et dessiner le fond:
		(pas de changement)
		// Dessiner le cadre et le rendre invisible:
		drawRectangle(
			minisMC[iMini].cadre,
			0.5, 0.5, minisMC[iMini]._width - 1, minisMC[iMini]._height - 1,
			1, 0xffffff, 100
		);
		minisMC[iMini].cadre._visible = false;
		// Rendre la miniature cliquable:
		(pas de changement)
	}
}

Il nous reste à écrire une fonction qui fait apparaître le cadre désiré et rend invisibles tous les autres, et à appeler cette fonction dans onMiniPress:

function encadrerMini(iCadre:Number) {
	for (var iMini:Number = 0; iMini < nbrImages; iMini++)
		minisMC[iMini].cadre._visible = false;
	minisMC[iCadre].cadre._visible = true;
}
function onMiniPress() {
	encadrerMini(this.index);
}

Et le tour est joué:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

4- drawRectangle

L'appel de la fonction drawRectangle nécessite un petit commentaire:

drawRectangle(
	minisMC[iMini].cadre,
	0.5, 0.5, minisMC[iMini]._width - 1, minisMC[iMini]._height - 1,
	1, 0xffffff, 100
);

Pourquoi ces 0.5 et ces - 1 ?
Voyez la figure suivante:

La ligne bleue est une ligne tracée du point (11,26) au point (15,26), avec une épaisseur de 1 pixel.
La ligne rouge est une ligne tracée du point (11.5,28.5) au point (15.5,28.5), avec une épaisseur de 1 pixel.

En ce qui concerne le cadre d'une miniature, nous ne voulons pas que le rectangle soit tracé “à cheval” sur la bordure de la miniature, comme se serait le cas si nous passions à drawRectangle des coordonnées entières, comme pour la ligne bleue.
Nous voulons que le rectangle soit tracé sur la bordure “intérieure” de la miniature, et recouvre exactement les pixels de cette bordure. C'est pour cela que nous devons ajuster de 0.5 pixel les coordonnées passées à drawRectangle, pour produire des lignes du type de la ligne rouge.

A ce stade, c'est vraiment un détail, mais nous verrons plus tard pourquoi il fallait s'y attarder.

Vous avez peut-être également remarqué un petit changement dans la fonction drawRectangle elle-même, par rapport à celle que nous avions écrite jusque-là. Il s'agit des paramètres passés à lineStyle:

Si nous nous en tenons à l'ancienne version de drawRectangle, les rectangles dessinés auront des coins légèrement arrondis. Comme nous voulons dessiner un cadre qui recouvre très précisément la bordure des miniatures, nous ajoutons à lineStyle les paramètres permettant d'obtenir des coins carrés (voir l'Aide de Flash > Référence du langage ActionScript 2.0 > Classes ActionScript > MovieClip > lineStyle).


V - Eclairer une miniature - La propriété blendMode

Cherchons maintenant un moyen de modifier l'aspect de la miniature qui est sous le curseur de la souris. Je vous propose de réaliser l'effet suivant:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

1- A la découverte de blendMode

Pour arriver à ce résultat, nous avons besoin d'apprendre à utiliser la propriété blendMode d'un MovieClip. Cette propriété permet de modifier la façon dont les pixels d'un MovieClip se combinent avec les pixels des MovieClips qui sont placés en dessous de lui (en terme de profondeur). Par défaut, les pixels d'un MovieClip apparaissent normalement par-dessus les pixels des MovieClips inférieurs. Mais blendMode permet de modifier ce comportement. On retrouve ici les possibilités offertes par des logiciels graphiques comme Photoshop ou The Gimp, qui permettent des modes de fusion différents entre les calques d'une image.

Pour avoir en image une vue des différentes possibilités qu'offre blendMode, voyez l'Aide de Flash > Référence du langage ActionScript 2.0 > Classes ActionScript > MovieClip > blendMode.

Pour obtenir un effet d'éclairage sur un MovieClip, j'ai trouvé satisfaisant de créer un MovieClip par-dessus celui à éclairer, d'y dessiner un rectangle blanc d'opacité 15, et de mettre la propriété blendMode de ce MovieClip à la valeur 8. C'est ce que réalise ce code 6) 7) :

// Création des MovieClips pour chaque miniature:
for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
	minisMC[iMini] = miniaturesMC.createEmptyMovieClip("mini" + iMini, iMini);
	minisMC[iMini].createEmptyMovieClip("image", 1);
	// Ce MovieClip contiendra le rectangle d'éclairage de la miniature:
	minisMC[iMini].createEmptyMovieClip("light", 2);
	minisMC[iMini].createEmptyMovieClip("cadre", 3);
}
function eclairerMini() {
	// Afficher le MovieClip d'éclairage
	this.light._visible = true;
}
function eteindreMini() {
	// Rendre invisible le MovieClip d'éclairage
	this.light._visible = false;
}
function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
		// Placer la miniature et dessiner le fond:
		(pas de changement)
		// Dessiner le cadre et le rendre invisible:
		(pas de changement)
		// Dessiner le MovieClip d'éclairage:
		drawFilledRectangle(minisMC[iMini].light, 0, 0, minisMC[iMini]._width,
		minisMC[iMini]._height, 0xffffff, 15);
		minisMC[iMini].light._visible = false;
		minisMC[iMini].light.blendMode = 8;
		// Rendre la miniature interactive:
		minisMC[iMini].index = iMini;
		minisMC[iMini].onPress = onMiniPress;
		minisMC[iMini].onRollOver = eclairerMini;
		minisMC[iMini].onRollOut = minisMC[iMini].onReleaseOutside = eteindreMini;
	}
}

C'est simple. Une remarque quand même: onReleaseOutside = eteindreMini est nécessaire pour gérer le cas où l'utilisateur clique sur une miniature et fait glisser le curseur de la souris en dehors de la miniature avant de relâcher le bouton. En effet, dans un tel cas de figure la méthode onRollOut n'est pas appelée.

Nous avons ainsi ce résultat:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

2- Un deuxième usage de blendMode

Tout à notre joie d'avoir découvert blendMode, utilisons-le aussi pour le cadre qui apparaît sur la miniature sélectionnée. Cette fois nous utiliserons un rectangle gris, d'une opacité de 100, et toujours avec un blendMode de 8 :

function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
		// Placer la miniature et dessiner le fond:
		(pas de changement)
		// Dessiner le cadre et le rendre invisible:
		drawRectangle(
			minisMC[iMini].cadre,
			0.5, 0.5, minisMC[iMini]._width - 1, minisMC[iMini]._height - 1,
			1, 0x888888, 100
		);
		minisMC[iMini].cadre._visible = false;
		minisMC[iMini].cadre.blendMode = 8;
		// Dessiner le MovieClip d'éclairage:
		(pas de changement)
		// Rendre la miniature interactive:
		(pas de changement)
	}
}

Ce qui nous donne:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Ce n'est qu'une question de goût, l'essentiel est d'avoir le choix :-) !


VI - Copier des images et les réduire

Comme annoncé au chapitre I, en bonus à cet article, nous allons voir comment il est possible de générer des miniatures à partir de MovieClip d'images grands formats. Ceci ne nous sera pas utile pour le diaporama, mais va introduire des notions qu'il est bon de savoir qu'elles existent. Ce chapitre me semble techniquement assez avancé par rapport à ce que nous avons fait jusqu'ici. S'il vous pose trop de difficultés, n'oubliez pas que sa lecture est facultative, et que nous oublierons dès l'article suivant le détour qu'il nous fait faire.

Ce chapitre va reprendre en les modifiant les bases posées dans cette page aux chapitres II et III.

1- Charger les images

Nous avons donc besoin d'images à miniaturiser. Mettons que ces images sont dans un dossier “images” placé à coté du swf, et que les fichier sont nommés image0.jpg, image1.jpg, image2.jpg, etc.

Chargeons ces images dans des MovieClips par la même méthode que celle utilisée ici, en utilisant un MovieClipLoader. Créez un nouveau document et placez-y ce code 8):

var nbrImages:Number = 16; // Ici le nombre d'images que vous désirez
// Tableau de MovieClip pour recevoir chaque image grand format:
var imagesMC:Array = new Array();
// Création des MovieClips pour chaque image:
for (var iImage:Number = 0; iImage < nbrImages; iImage++)
	imagesMC[iImage] = this.createEmptyMovieClip("image" + iImage, this.getNextHighestDepth());
// Mise en place du MovieClipLoader et de son écouteur
var mcl:MovieClipLoader = new MovieClipLoader();
var mclListener:Object = new Object();
mclListener.onLoadInit = onImageLoadInit;
mcl.addListener(mclListener);
 
function chargerImage() {
	// Lance le chargement de l'image numéro imageToLoad dans le bon MovieClip
	mcl.loadClip("images/image" + imageToLoad + ".jpg", imagesMC[imageToLoad]);
}
function onImageLoadInit(mc:MovieClip) {
	// A la fin du chargement d'une image,
	// la rendre invisible et lancer le chargement de la suivante:
	imagesMC[imageToLoad]._visible = false;
	imageToLoad++;
	if (imageToLoad < nbrImages) chargerImage();
}
// Lancement du chargement:
var imageToLoad:Number = 0;	// Index de la miniature à charger
chargerImage();

Pas de commentaires, tout ceci nous est familier, et ne fait pas l'objet de ce chapitre (voir ici). Il nous suffit de savoir que les images sont chargées et qu'on y accède par le tableau de MovieClip imagesMC[]. Comme les images sont rendues invisibles au fur et à mesure de leur chargement, ce programme ne semble pas faire grand-chose.

2- La classe BitmapData

Les images chargées ne sont pas destinées à aller dans la barre de miniatures. Si nous les utilisons à cet usage, nous ne pourrons plus nous en servir pour les afficher dans le diaporama, ou pour tout autre usage à quoi elles étaient destinées. Nous allons donc devoir créer une copie de ces images.

ActionScript 2 n'offre pas de méthode simple pour dupliquer un MovieClip chargé avec MovieClipLoader ou la méthode loadMovie. En effet, la méthode duplicateMovieClip ne s'applique pas dans ce cas de figure (voir Aide de Flash > Référence du langage ActionScript 2.0 > Classes ActionScript > MovieClip > duplicateMovieClip). Nous allons devoir passer par la classe BitmapData pour faire les copies.

La classe BitmapData permet de manipuler des images bitmap. Une image bitmap est une image décrite par une grille de points de différentes couleurs (voir par exemple cet article de Wikipédia pour en savoir plus). Ceci est une autre approche par exemple que les images créées par les outils de dessin de Flash IDE qui sont elles décrites par les coordonnées de différents objets (lignes, courbes, rectangles, polygones) et leurs caractéristiques (couleur, style du trait, style du remplissage, etc.) .

Un objet de classe BitmapData est donc une grille de points de couleurs, d'une dimension donnée. Par exemple:

import flash.display.BitmapData;
var bitmap:BitmapData = new BitmapData(400, 300);

Ceci crée un nouveau BitmapData de 400×300 pixels, qui sera rempli de points blancs. L'objet est créé dans la mémoire, et n'est pas affiché sur scène. Le import est nécessaire pour pouvoir utiliser BitmapData sans à chaque fois devoir écrire intégralement flash.display.BitmapData.

Si on désire l'afficher, il faut demander à un MovieClip de le faire:

import flash.display.BitmapData;
var bitmap:BitmapData = new BitmapData(400, 300);
this.attachBitmap(bitmap, 0);

Cette fois, le rectangle blanc bitmap va être placé sur la scène à la profondeur 0.

Il est possible de demander à dessiner dans un BitmapData les points correspondants à un MovieClip. Ceci se fait grâce à la méthode draw du BitmapData. Par exemple, si imageMC est un MovieClip comportant des graphismes:

import flash.display.BitmapData;
var bitmap:BitmapData = new BitmapData(400, 300);
bitmap.draw(imageMC);

Ceci va dessiner les points de imageMC dans le BitmapData. Comme le BitmapData fait 400×300 pixels, seuls les points de imageMC dont les coordonnées sont comprises entre 0 et 399 pour x, et 0 et 299 pour y seront placés dans le BitmapData.

Une fois le BitmapData dessiné, il est possible de l'afficher dans un autre MovieClip:

import flash.display.BitmapData;
var bitmap:BitmapData = new BitmapData(400, 300);
bitmap.draw(imageMC);
imageMC._visible = false;
this.attachBitmap(bitmap, this.getNextHighestDepth());

Ceci copie les graphismes de imageMC, rend invisible ce dernier, puis affiche sur scène la copie obtenue.

3- Copier les images et les réduire

Utilisons BitmapData pour copier nos images dans de nouveaux MovieClips, dont nous réduirons ensuite la taille pour en faire des miniatures. Ajoutez le code suivant à celui de chargement des images du 1) :

import flash.geom.Rectangle;
var minisWidth:Number = 60; // Largeur des miniatures
var zoneMinis:Rectangle = new Rectangle(0, 0, 400, 45); // Emplacement de la barre de miniatures
 
// Création d'un MovieClip regroupant les miniatures:
var miniaturesMC:MovieClip = this.createEmptyMovieClip("miniatures", this.getNextHighestDepth());
miniaturesMC._x = zoneMinis.x;
miniaturesMC._y = zoneMinis.y;
// Création des MovieClips pour chaque miniature:
var minisMC:Array = new Array();
for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
	minisMC[iMini] = miniaturesMC.createEmptyMovieClip("mini" + iMini, iMini);
}
function onImageLoadInit(mc:MovieClip) {
	// A la fin du chargement d'une image, lancer le chargement de la suivante
	imagesMC[imageToLoad]._visible = false;
	imageToLoad++;
	if (imageToLoad < nbrImages) chargerImage();
	else initialiserBarreMiniatures();
}

Ceci reprend la création des MovieClips pour accueillir les miniatures comme vu aux chapitres II et III. A la fin du chargement des images la fonction initialiserBarreMiniatures est appelée. C'est elle qui va créer les miniatures en copiant les images et en les rapetissant, et les placer dans la barre de miniatures. En voici le code 9) :

function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	var minisBMP:BitmapData;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
 
		// Copier l'image:
		minisBMP = new BitmapData(imagesMC[iMini]._width, imagesMC[iMini]._height);
		minisBMP.draw(imagesMC[iMini]);
		minisMC[iMini].attachBitmap(minisBMP, 0);
 
		// Réduire la miniature
		minisMC[iMini]._width = minisWidth;
		minisMC[iMini]._height = zoneMinis.height;
 
		// Placer la miniature:
		minisMC[iMini]._x = xMiniature;
		xMiniature += minisMC[iMini]._width + 5;
	}
}

Pour chaque miniature, nous créons un nouveau BitmapData aux dimensions de l'image grand format, dessinons l'image dedans avec draw, puis l'affichons dans le MovieClip minisMC[] correspondant. Ensuite nous redimensionnons ce MovieClip à la taille de miniature désirée.

Ce code, volontairement simple pour commencer, ne gère pas les différents formats d'images. Certaines miniatures paraissent donc déformées dans ce résultat 10) :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Nous pouvons maintenant améliorer les choses en gérant les différents formats d'images possibles :

function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	var minisBMP:BitmapData;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
 
		// Copier l'image:
		minisBMP = new BitmapData(imagesMC[iMini]._width, imagesMC[iMini]._height);
		minisBMP.draw(imagesMC[iMini]);
		minisMC[iMini].attachBitmap(minisBMP, 0);
 
		// Réduire la miniature:
		minisMC[iMini]._width = minisWidth;
		minisMC[iMini]._yscale = minisMC[iMini]._xscale;
		if (minisMC[iMini]._height > zoneMinis.height) {
			minisMC[iMini]._height = zoneMinis.height;
			minisMC[iMini]._xscale = minisMC[iMini]._yscale;
		}
 
		// Placer la miniature:
		minisMC[iMini]._x = xMiniature;
		xMiniature += minisMC[iMini]._width + 5;
		if (minisMC[iMini]._height < zoneMinis.height)
			minisMC[iMini]._y = zoneMinis.height / 2 - minisMC[iMini]._height / 2;
	}
}

Les BitmapDatas sont créés à la taille des images. Les MovieClips minisMC[] font donc, avant d'être redimensionnés, la même taille que l'image qu'ils représentent. Il s'agit ensuite de réduire leur taille à celle d'une miniature (c'est-à-dire au plus minisWidth pixels de large et zoneMinis.height pixels de haut) en prenant garde à ne pas les déformer. Si vous ne comprenez pas la méthode qui assure le redimensionnement sans déformation, vous trouverez ici le passage d'un tutoriel qui traite de ce sujet. Ensuite la miniature est centrée verticalement, dans le cas où elle est moins haute que la barre de miniatures, de la même façon que nous l'avons fait au chapitre III-2.

Voici le nouveau résultat obtenu 11) :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

4- Optimisation

Il est toutefois dommage de créer des BitmapDatas aussi grands que les images non miniaturisées. En effet, on consomme ainsi une place en mémoire largement supérieure à celle nécessaire pour représenter les miniatures. Le fait d'agir ensuite sur la taille des MovieClips des miniatures ne change rien à la quantité de mémoire utilisée.

Nous allons donc nous pencher sur une façon de faire qui ne demande de créer des BitmapData qu'à la taille exacte des miniatures. On pourrait penser faire ainsi:

function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	var minisBMP:BitmapData;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
 
		// Réduire l'image:
		imagesMC[iMini]._width = minisWidth;
		imagesMC[iMini]._yscale = imagesMC[iMini]._xscale;
		if (imagesMC[iMini]._height > zoneMinis.height) {
			imagesMC[iMini]._height = zoneMinis.height;
			imagesMC[iMini]._xscale = imagesMC[iMini]._yscale;
		}
 
		// Copier l'image:
		minisBMP = new BitmapData(imagesMC[iMini]._width, imagesMC[iMini]._height);
		minisBMP.draw(imagesMC[iMini]);
		minisMC[iMini].attachBitmap(minisBMP, 0);
 
		// Placer la miniature:
		minisMC[iMini]._x = xMiniature;
		xMiniature += minisMC[iMini]._width + 5;
		if (minisMC[iMini]._height < zoneMinis.height)
			minisMC[iMini]._y = zoneMinis.height / 2 - minisMC[iMini]._height / 2;
 
		// Redonner sa taille d'origine à l'image:
		imagesMC[iMini]._xscale = 100;
		imagesMC[iMini]._yscale = 200;
	}
}

Ceci nous donne le décevant résultat:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Quel est le problème? La documentation de la méthode draw nous l'apprend (Aide de Flash > Référence du langage ActionScript 2.0 > Classes ActionScript > BitmapData > draw) :
(Citation) Un objet MovieClip source n'utilise pas ses transformations sur scène pour cet appel. Il est traité de la manière dont il apparaît dans la bibliothèque ou dans le fichier, sans transformation de matrice, de couleurs et sans mode de fondu.

C'est-à-dire qu'on a beau modifier la taille du MovieClip que l'on veut copier, ou le déplacer, ou encore le faire pivoter, il se dessinera dans le BitmapData sans prendre en compte aucune de ces transformations. C'est pour cela que nous n'obtenons en miniature que le coin supérieur gauche de chaque image. Il va nous falloir utiliser le deuxième paramètre que l'on peut passer à la méthode draw: un objet de classe Matrix. Si vous ne savez rien des matrices, accrochez-vous, c'est un peu technique. Mais dans la pratique ce sera très simple, donc pas d'affolement :-D .

Qu'est-ce donc qu'un objet Matrix? Il s'agit d'un objet mathématique qui permet de décrire à lui seul l'ensemble des déplacements, redimensionnements et rotations qu'on peut appliquer à un MovieClip. Nous n'allons pas rentrer ici dans une explication détaillé d'un objet Matrix. Il nous suffira de lire la suite de la documentation de draw pour nous en sortir:

(Citation) Si vous souhaitez dessiner le clip en utilisant ses propres propriétés de transformation, vous pouvez utiliser son objet Transform pour transmettre les diverses propriétés de transformation.

Un nouveau petit tour du côté de la documentation nous apprend qu'un MovieClip possède une propriété nommée transform. Cette propriété est un objet de classe Transform. Un objet de cette classe a lui-même une propriété nommée matrix, qui est l'objet Matrix qui décrit l'ensemble des transformations géométriques appliquées au MovieClip. La phrase est indigeste, mais la pratique est plus simple: unClip.transform.matrix est la matrice qui représente les transformations géométriques actuellement appliquées au MovieClip unClip.

Donc si nous voulons copier les pixels de ce MovieClip tel qu'il est actuellement redimensionné, il nous suffit de faire:

bitmap.draw(unClip, unClip.transform.matrix);

Nous pouvons donc reprendre le code de initialiserBarreMiniatures en ajoutant ce deuxième paramètre à draw 12) :

function initialiserBarreMiniatures() {
	var xMiniature:Number = 0;
	var minisBMP:BitmapData;
	for (var iMini:Number = 0; iMini < nbrImages; iMini++) {
		// Réduire l'image:
		(Pas de changement)
		// Copier l'image:
		minisBMP = new BitmapData(imagesMC[iMini]._width, imagesMC[iMini]._height);
		minisBMP.draw(imagesMC[iMini], imagesMC[iMini].transform.matrix);
		minisMC[iMini].attachBitmap(minisBMP, 0);
		// Placer la miniature:
		(Pas de changement)	
		// Redonner sa taille d'origine à l'image:
		(Pas de changement)
	}
}

Cette fois, ça y est:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Le résultat est identique à celui obtenu au 3), mais cette fois la consommation de mémoire est optimisée.

Les plus tenaces d'entre vous se diront peut-être: “Mais au final, ça ne nous sert à rien de réduire les images pour les copier en petit, puisque draw n'en tient pas compte. L'important, c'est d'avoir un objet Matrix correspondant à cette réduction. N'aurions-nous pas pu fabriquer cette matrice par nous-même sans toucher aux dimensions du MovieClip?” La réponse est “si”, mais je leur laisse le soin de découvrir comme s'y prendre, tout en les renvoyant au fichier Bitmap5 placé en pièce jointe 13) pour en avoir l'exemple.

5- Remarques

Si vous désirez maintenant fignoler et ajouter de l'interactivité à cette nouvelle barre de miniatures, il vous suffit d'adapter tout ce qui a été fait dans cet article depuis le chapitre III-3, quand les miniatures étaient chargées à partir de fichiers d'images.

Puisque ce chapitre était techniquement assez poussé, et que vous êtes arrivés jusque là, faisons une dernière remarque, au sujet de la durée de vie des objets dans FlashPlayer: Dans la fonction initialiserBarreMiniatures, nous ne déclarons qu'une variable minisBMP. Pourtant nous fabriquons autant d'objets BitmapData qu'il y a d'images. Comment est-ce possible?

L'explication est que dans la boucle for, à chaque new BitmapData(), un nouveau BitmapData est créé. La variable minisBMP reçoit alors une référence à cet objet. Nous pouvons donc accéder à cet objet par cette variable. Ensuite le BitmapData est dessiné (draw) puis attaché à un MovieClip (attachBitmap). La boucle recommence et un nouveau BitmapData est créé. La variable minisBMP reçoit alors une référence à ce nouvel objet. On ne peut donc plus accéder au précédent BitmapData. Mais ceci ne nous gêne pas, car nous n'avons plus rien à faire dessus.

Nous pouvons nous demander ce qu'il se passerait avec le code suivant:

var a:Array;
for (var i:Number = 0; i < 10; i++) {
	a = new Array();
}

Dans ce cas, 10 tableaux seraient créés en mémoire. A la fin de la boucle, la variable a ferait référence au dernier de ces tableaux. Toute référence aux 9 premiers tableaux seraient perdues, et nous ne pourrions plus travailler avec. La question qui se pose est: ces 9 tableaux restent-ils en mémoire pour l'encombrer? La réponse est non, heureusement. Flash intègre un mécanisme dit de “ramasse-miettes” (garbage collector en anglais, lien sur Wikipédia), qui s'occupe de détruire les objets vers lesquels plus aucune référence n'existe, libérant ainsi la mémoire. Ce genre d'attention à l'occupation de la mémoire est une préoccupation dans le développement d'applications, car un programmeur doit toujours faire attention aux ressources que va utiliser son programme.

Dans notre cas, il n'existe plus de références aux BitmapData créés. Mais comme ils sont attachés à un MovieClip, ils ne sont pas détruits, heureusement pour nous, car nous voulons bien sûr que nos miniatures restent affichées après la fin de la fonction initialiserBarreMiniatures.

Allez, il faut savoir s'arrêter.


Conclusion

La barre de miniatures est maintenant construite. Si la barre n'est pas assez large pour présenter simultanément toutes les miniatures, il va falloir trouver le moyen de faire défiler son contenu. Ceci sera l'objet de l'article suivant. En dernier lieu, nous verrons comment intégrer cette barre au diaporama.

Vous remarques sur cet articles sont nécessaires pour le faire évoluer. Pour toute difficulté rencontrée ou toute suggestion d'amélioration, merci de vous rendre sur le forum et d'ajouter une réponse au message concernant ce tutoriel.


Navigation: Sommaire | page précédente Index

1) , 6) , 8) , 9) , 13) Vous pouvez télécharger les fichiers de code correspondants à cet article et les swf compilés, le tout dans une archive compressée. Si vous utilisez Flash IDE, cliquez ici. Si vous utilisez un environnement intégrant mtasc et swfmill, cliquez ici
2) , 3) , 10) , 11) Pourquoi n'y a-t-il que 4 images qui se répètent dans les exemples de la page? Sur ce wiki, il n'est pas possible d'intégrer des swf qui chargent des images externes. Aussi, depuis le début de cette série de tutoriaux, j'ai été contraint d'intégrer les images utilisées dans la bibliothèque des swf exemples, et j'ai écrit juste pour eux un code supplémentaire qui simule les temps de chargement. La conséquence est que chacun des swf de la page embarque avec lui les images dont il a besoin, ce qui les rend lourds à télécharger. Maintenant, l'album d'images du diaporama va devoir dépasser 4 images, pour que la barre de miniatures soit assez conséquente et puisse avoir à défiler horizontalement. Pour minimiser le poids des swf, je n'utiliserais en fait que les 4 mêmes images qui se répètent, dans les exemples donnés. Cette modification ne concerne que les swf exemples. Les codes fournis tout au long de l'article permettent eux d'utiliser autant d'images différentes que désiré.
4) , 5) , 7) , 12) “Pas de changement” est mentionné dans le code pour les parties qui ne sont pas modifiées par rapport à la version précédente d'une même fonction.