Plugin GIMP : Transformation YUV - RGB

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.

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 :

- `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`

### 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) :

- R = Y + (1.4075 * (V - 128));
- G = Y - (0.3455 * (U - 128) - (0.7169 * (V - 128));
- B = Y + (1.7790 * (U - 128);

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 :
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 :

- 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)

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

- http://www.gimp.org/ : Le site officiel du logiciel libre GIMP
- http://registry.gimp.org : Répertoire des plugins GIMP.
- http://gimp-plug-ins.sourceforge.net/doc/libgimp/html/ : La référence de la libGIMP.
- http://gimp-plug-ins.sourceforge.net/doc/Writing/html/plug-in.html : Tutoriel sur le développement de plugins GIMP.
- http://www.cse.msu.edu/%7Ecbowen/docs/yuvtorgb.html [ Lien cassé, voir l'ancienne page sur archive.org ] : Formules de conversions, au comportement étrange.
- http://www.northpoleengineering.com/rgb2yuv.htm [ Lien cassé, voir l'ancienne page sur archive.org ] : Conversion YUV matérielle.
- http://www.cs.sfu.ca/undergrad/CourseMaterials/CMPT479/material/notes/Chap3/Chap3.3/Chap3.3.html [ Lien cassé, voir l'ancienne page sur archive.org ] : Présentation des différents espaces de couleurs.
- http://www.funducode.com/freec/Fileformats/format3/format3c.htm [ Lien cassé, voir l'ancienne page sur archive.org ] : Compression JPEG, comportant les formules utilisées par le JPEG
- http://www-rocq.inria.fr/scilab/ [ Lien cassé, voir le nouveau site de Scilab, une compilation de ressources et informations supplémentaires, ou pour information voir l'ancienne page sur archive.org] : Page d'accueil du logiciel SciLab