Licence CC BY-SA

De TSV à RVB

Publié :

Le modèle de couleurs RVB est assez limité, avec les filtres que l'on a créé on avait du mal à gérer la teinte des images. En échangeant les canaux par exemple on pouvait transformer une image à dominante rouge en une image à dominante bleue. Mais les modifications de teintes en RVB sont assez limitées : on ne pouvait pas la rendre un peu moins rouge et un peu plus orange pour continuer l'exemple. De plus, la gestion du noir & blanc était un peu empirique.

On va donc s'intéresser dans les chapitres qui suivent au modèle TSV qui va nous permettre de créer de meilleurs filtres plus simplement.

Dans ce chapitre le but va être de programmer une fonction qui à partir d'une couleur en TSV, la transforme en couleur exprimée dans l'espace de couleurs RVB. Nous allons commencer par créer une simple roue des couleurs ; ensuite nous verrons les notions de Saturation et de Valeur.

Teintes "pures"

On cherche à générer un dégradé de toutes les couleurs pures. On appelle couleur pure la couleur que l'on voit lorsque la lumière est monochromatique i.e. il n'y a qu'un seul type de photon avec une seule longueur d'onde présent dans ses faisceaux. La couleur perçue n'est pas mélangée avec d'autres couleurs.

On cherche donc à recréer.. l'arc en ciel ! Ou encore ce que l'on voit lors du passage de la lumière blanche à travers un prisme :

Diffraction de la lumière par un prisme

Vous pouvez apercevoir sur cette photo que l'arc-en-ciel est un dégradé entre les trois couleurs primaires et secondaires alternées du modèle RVB.

Nous allons essayer de recréer ce dégradé, c'est-à-dire trouver une fonction qui à partir d'un réel nous donne une des couleurs de l'arc-en-ciel de façon aussi précise que l'on veut.

Pour résumer le plus important, un des canaux dans notre nouvel espace de couleurs sera la Teinte :

  • qui indique l'emplacement de la couleur sur l'arc-en-ciel et permettra directement de catégoriser la couleur parmi Rouge Jaune Vert Cyan Bleu Magenta,
  • et qui n'est défini que si la couleur n'est pas noire (puisqu'à ce moment là il n'y a pas de lumière émise)
  • et que si la couleur n'est pas blanche puisque la lumière blanche est composée de toutes les teintes.

Recréer l'arc-en-ciel

Barycentre entre deux couleurs

Avant de continuer, il faut savoir réaliser une fonction de barycentre entre deux valeurs réelles a et b. Voici les hypothèses que l'on pose pour trouver cette fonction :

  1. Cette fonction de la variable x est affine en fonction de x
  2. Quand x vaut 0 la fonction vaut a
  3. Quand x vaut 1 la fonction vaut b

Voici les transcriptions mathématiques de ces hypothèses :

  1. il existe deux coefficients c et d tels que $f(x)=c x+d$
  2. $f(0) = a$
  3. $f(1) = b$

Voici une solution au problème si vous n'avez toujours pas trouvé :

Il faut remplacer $f(0)$ et $f(1)$ par son expression affine et les équations nous donnent : $c = b - a$ ainsi que $d = a$ .

D'où $f(x)=(1-x)a + xb$ .. C'est la formule du barycentre entre a et b !

Coder une fonction d'arc-en-ciel

Pour recréer l'arc-en-ciel on va donc tout simplement réaliser 6 barycentres entre les couleurs primaires et secondaires (de façon alternative). Par exemple entre le Rouge et le Vert se trouve le Jaune. L'arc-en-ciel débute donc par un dégradé (comprendre mathématiquement un barycentre) entre le Rouge et le Jaune, puis continue avec un dégradé entre le Jaune et le Vert.

La même logique continue :

L'arc-en-ciel se poursuit avec un dégradé du Vert vers le Cyan, puis du Cyan vers le Bleu, puis du Bleu vers le Magenta et enfin du Magenta vers le Rouge.

Ne pas oublier le dernier dégradé qui permet de revenir à la couleur initiale. Je rappelle que la teinte est une information circulaire, comme un angle qui peut s'exprimer de façon unique entre 0 et 360° non inclus. Le fait que le rouge débute l'arc-en-ciel est arbitraire, vous faites ce que vous voulez de ce point de vue-là. Dans la suite du tutoriel, par convention, le Rouge correspondra à une teinte de 0.

Intervalle de Teinte

Il ne vous reste plus qu'à décider des bornes dans lesquels l'information de Teinte va se situer. Cette borne est arbitraire mais il est important de savoir qu'elle change la précision de l'information de teinte.

Par exemple la teinte peut-être entre 0 et 1 (pour faire comme les autres variables), entre 0 et 6 (puisqu'il y a RJVCBM), entre 0 et $2 \pi$ ou 360 (si on fait l'analogie avec un angle exprimé en radians ou en degrés)…

Algorithme final

L'algorithme final consiste simplement à repérer entre quelles couleurs il faut réaliser un dégradé. Pour cela, on divise notre intervalle en 6 intervalles, on sélectionne les deux couleurs avec lesquelles il faut faire un dégradé, on calcule le coefficient du barycentre. On a plus qu'à appliquer la fonction de barycentre avec le coefficient et les deux couleurs choisies.

Pour sélectionner les deux couleurs avec lesquelles faire le dégradé, je vous conseille de trouver la première en s'inspirant de la fonction en escalier du sous-chapitre précédent. La seconde couleur est naturellement la suivante parmi RJVCBM.

Ne pas oublier le dernier dégradé qui est celui du Magenta au Rouge.

Vous avez maintenant tout ce qu'il vous faut pour produire l'algorithme final qui permet d'avoir une couleur pure à partir de son information de Teinte.

Exo : générer une roue des couleurs

Ce qui est demandé

Maintenant que vous avez programmé votre fonction de Teinte, vous pouvez vous amuser à générer une roue des couleurs : cela permettra en plus de vérifier que vous ne vous êtes pas trompé !

Pour cela, vous avez juste besoin de changer la teinte en fonction de l'angle du pixel par rapport au centre de l'image.

Indice : | La fonction en Processing atan2(y,x) permet de récupérer l'angle modulo du point (x,y) par rapport au point (0,0).

Une solution pour la roue des couleurs en Processing

Voici une solution possible de la roue des couleurs en Processing si votre fonction de teinte est RVB RVB_Depuis_Teinte(float teinte){...} :

1
2
3
4
5
6
7
pixels[ y*Largeur + x ] =
  RVB_Depuis_Teinte(
    atan2(
        float(y) - Hauteur/2.0
      , float(x) - Largeur/2.0
    ) / TWO_PI
  ).ConvertirColor();

Roue des couleurs

Si vous ne trouvez pas le même type d'image (si elle est simplement pivoté c'est bon), vous vous êtes peut-être trompé dans l'algorithme de roue des couleurs.

Je vous invite alors à déboguer votre programme et à réfléchir encore une fois aux formules de maths vues depuis le début du tutoriel.

Je ne vais pas vous donner le code pour RVB_Depuis_Teinte. C'est à vous de le coder ! Vous avez toutes les indications dans ce tutoriel pour y arriver ;) .

Dans la suite du tutoriel, toutes les fonctions que vous devez coder vous même (classe RVB, TSV & méthodes de conversion) se trouveront dans le fichier RVBTSV.pde qui doit être placé dans le même dossier que votre application pour que celle-ci fonctionne. Je n'ai pas uploadé ce fichier sur mon site : les exemples que vous téléchargez ne peuvent pas marcher tels quels.

La roue des couleurs postérisée !

Vous pouvez dès maintenant créer une roue des couleurs "discrète" en postérisant la teinte :

1 et 7 marches 2 et 8 marches 3 et 12 marches 4 et 16 marches 5 et 32 marches 6 et 64 marches

Ici les images pivotent puisque la postérisation dépend aussi d'où "commencent" les marches. Je rappelle que la valeur de la teinte à 0 est arbitraire ; ces animations montrent donc pour chaque valeur de la teinte à l'origine ce que la postérisation de la teinte donne.

Sur la roue des couleurs à 2 marches, vous pouvez apercevoir les couleurs opposées. Sur la roue des couleurs à 3 marches celles qui sont à 120° les unes des autres (par exemple les couleurs primaires sont à 120° des couleurs secondaires et vice-versa).

Saturation et valeur

Pour finir de décrire l'espace TSV nous devons nous pencher sur les notions de Valeur et de Saturation.

Qu'est-ce que la Valeur ?

La Valeur permet d'assombrir une couleur : une valeur de 0 (le minimum) rend toute couleur Noire (absence de lumière). Les couleurs qui sont les plus lumineuses ont une valeur de 1 (le maximum), comme les couleurs générées par l'algorithme de roue des couleurs vu dans le chapitre précédent.

La Valeur ressemble à la luminosité et est parfois confondue avec celle-ci, puisqu'une valeur plus grande indique une couleur plus lumineuse et vice-versa. Cependant les imperfection du modèle TSV font que ce sont des grandeurs différentes. Le modèle TSV présenté dans ce tutoriel ne doit pas être confondu avec le modèle TSL (Teinte Saturation Luminosité).

Qu'est-ce que la Saturation ?

Comme les deux sortes de robinets qui disposent tous les deux de deux grandeurs indépendantes, nos espaces de couleurs nécessitent trois degrés de libertés pour pouvoir exprimer toutes les combinaisons de couleurs possibles.

Le modèle TSV manque d'une troisième quantité pour pouvoir décrire l'ensemble des couleurs prises par le modèle RVB (puisque le TSV dérive du RVB).

Remarquez qu'on ne peut pas encore décrire une image en niveau de gris avec notre modèle Teinte-Valeur. Cette troisième quantité est la Saturation et permet de décrire la "distance" de la couleur T-V au niveau de gris pris par la Valeur.

Le niveau de gris donné par son (unique) indication de Valeur est $(R,V,B) = (Valeur,Valeur,Valeur)$.

Exemples avec des images !

Opération piano.jpg normandie.jpg
Saturation moyenne et écart-type $29\% \pm 30\%$ $28\% \pm 20\%$
Valeur moyenne et écart-type $32\% \pm 30\%$ $50\% \pm 35\%$
Image d'origine
Saturation divisée par 2
Saturation multipliée par 2
Image d'origine
Valeur divisée par 2
Valeur multipliée par 2

Encore des barycentres !

Pour implémenter la Valeur et la Saturation nous allons encore avoir besoin des barycentres ! Et encore une fois vous allez devoir trouver vous-même !

Je vais vous pré-mâcher une dernière fois le travail :

Il y a plusieurs façons de trouver la formule finale : soit vous commencez par appliquer la Valeur puis la Saturation, ou le contraire :

  1. La Saturation est le coefficient d'un barycentre entre la couleur pure et sa Valeur ;
  2. La Valeur est le coefficient d'un barycentre entre le Noir et la couleur à laquelle on a déjà appliqué la Saturation.

Vérifications mathématiques de la formule finale

Comme d'habitude, voici quelques vérifications que vous devez faire sur la formule que vous venez de trouver (sachant que l'on applique la formule sur une couleur pure) :

  1. $\text{Saturation} = 0$ donne un gris, l'information de teinte est perdue (peu importe la $Teinte$ la couleur est la même) ;
  2. $\text{Saturation} = 1$ donne toujours une des trois composantes RVB qui est nulle ;
  3. $\text{Saturation}$ réalise bien une transformation linéaire vers $Valeur$ lorsqu'on fixe $Valeur$ ;
  4. $\text{Valeur} = 0$ donne du noir, les informations de teinte et de saturation sont perdues ;
  5. $\text{Valeur} = 1$ donne toujours une des trois composantes RVB qui est maximale ;
  6. $\text{Valeur}$ réalise bien une transformation linéaire vers le noir lorsqu'on fixe $Saturation$.

TSV vers RVB

Au travail !

Maintenant vous devez simplement mettre toutes vos formules dans une méthode de classe qui permet de calculer une couleur en RVB depuis une couleur en TSV.

Vérifications avec deux roues des couleurs

Voici deux images pour vérifier que nous avons bien codé la même chose :

Roue des couleurs avec Saturation

Roue des couleurs avec la distance normalisée au centre proportionnelle à la Saturation

Roue des couleurs avec Valeur

Roue des couleurs avec la distance normalisée au centre proportionnelle à la Valeur

A vos claviers ;) !

Une solution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
dx = float(x)/Largeur - 0.5;
dy = float(y)/Hauteur - 0.5;

C_RVB.DefinirDepuisTSV(
  new TSV(
      atan2( dy , dx ) / TWO_PI
    , sqrt( dx*dx + dy*dy )
    , 1.0
  )
);

Exo : carrés de couleurs aléatoires

Ce qui est demandé

Votre tâche est de créer un programme qui génère une image carrée composée de carrés dont la couleur est aléatoire dans l'espace TSV, chaque composante étant tirée au hasard dans un certain intervalle paramétrable. Vous pouvez lire la documentation officielle Processing au cas où il vous manquerait une fonction.

Une solution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Parametres utilisateurs
String AdresseImageSortie = "/CouleursAleatoires_4.png";
int NbCarresEnLargeur = 32;
int CoteCarreEnPixels = 6;

float Teinte_min = 0.4;
float Teinte_max = 0.8;

float Saturation_min = 0.2;
float Saturation_max = 0.7;

float Valeur_min = 0.3;
float Valeur_max = 0.8;

// parcours des carrés en Hauteur
for( int y = 0 ; y < NbCarresEnLargeur ; y++ )
{
  // parcours des carrés en Largeur
  for( int x = 0 ; x < NbCarresEnLargeur ; x++ )
  {
    C_TSV.Definir(
        random( Teinte_min     , Teinte_max )
      , random( Saturation_min , Saturation_max )
      , random( Valeur_min     , Valeur_max )
    );
    
    C_RVB.DefinirDepuisTSV( C_TSV );
    C_color = C_RVB.ConvertirColor();
    
    // parcours des pixels du carré en Hauteur
    for( int dy = 0 ; dy < CoteCarreEnPixels ; dy++ )
    {
      // parcours des pixels du carré en Largeur
      for( int dx = 0 ; dx < CoteCarreEnPixels ; dx++ )
      {
        pixels[ ( y*CoteCarreEnPixels + dy )*Largeur + x*CoteCarreEnPixels + dx ] = C_color;
      }
    }
  }
}

Quelques rendus

Rendu 1 Rendu 2 Rendu 3 Rendu 4
Rendu Rendu 1 Rendu 2 Rendu 3 Rendu 4
$\text{Teinte} \in$ $[0;1]$ $[0;1]$ $[0.05;0.15]$ $[0.4;0.8]$
$\text{Saturation} \in$ $[0;1]$ $[0.25;0.75]$ $[0.5;1.0]$ $[0.2;0.7]$
$\text{Valeur} \in$ $[0;1]$ $[0.5;1.0]$ $[0.4;0.9]$ $[0.3;0.8]$

Essayez de faire la même chose dans l'espace RVB avec autant de facilité ^^ .