Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox



Afficher, centrer, déplacer et zoomer une image à l'intérieur d'une fenêtre (2)

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

Deuxième partie : les zooms

Article écrit le 06/06/2008 00:40. Par lilive ( olivier tarasse ).


Ceci est la suite de cet article

Le code est toujours donné en actionscript 2. La totalité du code utilisé est disponible ici. Les .fla de l'application finale sont ici.

Nous avons donc un clip nommé imageMC contenant une image (et cela marche aussi avec une animation) qui s'affiche dans une fenêtre dont les coordonnées sont stockées dans l'objet Rectangle zoneAffichage.
Je vais expliquer des façons possibles de gérer les zooms avant ou arrières sur cette image.


I - Zooms avant et arrière sur l'image, premiers essais

Il est facile avec Flash de modifier la taille d'un MovieClip par un certain facteur. Par exemple…

imageMC._xscale = 200;
imageMC._yscale = 200;

… va afficher le MovieClip à 200% de sa taille d'origine, c'est-à-dire la doubler. Ou encore …

imageMC._xscale /= 4;
imageMC._yscale /= 4;

… va faire rétrécir le MovieClip au quart de sa taille actuelle.
Explication :

imageMC._xscale /= 4;

se lit comme

imageMC._xscale = imageMC._xscale / 4;

c'est-à-dire que l'échelle est divisée par 4.
Rien de bien difficile pour l'instant.

On pense donc à faire deux boutons, un pour zoomer vers l'avant “zoom +” et un pour zoomer vers l'arrière “zoom -”, et on leur associe le code suivant:

zoomPlusBtn.onPress = function () {
        imageMC._xscale *= 1.5;
        imageMC._yscale *= 1.5;
}
zoomMoinsBtn.onPress = function () {
	imageMC._xscale /= 1.5;
	imageMC._yscale /= 1.5;
}

Ce qui donne comme résultat:

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.
Le code correspondant est celui de zoom-1.as

On s'aperçoit alors que si la fonction de zoom fonctionne, elle n'est ni pratique ni intuitive : L'image semble se décaler vers la droite et vers le bas au fur et à mesure qu'on zoome de plus près avec le bouton “zoom +”.
Il est difficile de zoomer fortement sur la partie voulue de l'image. Si on veut aller regarder de près le soleil, dans l'exemple fourni, on est contraint à cumuler des glissés de l'image avec les zooms pour amener le soleil dans la fenêtre. Essayez donc, pour comprendre ce que je veux dire, de zoomer fortement sur le rond rouge au centre de la fleur. Plutôt fastidieux, non ?
C'est la résolution de ce problème qui va occuper la suite cet article.


II - Le problème : zoomer semble déplacer l'image

D'où vient ce phénomène ? Explication :
Un MovieClip possède ce qu'on appelle un “point d'alignement”. Ce point est le point du MovieClip qui va rester en place quand on modifie l'échelle du MovieClip, ou sa rotation. Par défaut, ce point est le point de coordonnées (0,0), c'est-à-dire le point supérieur gauche de l'image. Donc quand on clique sur les boutons de zoom, le seul point de l'image qui ne “bouge” pas dans la fenêtre est ce point là. D'où la difficulté à zoomer sur des points qui sont vers la droite ou vers le bas de l'image.

Comment s'y prendre ? On pourrait désirer que le point de l'image qui ne bouge pas lors du zoom soit le point de l'image au centre de la fenêtre. Par exemple, si l'utilisateur voulais zoomer sur le coeur de la fleur, sur le rond rouge dessiné en son centre : il placerait ce rond rouge au centre de la fenêtre avec un glissé, puis cliquerais sur “zoom +”. Il faudrait alors que le rond rouge reste au centre de la fenêtre, mais vu de “plus près”. Le résultat que je viens de décrire est réalisé ici :

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

Essayez les boutons pour être sûr de comprendre le but poursuivi :-)

Ce qui vient à l'esprit pour avoir ce résultat est : “tiens, je vais changer ce fameux point d'alignement, et le mettre sur la partie de l'image qui est au centre de la fenêtre. Ainsi mon zoom sera satisfaisant.” Hélas, si on peut modifier une fois pour toute ce point dans l'IDE de Macromedia, on n'y a pas accès à l'exécution, et c'est bien dommage. Il va donc falloir passer par des calculs pour repositionner le clip et avoir le résultat escompté.
Ouf. Sacré explication.
Pour résoudre le problème, je vais procéder par étapes.
Première étape :


III - Placer un point d'une image exactement sur un point de la scène

Je vais écrire une fonction qui place une image de façon à faire coïncider un point de cette image avec un point de la scène. Je pourrais par la suite utiliser cette fonction pour demander par exemple de positionner le point de l'image correspondant au coeur de la fleur sur le point central de la zone d'affichage. On suppose que le point d'alignement de l'image est le point de coordonnées (0,0) de cette image (voir article précédent).

a - Avec une image à 100%

Commençons très simplement, et imaginons que l'échelle de l'image est 100%, c'est-à-dire _xscale=100 et _yscale=100 .

function placerPoint(imageMC:MovieClip, x:Number, y:Number, destX:Number, destY:Number):Void {
	// Place le point (x,y) de imageMC sur le point (destX, destY)
	imageMC._x = destX - x;
	imageMC._y = destY - y;
}

Si j'utilise cette fonction ainsi :

placerPoint(imageMC, 617, 444, 250, 150);

Le point de coordonnées (617,44), qui est le centre de la fleur, se placera sur le point (250,150) de la scène, qui est le centre de la zone d'affichage. Voici ce que ça donne :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.
Le code correspondant est celui de zoom-3.as

C'est un succès !

La figure 1 ci-dessous illustre le calcul qui est fait pour positionner l'image horizontalement par rapport à x et destX. (Cette figure est faite avec une image moins grande que dans le résultat ci-dessus, par commodité. Nous sommes plutôt dans le cas où x=130 plutôt que 617 pour le coeur de la fleur. destX=250 reste vrai)

zoom-fig-1.jpg
Figure 1

Voici une explication détaillée, pour ceux qui ne sont pas familiers de ce genre de gymnastique :
1-Si on fait simplement imageMC._x = destX, le bord gauche de l'image se place à la coordonnée destX. Le rond rouge est alors à 130 pixels vers la droite (voir figure 2)

zoom-fig-3.jpg
Figure 2

2-Donc pour amener le rond rouge à la hauteur de destX, il faut décaler l'image de x=130 pixels vers la gauche. C'est exactement ce que fait le calcul :
imageMC._x = destX - x

Le calcul de la position verticale suit exactement la même logique.

b - Avec une image redimensionnée

La fonction placerPoint() ne marche pas si imageMC n'est pas à 100%.
Pourquoi ? Disons par exemple qu'elle est agrandie à 200%, c'est-à-dire imageMC._xscale=200 et imageMC._yscale=200.

Dans ce cas, le point (617,444) est toujours le point central de la fleur, en coordonnées locales à l'image 1). Qu'est-ce que ça veut dire ? Cela veut dire que si on part du point d'alignement de l'image, qu'on compte dans l'image 617 pixels vers la droite et 444 pixels vers le bas, on tombe bien sur le pixel au centre de la fleur. Mais si on place l'image sur la scène, au double de sa taille, flash va afficher d'avantage de pixels, et le centre de la fleur ne sera pas à 617 pixels du bord gauche de l'image, mais à 1234=617*2 pixels. Le calcul précédent ne conviendra alors plus.

Si on reprend la démarche expliquée en détail au a) :
1-En faisant imageMC._x = destX, le bord gauche de l'image se place à la coordonnées destX. Le rond rouge est alors à 617 * 2 pixels vers la droite, puisque l'image est 2 fois plus grande.
2-Donc pour amener le rond rouge à la hauteur de destX, il faut décaler l'image de x=617*2 pixels vers la gauche. C'est exactement ce que ferait le calcul :

imageMC._x = destX - x * 2;

Ceci ne marcherait que pour une échelle de 200%. Pour que le calcul soit valable à n'importe quelle échelle, on va faire:

imageMC._x = destX - x * imageMC._xscale / 100;

Si _xscale vaut 200, on retrouve bien une multiplication par deux. Mais cela restera valable pour _xscale=400, ce qui donnera une multiplication par 4, ou encore pour _xscale=50, ce qui donnera une multiplication par 0.5, c'est-à-dire une division par 2.

La fonction devient donc:

function placerPoint(imageMC:MovieClip, x:Number, y:Number, destX:Number, destY:Number):Void {
	// Place le point (x,y) de imageMC sur le point (destX, destY)
	imageMC._x = destX - x * imageMC._xscale / 100;
	imageMC._y = destY - y * imageMC._yscale / 100;
}

Résultat :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.
Le code correspondant est celui de zoom-4.as

Pour finir, et parce-que c'est plus pratique pour la suite, j'écris la version définitive de placerPoint() avec des objets Point comme paramètres :

function placerPoint(imageMC:MovieClip, cible:Point, dest:Point):Void {
	// Place le point cible de imageMC sur le point dest
	imageMC._x = dest.x - cible.x * imageMC._xscale / 100;
	imageMC._y = dest.y - cible.y * imageMC._yscale / 100;
}

Ce qui revient au même, sauf qu'au lieu de donner 4 paramètres on en donne plus que deux.

var rondRouge:Point = new Point(617, 444);
var centre:Point = new Point(250, 150);
placerPoint(imageMC, rondRouge, centre);

Ce qu'on retrouve dans zoom-5.as pour exactement le même résultat que auparavant.


IV - Zoomer sur le point de l'image qui est au centre de la zone d'affichage

Nous pouvons maintenant revenir à notre problème de départ :
Faire deux boutons, zoom + et zoom -, qui modifient l'échelle de l'image, de façon à ce que le point de l'image qui était au centre de la zone d'affichage reste bien au centre.
Nous savons presque tout faire : nous savons modifier l'échelle avec _xscale et _yscale, et nous avons une fonction placerPoint() qui nous permet de placer l'image comme on le désire.
Alors sans plus attendre, le code de la fonction:

function zoomerSurCentre(imageMC:MovieClip, zoneAffichage:Rectangle, scale:Number):Void {
	// Modifie l'échelle de imageMC en le déplaçant de manière à garder
	// immobile le point qui est au centre de zoneAffichage
 
	// Calcul des coordonnées du point central de zoneAffichage :
	var centreRectX:Number = zoneAffichage.left + zoneAffichage.width / 2;
	var centreRectY:Number = zoneAffichage.top + zoneAffichage.height / 2;
	var centreRect:Point = new Point(centreRectX, centreRectY);
	// Calcul des coordonnées du point de imageMC qui occupe
	// actuellement le centre de la zone d'affichage
	var centreImage:Point = centreRect.clone();
	imageMC.globalToLocal(centreImage);
	// Opération de zoom :
	imageMC._xscale = scale;
	imageMC._yscale = scale;
	// Placement de l'image :
	placerPoint(imageMC, centreImage, centreRect)
}

Explications, la partie délicate étant “quels points va-t'on fournir à placerPoint() ?” :

1 - Le point cible est assez facile à calculer. C'est le point central de la zone d'affichage qui se calcule par:

var centreRectX:Number = zoneAffichage.left + zoneAffichage.width / 2;
var centreRectY:Number = zoneAffichage.top + zoneAffichage.height / 2;
var centreRect:Point = new Point(centreRectX, centreRectY);

Que je ne commente pas plus car nous avons déjà fait plus difficile. Ce code calcule les coordonnées sur la scène du centre de la zone d'affichage. Jusque-là j'avais tout simplement utilisé les coordonnées (250, 150), que je sais être les bonnes puisque c'est moi qui ai choisit les coordonnées de la zone d'affichage. Mais il est quand même préférable de pouvoir faire le calcul, comme cela on pourrait très simplement changer la taille et la dimension de cette zone en modifiant comme seule ligne de code :
var zoneAffichage:Rectangle = new Rectangle(25,25,450,250);

2 - Maintenant plus délicat : quelles sont donc les coordonnées dans l'image du point qui est actuellement au milieu de la zone d'affichage. Quand je dis “coordonnées dans l'image”, je parle de ce qu'on appelle les coordonnées locales à l'image, c'est-à-dire les coordonnées du point comme si l'image n'était ni agrandie, ni déplacée 2). Si nous restons avec notre exemple du rond rouge, il faudrait que, quelque soit le zoom actuel sur l'image, si le rond rouge est au centre de la zone d'affichage, nous trouvions un moyen d'obtenir des coordonnées qui seront proches de (617,444).

En fait c'est très simple. Ouf. Il existe une méthode d'un MovieClip qui permet de connaître les coordonnées locales dans une image à partir des coordonnées sur la scène. Cette méthode s'appelle globalToLocal(). Elle s'utilise comme suit:

Imaginons que l'utilisateur à placé le rond rouge pile au centre de la zone d'affichage.
Nous calculons centreRect qui va nous donner le point (250,150).
Les coordonnées de ce point sont exprimées en fonction de la scène.
Maintenant nous voudrions les coordonnées du même point, mais dans imageMC.
Il suffit de faire:

// Dupliquer centreRect
var centreImage:Point = centreRect.clone();
// Convertir ce point en coordonnées locales à imageMC
imageMC.globalToLocal(centreImage);

Et voilà ! Si on faisait à présent trace(centreImage.x) et trace(centreImage.y) nous obtiendrions en sortie 617 et 444.

3 - Il ne reste plus qu'à changer l'échelle de l'image et à centrer de nouveau le point de l'image dans la zone d'affichage :

// Opération de zoom :
imageMC._xscale = scale;
imageMC._yscale = scale;
// Placement de l'image :
placerPoint(imageMC, centreImage, centreRect)

Et notre fonction est prête à l'emploi.
Pour l'utiliser je rajoute:

zoomPlusBtn.onPress = function () {
	zoomerSurCentre(imageMC, zoneAffichage, imageMC._xscale * 1.5)
}
zoomMoinsBtn.onPress = function () {
	zoomerSurCentre(imageMC, zoneAffichage, imageMC._xscale / 1.5)
}

Qui fera que l'échelle sera multiplié ou diminuée de 150% à chaque clic sur les boutons correspondants.
Le résultat final et mérité par tant d'efforts :

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.
Le code correspondant est celui de zoom-final.as


V - Pour finir

Car il faut bien finir, et ce tutoriel est déjà très long :-)
J'ai pris le parti de détailler beaucoup de choses, en espérant être accessible à des débutants en programmation actionscript.
Il y aurait bien sûr d'autres stratégies pour arriver au même résultat. J'en donne une dans zoom-bonus.as. Elle est plus économique, mais je la trouvais dure à expliquer, mathématiquement parlant. C'est pour cela que j'ai adopté une démarche plus “pas à pas” avec d'abord l'écriture d'une fonction placerPoint().

Merci de tous retours quand à la qualité ou aux défauts de cet article.



1) , 2) Pour en savoir plus sur les coordonnées locales et globales, lire le début de ce très bon article