L'objet Color(), the color of sprite (et setPixel) 

 

 

Il est possible que votre navigateur ait quelque mal à afficher ce Shockwave. Accordez lui un peu plus de Ram et tout devrait rentrer dans l'ordre.

 

 

Cette animation fait un usage intensif du nouveau terme color() de Director 7. Sur cette nouveauté, l'aide en ligne de Director est plutôt avare. color() c'est d'abord une fonction qui crée une entité de type rgb ou paletteIndex selon les paramètres qui lui sont transmis. Par cet formule "entité" on peut provisoirement entendre un analogue des rect() ou des point(). Une fois cette entité constituée, on va bénéficier de plusieurs fonctions permettant de la manipuler. Voyons cela et d'abord dans la fenêtre message.

set monMagenta to color(#rgb, 255, 0 , 255)

Si l'on demande à Director de nous montrer "magenta" nous obtenons

put monMagenta
-- rgb( 255, 0, 255 )

On créé l'entité par la fonction color(). Cette fonction prend les arguments qu'on lui passe et retourne une entité rgb() ou paletteIndex(). Qu'allons nous bien pouvoir faire de ce type de données ? Des opérations diverses comme par exemple celle-ci :

put monMagenta /2
-- rgb( 127, 0, 127 )

Ce qui correspond au violet royal. La possibilité d'effectuer des opérations arithmétique avec cet objet ne doit pas nous faire penser qu'il fonctionne comme les types de données point() ou rect(). Un extraction de liste par exemple n'est pas possible ici et une expression comme getAt(magenta, 2) provoquera une erreur. En revanche une entité de ce type dispose de propriétés et c'est pourquoi l'on dit que c'est un OBJET color. (L'objet Date repose sur le même principe)

put the green of monMagenta
-- 0
put the blue of monMagenta
--127
put the colorType of monMagenta
-- #rgb

On peut connaître aisément l'équivalent indexé de notre couleur (propriété paletteIndex)

put the paletteIndex of monMagenta
-- 2

Cette dernière propriété peut être modifiée pour changer la couleur mais alors la propriété colorType le sera aussi. Il est plus logique de changer ouvertement le mode colorimétrique de référence de notre objet par :

set the colorType of magenta to #paletteIndex
put monMagenta
-- paletteIndex( 2 )

Ici la propriété colorType de l'objet est modifiée de sorte qu'il est référé à la palette en cours d'utilisation au moment de la conversion. Ceci dit on peut toujours connaître la valeur d'une composante RVB.

put monMagenta.red
-- 127

L'objet color possède également une méthode qui renvoie la notation hexadécimale lui correspondant.

put hexString(magenta)
-- "#FF00FF"

 

Conclusion

Un objet color, c'est donc une entité qui peut être exprimée rgb(1,2,3) ou paletteIndex(1) selon l'état de sa propriété colorType, et qui peut-être créée par la fonction color(). Ça représente une couleur - Notez bien ! PAS la couleur de ceci ou de cela, PAS dans un mode colorimétrique particulier, non , une couleur abstraitement dont on peut obtenir les composante RVB ou bien l'équivalent indexé. On peut donc travailler avec l'objet color et définir des couleurs en RVB même si l'animation n'utilise que 256 couleurs. La puissance d'un tel outil apparaît quand on découvre que des mélanges sont possibles.

set uneCouleur to color(#rgb, 255,200,200)
set uneAutreCouleur to color(#rgb, 150,210,60)

put uneCouleur - uneAutreCouleur
-- rgb(105,10,140)

Si vous connaissez Photoshop vous méditerez cette illustration où à droite la couleur verte est appliquée par dessus le rose avec l'encre différence :

Bien sûr rien ne vous empêche de tester les encres de Director lui-même (Somme, différence,...) mais ne lui demandez pas trop de rigueur quand-même !

Faire des mélanges de couleurs dans l'abstrait c'est bien mais que va-t-on faire du résultat si on ne peut finalement l'affecter aux sprites. Nous ne connaissions jusqu'à présent que la propriété the foreColor of sprite qui attend un nombre de 0 à 255 et certes pas un objet. La propriété the paletteIndex of... permet de connaître à tout instant l'équivalent indexé de notre objet couleur. On pourra donc jouer seul dans son coin avec un objet couleur (monMelange), puis finalement pour que le sprite en profite :

set the foreColor of sprite n to the paletteIndex of monMélange

Mais il y a mieux ! La nouvelle propriété the color of sprite représente non pas une valeur numérique indexée mais un entité couleur. Un sprite à donc une propriété "the color of sprite" qui dans tous les cas est un objet color. On pourra donc écrire directement et sans passer par une conversion :

set the color of sprite n to monMélange

On encore et plus respectueusement de la nouvelle notation Lingo :

sprite(n).color = monMelange

Forecolor, color of sprite... C'est la même chose direz-vous ? Non car l'objet color permet des mélanges lumineux ou même seulement peut subir des opérations arithmétiques que la manipulation d'une valeur indexée rend impossible. Démonstration :

On se donne un objet color bleu pur. Dans la palette système Mac (seulement) ce primaire est indexé 210  et nous créons l'objet color avec le type RVB.

set monBleu to color(#rgb, 0,0,255)
put the paletteIndex of monBleu
-- 210

Si je divise maintenant l'intensité lumineuse de ce bleu par 2, j'obtiens l'Obsidienne. Cette très belle teinte bleu sombre est indexée 240 dans la palette Mac. Voyons comment l'objet color se sort d'une division (parenthèses impératives) :

put the paletteIndex of (monBleu/2)
-- 240

Cette simple modification de teinte est quasiment impossible lorsque l'on ne manipule que des valeurs indexées.

 

Créer le sélecteur de couleurs

LES CURSEURS (UN CLASSIQUE)
On dispose sur la scène un acteur forme longiligne et par dessus un bitmap qui constitue notre curseur. 10 piste plus bas on place un acteur QuickDraw de forme ronde. On associe au curseur le comportement suivant.

property pDecalageH, pN, pDebutH

on beginSprite me
  pN = the currentSpriteNum
  pDebutH = sprite (pN - 1).locH
 
 -- la position minimum du curseur c'est l'extrême gauche de la glissière
  -- ici pour un acteur forme c'est "the loc"
  -- nous avons besoin de connaître cette valeur pour
  -- déterminer la position relative du curseur lors des
  -- déplacements le long de la glissière
   sprite(pN).constraint = pN -1
   -- rappel : la glissière est une piste plus haut que le curseur (pN -1)
end

on mouseDown
pDecalageH = the mouseH - sprite(pN).locH
-- classique : on mesure l'écart du bitmap à la souris
-- pour éviter un saut lors du déplacement


 repeat while the stilldown
  sprite( pN). locH = the mouseH - pDecalageH
   -- on déplace le curseur sous la souris
   -- et on force le rafraîchissement
   updateStage
   
  x = (sprite(pN).locH - pDebutH)/0.4
  -- 100 position sont possibles (largeur de la glissière)
  -- la division par 0.4 permet d'obtenir 255 valeurs (100/0.4=255)

   sendSprite(pN+10, #changeDeValeur, x)
   -- on expédie l'info à la forme ronde 10 piste plus bas
 end repeat
end

Selon le même principe les mêmes acteurs sont de nouveau placés sur la scène : l'acteur forme en premier, sur la piste immédiatement dessous l'acteur curseur, dix pistes plus bas la forme ronde. Le même comportement peut dès lors être derechef attaché aux deux nouveaux curseurs.

 

 

 

LES CERCLES DE LUMIÈRE ROUGE, VERTE ET BLEU
Ce sont trois forme QuickDraw placés pistes 13, 15 et 17, la couleur en est indifférente puisque nos curseur la modifient mais pour obtenir l'effet de lumières se superposant on affecte au deux derniers cercles (dessus) l'encre "Somme". Chaque cercle recevra le message #changeDeValeur lors du déplacement du curseur lui correspondant. Afin que ces cercles sachent traiter l'instruction, nous devons à chacun associer le comportement suivant :

property pN

on beginSprite me
pN = the currentSpriteNum
end

on changeDeValeur me, laquelle
case pN of
13: -- rouge
 sprite(pN).color = rgb(laquelle,0,0)
 -- c'est ici que l'on affecte au cercle une intensité de rouge
 sendSprite(10, #changeDeCouleur, #rouge, laquelle)
 sendSprite(11, #changeDeCouleur, #rouge, laquelle)
 -- on adresse aussi un message et deux arguments à deux
 -- autres sprites pistes 10 et 11, ce sont les témoins
 -- rectangulaires voir plus bas

15: -- vert
sprite(pN).color = rgb(0,laquelle,0)
 -- on affecte au cercle une intensité de vert uniquement
 sendSprite(10, #changeDeCouleur, #vert, laquelle)
 sendSprite(11, #changeDeCouleur, #vert, laquelle)

17: -- bleu
sprite(pN).color = rgb(0,0,laquelle)
 sendSprite(10, #changeDeCouleur, #bleu, laquelle)
 sendSprite(11, #changeDeCouleur,#bleu, laquelle)
end case
updatestage
end

 

LE RECTANGLE DE COULEUR COMPOSITE RVB
Un acteur forme là encore (piste 10) qui recevra des trois cercles le message #changeDeCouleur joint à ses paramètres. Nous dotons ce rectangle de ce comportement.

property pN

on beginSprite me
   pN = the currentSpriteNum
end

on changeDeCouleur me, quelleCouleur, quelleProportion
uneCouleur = sprite(pN).color
-- NOTA : bizarrement "sprite(pN).color.red = x" ne marche pas !
-- Director exige pour le sprite un changement global d'objet couleur
-- on créé donc un autre objet color reprenant les valeurs actuelle de
-- sprite(pN).color. C'est ce nouvel objet (unCouleur) que l'on modifie
-- puis avec lequel on remplace sprite(pN).color
case quelleCouleur of
   #rouge :
     uneCouleur.red = quelleProportion
   #vert:
     uneCouleur.green = quelleProportion
   #bleu :
     uneCouleur.blue = quelleProportion
   end case
   sprite(pN).color = uneCouleur
  updateStage
end

 

LE RECTANGLE D'AFFICHAGE DE COULEUR INDEXÉES
Le grand rectangle d'affichage de la couleur indexée correspondante (piste 11). Un acteur forme identique au précèdent et dont le comportement sera très semblable. Une seule ligne diffère :

property pN

on beginSprite me
pN = the currentSpriteNum
end

on changeDeCouleur me, quelleCouleur, quelleProportion
uneCouleur = sprite(pN).color
case quelleCouleur of
   #rouge :
   uneCouleur.red = quelleProportion
   #vert:
   uneCouleur.green = quelleProportion
   #bleu :
   uneCouleur.blue = quelleProportion
 end case
uneCouleur.colorType = #paletteIndex
-- on change le type de l'objet avant
-- d'en faire the color of sprite..

sprite(pN).color = uneCouleur
updateStage
end

Dans ces dernières lignes une autre possibilité s'offrait qui consistait à récupérer l'équivalent numérique indexé de notre objet RVB puis à en nourrir la propriété the foreColor of sprite.

 sprite(pN).foreColor = uneCouleur.paletteIndex

 

 Application : setPixel() et getPixel()

Afin d'exploiter l'objet color nous avons créé une animation mettant en oeuvre deux fonctions non documentées de D7 : getPixel() et setPixel(). La fonction getPixel() permet de connaître la valeur indexée d'un pixel d'un un acteur bitmap 8 bits, La fonction setPixel() permet de changer la couleur d'un pixel précis. Promenez donc votre curseur sur les Imacs pls bas... (shockWave 7.0)

 

 

 getPixel()

GetPixel() requiert 3 arguments : un acteur bitmap et le delta d'un pixel, noté - sur le Mac - depuis le coin supérieur gauche de l'acteur ET SUR LE PÉCÉ DEPUIS LE COIN INFÉRIEUR GAUCHE. Étrangement le premier pixel de l'acteur a les coordonnées 0,0. Si on place une apparition de l'acteur sur la scène, on s'épargnera une soustraction en le calant sur le coin supérieur gauche. De la sorte - sur le Mac - le premier pixel de l'acteur (0,0) à les coordonnées point(1,1) sur la scène.

A coté du bitmap, sur la scène, on a disposé 49 fois la même forme QuickDraw et demandé en Lingo que lors d'un mouseDown sur la photo, les 49 formes reprennent la couleurs des pixels survolés. Pour la forme carrée centrale (sprite z) 4ème ligne, 4ème carré, qui doit afficher la couleur du pixel exactement sous le pointeur, le code est le suivant :

 

repeat while the stilldown
  x = getAt(the mouseloc,1) - 1
  y = getAt(the mouseloc, 2) -1
w = 1
 if the platform contains "Windows" then w = 7
-- Rappel : le système de coordonnées n'est plus le même sous windows
-- l'acteur 7 ici est une copie du bitmap (Édition/Dupliquer) à laquelle
-- on a fait subir une symétrie verticale dans la fenêtre dessin de Director.
-- C'est l'image normale (acteur 1) que l'on a placée sur la scène, mais c'est
-- cette copie inversée dont on extrait la couleur des pixels.

  sprite( z ).forecolor = getpixel(member w, x , y )

 

Les autres pixels de l'image source sont récupérés en déplaçant le focus de la fonction vers la droite (x + 1), la gauche (x - 1) ou vers le bas (y + 1). Et ainsi:

  sprite (z + 1).forecolor = getpixel(member w, x + 3, y )
  sprite (z + 7).forecolor = getpixel(member w, x, y + 1 )
 etc.

On finit par l'inévitable updateStage qui force au sein d'une boucle repeat le rafraîchissement écran.

  updatestage
end repeat
end

ATTENTION ! La fonction getPixel() fonctionne un peu différemment selon que l'on utilise la version 7.0 ou la mise à jour 7.0.2. Avec cette dernière version, il faut pour obtenir la référence indexée de la couleur du pixel effectuer une petite soustraction. La couleur est dans ce cas 255 moins la valeur retournée. Dans D7.0.2, on écrira donc :

set the forecolor of sprite z to 255 - getpixel(member w, x , y )

Surtout ne me demandez pas pourquoi cette différence ! Et ne le demandez pas non plus à Macromedia, ils vous répondront que getPixel() n'existe pas. Pour réaliser le shockwave ci-dessus, nous avons utilisé D7.0F parce que le plug-in semble s'y accorder.

Dernière remarque : GetPixel() retourne aussi une valeur (entre 0 et 16777215 lorsque l'image est en 16 bits), valeur qu'il est aisé de convertir en base hexadécimale pour obtenir un triplet rgb (en 38 bit la valeur retournée sera divisée par 8). toutefois les différences de plate-formes et de versions rendent difficile son exploitation. Alors : à vous de jouer.

 

SetPixel() et... l'objet Color

 

 

SetPixel() permet comme on l'aura deviné de modifier la valeur indexée d'un pixel d'acteur bitmap. L'instruction requiert 4 arguments. L'acteur bitmap a modifier, le delta du pixel et la référence indexée de la couleur.

Nos essais nous ont montré que l'exécution sur un grand nombre de pixels n'était pas des plus rapide aussi nous avons préféré utiliser une petite image que nous avons fortement étirée sur la scène de Director.

Lors du clic sur le bouton "+ clair" nous souhaitons que chaque pixel du nounours soit éclairci. Nous savons que nous pouvons connaître sa valeur indexée par getPixel() mais pour connaître la valeur indexée immédiatement plus claire nous devons en passer par l'objet color. Rappel : le premier pixel de l'acteur est noté 0,0, nous devons commencer le recensement à 0 et ainsi écrire :

on mouseUp
  repeat with x = 0 to (the width of member 1) - 1
    repeat with y = 0 to (the height of member 1) - 1

Pour chaque pixel de l'image, pris un à un, nous notons sa valeur par getPixel puis, au fin de conversion et de calculs, nous créons un objet color correspondant.

       unObjetColor = paletteIndex ( getpixel(member 1, x, y))

Sous D7.0.2, il aurait fallu écrire  unObjetColor = paletteIndex (255 - getpixel(member 1, x, y)). Maintenant, nous devons convertir le type de l'objet en RVB afin de rendre possible les opération arithmétiques :

     unObjetColor.colortype = #rgb
     unObjetColor = unObjetColor + 60

Nous retrouvons l'équivalent indexée grâce à la propriété the paletteIndex of unObjetColor

     z = unObjetColor.paletteIndex

Et enfin nous convertissons le pixel en cette nouvelle valeur.

     setPixel(member 1, x, y, z)

Rappel, sous D7.0.2 : setPixel(member 1, x, y, 255 -z)

     end repeat
    
updateStage
  end repeat
end

 le dernier updateStage ne rendra certes pas le processus plus rapide mais il nous permet de voir Director travailler. Nous renonçons toutefois à le placer dans la boucle intérieure. Ici, UpdateStage rafraîchira l'écran après chaque changement d'une colonne.

 

 

L'animation "paletteRGB" constitue , une fois placée dans le dossier Xtras de Director 7, un sélecteur de couleur rgb fort utile... et en plus, le code de David est ouvert. Cliquez pour télécharger le fichier (Mac Stuffit 26 Ko).