Espaces colorimétriques et applications à flash MX

Par Damien

img:coupe


img:remarque Le but de ce tutorial/article est de présenter en première partie l'écriture héxadécimale des couleurs, avec deux fonctions qui permettent de passer facilement d'une écriture (r,b,v) à l'écriture héxadécimale.
En deuxième partie, deux systèmes colorimétriques sont étudiés: RVB, synthèse additive, et HSB qui est une approche beaucoup plus naturelle pour nous autres êtres humains.
Le système CMJN (synthèse soustractive) ne sera pas abordé, il est surtout utilisé pour l'impression.
A part de sombres histoires de saturation et de teintes, deux fonctions seront fournies qui permettent de passer d'un système à l'autre (rgb <-> hsb).
Enfin, en troisième partie, on parlera du Color.setTransform(obj); de Flash, et de ses multiples applications.
Par exemple, comment obtenir le négatif d'un photo par actionSCript, ou reproduire des modes de filtre de PhotoShop.
Quatre de ces modes seront expliqués: densité linéaire +/- , écran, et densité couleur - (très populaire smile.gif )

img:coupe

img:remarque plan (chacune de ces parties fera l'objet d'un post dans ce meme sujet, pour mieux structurer:

I. Conversion hexadécimal / décimal
II. Espaces colorimétriques, teintes, complémentaires
III. setTransform() sous flash, effets de base


img:coupe

I. Conversion hexadécimal -> décimal

L'écriture en héxadécimal, c'est l'écriture en base 16.
C'est-à-dire que l'on utilise un alphabet de 16 'chiffres' pour compter:
0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f
(pour completer, on a choisi des lettres de l'alphabet occidental, ce qui nous évite d'avoir à inventer de nouveaux symboles...)

Dans cette écriture, '2' correspond à 2, 'a' correspond à 10, et '10' correspond à 16.
Fort bien.
Autre exemple, 'ff' correspond à 255.

On veut écrire deux fonctions, l'une permettant de passer de l'écriture décimale à l'héxadécimale, et la seconde pour procéder à la transformation inverse.

Notre premier problème est assez simple, il s'agit de changer de base un nombre.
Pour cela, on utilise la division euclidienne (ah à l'époque j'avais un programme sur ma TI82... séquence souvenir):
137 = 16 * 8 + 9

donc le premier chiffre est 8, le second et bien c'est le reste, c'est-à-dire: 9.

'89' en héxadécimal correspond à 137 en notation décimale traditionnelle.

Pour vous en convaincre wink.gif , vous pouvez tester sous flash:

CODE
/* sous flash, le préfixe '0x' sert à forcer en héxadécimal */

test= ( 0x89 == 137 );

trace("Est-ce que Damien a raison ? "+test);



/* aaah j'aime avoir raison :-) */


On a vu que pour trouver les chiffres qui vont constituer notre nombre en héxadécimal il suffit de proceder à une division euclidienne. Voici un autre exemple:

234 = 16 * 14 + 10

Donc:
le premier chiffre est 14 , c'est-à-dire 'e'
le second chiffre est 10 , c'est-à-dire 'a'

CODE
trace( (0xea == 234) ); /* affiche 'true' */



Bon, maintenant, il faut que j'avoue.
Tout cette théorie ne vous servira à rien (enfin c'est bon à savoir quand mème) ...
Pourquoi ?
Et bien comme vous avez pu le constater avec les exemples, flash ne fait absolument aucune différence entre '0xea' et '234' ...
Donc connaitre l'écriture explicite ne vous servira en général pas.

Par contre, ce qu'on voudrait, c'est convertir un triplet (rouge, vert,bleu) par exemple (123,255,43) , en un seul nombre, ici 8126251 (strictement pareil que 0x7bff2b , je le repete).

:arrow: La fonction est très simple et utilise les opérateurs binaires << qui décale à gauche, et | pour 'souder' les bouts.
ainsi r<<16 correspond à r*(2^16) donc r*(256²)
de mème g<<8 correspond à g*(2^8 ) donc g*256
Etant donné que r, g, b sont compris entre 0 et 255, on reconnait une écriture en base 256=2^8 , et on est content. smile.gif

CODE
rgb2hex = function(r,g,b){

return((r<<16)|(g<<8)|b);

}


:arrow: Pour la transformation inverse, on utilise le << déjà vu, le >> décalage à droite, correspond au quotient entier de la division euclidienne, et ^, le XOR qui donne le reste de la division euclidienne.
<< est prioritaire sur ^ comme vous pouvez le voir:

CODE
hex2rgb = function(n){

var R = n >> 16;

/* On prend le quotient par 256²,

** donc on décale RVB de 2 rangs vers la droite,

** on n'a donc plus que __R, la composante en rouge */



var temp = n ^ R << 16;

/* on enlève cette composante à la couleur,

** on se retrouve donc avec : _GB */



var G =  temp >> 8;

/* redécale _GB de 1 rang en base 256 vers la droite,

** on a donc: __G  la composante en vert */



var B = temp ^ G << 8;

/* on elève la composante en vert (G) à _GB

** on se retrouve avec __B, la composante en bleu */



return {r:R, g:G, b:B};

}


Je regrette de ne pouvoir expliquer plus clairement, il faut être un minimum familiarisé avec les changements de bases, mais ça vient vite.

On peut avoir maintenant l'inexprimable satisfaction de tester ces deux fonctions:

CODE
couleur=0x76f29a;

test2= (couleur == rgb2hex( hex2rgb(couleur).r , hex2rgb(couleur).g , hex2rgb(couleur).b ) );

trace("Ca marche ? "+ test2);



/* y'a interet :D */


img:coupe

img:remarque Dans le prochain post: espaces colorimétriques RGB / HSB

img:coupe


II: Les Espaces colorimétriques RGB et HSB:
img:coupe

:arrow: RGB : Red, Green, Blue
pour 'rouge', 'vert', 'bleu'
:arrow: HSV ou HSB : Hue, Saturation, Value/Brightness pour 'teinte', 'saturation', 'valeur'.

img:coupe
img:remarque En informatique, lorsqu'il s'agit de definir une couleur, on emploie en général le système RGB
qui donne les composantes (synthèse additive) en rouge, vert et bleu.

En prenant toutes les combinaisons possibles, on obtient toutes les couleurs de l'arc-en-ciel.

Pourtant, ce système ne nous est pas naturel: on a tendance, pour décrire une couleur, à parler de teinte, de luminosité, éclat, saturation, et autres notions qui peuvent paraitre quelque peu vagues à un guru de l'héxadécimal...
Un système exploitant ces notions existe pourtant, c'est le HSB pour 'hue', 'saturation', 'brightness' que l'on a tendance à traduire par 'teinte', 'saturation', 'valeur' en français.
Cependant, on préférera dans ce tutorial utiliser la traduction "exacte" de 'brightness' qui me semble plus claire: il s'agit de l'éclat, ou, au choix, de l'intensité d'éclairage ou encore brillance (on emploiera indistinctement les trois termes).
Quant à la saturation, on peut dire qu'elle exprime la richesse en teinte.

Ainsi, une couleur peu saturée n'est pas très "colorée" smile.gif : elle se rapproche d'un gris quelconque.
De même, une couleur de faible intensité se rapproche du noir, elle reçoit peu de lumière.

Il est conseillé de d'étudier de près comment marchent les palettes de couleurs de vos logiciels préférés, vous avez ici une capture d'écran comentée de photoshop 7.

Pour mieux faire le lien avec le système RGB, on peut constater que:

Une couleur saturée à 100% a une composante égale à 0;
Une couleur d'intensité 100% a une composante égale à 255.

Ceci est très important, et permet de bien comprendre qu'une baisse de saturation traduit une dilution dans du blanc, une baisse d'intensité/brillance correspond à une dilution dans du noir.

user posted image
Maintenant que l'on a répété trois ou quatre fois la même chose smile.gif , on peut parler de 'teinte'.

Une teinte est une couleur saturée à 100% et d'intensité 100% .
Il n'y a donc aucune perte d'information due à l'ajout de blanc ou noir.

Les couleurs de ce type sont de la forme : (0,255,34) , (255,213,0) , ou encore 0x0034ff .

On numérote toutes ces couleurs de 0 à 359 (par analogie aux degrés) suivant le schéma ci-dessous:
(valeur repères RGB en haut, numéros de teinte en bas).

[img]http://flash.media-box.net/files/2_1085583133.jpg[/img]
Les transformations entre chaque repère sont linéaires, on a donc:

teinte 0 -> 255,0,0
teinte 1 -> 255,4,0
teinte 2 -> 255,8,0
...
teinte 60 -> 255,255,0
teinte 61 -> 251,255,0
teinte 62 -> 246,255,0
...
teinte 120 -> 0,255,0
... etc

(on arrondit quand il faut pour que ça tombe juste)


Le fait de placer les teintes sur un cercle est compréhensible quand on voit que:
1) la teinte 360 est la meme que 0.
2) et que d'ailleurs on aurait pu commencer par autre chose que le rouge.

Donc on voit une fonction périodique (2) et continue (1) (sans coupures)
d'où l'idée de placer tout ça sur un cercle smile.gif

De plus, le complémentaire d'un couleur se trouve comme par magie 180 teintes plus loin, c'est-à-dire diametralement opposé smile.gif ce qui est tout à fait satisfaisant smile.gif .

(complémentaire au sens propre du terme, c'est-à-dire "ce qu'il manque pour arriver à 255". Le complémentaire de (127,10,220) est (128,245,35).


On a maintenant bien cerné toutes les notions que l'on va utiliser par la suite, à savoir teinte, saturation, et intensité/brillance.

img:remarque Il faut maintenant se recentrer sur notre objectif, qui est d'écrire deux fonctions permettant de passer de RGB à HSB et inversement.

Avant tout, il nous faut une fonction pour trier nos futurs tableaux, allez voir du coté de l'aide sur Array pour plus d'informations



img:as
CODE
fsort = function( a , b ) {

var val1 = Number(a.split(':')[1]);

var val2 = Number(b.split(':')[1]);

if (val1 > val2) return -1;

else if (val1 < val2) return 1;

else return 0;

}


Cette fonction servira à trier des tableaux temporaires.
Mais voici la problématique:

:arrow: La première fonction transforme le format RGB en HSB.
Cette fonction reçoit par exemple (100,23,236) qu'elle transforme en objet couleur={r:100,g:23,b:236};
Un des points importants comme on l'a vu est de determiner les couleurs fortes et faibles; En effet composante la plus forte sera transformée en 255, la plus faible en 0 pour obtenir notre teinte. Au passage on aura aussi les pourcentages et le tour sera joué.
Dans notre exemple, (100,23,236) deviendra ( ? , 0 , 255) , il nous reste à determiner la composante en rouge.

Revenons à nos tableaux;
A partir de l'objet {r:100,g:23,b:236} on aimerai obtenir un tableau de classement de la forme :
tab[0]="b";
tab[1]="r";
tab[2]="g";

'0' désignant la composante la plus forte de la couleur.

On pourra ainsi facilement manipuler les couleurs avec des expresions:
couleur[tab[0]] == couleur["b"] == couleur.b= 236
(la couleur la plus forte)
De meme pour avoir la composante la plus faible on utilisera
couleur[tab[2]]


img:as
CODE


function rgb2hsv(red,green,blue){

var temp=["r:"+red,"g:"+green,"b:"+blue].sort(fsort);

var order=[ temp[0].split(':')[0] , temp[1].split(':')[0] , temp[2].split(':')[0] ];

var final={r:red,g:green,b:blue};



/* la brillance correspond au rapport de la plus forte composante sur 255 */

var V=100*final[order[0]]/255; /* multiplié par 100 pour avoir un pourcentage */

/* on met à jour notre couleur grace à ce pourcentage

** on a maintenant 100% de brillance */

final.r*=100/V;final.g*=100/V;final.b*=100/V;



/* la saturation correspond au rapport

** du complementaire de la nouvelle composante plus faible

** sur 255 */

var S = 100* (255-final[order[2]])/255; /* saturation, multipliée par 100 pour avoir un % */



/* on remodifie notre couleur pour la saturer, selon le pourcentage trouvé */

final[order[1]]=Math.round( 255*(1 - (255-final[order[1]])/(255-final[order[2]]) ));

final[order[0]]=255; final[order[2]]=0; // couleur saturée

return({h:final,s:S<<0,v:V<<0});

}


A mon avis, une étape indispensable pour pouvoir comprendre quelque chose est de s'amuser avec sa palette de couleurs pendant 30 minutes, en particulier de voir ce qui change quand on ne modifie que la saturation ou que la brillance, de 0 à 100.
Après, tout coule de source : dans la capture, on peut voir que:
en RGB, la composante la plus forte est celle en rouge, et vaut 205.
205/255 = 0.8 donc la brillance vaut 80%
Après, la saturation est un peu moins évidente mais néanmoins compréhensible.

Une fois ce système bien assimilé, il ne devrait pas y avoir de problèmes pour la fonction inverse.
Celle-ci prend en parametre une teinte, donc un objet {r:255,g:0,b:134} (par exemple) , et deux pourcentages.


img:as
CODE


function hsv2rgb(myColor,s,v){

var temp=["r:"+myColor.r,"g:"+myColor.g,"b:"+myColor.b].sort(fsort);

var order=[temp[0].split(':')[0],temp[1].split(':')[0],temp[2].split(':')[0]];

var final={r:myColor.r,g:myColor.g,b:myColor.b};



final[order[2]]+=(100-s)*2.55;

final[order[1]]+=(255-final[order[1]])*(100-s)/100; /* voilà on a désaturé notre couleur */

final[order[0]]*=v/100;final[order[1]]*=v/100;final[order[2]]*=v/100;

return(final);

}


img:coupe
Dans la prochaine partie, le code des filtres photoshop


img:coupe


III Color.setTransform(obj);
img:coupe

Passons maintenant aux choses sérieuses :twisted:

Ouvrez flash, importez une photo, cliquez dessus puis sur f8 pour la transformer en MovieClip , avec un nom quelconque.
Cliquez sur votre clip pour le selectionner, puis donnez-lui le nom d'occurence 'clip' dans le panneau "propriétés" de flash.


Dans les actions de la première image (frame) , entrez ce code:

couleur = new Color(_root.clip);

Vous venez de déclarer une variable de type 'Color' qui est affectée au clip '_root.clip' c'est-à-dire le MovieClip avec votre photo.

Il est maintenant possible d'apliquer des transformations de couleur sur ce clip avec la fonction setTransform(objet);

L'aide sur cette fonction est disponible ici, si par la suite vous souhaitez eclaircir quelques points:
http://www.flash-forum.net/aide/Flash/html...3_a_to_c76.html


Le transformations que l'on peut appliquer à un MovieClip sont uniformes (on n'a pas accès aux données sur chaque pixel) mais permettent quand même de jolis effets.
Une transformation de couleur est fixée par six paramêtres:
rb , gb , bb le décalage des composantes r, g, b, et:
ra , ga , ba le pourcentage en r, g, b .
On remarque que la transformation est finalement une simple fonction affine y= A.x + B (Macromédia a meme gardé les lettres 'a' et 'b' , pour le coefficient et "l'ordonnée à l'origine".)

Ces parametres doivent etre compris comme d'habitude, l'ordonnée à l'origine étant la nouvelle composante lorsque l'ancienne valait 0.

Mais donnons un exemple:
rb = gb = bb = 100;

le noir devient du gris (100,100,100) , et toutes les composantes sont augmentées de 100 (jusqu'à la limite de 255)
L'image va etre eclaircie, et de plus pour toutes les couleurs qui ont des composantes supérieures à 155, la teinte va etre déformée et "brulée" comme en photo (trop de lumière).

Autre exemple: on va transformer une image de façon a obtenir son négatif:
rb = gb = bb = 255;
ra = ga = ba = -100;


En effet pour un composante, on cherche sont complémentaire, c'est à dire ce qu'il manque pour ariver à 255, donc:
255-x

donc, une pixel (r , g , b ) devient: (255-r , 255-g , 255-cool.gif
On a bien:
un décalage 'b' de 255 sur les trois couleurs
un coefficient 'a' négatif égal à -1, donc -100%

Pour tester cet objet de transformation, c'est très simple:

CODE
couleur= new Color(_root.clip);

// _root.clip contient une photo par exemple

var obj = new Object();

obj.ra = obj.ga = obj.ba = -100;

obj.rb = obj.gb = obj.bb = 255;

couleur.setTransform(obj);


On peut transformer ce code en un prototype pour l'objet Color, de cette façon:

CODE
Color.prototype.negative = function () {

var obj = new Object();

obj.ra = obj.ga = obj.ba = -100;

obj.rb = obj.gb = obj.bb = 255;

this.setTransform(obj);

}


On peut placer ce code dans un calque à part, et ensuite il suffira d'écrire:

CODE
couleur= new Color(_root.clip);

couleur.negative();


qui est beaucoup plus pratique : la fonction (méthode) que l'on a ajouté au prototype de l'objet Color est maintenant disponible à partir de n'importe où.

Voici donc des prototypes pour des effets elementaires, que l'on retrouve dans beaucoup de logiciels de traitement d'image: "addition", "soustraction", "difference" , "multiplication", "division":


CODE
Color.prototype.addition = function(r,g,b){

var trans = new Object();

trans.rb=r;

trans.gb=g;

trans.bb=b;

this.setTransform(trans);

}

Color.prototype.soustraction = function(r,g,b){

var trans = new Object();

trans.rb=r; trans.ra=-100;

trans.gb=g; trans.ga=-100;

trans.bb=b; trans.ba=-100;

this.setTransform(trans);

}



Color.prototype.difference = function(r,g,b){

var trans = new Object();

trans.rb=-r;

trans.gb=-g;

trans.bb=-b;

this.setTransform(trans);

}

Color.prototype.diviser = function(r,g,b){

var trans = new Object();

trans.ra=100/((r+1)/256);

trans.ga=100/((g+1)/256);

trans.ba=100/((b+1)/256);

this.setTransform(trans);

}

Color.prototype.multiplier = function(r,g,b){

var trans = new Object();

trans.ra=r/2.55;

trans.ga=g/2.55;

trans.ba=b/2.55;

this.setTransform(trans);

}


Comme vous voyez on ne fait que...multiplier, additionner, soustraire et diviser smile.gif

img:remarque A noter que la division et multiplication pour intervenir des facteurs 100 et 255, pour avoir des pourcentages et non des rapports entre 0 et 1.

img:remarque Autre remarque: il ne faut pas confondre "soustraction" et "difference", il s'agit de l'operation inverse...

:idea: Maintenant que l'on a ces fonctions de base, on peut essayer de voir qu'est-ce que l'on en peut tirer de bon smile.gif

On remarque que le mode de calque 'densité linéaire -' c'est à dire 'linear dodge' de photoshop correspond à notre bonne fonction 'addition', que l'on rebaptise illico:
CODE
Color.prototype.linearDodge = function(r,g,b){

// correspond à:

// calque rempli de (r,v,b) en mode linear Dodge

// par dessus l'image

// Conforme à PHOTOSHOP

var trans = new Object();

trans.rb=r;

trans.gb=g;

trans.bb=b;

this.setTransform(trans);

}


On essaie de reproduire le mode 'linear Burn' ou 'densité linéaire +' :
Pour eclaircir selon une couleur, on a vu qu'il fallait additionner cette couleur, donc pour assombrir selon cette meme couleur, on est tenté de soustraire cette couleur ? En fait, il est facile de voir que non, car si on enlève un couleur on augmente implicitement son complémentaire dans la teinte.
Il faut donc soustraire le complémentaire de cette couleur :
CODE
Color.prototype.linearBurn = function(r,g,b){

// correspond à :

// calque avec rgb en mode linearBurn

// par dessus la photo

// Conforme à PHOTOSHOP

var trans = new Object();

trans.rb=r-255;

trans.gb=g-255;

trans.bb=b-255;

this.setTransform(trans);

}


Maintenant, il faudrait songer à utiliser la multiplication et la division:
On remarque que la mutiplication assombrit notre image.
Pour eclaircir notre image selon une couleur, on a donc l'idée de diviser par son complémentaire:
L'éclaircissement est beaucoup plus violent qu'avec le filtre précédent, il s'agit du fameux 'densité couleur -' cher aux amateurs de photoshop smile.gif

CODE
Color.prototype.colorDodge = function(r,g,b){

// correspond à:

// calque avec rvb en mode 'colorDodge'

// par DESSUS l'image



// conforme PHOTOSHOP

var trans = new Object();

trans.ra=100/((258-r)/256);

trans.ga=100/((258-g)/256);

trans.ba=100/((258-b)/256);

this.setTransform(trans);

}



Vous pourrez verifier que ces trois derniers prototypes correspondent bien aux modes de calque photoshop.

Voici pour finir un dernier effet de calque: le mode "écran" ou "screen" :

CODE
Color.prototype.screen = function(r,g,b){

// correspond à :

// image en mode screen

// par dessus calque rvb

// conforme à photoshop

var trans = new Object();

trans.rb=r; trans.ra=100*(255-r)/255;

trans.gb=g; trans.ga=100*(255-g)/255;

trans.bb=b; trans.ba=100*(255-b)/255;

this.setTransform(trans);

}


En étudiant le code, on remarque:
un décalage exact de la couleur fournie en parametre, qui prend la place du noir.
Ensuite, une homothétie pour que les composantes ne dépassent pas 255 et gardent la meme proportion.
Les couleurs sont moins saturées, mais l'ensemble garde un aspect cohérent, c'est... le mode écran smile.gif .

Voilà, des exemples sont joints, avec les sources.

A bientot,
Damien