Vous trouverez la derni�re version de ce plugin sur la page de mes plugins Gimp.


Plugin GIMP : Transformation YUV - RGB

R�mi Peyronnet - F�vrier 2002

Introduction
Elaboration du plug-in
Utilisation
Code
Conclusion
R�f�rences

Introduction

Gimp est un logiciel de traitement d'image issu du monde libre, et de plus en plus employ�. OpenSource, il profite de nombreuses contributions, en particulier gr�ce � de nombreux plugins qu'il est possible de d�velopper.

Cependant, je n'ai pas encore vu � ce jour de plugin permettant la conversion des canaux Rouge Vert Bleu (RGB), dans l'espace de couleur YUV (Luminance, Chrominances rouges et bleues). Il peut �tre souvent utile de travailler dans un tel mode, et de d�composer l'image en ces trois composantes. Ce plug-in a donc pour but de r�pondre � ce besoin.

I. Elaboration du plug-in

I. 1. Espaces de couleur

Le probl�me se pose de savoir comment repr�senter une couleur sur un ordinateur. Il existe pour cela beaucoup de techniques. La plus utilis�e sur les ordinateurs est la repr�sentation RGB, qui d�crit une couleur par ses composantes rouges, vertes et bleues. Cette repr�sentation correspond exactement � l'affichage des couleurs sur un �cran par projection d'un flux d'�lectrons plus ou moins intense sur des pastilles rouges, vertes et bleues, qui en se m�langeant nous donne l'impression de couleur.

La repr�sentation YUV est aussi tr�s utilis�e, principalement dans tout ce qui est compression d'image. Y repr�sente la luminance de la couleur, et U et V, la chrominance de cette couleur dans le rouge et le bleu. Cette repr�sentation est utile, car l'oeil est plus sensible aux variations de luminances qu'aux variations de chrominance. S�parer ces trois composantes permettra donc de pouvoir d�grader plus les chrominances, tout en conservant mieux la luminance.

I. 2. Formules de conversions

I. 2. a. Du RGB vers le YUV

La principale difficult�, outre l'�criture du plug-in lui-m�me, a �t� de trouver les formules ad�quates pour la conversion YUV-RGB. En effet, en premi�re approche, nous pouvons prendre pour la luminance simplement la moyenne des trois composantes Rouge, Vert et Bleu. Cependant, cette fa�on de faire n'est pas tr�s juste, car elle ne tient pas compte de la sensibilit� de l'oeil aux couleurs. Comme nous pouvons le constater dans les courbes ci-dessous, l'oeil � l'impression que le vert est beaucoup plus lumineux que le bleu. Il nous faut donc en tenir compte.

Perception des couleurs

J'ai donc cherch� sur Internet quelles formules utiliser, et on en trouve de tr�s diverses. La formule la plus fr�quente est : Y = 0.299*R + 0.587*G + 0.114*B. On peut constater que cette formule tient bien compte du fait que l'oeil per�oit le vert comme plus lumineux que le rouge, qui est lui-m�me plus lumineux que le bleu.

Pour le calcul des chrominances, nous utiliseront les formules : U = -0.169*R - 0.331*G + 0.500*B + 128.0 et V = 0.500*R - 0.419*G - 0.081*B + 128.0. Comment peut-on comprendre ces formules ? Premi�rement, nous pouvons remarquer que ces deux formules sont pr�vues pour osciller entre 0 et 255 avec comme valeur moyenne 128. Par exemple, une valeur 255 pour Bleu, avec le coefficient 0.5, sera ramen� dans les limites. De m�me pour le rouge et le vert (dans le sens n�gatif, -(0.169+0.331)=-0.500). Nous sommes ainsi assur�s qu'avec des valeurs comprises entre 0 et 255 en entr�e, nous aurons bien des valeurs entre 0 et 255 en sortie, ce qui est vital pour notre application. Il est de plus possible d'exprimer U et V en fonction de Y, et de retrouver ainsi le sens premier des chrominances (Y-R), (Y-B).

R�capitulatif :

I. 2. b. Du YUV au RGB

Si les formules ci-dessus abondent, il en va autrement dans le sens inverse. J'avais en premier lieu trouv� les formules (associ�es aux pr�c�dentes) :

Cependant cet ensemble de formules ne conserve pas du tout correctement les couleurs, comme en t�moignent ces deux images, avant et apr�s une conversion RGB vers YUV puis YUV vers RGB :
avant et après double conversion
La distorsion entre les deux est alors intol�rable.

Ce que nous cherchons � obtenir en fait, c'est que le produit des transformations RGB vers YUV et YUV vers RGB fasse l'identit�. Il suffit donc de chercher � inverser les formules trouv�es pour le premier passage. J'ai donc cherch� � utiliser MatLab, disponible � l'�cole. Les r�sultats obtenus ont �t� tout � fait d�cevant, puisque le produit manuel des deux matrices ne donnait pas l'identit�... L'utilisation de SciLab, logiciel gratuit, s'est r�v�l�e beaucoup plus efficace. Les formules obtenues sont alors :

En raison des arrondis, il faut veiller � arrondir les nombres plus grands que 255 � 255, et n�gatifs � 0, au risque de voir appara�tre des d�fauts dans l'image, 255.6 �tant enregistr� � 0 lors de sa conversion � un octet.

Il est tout � fait rassurant de constater apr�s coup que les formules obtenues sont conformes � celles utilis�es dans la norme JPEG, trouv�e ensuite. Notons qu'il serait pr�f�rable d'arrondir les 0.009267 � 0, comme dans la norme.

Avec ces formules, la transformation RGB vers YUV suivi de son inverse est invisible � l'oeil, malgr� les l�g�res erreurs d'arrondi lors de la conversion de la valeur r�elle en un octet.

I. 3. Programmation d'un Plug-in GIMP

L'int�gration dans GIMP n'a pas �t� la chose la plus facile du projet, car la documentation est assez elliptique sur ce sujet. Heureusement, il existe beaucoup de plugins pour GIMP OpenSource, ce qui m'a permis de comprendre le fonctionnement normal d'un Plugin.

Le plugin poss�de un code d'initialisation, qui se charge d'enregistrer la fonction dans GIMP, en inscrivant par exemple une entr�e dans un menu. Lorsque ce menu est appel�, la fonction principale du plugin est appel�e, en donnant en param�tre un pointeur vers l'image. GIMP poss�de une mani�re optimis�e de traiter les images, en les d�coupant par carreaux. Pour ce plugin, qui n'a pas besoin d'une connaissance compl�te de l'image mais seulement d'un pixel pour op�rer, nous avons opt� pour cette m�thode. Le traitement est donc inclus dans uns boucle, qui s'occupe de d�couper l'image en blocs, puis nous traitons ensuite, chaque pixel, ligne par ligne. GIMP s'occupe ensuite de g�rer l'affichage et la gestion des blocs, ce qui permet par exemple d'annuler l'effet appliqu�.

II. Utilisation

Ce plugin est distribu� soit sous forme de fichier source � compiler, soit en un binaire pour windows, � placer dans le r�pertoire 'plugins' de GIMP. Il faut alors relancer GIMP, et les entr�es 'RGB to YUV' et 'YUV to RGB' sont ajout�es dans le menu Image/Couleurs. Il suffit juste de cliquer sur une de ces entr�es pour lancer le plugin.

En sortie, le canal Y sera plac� dans le canal Rouge, le U dans le Vert, et le V dans le Bleu. Gr�ce � la bo�te de dialogue des canaux, vous pouvez demander de ne modifier ou voir qu'un ou plusieurs de ces canaux YUV � la fois.

III. Code

/*
 * 2002 Peyronnet R�mi 
 *
 * This plugin transforms your image RGB in YUV
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

/* Many parts of this code are borrowed from other plugins source code. */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include "libgimp/gimp.h"

#define MAX(x,y) ( ((x)>(y))?(x):(y) )
#define MIN(x,y) ( ((x)<(y))?(x):(y) )


/** defines ***********************************************************/

#define PLUG_IN_NAME "plug_in_yuv"
#define PLUG_IN_VERSION "Feburar 2002, 1.0"


/** Plugin interface *********************************************************/

void query(void);
void run(char *name, int nparams, GimpParam *param, 
              int *nreturn_vals, GimpParam **return_vals);


GimpPlugInInfo PLUG_IN_INFO = {
  NULL, /* init_proc */
  NULL, /* quit_proc */
  query,        /* query_proc */
  run   /* run_proc */
};


MAIN()

void
query(void)
{
  /* Definition of parameters */
  static GimpParamDef args[] = {
    { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" },
    { GIMP_PDB_IMAGE, "image", "Input image (unused)" },
    { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }
  };

  static GimpParamDef *return_vals  = NULL;
  static int        nargs = sizeof(args) / sizeof(args[0]);
  static int        nreturn_vals = 0;

  gimp_install_procedure(
    "plug_in_rgb_yuv",
    "Transform the image from RGB to YUV",
    "This plugin replaces the RGB channels with YUV values.",
    "R�mi Peyronnet",
    "R�mi Peyronnet",
    PLUG_IN_VERSION,
    "<Image>/Image/Colors/RGB->YUV",
    "RGB*",
    GIMP_PLUGIN,
    nargs,
    nreturn_vals,
    args,
    return_vals);
  gimp_install_procedure(
    "plug_in_yuv_rgb",
    "Transform the image from YUV to RGB",
    "This plugin replaces the RGB channels of an YUV image with the good RGB values.",
    "R�mi Peyronnet",
    "R�mi Peyronnet",
    PLUG_IN_VERSION,
    "<Image>/Image/Colors/YUV->RGB",
    "RGB*",
    GIMP_PLUGIN,
    nargs,
    nreturn_vals,
    args,
    return_vals);
}

void
run(char *name, int nparams, GimpParam *param,
    int *nreturn_vals, GimpParam **return_vals)
{
  /* Return values */
  static GimpParam values[1];

  gint sel_x1, sel_y1, sel_x2, sel_y2;
  gint img_height, img_width, img_bpp, img_has_alpha;

  GimpDrawable     *drawable;
  GimpPixelRgn dest_rgn, src_rgn, *pr;
  GimpRunModeType  run_mode;
  GimpPDBStatusType   status;

  double progress, max_progress;

  guchar * dest_row, *src_row, *dest, *src;
  double  r,g,b,a=0,y,u,v,m,mi;
  gint row, col;

  *nreturn_vals = 1;
  *return_vals  = values;

  status = GIMP_PDB_SUCCESS;

  if (param[0].type!= GIMP_PDB_INT32)  status=GIMP_PDB_CALLING_ERROR;
  if (param[2].type!=GIMP_PDB_DRAWABLE)   status=GIMP_PDB_CALLING_ERROR;

  run_mode = param[0].data.d_int32;

  drawable = gimp_drawable_get(param[2].data.d_drawable);

  img_width     = gimp_drawable_width(drawable->id);
  img_height    = gimp_drawable_height(drawable->id);
  img_bpp       = gimp_drawable_bpp(drawable->id);
  img_has_alpha = gimp_drawable_has_alpha(drawable->id);
  gimp_drawable_mask_bounds(drawable->id, &sel_x1, &sel_y1, &sel_x2, &sel_y2);

  max_progress = (sel_x2-sel_x1)*(sel_y2-sel_y1);


  if (status == GIMP_PDB_SUCCESS)
  {
    // Tile 
    gimp_tile_cache_ntiles((drawable->width + gimp_tile_width() - 1) / gimp_tile_width());


    if (strcmp("plug_in_rgb_yuv",name) == 0)
    {
      // RGB -> YUV
      // !!! Warning !!! Duplicated code... 'cause it'is quick'n dirty :)
        gimp_progress_init("Converting RGB to YUV...");
        progress = 0;

        // Process
	gimp_pixel_rgn_init (&dest_rgn, drawable, sel_x1, sel_y1, 
	                     (sel_x2-sel_x1), (sel_y2-sel_y1), TRUE, TRUE);
	gimp_pixel_rgn_init (&src_rgn, drawable, sel_x1, sel_y1, 
			      (sel_x2-sel_x1), (sel_y2-sel_y1), FALSE, FALSE);

        // Methode de traitement par dest_rgns -----------------------
        for (pr = gimp_pixel_rgns_register (2, &src_rgn, &dest_rgn);
             pr != NULL;
             pr = gimp_pixel_rgns_process (pr))
         { //Fun Goes On Here
           dest_row = dest_rgn.data;
           src_row = src_rgn.data;
           for (row = 0; row < dest_rgn.h; row++) {
             dest = dest_row;
             src = src_row;
             for (col = 0; col < dest_rgn.w; col++) {

               // D�but du traitement sp�cifique *************
               r = *src++; //y 
               g = *src++; //u
               b = *src++; //v
               if (img_has_alpha)       a = *src++;

               /* First set of formula, probably not the best... ----
               y =   (0.257*r) + (0.504*g) + (0.098*b) + 16;
               u =   (0.439*r) - (0.368*g) + (0.071*b) + 128;
               v = - (0.148*r) - (0.291*g) + (0.439*b) + 128;

               // YUV->RGB
               // r = 1.164 * (y-16) + 1.596*(v-128);
               // g = 1.164 * (y-16) + 0.813*(v-128) - 0.391*(u-128);
               // b = 1.164 * (y-16) + 2.018*(u-128);
               */

               /* Second set, not much better...*/
               y =   (0.299*r) + (0.587*g) + (0.114*b);
               u =  -(0.169*r) - (0.331*g) + (0.500*b) + 128.0;
               v =   (0.500*r) - (0.419*g) - (0.081*b) + 128.0;

               // YUV->RGB^M
               //r = y + 1.402*(v-128.0);
               //g = y - 0.34414*(u-128.0) + 0.71414*(v-128.0);
               //b = y + 1.772*(u-128.0);
               //
                           // From SciLab : This is the good one.
                           //r = 1 * y -  0.0009267*(u-128)  + 1.4016868*(v-128);^M
                           //g = 1 * y -  0.3436954*(u-128)  - 0.7141690*(v-128);^M
                           //b = 1 * y +  1.7721604*(u-128)  + 0.0009902*(v-128);^M

               /** Third : home-made...*/
                           /*y = 0.333 * r + 0.333 * g + 0.333 * b;
                           u = r - y;
                           v = g - y;
                           r = y + u;
                           g = y + v;
                           b = y -u -v;
                           */

               *dest++ = (guchar) (y>255)?255:((y<0)?0:y);
               *dest++ = (guchar) (u>255)?255:((u<0)?0:u);
               *dest++ = (guchar) (v>255)?255:((v<0)?0:v);

               if (img_has_alpha)       *dest++ = a;
               // Fin du traitement sp�cifique ****************

           } // for
           dest_row += dest_rgn.rowstride;
           src_row += src_rgn.rowstride;
          } // for 
          // Update progress 
          progress += dest_rgn.w * dest_rgn.h;
          gimp_progress_update((double) progress / max_progress);
       }

    }
    else if (strcmp("plug_in_yuv_rgb",name) == 0)
    {
      // RGB -> YUV
      // !!! Warning !!! Duplicated code... 'cause it'is quick'n dirty :)
      // You should consider just edit the previous version and copy/paste this one.
        gimp_progress_init("Converting YUV to RGB...");
        progress = 0;

        // Process
	gimp_pixel_rgn_init (&dest_rgn, drawable, sel_x1, sel_y1,
	                     (sel_x2-sel_x1), (sel_y2-sel_y1), TRUE, TRUE);
	gimp_pixel_rgn_init (&src_rgn, drawable, sel_x1, sel_y1, 
	                     (sel_x2-sel_x1), (sel_y2-sel_y1), FALSE, FALSE);

        // Methode de traitement par dest_rgns -----------------------
        for (pr = gimp_pixel_rgns_register (2, &src_rgn, &dest_rgn);
             pr != NULL;
             pr = gimp_pixel_rgns_process (pr))
         { //Fun Goes On Here
           dest_row = dest_rgn.data;
           src_row = src_rgn.data;
           for (row = 0; row < dest_rgn.h; row++) {
             dest = dest_row;
             src = src_row;
             for (col = 0; col < dest_rgn.w; col++) {

               // D�but du traitement sp�cifique *************
               y = *src++;
               u = *src++;
               v = *src++;
               if (img_has_alpha)       a = *src++;

               /* Second set, not much better...*/
               //y =   (0.299*r) + (0.587*g) + (0.114*b);
               //u =  -(0.169*r) - (0.331*g) + (0.500*b) + 128.0;
               //v =   (0.500*r) - (0.419*g) - (0.081*b) + 128.0;
               // From SciLab

                                 r = 1 * y -  0.0009267*(u-128)  + 1.4016868*(v-128);
                                 g = 1 * y -  0.3436954*(u-128)  - 0.7141690*(v-128);
                                 b = 1 * y +  1.7721604*(u-128)  + 0.0009902*(v-128);


               *dest++ = (guchar) (r>255)?255:((r<0)?0:r);
               *dest++ = (guchar) (g>255)?255:((g<0)?0:g);
               *dest++ = (guchar) (b>255)?255:((b<0)?0:b);
               if (img_has_alpha)       *dest++ = a;
               // Fin du traitement sp�cifique ****************

           } // for
           dest_row += dest_rgn.rowstride;
           src_row += src_rgn.rowstride;
          } // for 
          // Update progress 
          progress += dest_rgn.w * dest_rgn.h;
          gimp_progress_update((double) progress / max_progress);

       }

    }
    else
    {
       // Ouch, ugly :)
       printf("Plugin not found.\n");
    }

    gimp_drawable_flush(drawable);
    gimp_drawable_merge_shadow(drawable->id, TRUE);
    gimp_drawable_update (drawable->id, sel_x1, sel_y1, (sel_x2-sel_x1), (sel_y2-sel_y1));
    gimp_displays_flush();
  }

  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;
  gimp_drawable_detach(drawable);
}

Conclusion

Voici donc un plugin Gimp compl�tement fonctionnel, permettant de convertir les images en YUV, de travailler dessus, puis de repasser dans le domaine RGB.

R�f�rences