Représentation en mémoire d’un triplet de canaux de couleur
Réflexion sur l’organisation des données
jeudi 4 janvier 2007, par Pierre-Luc Bacon
Tableau bidimensionnel d'entiers
Sommaire
| Tableau bidimensionnel d'entiers |
| Tableau unidimensionnel de type unsigned char |
| Utilisation d'une structure |
| Utilisation d'une Union |
Il peut parfois être utile de disposer d’une image dans un tableau de telle sorte qu’on puisse retrouver la valeur d’un pixel directement par sa position. Un tableau du genre :
J’ai été cependant confronté au problème suivant en voulant faire ainsi : comment exprimer ce triplet, en l’occurence trois char, dans un seul élément du tableau ? La solution trouvée, peu élégante à certains égards, est de disposer les trois canaux dans l’ordre RGB un à la suite de l’autre sur un int. Puisque nous avons trois char, qui occupent généralement 24bit (3*8bit), un int, codé sur 32 bits, suffira à les contenir.
int rgb;
unsigned char r,g,b;Si on applique un opérateur de cast sur un char vers un int, alors on aura de gauche à droite 16 bits à 0 et les 8 autres à droite qui expriment la valeur contenue dans le char. Ensuite on déplace ces 8 bits qui nous intéressent vers la gauche de 16 bits avec un opérateur de shift pour le canal R. Pour G, on applique la même procédure mais pour un déplacement de 8 bits. Quant à B, rien ne doit bouger. On a alors quelque chose semblable à :
R=111111110000000000000000
G=000000001111111100000000
B=000000000000000011111111
Avec cet encadré montrant une représentation où les trois canaux seraient à 255, on voit qu’un OR des ces valeurs suffiraient à les combiner dans l’ordre voulu.
En des termes plus applicable à un programme, on pourrait faire :
Une autre astuce pourrait consister à déclarer un pointeur unsigned char sur une variable int 32 bits. Par la suite, on accède à la variable int par le pointeur comme tableau :
unsigned int pixel32;
unsigned char *pixel = (unsigned char *)&pixel32;
pixel[0] = r;
pixel[1] = g;
pixel[2] = b
pixel[3] = 0;
Maintenant pour retrouver les canaux individuels, on appliquera le masque approprié et effectuera le décalage requis pour avoir les 8 bits tous à droite dans la représentation sur 32 bits. Ceci permettra ensuite de faire un cast sur l’entier int et d’obtenir la valeur souhaitée dans la variable. Les masques utilisés dans ce AND binaire sont trouvés du fait que l’opération d’un bit ET d’un autre à 1 renvera nécessairement la même valeur de base. De même manière, un ET avec 0 aurait l’effet d’un NON. Par exemple :
1011110101110111
ET 0000000011111111
-----------------
0000000001110111
On pourrait donc coder quelque chose semblable à :
r=(unsigned char) ((rgb & 0xFF0000)>>16);
g=(unsigned char) ((rgb & 0xFF00)>>8);
b=(unsigned char) (rgb & 0xFF);
Cette solution n’est peut-être pas celle qui fait preuve du bon usage du langage C, mais devrait convenir dans la plupart des cas. De plus, elle a l’avantage de n’utiliser aucune fonctions spéciales des bibliothèques. L’utilisation d’unions est aussi une possibilité plus lisibile à considérer pour effectuer une opération similaire (merci à Pierre pour cette suggestion).
Tableau unidimensionnel de type unsigned char
Lorsqu’on capture une image depuis une caméra, celle-ci est accessible par un pointeur. Cette notion de pointeur étant liée à celle de tableau, la zone mémoire occupée par l’image sera assimilable à un tableau unidimensionnel. En effet, soit un tableau tbl[100], alors *tbl pointera sur le premier élément en mémoire de celui-ci. De cette manière, si *tbl correspond à tbl[0], alors *(tbl+1) sera équivalent à tbl[1]. Connaissant ceci, on peut donc calculer le bond à effetuer pour atteindre une case mémoire donnée. Dans le cas de notre image, on peut en déduire les relations suivantes d’après l’illustration ci-dessous :
Le trois (3) dans ces équations provient du nombre de canaux à sauter pour atteindre un emplacement donné.
Voici un bout de code montrant cette idée :
/*rgb(1;0)*/
*(tbl+3)=55;
*(tbl+4)=56;
*(tbl+5)=57;
r=*(tbl+(3*y*LARGEUR)+(3*x));
g=*(tbl+(3*y*LARGEUR)+(3*x)+1);
b=*(tbl+(3*y*LARGEUR)+(3*x)+2);
printf("pos(%d;%d): rgb(%d;%d;%d)\n",x,y,r,g,b);
Utilisation d'une structure
Voilà maintenant un moyen plus lisible et plus direct de représenter ce triplet dans un emplacement du tableau à deux dimensions. On peut donc déclarer un type triplet ainsi :
typedef struct {
unsigned char r;
unsigned char g;
unsigned char b;
} triplet;
Il suffit par la suite d’initialiser dynamiquement ou statiquement un tableau de type triplet. On accédera aux éléments du tableau ainsi :
triplet rgb[LARGEUR][HAUTEUR];
rgb[x][y].r=200;
rgb[x][y].g [...]
rgb[x][y].b [...]
Utilisation d'une Union
Un collaborateur (user0081) propose une solution tout aussi élégante qui consiste à utiliser une union. Ceci permettra d’arriver au même résultat que ci-haut, sans la complexité des opérateurs binaires.
typedef union {
int rgb;
struct {
unsigned char r;
unsigned char g;
unsigned char b;
} chan;
} pixel
Pour un élément donné d’un tableau à deux dimensions qui contient tous les pixels de l’image, on pourra retrouver à la fois chacun des canaux séparément ainsi que leur combinaison sur un entier :
pixel image[LARGEUR][HAUTEUR];
rgb=image[x][y].rgb;
r=image[x][y].chan.r;
g=image[x][y].chan.g;
b=image[x][y].chan.b;L’exemple suivant illustre davantage l’astuce mise en jeu :
typedef union {
int yuv;
struct {
unsigned char y;
unsigned char u;
unsigned char v;
} chan;
} pixel;
pixel image[240][240];
image[0][2].chan.y=23;
image[0][2].chan.u=128;
image[0][2].chan.v=57;
printf("YUV: %d:%d:%d\t YUV32:%d\n",image[0][2].chan.y,image[0][2].chan.u,image[0][2].chan.v,image[0][2].yuv);On obtiendra alors en sortie :
YUV: 23:128:57 YUV32:3768343
R,G,B: ff,c8,9b
Soit 23->10111 en binaire, 128->10000000 et 57->111001, 3768343 correspond alors à 1110011000000000010111. Ce résultat est alors de la même forme que celui obtenu par l’approche "binaire pure" avec les canaux YUV (ou RGB) dans l’ordre voulu.




Répondre à l'auteur: