Images sans objet

Amusez-vous à peindre à la manière de Piet Mondrian dans l'animation Director 8 ci-dessus. Il suffit de cliquer sur le canevas noir et déplacer le pointeur de la souris, tout en gardant le bouton de la souris enfoncé. Cliquez sur la palette pour changer de couleur.

Remarquez que si vous cliquez sur l'une des couleurs de la palette, cette couleur passe devant les autres. Remarquez que la palette se dessine en premier plan : vous ne pouvez pas peindre par-dessus. Remarquez que les rectangles ne laissent pas de "trace" : lorsque vous diminuez la taille d'un rectangle, la zone qu'il cachait se rafraîchit pour montrer l'image qu'il y avait avant.

Cliquez sur le dernier rectangle que vous avez dessiné. Vous pouvez maintenant déplacer ce rectangle tant que vous gardez le bouton de la souris enfoncé. Si vous changez de couleur avant de déplacer le rectangle, le rectangle change de couleur.

Rien dans les manches
Ouvrez maintenant l'animation non protégée mondrian.dir (29Ko). Pour copier cette animation sur votre disque dur plutôt que de l'ouvrir dans votre browser, cliquez sur le lien avec le bouton droit de votre souris (si vous êtes sur PC), ou cliquez sur le lien et attendre (si vous êtes sur Mac). Un menu contextuel apparaîtra : choisissez "Enregistrer la cible sous..." (ou une autre instruction semblable).

Une fois que vous avez ouvert cette animation dans Director 8, regardez la fenêtre du Scénario : que voyez-vous ?

Rien. L'animation n'utilise aucune image-objet. Les pistes sont toutes vides. Oh pardon : il y a un petit comportement dans la piste des scripts dans l'image 1. Regardez donc dans la fenêtre de la Distribution.

Voilà la confirmation : cette animation comporte un seul acteur Script et rien d'autre. Alors d'où viennent ces images dont vous pouvez changer la taille, la position et le locZ ?

Imaging Lingo
Director 8 vous propose tout un jeu de commandes pour gérer les images. Ces commandes sont rapides et puissantes. Vous pouvez créer des images à la volée, les stocker en mémoire vive, et manipuler leurs pixels à volonté. Vous pouvez pratiquer des encres et des distorsions quad. Vous pouvez sauver le résultat dans un acteur bitmap, et même l'envoyer par Internet par le biais du xtra Multiuser.

Dans cet article, je vous montre comment :

  • Dessiner directement sur la Scène ;
  • Remplir une partie d'une image avec aplat de couleur ;
  • Détecter des clics sur une zone de l'écran ;
  • Créer une image en mémoire vive à partir de rien
  • Copier une partie d'une image vers une autre ;
  • Manipuler l'image de la Scène pour imiter la présence des images-objets.

Ceci n'est qu'une introduction aux possibilités des commandes de gestion d'images.

Malgré les apparences, mon intention n'est nullement de vous encourager à abandonner les images-objets dans vos projets. Dans une animation complexe, gérer l'image à l'écran sans se servir des images-objets reviendrait à réinventer la roue. Au contraire, j'espère que cette démonstration vous aidera à mieux comprendre comment les images-objets fonctionnent "sous le capot". Cette connaissance vous permettra d'imaginer comment le nouveau Lingo peut complémenter les fonctionnalités image-objet existantes.

Dessiner sur la Scène
Voici deux gestionnaires simples qui dessinent une forme sur la Scène.

on carreRouge
  carre = rect(60, 20, 260, 220)
  rouge = rgb(255, 0, 0)
  (the stage).image.fill(carre, rouge)
  updateStage
end

on rondBleu
  carre   = rect(50, 10, 270, 230)
  bleu    = rgb(0, 0, 255)
  options = [#color: bleu, #shapeType: #oval]
  (the stage).image.fill(carre, options)
  updateStage
end

Le mot anglais "fill" signifie "remplir". Pensez à utiliser le bouton "L" (pour Lingo) dans la barre d'outils des fenêtres Messages ou Script pour insérer des mots clefs avec la bonne syntaxe, lorsque vous créez vos propres gestionnaires.

Référez-vous également au Dictionnaire Lingo pour avoir des détails sur l'utilisation des commandes. Vous verrez que la commande "fill()" accepte plusieurs syntaxes différentes.

Remarquez aussi la nouvelle propriété "image", qui vous donne accès aux pixels. Ici, vous modifiez les pixels de l'image de la Scène. Les acteurs bitmap, vectorShape et Flash ont aussi une propriété "image". Vous pouvez également créer des images "crues", stockées dans une variable, avec la fonction "image()". C'est d'ailleurs ce que vous allez voir bientôt.

Créez une nouvelle animation, ouvrez la fenêtre de Scripts, et collez ces gestionnaires dans un Script d'Animation. Pour l'instant il est inutile de démarrer l'animation, puisqu'elle ne contient pas d'image-objet, et s'arrêtera aussitôt lancée. Vous pouvez, par contre, tapez les commandes suivantes dans la fenêtre de Messages avec l'animation à l'arrêt.

carreRouge
rondBleu

Notez que, pour faire une ellipse, il faut quand même définir un rectangle, puis utiliser une liste d'options pour indiquer qu'il s'agit de remplir le rectangle avec une forme ovale. Amusez-vous avec les propriétés possibles de cette liste d'options : #shapeType, #lineSize, #color et #bgColor.

D'autres formes avec des quads
Vous pouvez créer des formes plus intéressantes encore en utilisant un quad comme destination, plutôt qu'une zone rectangulaire. Un quad est une liste de quatre points : chaque point définit l'un des coins d'un quadrilatère quelconque. Dans l'exemple ci-dessous, deux de ces points se superposent : le quad en question forme donc un triangle.

on triangleJaune
  gauche   = point( 50, 35)
  droite   = point(270, 35)
  bas      = point(160, 235)
  triangle = [gauche, droite, bas, bas] -- quad triangulaire
  
  jaune    = rgb(255, 255, 0)
  carre    = image(200, 200, 8) -- image carrée vide
  carre.fill(carre.rect, jaune) -- image jaune carrÈe
  
  (the stage).image.copyPixels(carre, triangle, carre.rect)
  updateStage
end
Ajoutez ce gestionnaire à votre script, puis testez-le depuis la fenêtre des Messages :
triangleJaune

Notez la fonction "image()", qui vous permet de créer une image en mémoire vive, de toutes pièces. Les pixels de l'image créée sont tous blancs au départ. (Pour vérifier ceci, mettez la ligne qui remplit l'image carrée de jaune en commentaire, en plaçant des tirets "--" devant cette ligne).

Notez aussi la commande "copyPixels()". Vous verrez plus bas une utilisation plus simple. Ici, on copie l'image carrée sur la Scène, en la déformant pour la faire tenir dans une zone définie par le quad "triangle". CopyPixels est une commande très puissante. Vous pouvez, par exemple, utiliser une liste de paramètres pour modifier les modalités de la copie. Vous pouvez ainsi manipuler :

  • #color et #bgColor: la couleur des zones opaques et transparentes ;
  • #ink et #blendLevel: l'effet d'encre et l'opacité ;
  • #maskImage et #maskOffset: l'image en noir et blanc utilisée pour délimiter des zones non quadrilataires ;
  • ... et d'autres paramètres encore plus spécialisés.
Consultez le Dictionnaire Lingo pour plus de précisions.

Esquisser un comportement
Essayons maintenant d'automatiser cette tache. Convertir votre script d'animation en comportement. Pour ce faire, cliquez sur le bouton avec un "i" blanc dans un rond bleu, pour ouvrir le nouvel Inspecteur de propriétés, puis choisir "Comportement" dans le menu local.

Ajoutez ces deux gestionnaires à votre comportement :

on beginSprite
  carreRouge
  rondBleu
  triangleJaune
end

on exitFrame
  go the frame
end exitFrame

Glissez votre comportement sur la piste Script de la première image de votre animation, puis lancez celle-ci.

Le gestionnaire "on beginSprite" est exécuté avant que la Scène ne soit dessinée pour la première image. Mais la Scène reste vide. Rien ne se dessine dessus. Ce n'est pas la faute de vos gestionnaires. C'est une question de timing. Les gestionnaires "on carreRouge", "on rondBleu" et "on triangleJaune" ont été appelés trop tôt. Vous pouvez rappeler le gestionnaire "on beginSprite", qui les appelle à son tour, en tapant la commande suivante dans la fenêtre de Messages :

sendAllSprites(#beginSprite)

Et voilà, les trois formes apparaissent. Mais pourquoi n'étaient-elles pas apparues auparavant ?

Quand est-ce qu'on dessine ?
Director reconnaît plusieurs étapes dans l'affichage d'une image à l'écran. Il ponctue ces étapes avec des messages. En ce qui nous concerne ici, en voici l'ordre :

  • #beginSprite...envoyÈ aux comportements qui commencent dans l'image à afficher
  • #prepareFrame..envoyé avant que l'image ne se dessine
  • #enterFrame....envoyé après que l'image a été dessinée
  • #exitFrame.....envoyé avant de sortir de l'image courante

Si vous dessinez sur la Scène dans un gestionnaire "on beginSprite" ou "on prepareFrame", Director risque de plaquer les images-objets par-dessus votre dessin. Dans notre cas, il a carrément refusé d'exécuter la commande "updateStage", puisque cette commande l'oblige à redessiner la Scène, et donc d'envoyer de nouveau les messages #beginSprite et #prepareFrame, ce qui le ferait entrer dans un cercle vicieux.

Pour faire apparaître vos formes, il faut donc attendre le message #enterFrame. Remplacez le gestionnaire "on beginSprite" avec ce qui suit :

property debut

on beginSprite
  debut = TRUE
end

on enterFrame
  if debut then
    -- Ces lignes ne seront exÈcutÈes qu'une seule fois
    carreRouge
    rondBleu
    triangleJaune
    debut = FALSE
  end if
end   

Relancez votre animation : les formes se dessinent correctement. Le timing de la gestion de l'image de la Scène est donc important.

Changer de locZ
Le locZ d'une image-objet détermine dans quel ordre son image sera affichée à l'écran. Vous pouvez très facilement simuler cet effet : il suffit de redessiner une forme par-dessus une autre.

Puisque nous avons des formes simples, d'une seule couleur, nous allons pouvoir détecter un clic sur une forme selon la couleur qui se trouve sous le pointeur de la souris. (Dans l'animation mondrian.dir, j'utilise une liste de rects pour arriver à la même fin).

Ajoutez les lignes suivantes à votre comportement (les propriétés doivent être déclarées tout en haut du script, avant les gestionnaires "on carreRouge", "on rondBleu" et "on triangleJaune") :

property rouge
property bleu
property jaune

on mouseUp
  couleurSousPointeur = (the stage).image.getPixel(the mouseLoc)
  case couleurSousPointeur of
    rouge: carreRouge
    bleu:  rondBleu
    jaune: triangleJaune
  end case
end

Relancez votre animation. Maintenant, lorsque vous cliquez sur une partie d'une forme la forme en question est redéssiné devant les autres.

Vous pouvez télécharger une version complète de cette animation : formes.dir.

getPixel() se métamorphose
Dans Director 7, la fonction "getPixel()" existait déjà, mais elle n'était pas officiellement supportée par Macromedia. Elle ne paraissait donc pas dans le Dictionnaire Lingo, et Macromedia se réservait le droit de modifier son utilisation. Ce qui a été fait : dans Director 8, elle ne marche pas de la même façon que dans Director 7. Si vous mettez à jour une animation où vous utilisiez la version non officielle de cette commande, vous devez donc changer la syntaxe. De la même façon, "setPixel()" a aussi pris de l'envergure. Voir le Dictionnaire Lingo pour les détails.

Tampon d'écran
Lorsque vous faites déplacer une image-objet sur la Scène, le fond réapparaît inchangé derrière. La partie du fond cachée par l'image-objet doit être stockée en mémoire, sinon Director ne saurait pas quoi afficher après le passage de l'image-objet. En effet, Director réserve un bloc de mémoire vive appelé "Tampon d'écran", qui stocke l'ensemble de l'image de la Scène, avant de l'afficher.

Pour imiter cette indépendance de l'image-objet, vous allez créer votre propre tampon d'écran. C'est une démarche assez gourmande en mémoire : les dimensions de l'écran doivent être multipliées par la résolution couleur de l'écran. Essayez ceci dans la fenêtre de Messages :

put the stage.rect.width * the stage.rect.height * the colorDepth / 8 / 1024 && "Ko"

Pour une Scène de 800 x 600 pixels avec une résolution couleur de 32 bits, cela représente presque 2 Mo, rien que pour laisser aux images-objets leur indépendance. Puisque Director s'octroie déjà un espace mémoire de cette taille, il est déconseillé de la dédoubler. Je le fais ici uniquement dans un but pédagogique... et avec une petite Scène.

Une image qui en cache une autre
Voici une version dépouillée du comportement "* Mondrian *". Il vous permet de dessiner une série de rects rouges à l'écran (dans cette version simplifiée, il faut tirer depuis le haut à gauche vers le bas à droite). Créez un nouveau comportement et collez ce script dedans. Glissez le comportement sur la piste Script de la première image de votre animation, pour remplacer le comportement qui y est actuellement, puis relancez votre animation.

property monClicH       -- the mouseH on mouseDown
property monClicV       -- the mouseV on mouseDown
property monRect        -- rect de la zone de peinture active
property monTamponEcran -- copie de l'image de la Scène avant dessin


on mouseDown(me)
  -- Commencer ý dessiner un nouveau rect
  monClicH       = the mouseH
  monClicV       = the mouseV
  monRect        = rect(0, 0, 0, 0)             -- valeur factice
  monTamponEcran = duplicate((the stage).image) -- Ètat actuel
end mouseDown


on exitFrame(me)
  if the mouseDown then
    -- Restaurer la zone modifiÈe la derniËre fois
   (the stage).image.copyPixels(monTamponEcran, monRect, monRect)

    -- Garder en mÈmoire la zone qui est sur le point d'Ítre modifiÈe
    monRect = rect(monClicH, monClicV, the mouseH, the mouseV)
    
    -- Dessiner ce rect ý sa nouvelle taille
    (the stage).image.fill(monRect, rgb(255, 0, 0))
  end if
  
  go the frame
end exitFrame
Remarquez que la partie cachée derrière le rectangle est dessiné en premier, puis le rectangle est dessiné par-dessus. L'écran n'est pas rafraîchi entre ces deux actions : il n'y a pas de "updateStage", et Director attend de passer à l'image suivante avant de remettre la Scène à jour. Ainsi, l'utilisateur ne voit que le résultat des deux actions, en une seule fois.

Notez la fonction "duplicate()" utilisée sur l'image de la Scène. Pour Lingo, une image est un pointeur. C'est à dire qu'il contient l'adresse mémoire ou les informations sur les pixels sont stockées en mémoire. Si deux variables contiennent une même adresse mémoire, elles peuvent toutes les deux être utilisées pour manipuler les mêmes pixels. Une modification faite sur une des variables est immédiatement ressentie par l'autre. C'est comme deux portes qui donnent sur la même pièce.

Si on affecte à la variable "monTamponEcran" le pointeur "(the stage).image", alors les pixels adressés par monTamponEcran changeront continuellement pour refléter les changements sur la Scène. C'est pourquoi il faut créer une copie de l'état actuel de la Scène, en utilisant la fonction "duplicate()". C'est cette fonction qui, ici, accapare tant de mémoire vive. Pour reprendre la métaphore, elle crée une nouvelle pièce et nous indique où en trouver la porte.

Ici la commande "copyPixels()" est utilisée d'une manière simple. Dans cet exemple, elle copie une zone de notre tampon d'écran vers la zone correspondante sur la Scène. Les images source et destination ont la même taille, et les zones ont les même coordonnées. Comme vous avez vu avec le gestionnaire "on triangleJaune" plus haut, la commande "copyPixels()" peut faire des manipulations bien plus complexes que celle-ci. Ici, par contre, l'image qui est copiée n'est pas un simple aplat de couleur. Vous créez une peinture personnalisée sur la Scène : "copyPixels()" s'occupe de copier une partie de cette peinture à la manière d'un emporte-pièce. Les pixels copiés forment un arrangement unique de rectangles de couleurs différentes.

Une animation qui démontre ce comportement est disponible : depouille.dir.

Pour aller plus loin
L'animation mondrian.dir propose plus de fonctionnalités, et est construite de façon bien plus rigoureuse, mais le principe est le même. Le comportement "* Mondrian *" est bien commenté. Vous pouvez essayer de compléter les deux comportements dépouillés que vous avez étudiés, en vous inspirant des gestionnaires de l'animation complète. Vous pouvez même ajouter vos propres fonctionnalités : pour permettre à l'utilisateur de se servir d'autres formes (ligne, ovale, quad ...), pour "Annuler", ou "Rejouer la Création", ...

Si vous avez des questions sur les concepts abordés, vous pouvez me contacter à newton@planetb.fr.