/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * GRetinex plug-in -- 
 * Copyright (C) 2003 Fabien Pelisson <Fabien.Pelisson@inrialpes.fr>
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include <gtk/gtk.h>

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include <libgimp/gimpintl.h>

/*
  Taille de l'image preview. La fonction
  gimp_drawable_get_thumbnail_data retourne
  l'image reduite en gardant l'aspect.
  Le probleme est que cette fonction est
  limite a une taille maximal de 128.
 */
#define PREVIEW_SIZE  128

/*
  C'est le nombre maximal de filtrages
  autorises. 
 */
#define MAX_RETINEX_SCALES 8
/*
  Definit comment sont repartis les
  differents filtres en fonction de
  l'echelle (~= ecart type de la gaussienne)
 */
#define RETINEX_UNIFORM 0
#define RETINEX_LOW 1
#define RETINEX_HIGH 2

/*
  Cette structure est utilisee pour gerer
  les parametres de l'algo de normalisation
  couleur. Seulement on separe de la structure
  les parametres qui ne doivent pas etre
  accessibles via GIMP.
  (1) les valeurs d'echelles 
 */
typedef struct 
{
  gint nscales; 
  gint scales_mode; 
  gfloat cvar; 
} RetinexParams;

static gfloat RetinexScales[MAX_RETINEX_SCALES]; 

typedef struct 
{
  /* 
     Indique le nombre de coefs utilises 
     par l'algo de filtrage recursif.
     On utilise ici 3 coefs mais il existe
     des implementations avec 5 coefs.
     Un nombre + important augmente la qualite
     du filtrage mais surtout pour des petites
     echelles (< 2).
  */
  int N; 
  /* 
     C'est l'echelle du filtre ~= ecart type.
     Sigma est le nom utilise courament en stat
     ou vision par ordinateur.
  */
  float sigma; 
  /*
    Parametres internes du filtrage.
   */
  float alpha;
  float b[4]; 
} gauss3_coefs;

/*
  C'est une sorte de pointeur
  de tableau qui permet de 
  naviguer selon la position
  verticale dans les images.
 */
typedef struct 
{
  float* current;
  int offset;
} oiterator;

typedef struct
{
  gint run;
} RetinexInterface;

/*
  Structure principale
  qui gere l'interface graphique
  et l'image preview.
 */
typedef struct
{
  GtkWidget* all;
  GtkWidget* preview;  
  gfloat preview_scale_x;
  gfloat preview_scale_y;
  guchar* preview_cache;
  gint preview_cache_rowstride;
  gint preview_cache_bpp;
} RetinexDialog;

/*
  ## Ces fonctions ne dependent pas de GIMP ##
  Ce sont les fonctions associees
  a l'algo de filtrage recursif.
  + une fonction de calcul de la moyenne et de la variance
  en une passe.
*/
static void compute_coefs3(gauss3_coefs* c, float sigma);
static void GaussG0mirror(float* src, float* dst, int w, int h, gauss3_coefs* c);
static void gausssmoothmirror_X(float* src, float* dst, float* cache, int s, gauss3_coefs* c);
static void gausssmoothmirror_Y(oiterator* src, oiterator* dst, float* cache, int s, gauss3_coefs* c);
static void compute_mean_var(float* src, float* mean, float* var, int s);
/* 
   Ce sont les fonctions associees
   a l'algorithme de normalisation couleur.
   Elles vont de paire avec les fonctions
   de l'algo de filtrage recursif.
   MSRCR = MultiScale Retinex with Color Restoration
 */
static void MSRCR(float* cache, int w, int h, float* scales, int nscales);
static void MSRCR_aux(float* cache, int w, int h, float scale, float weight);
/*
  ## Ces fonctions dependent de GIMP ##
  Ces fonctions permettent l'utilisation
  de l'algo de normalisation couleur sous gimp.  
 */
static void retinex(GimpDrawable* drawable);
static void retinex_preview(GtkWidget* preview);
static void retinex_scales_distribution(gfloat* scales, gint nscales, gint mode, gint s);
static void drawable_to_floatmatrix(GimpDrawable* src, gint x1, gint y1, gint w, gint h, gfloat* dst);
static void preview_to_floatmatrix(guchar* src, gint w, gint h, gfloat* dst);
/*
  Fonctions d'interfaces obligatoires
  pour que Gimp puisse demarrer le plugin/
 */
static void plugin_query(void);
static void plugin_run(gchar* name,
		       gint nparams,
		       GimpParam* param,
		       gint* nreturn_vals,
		       GimpParam** return_vals);
/*
  Fonctions qui gerent l'interface
  graphique (gtk et gimp).
 */
static gint retinex_dialog(GimpDrawable *drawable);
static GtkWidget* retinex_preview_widget(GimpDrawable* drawable);
static void retinex_fill_preview(gint32 drawable_ID);
static void retinex_preview_do_row(gint row,
				   gint width,
				   guchar* even,
				   guchar* odd,
				   guchar* src);
static void retinex_ok_callback(GtkWidget* widget,
				gpointer data);
static void retinex_button_callback(GtkWidget* widget, 
				    gpointer value);

/*
  Variables globales.
*/
static RetinexParams rvals = 
  {
    3, /* Nombre de filtres */
    RETINEX_UNIFORM, /* Echelles reparties uniformement */
    1.2 /* A voir */
  };

static RetinexInterface retint = 
  {
    /* 
       Indique si on a appuye sur OK (TRUE)
       ou CANCEL (FALSE) lorsque l'interface
       graphique est detruite.
    */
    FALSE 
  };

GimpPlugInInfo PLUG_IN_INFO = 
  {
    NULL, /* init_proc */
    NULL, /* quit_proc */
    plugin_query, /* query_proc */
    plugin_run, /* run_proc */
  };

static RetinexDialog* dlg = NULL;

/*************************************************************************/
/**									**/
/**		+++ Plug-in Interfaces					**/
/**									**/
/*************************************************************************/

/*
  macro definissant la fonction main (gimp_main) appelee par GIMP.
  Cette macro est definie dans libgimp/gimp.h.
 */
MAIN();

/*
  Tout le necessaire pour enregistrer le plugin dans la
  PDB (procedural database) de GIMP. La fonction importante
  est gimp_install_procedure qui indique a GIMP le nom
  du plugin, l'endroit (dans les menus) ou le plugin
  est installe, le type d'images autorisees, ...
*/
static void plugin_query(void)
{
  static GimpParamDef args[] = 
    {
      /*
	Les trois premiers parametres sont necessaires
	lorsque le plugin se trouve dans le sous-menu <Image>.
       */
      { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" },
      { GIMP_PDB_IMAGE, "image", "Input image (unused)" },
      { GIMP_PDB_DRAWABLE, "drawable", "Input drawable"},
      { GIMP_PDB_INT32, "nscales", "Number of retinex"},
      { GIMP_PDB_INT32, "scales_mode", "Retinex distribution through scales"},
      { GIMP_PDB_FLOAT, "cvar", "variance value"}
    };

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

  gimp_install_procedure("plug_in_retinex",
			 "Image color restoration algorithm",
			 "This plug-in normalise the image with the multiscale retinex algorithm.",
			 "Fabien Pelisson",
			 "Fabien Pelisson",
			 "2003",
			 /*
			   Position dans les menus, sous-menus...
			   Pourquoi pas egalement <Filters>/Enhance/Retinex...
			   Le choix de la position dans le menu 
			   implique la presence de certains parametres
			   dans la structure GimpParamDef.
			  */
			 N_("<Image>/Image/Colors/Retinex..."),
			 /*
			   On peut modifier l'algo pour travailler
			   pour prendre en compte l'alpha channel
			   des RGBA et/ou seulement l'information
			   d'intensite pour les format GRAY, GRAYA.
			  */
			 "RGB", 
			 GIMP_PLUGIN,
			 nargs, 
			 nreturn_vals,
			 args, 
			 return_vals);
}

/*
  Fonction appelee par GIMP lorsque le plugin
  est demarre.
 */
static void plugin_run(gchar* name,
		       gint nparams,
		       GimpParam* param,
		       gint* nreturn_vals,
		       GimpParam** return_vals)
{
  static GimpParam values[1];
  gint32 run_mode;
  GimpPDBStatusType status;
  GimpDrawable* active_drawable;

  *nreturn_vals = 1;
  *return_vals = values;

  status = GIMP_PDB_SUCCESS;

  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;
  /*
    Recupere l'identifiant de l'image courante.
    Cette info nous est fournie directement par GIMP.
   */
  active_drawable = gimp_drawable_get(param[2].data.d_drawable);
  /*
    Ici on regarde comment le plugin a ete
    execute. Soit par le menu (INTERACTIVE),
    par l'appel direct via la PDB (NONINTERACTIVE)
   */
  run_mode = param[0].data.d_int32;
  switch(run_mode)
    {
    case GIMP_RUN_INTERACTIVE:
      gimp_get_data("plug_in_retinex", &rvals); 
      /*
	Lance l'interface graphique et regarde
	si on a appuye sur OK (TRUE) ou CANCEL (FALSE)
       */
      if(!retinex_dialog(active_drawable))
	return;
      break;

      /*
	Pas encore implemente. Notament
	il faut recupere les arguments.
       */
    case GIMP_RUN_NONINTERACTIVE:
      if(nparams != 6)
	{
	  status = GIMP_PDB_CALLING_ERROR;
	}
      if(status == GIMP_PDB_SUCCESS)
	{
	  rvals.nscales = param[3].data.d_int32;
	  rvals.scales_mode = param[4].data.d_int32;
	  rvals.cvar = param[5].data.d_float;
	}
      break;

    case GIMP_RUN_WITH_LAST_VALS:
      gimp_get_data("plug_in_retinex", &rvals); 
      break;

    default:
      break;
    }
  /*
    Verifie si le plugin a bien ete enregistre
    et le format de l'image est correct pour
    lancer l'algorithme sur l'image courante.
   */
  if((status == GIMP_PDB_SUCCESS) &&
     (gimp_drawable_is_rgb(active_drawable->id)))
    {
      gimp_progress_init(_("Retinex..."));
      /* FALSE = on n'est plus dans le mode preview */
      retinex(active_drawable);
      gimp_progress_init(_("Retinex (4/4): Mise à jour..."));
      /* mis a jour de l'affichage seulement en mode graphique */
      if(run_mode != GIMP_RUN_NONINTERACTIVE)
	gimp_displays_flush();

      if(run_mode == GIMP_RUN_INTERACTIVE)
	{
	  gimp_set_data("plug_in_retinex", &rvals, sizeof(RetinexParams));  
	  /*
	    Si on utilisait l'interface
	    graphique il faut liberer la memoire
	    des donnes concernant l'image preview.
	  */
	  g_free(dlg->preview_cache);
	  g_free(dlg);
	}
    }
  else if(status == GIMP_PDB_SUCCESS)
    { /* mauvais format d'image (pas RGB) */
      g_message(_("Retinex: format d'image incorrect."));
      status = GIMP_PDB_EXECUTION_ERROR;
    }

  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;
  /* libere l'acces a l'image courante */
  gimp_drawable_detach(active_drawable);
}

/*
  Cette fonction cree l'interface graphique
  permettant a l'utilisateur de modifier
  les parametres de l'algo de normalisation
  couleur. On a besoin de l'image courante
  pour initialiser l'image preview.
 */
static gint retinex_dialog(GimpDrawable *drawable)
{
  /*
    Ces variables sont anonymes car on
    les utilises que tres localement.
    La gestion des pointeurs et de la memoire
    est faite par gtk.
   */
  GtkWidget* hbox;
  GtkWidget* frame;
  GtkWidget* abox;
  GtkWidget* table;
  GtkWidget* button;
  GtkObject* entry;
  GSList *group = NULL;
  
  gimp_ui_init("retinex",TRUE);
  
  dlg = g_new(RetinexDialog, 1);
  dlg->all = gimp_dialog_new(_("Retinex"), "retinex",
			     gimp_standard_help_func, "filters/mosaic.html",
			     GTK_WIN_POS_MOUSE,
			     FALSE, TRUE, FALSE,
			     "Cancel", gtk_widget_destroy,
			     NULL, 1, NULL, FALSE, TRUE,
			     "Ok", retinex_ok_callback,
			     NULL, NULL, NULL, TRUE, FALSE,
			     NULL);

  gtk_signal_connect(GTK_OBJECT(dlg->all), "destroy",
		     gtk_main_quit,
		     NULL);

  /*
    La boite horizontale qui contient (2 frames) :
    (a) l'image preview 
    (b) l'interface avec les parametres
   */
  hbox = gtk_hbox_new(FALSE,6);
  gtk_container_set_border_width(GTK_CONTAINER(hbox),6);
  gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg->all)->vbox),hbox,FALSE,FALSE,0);
  gtk_widget_show(hbox);

  /* 
     (a) Interface graphique de l'image preview
   */
  frame = gtk_frame_new(_("Prévisualisation"));
  gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, FALSE, 0);
  gtk_widget_show(frame);

  abox = gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
  gtk_container_set_border_width(GTK_CONTAINER(abox), 4);
  gtk_container_add(GTK_CONTAINER(frame), abox);
  gtk_widget_show(abox);

  frame = gtk_frame_new(NULL);
  gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
  gtk_container_add(GTK_CONTAINER(abox), frame);
  gtk_widget_show(frame);

  /* Initialisation a partir de l'image courante */
  dlg->preview = retinex_preview_widget(drawable);
  retinex_fill_preview(drawable->id);
  gtk_container_add(GTK_CONTAINER(frame), dlg->preview);
  retinex_preview(dlg->preview);
  gtk_widget_show(dlg->preview);

  /* 
     (b) Interface graphique des parametres
   */
  frame = gtk_frame_new(_("Principaux paramètres"));
  gtk_frame_set_shadow_type(GTK_FRAME(frame),GTK_SHADOW_ETCHED_IN);
  gtk_container_set_border_width(GTK_CONTAINER(frame),6);
  gtk_box_pack_start(GTK_BOX(hbox),frame,TRUE,TRUE,0);
  gtk_widget_show(frame);

  /* La table permet d'aligner facilement des listes de boutons */
  table = gtk_table_new(1,3,FALSE);
  gtk_table_set_row_spacings(GTK_TABLE(table),4);
  gtk_container_set_border_width(GTK_CONTAINER(table),4);
  gtk_container_add(GTK_CONTAINER(frame),table);
  gtk_widget_show(table);

  /* Interface pour regler le nombre de filtres 
     L'ordre dans lequel les signaux sont declares
     a une importance. D'abord on modifie la valeur
     dans la structure globale des parametres puis
     on reapplique l'algo sur l'image preview.
  */
  button = gimp_spin_button_new(&entry,
				rvals.nscales, 1, MAX_RETINEX_SCALES, 1, 1,
				0, 1, 0);
  gimp_table_attach_aligned(GTK_TABLE(table), 0, 0,
			     "Quantité :", 1.0, 0.5,
			     button, 1, TRUE);
  gtk_signal_connect(GTK_OBJECT(entry), "value_changed",
		     gimp_int_adjustment_update,
		     &rvals.nscales);
  gtk_signal_connect(GTK_OBJECT(entry), "value_changed",
		     retinex_preview,
		     NULL);
  gtk_widget_show(button);

  /* Interface pour regler comment sont repartis ces filtres */
  hbox = gtk_hbox_new(FALSE, 4);
  gtk_table_attach(GTK_TABLE(table), hbox, 0, 3, 1, 2,
		   GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show(hbox);

  button = gtk_radio_button_new_with_label(group, _("Uniforme"));
  group = gtk_radio_button_group(GTK_RADIO_BUTTON(button));
  gtk_box_pack_start(GTK_BOX(hbox),button,TRUE,TRUE,0);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 
			       rvals.scales_mode == RETINEX_UNIFORM ? 1 : 0);
  gtk_widget_show(button);
  gtk_signal_connect(GTK_OBJECT(button), "toggled",
		     retinex_button_callback,
		     (gpointer)RETINEX_UNIFORM);

  button = gtk_radio_button_new_with_label(group, _("Faible"));
  group = gtk_radio_button_group(GTK_RADIO_BUTTON(button));
  gtk_box_pack_start(GTK_BOX(hbox),button,TRUE,TRUE,0);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
			       rvals.scales_mode == RETINEX_LOW ? 1 : 0);
  gtk_widget_show(button);
  gtk_signal_connect(GTK_OBJECT(button), "toggled",
		     retinex_button_callback,
		     (gpointer)RETINEX_LOW);

  button = gtk_radio_button_new_with_label(group, _("Elevée"));
  group = gtk_radio_button_group(GTK_RADIO_BUTTON(button));
  gtk_box_pack_start(GTK_BOX(hbox),button,TRUE,TRUE,0);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
			       rvals.scales_mode == RETINEX_HIGH ? 1 : 0);
  gtk_widget_show(button);
  gtk_signal_connect(GTK_OBJECT(button), "toggled",
		     retinex_button_callback,
		     (gpointer)RETINEX_HIGH);


  /* Interface pour regler la dynamique de la couleur */
  entry = gimp_scale_entry_new(GTK_TABLE(table), 0, 2,
			       "Dynamique :", 100, 0,
			       rvals.cvar, 0, 4,
			       0.1, 0.1, 1,
			       TRUE, 0, 0, NULL, NULL);
  gtk_signal_connect(GTK_OBJECT(entry), "value_changed",
		     gimp_float_adjustment_update,
		     &rvals.cvar);
  gtk_signal_connect(GTK_OBJECT(entry), "value_changed",
		     retinex_preview,
		     NULL);

  /* Au final on affiche tout et on lance la boucle principale */
  gtk_widget_show(dlg->all);
  gtk_main();
  gdk_flush();
  return retint.run;
}

static GtkWidget* retinex_preview_widget(GimpDrawable* drawable)
{
  GtkWidget* preview;
  preview = gtk_preview_new(GTK_PREVIEW_COLOR);
  return preview;
}

static void retinex_fill_preview(gint32 drawable_ID)
{
  gint bpp;
  gint y;
  gint width = PREVIEW_SIZE;
  gint height = PREVIEW_SIZE;
  guchar* src;
  guchar* even;
  guchar* odd;

  dlg->preview_cache =
    gimp_drawable_get_thumbnail_data(drawable_ID,&width,&height,&bpp);

  if(width < 1 || height < 1)
    return;

  dlg->preview_cache_rowstride = width * bpp;
  dlg->preview_cache_bpp = bpp;

  gtk_preview_size(GTK_PREVIEW(dlg->preview),width,height);

  dlg->preview_scale_x = (gfloat) width / (gfloat) gimp_drawable_width(drawable_ID);
  dlg->preview_scale_y = (gfloat) height / (gfloat) gimp_drawable_height(drawable_ID);

  src = dlg->preview_cache;
  even = g_malloc(width * 3);
  odd = g_malloc(width * 3);

  for(y = 0; y < height; ++y)
    {
      retinex_preview_do_row(y, width, even, odd, src);
      src += width * bpp;
    }

  g_free(even);
  g_free(odd); 
}

static void retinex_preview_do_row(gint row,
				   gint width,
				   guchar* even,
				   guchar* odd,
				   guchar* src)
{
  gint x;
  guchar* p0 = even;
  guchar* p1 = odd;
  gdouble r, g, b;
  gdouble c0, c1;

  for(x = 0; x < width; ++x)
    {
      r = ((gdouble) src[x*3])     / 255.0;
      g = ((gdouble) src[x*3 + 1]) / 255.0;
      b = ((gdouble) src[x*3 + 2]) / 255.0;
      
      if((x / GIMP_CHECK_SIZE) & 1)
	{
	  c0 = GIMP_CHECK_LIGHT;
	  c1 = GIMP_CHECK_DARK;
	}
      else
	{
	  c0 = GIMP_CHECK_DARK;
	  c1 = GIMP_CHECK_LIGHT;
	}
      *p0++ = (c0 + (r - c0)) * 255.0;
      *p0++ = (c0 + (g - c0)) * 255.0;
      *p0++ = (c0 + (b - c0)) * 255.0;

      *p1++ = (c1 + (r - c1)) * 255.0;
      *p1++ = (c1 + (g - c1)) * 255.0;
      *p1++ = (c1 + (b - c1)) * 255.0;
    }

  if((row / GIMP_CHECK_SIZE) & 1)
    {
      gtk_preview_draw_row(GTK_PREVIEW(dlg->preview),
			   (guchar*) odd, 0, row, width);
    }
  else
    {
      gtk_preview_draw_row(GTK_PREVIEW(dlg->preview),
			   (guchar*) even, 0, row, width);
    }
}

/*
  Ce callback correspond a l'appui du bouton
  OK de l'interface graphique et permet
  de lancer l'algo sur l'image courante.
 */
static void retinex_ok_callback(GtkWidget* widget,
				gpointer data)
{
  retint.run = TRUE;
  gtk_widget_destroy(GTK_WIDGET(data));
}

/*
  Ce callback gere le choix entre les differentes
  manieres de repartir les echelles. Pour les trois
  boutons (choix) le meme callback est utilise car
  seul un des trois boutons est actif.
  D'abord on regarde si on est sur le bouton actif,
  et on affecte l'identifiant du bouton au
  parametre de l'algo. Cette valeur represente le bouton 
  et la valeur de l'algo.
 */
static void retinex_button_callback(GtkWidget* widget, 
				    gpointer data)
{
  if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
    {
      rvals.scales_mode = (gint) data;
      retinex_preview(NULL);
    }
}
/*
  Applique l'algo sur l'image preview.
 */
static void retinex_preview(GtkWidget* widget)
{
  gint w, h;
  gint s;
  gfloat mean, var, mini, maxi, range;
  gfloat* cache;
  guchar* odd;
  guchar* even;
  guchar* dest;
  gfloat* it;
  guchar* ot;
  gint i,j;
  /*
    Recupere les donnees et 
    la taille de l'image preview generee par gtk. 
    On transforme ensuite cette image en float.
  */
  w = GTK_PREVIEW(dlg->preview)->buffer_width;
  h = GTK_PREVIEW(dlg->preview)->buffer_height;
  s = w*h;
  /* memoire de l'image float */
  cache = g_new(gfloat, 9*s);
  preview_to_floatmatrix(dlg->preview_cache,w,h,cache);
  /*
    Calcule les echelles de filtrage en fonction du nombre
    de filtre et de leur repartition.
   */
  retinex_scales_distribution(RetinexScales,rvals.nscales,rvals.scales_mode,w < h ? w : h);
  /*
    Algo de normalisation couleur
  */
  MSRCR(cache, w, h, RetinexScales, rvals.nscales);
  /* 
     Adapte la dynamique des couleurs
     en fonction des statistiques du premier et second ordre.
     L'utilisation de la variance permet de controler
     le degre de saturation des couleurs.
  */
  compute_mean_var(cache + 6*s, &mean, &var, 3*s);
  mini = mean - rvals.cvar*var;
  maxi = mean + rvals.cvar*var;
  range = maxi - mini;
  if(!range)
    {
      range = 1.0;
    }
  even = g_malloc(w * 3);
  odd = g_malloc(w * 3);      
  dest = g_new(guchar, w * 3);
  it = cache;
  for(i = 0; i < h; ++i)
    {
      ot = dest;
      for(j = 0; j < w; ++j, ++it)
	{
	  gfloat r, g, b;
	  r = 255 * ( *(it + 6*s) - mini) / range;
	  if(r < 0) { r = 0; }
	  if(r > 255) { r = 255; }
	  g = 255 * ( *(it + 7*s) - mini) / range;
	  if(g < 0) { g = 0; }
	  if(g > 255) { g = 255; }
	  b = 255 * ( *(it + 8*s) - mini) / range;
	  if(b < 0) { b = 0; }
	  if(b > 255) { b = 255; }
	  ot[0] = (guchar) r;
	  ot[1] = (guchar) g;
	  ot[2] = (guchar) b; 
	  ot += 3;
	}
      retinex_preview_do_row(i,w,even,odd,dest);
    }
  gtk_widget_queue_draw(GTK_WIDGET(dlg->preview));
  g_free(dest);
  g_free(odd);
  g_free(even);
  g_free(cache);
}
/*
  Applique l'algo sur l'image GIMP.
 */
static void retinex(GimpDrawable* drawable)		    
{
  gint x1, y1, x2, y2;
  gint w,h;
  gint s;
  gfloat* cache;
  gfloat mean, var;
  gfloat mini, maxi, range;
  gfloat* it;
  guchar* ot;
  gint i,j;
  GimpPixelRgn dst_rgn, *pr;
  /* 
     Recupere la taille de l'image courante ou sa selection.
     On transforme ensuite cette image/selection en float.
  */
  gimp_drawable_mask_bounds(drawable->id, &x1, &y1, &x2, &y2);
  w = (x2 - x1);
  h = (y2 - y1);
  s = w*h;
  /* memoire de l'image float */
  cache = g_new(gfloat, 9*s);
  drawable_to_floatmatrix(drawable, x1, y1, w, h, cache);
  /*
    Calcule les echelles de filtrage en fonction du nombre
    de filtre et de leur repartition.
   */
  retinex_scales_distribution(RetinexScales,rvals.nscales,rvals.scales_mode,w < h ? w : h);
  /*
    Algo de normalisation couleur
   */
  gimp_progress_init(_("Retinex (1/4): filtrages..."));
  MSRCR(cache, w, h, RetinexScales, rvals.nscales);
  /* 
     Adapte la dynamique des couleurs
     en fonction des statistiques du premier et second ordre.
     L'utilisation de la variance permet de controler
     le degre de saturation des couleurs.
   */
  gimp_progress_init(_("Retinex (2/4): statistiques..."));
  compute_mean_var(cache + 6*s, &mean, &var, 3*s);
  mini = mean - rvals.cvar*var;
  maxi = mean + rvals.cvar*var;
  range = maxi - mini;
  if(!range)
    {
      range = 1.0;
    }
  gimp_progress_init(_("Retinex (3/4): dynamiques..."));
  gimp_pixel_rgn_init (&dst_rgn, drawable, x1, y1, w, h, TRUE, TRUE);
  for(pr = gimp_pixel_rgns_register(1,&dst_rgn);
      pr != NULL;
      pr = gimp_pixel_rgns_process(pr))
    {
      it = cache + ((dst_rgn.y - y1) * w) + (dst_rgn.x - x1);
      ot = dst_rgn.data;
      for(i = 0; i < dst_rgn.h; ++i)
	{
	  for(j = 0; j < dst_rgn.w; ++j, ++it)
	    {
	      gfloat r, g, b;
	      r = 255 * ( *(it + 6*s) - mini) / range;
	      if(r < 0) { r = 0; }
	      if(r > 255) { r = 255; }
	      g = 255 * ( *(it + 7*s) - mini) / range;
	      if(g < 0) { g = 0; }
	      if(g > 255) { g = 255; }
	      b = 255 * ( *(it + 8*s) - mini) / range;
	      if(b < 0) { b = 0; }
	      if(b > 255) { b = 255; }
	      ot[0] = (guchar) r;
	      ot[1] = (guchar) g;
	      ot[2] = (guchar) b; 
	      ot += dst_rgn.bpp;
	    }
	  it += w - dst_rgn.w;
	  ot += (dst_rgn.rowstride - dst_rgn.bpp * dst_rgn.w);
	}
    }
  g_free(cache);
  gimp_drawable_flush(drawable);
  gimp_drawable_merge_shadow(drawable->id, TRUE); 
  gimp_drawable_update(drawable->id, x1, y1, w, h);
}
/*
  Cette fonction convertit une image GIMP au format flottant necessaire
  a l'application de l'algo de normalisation couleur.
  Le format utilise dans l'algo est une matrice flottante a 9 dimensions.
  Les 3 premieres dimensions representent les donnees originales de l'image
  (avec un decalage de 1. pour la fonction log)
  Les 3 suivantes sont les valeurs apres filtrages.
  Les 3 dernieres sont les valeurs finales (cumuls ponderes des valeurs de filtrages).
 */
static void drawable_to_floatmatrix(GimpDrawable* src, gint x1, gint y1, gint w, gint h, gfloat* dst)
{
  GimpPixelRgn src_rgn, *pr;
  guchar* it;
  gfloat* ot;
  guint i, j;
  gint s;
  /* 
     On declare l'utilisation de l'image en lecture seulement 
     et sans utiliser de cache (FALSE,FALSE).
  */
  gimp_pixel_rgn_init (&src_rgn, src, x1, y1, w, h, FALSE, FALSE);
  s = w*h;
  /*
    Pour acceder aux donnees, on itere sur les "tiles"
    (partition de l'image) comme indique dans la doc
    de GIMP.
   */
  for(pr = gimp_pixel_rgns_register(1,&src_rgn);
      pr != NULL;
      pr = gimp_pixel_rgns_process(pr))
    {
      it = src_rgn.data;
      ot = dst + ((src_rgn.y - y1) * w) + (src_rgn.x - x1);
      for(i = 0; i < src_rgn.h; ++i)
	{
	  for(j = 0; j < src_rgn.w; ++j, ++ot)
	    {
	      *ot = 1. + it[0]; /* red */
	      *(ot + s) = 1. + it[1]; /* green */
	      *(ot + 2*s) = 1. + it[2]; /* blue */
	      *(ot + 6*s) = 0.0; /* ssr red */
	      *(ot + 7*s) = 0.0; /* ssr green */
	      *(ot + 8*s) = 0.0; /* ssr blue */
	      it += src_rgn.bpp;
	    }	  
	  /* passage a la ligne suivante */
	  it += (src_rgn.rowstride - src_rgn.bpp * src_rgn.w);
	  ot += w - src_rgn.w;
	}
    }
}
/*
  Cette fonction convertit l'image preview au format utile
  a l'algo.
 */
static void preview_to_floatmatrix(guchar* src, gint w, gint h, gfloat* dst)
{
  gint s;
  gint count;
  s = w*h;
  for(count = 0; count < s; ++count, ++dst)
    {
      *dst = 1. + src[0]; /* red */
      *(dst + s) = 1. + src[1]; /* green */
      *(dst + 2*s) = 1. + src[2]; /* blue */
      *(dst + 6*s) = 0.0; /* ssr red */
      *(dst + 7*s) = 0.0; /* ssr green */
      *(dst + 8*s) = 0.0; /* ssr blue */
      src += 3;
    }
}
/*
  Cette fonction calcule les valeurs des differentes echelles en
  fonction de la distribution voulue.
*/
static void retinex_scales_distribution(gfloat* scales, gint nscales, gint mode, gint s)
{
  if(nscales == 1)
    { /* pour un unique filtre on choisit l'echelle mediane */
      scales[0] = (gint) s / 2;
    }
  else if(nscales == 2)
    { /* pour deux filtres on choisit l'echelle mediane
         et l'echelle maximum */
      scales[0] = (gint) s / 2;
      scales[1] = (gint) s;
    }
  else
    {
      gfloat size_step = (gfloat) s / (gfloat) nscales;
      switch(mode)
	{	  
	case RETINEX_UNIFORM:
	  {
	    gint i;
	    for(i = 0; i < nscales; ++i)
	      {
		scales[i] = 2. + (gfloat) i * size_step;
	      }
	  }
	  break;
	case RETINEX_LOW:
	  {
	    gint i;
	    size_step = (gfloat) log(s - 2.0) / (gfloat) nscales;
	    for(i = 0; i < nscales; ++i)
	      {
		scales[i] = 2. + pow(10,(i*size_step)/log(10));
	      }
	  }
	  break;
	case RETINEX_HIGH:
	  {
	    gint i;
	    size_step = (gfloat) log(s - 2.0) / (gfloat) nscales;
	    for(i = 0; i < nscales; ++i)
	      {
		scales[i] = s - pow(10,(i*size_step)/log(10));
	      }
	  }
	  break;
	default:
	  break;
	}
    }  
}

/*
 * ===================================
 * FONCTIONS DE L'ALGO MSRCR
 * ===================================
 * Ces fonctions peuvent etre utilisees
 * aisement dans d'autres programmes car
 * elles ne dependent pas de GIMP.
 */
/*
  Cette fonction est le coeur de l'algo.
  (a) Filtrages a plusieurs echelles et cumul des resultats.
  (b) Calcul des valeurs finales.
 */
static void MSRCR(float* cache, int w, int h, float* scales, int nscales)
{
  int count;
  int s;
  float W;
  float* vot;
  s = w*h; 
  /* 
     Operations de filtrages en fonction des differentes echelles.
     On sommes les resultats des differents filtres selon une
     ponderation particuliere (ici equivalente pour tous).
  */
  W = 1./ (float) nscales;
  for(count = 0; count < nscales; ++count)
    {
      MSRCR_aux(cache,w,h,scales[count],W);
    }
  /* 
     Calcul du resultat final :
     Le resultat est fonction des valeurs originales de l'image
     (sur les 3 premieres dimensions) et les valeurs cumulees
     des filtrages (sur les 3 dernieres dimensions).
     Les parametres gain, alpha et offset sont constants.
   */
  vot = cache;
  for(count = s; (count); --count, ++vot)
    {
      float logl = log(*vot + *(vot + s) + *(vot + 2*s));
      *(vot + 6*s) = (log(128. * *vot) - logl) * *(vot + 6*s);
      *(vot + 7*s) = (log(128. * *(vot + s)) - logl) * *(vot + 7*s);
      *(vot + 8*s) = (log(128. * *(vot + 2*s)) - logl) * *(vot + 8*s);
    }
}
/*
  Fonction qui s'occupe des filtrages et de leur somme cumulee.
 */
static void MSRCR_aux(float* cache, int w, int h, float scale, float weight)
{
  gint s;
  float* vot;
  int count;
  /*
    L'algo de filtrage recursif necessite des coefficients
    qui varies en fonction de l'echelle (~= ecart type de la gaussienne)
    choisie.
   */
  gauss3_coefs cf;
  compute_coefs3(&cf,scale);
  s = w*h;
  /*
    Filtrage (lissage) gaussien recursif.
    La complexite du filtrage est fixe et ne depend pas de l'echelle.    
    Un effet mirroir est applique pour diminuer les effets de bord
    du lissage lorsque l'echelle est elevee. Cependant cet effet mirroir
    n'est pas complet et il subsiste encore des artefacts notament sur
    la partie inferieure droite de l'image.
   */
  GaussG0mirror(cache, cache + 3*s, w, h, &cf);
  GaussG0mirror(cache + s, cache + 4*s, w, h, &cf);
  GaussG0mirror(cache + 2*s, cache + 5*s, w, h, &cf);
  /*
    Cumul des valeurs de filtrages.
    En fait on effectue un ratio entre les valeurs originales
    et les valeurs filtrees.
   */
  vot = cache;
  for(count = s; (count); --count, vot++)
    {
      *(vot + 6*s) += weight * (log(*vot) - log(*(vot + 3*s))); /* msr red */
      *(vot + 7*s) += weight * (log(*(vot + s)) - log(*(vot + 4*s))); /* msr red */
      *(vot + 8*s) += weight * (log(*(vot + 2*s)) - log(*(vot + 5*s))); /* msr red */
    }
} 
/*
 * ===================================
 * FONCTIONS DE FILTRAGES RECURSIFS
 * ===================================
 * Ces fonctions peuvent etre utilisees
 * aisement dans d'autres programmes car
 * elles ne dependent pas de GIMP et 
 * n'utilisent que des routines standards :
 * malloc, sqrt
 */
/*
  Calculs des coefficients de l'algo de filtrage recursif.
 */
static void compute_coefs3(gauss3_coefs* c, float sigma)
{
  float q, q2, q3;
  q = 0;
  if(sigma >= 2.5)
    {
      q = 0.98711*sigma-0.96330;
    }
  else if((sigma >= 0.5) && (sigma < 2.5))
    {
      q = 3.97156-4.14554 * (gfloat) sqrt((double) 1-0.26891 * sigma);
    }
  else
    {
      q = 0.1147705018520355224609375;
    }
  q2 = q*q;
  q3 = q*q2;
  c->b[0] = (1.57825+(2.44413*q)+(1.4281 *q2)+(0.422205*q3));
  c->b[1] = (        (2.44413*q)+(2.85619*q2)+(1.26661 *q3))/c->b[0];
  c->b[2] = (                   -((1.4281*q2)+(1.26661 *q3)))/c->b[0];
  c->b[3] = (                                  (0.422205*q3))/c->b[0];
  c->alpha = 1.0-(c->b[1]+c->b[2]+c->b[3]);
  c->sigma = sigma;
  c->N = 3;
}

static void gausssmoothmirror_X(float* src, float* dst, float* cache, int s, gauss3_coefs* c)
{
  float alpha;
  float* b;
  int count;
  alpha = c->alpha;
  b = c->b;
  /* first mirror forward */
  src += (s-1);
  *cache = alpha * *src;
  --src; ++cache;
  *cache = alpha * *src + b[1]*cache[-1];
  --src; ++cache;
  *cache = alpha * *src + b[1]*cache[-1] + b[2]*cache[-2];
  --src; ++cache;
  for(count = c->N; count < s; ++count, --src, ++cache)
    {
      *cache = alpha * *src + (b[1]*cache[-1] + b[2]*cache[-2] + b[3]*cache[-3]);
    }
  /* first pass forward */
  for(count = 0; count < s; ++count, ++src, ++cache)
    {
      *cache = alpha * *src + (b[1]*cache[-1] + b[2]*cache[-2] + b[3]*cache[-3]);
    }
  /* second mirror forward */
  for(count = 0; count < s; ++count, --src, ++cache)
    {
      *cache = alpha * *src + (b[1]*cache[-1] + b[2]*cache[-2] + b[3]*cache[-3]);
    }
  /* first mirror backward */
  --cache;
  *cache = alpha * *cache;
  --cache;
  *cache = alpha * *cache + (b[1]*cache[1]);
  --cache;
  *cache = alpha * *cache + (b[1]*cache[1] + b[2]*cache[2]);
  --cache;
  for(count = c->N; count < s; ++count, --cache)
    {
      *cache = alpha * *cache + (b[1]*cache[1] + b[2]*cache[2] + b[3]*cache[3]);
    }    
  /* second pass backward */
  dst += s-1;
  for(count = 0; count < s; ++count, --dst, --cache)
    {
      *cache = alpha * *cache + (b[1]*cache[1] + b[2]*cache[2] + b[3]*cache[3]);
      *dst = *cache;
    }	
}

static void gausssmoothmirror_Y(oiterator* src, oiterator* dst, float* cache, int s, gauss3_coefs* c)
{
  float alpha;
  float* b;
  int count;
  alpha = c->alpha;
  b = c->b;
  /* first mirror forward */
  src->current += src->offset*(s-1);
  *cache = alpha * *src->current;
  src->current -= src->offset; ++cache;
  *cache = alpha * *src->current + b[1]*cache[-1];
  src->current -= src->offset; ++cache;
  *cache = alpha * *src->current + b[1]*cache[-1] + b[2]*cache[-2];
  src->current -= src->offset; ++cache;
  for(count = c->N; count < s; ++count, ++cache)
    {
      *cache = alpha * *src->current + (b[1]*cache[-1] + b[2]*cache[-2] + b[3]*cache[-3]);
      src->current -= src->offset;
    }
  /* first pass forward */
  for(count = 0; count < s; ++count, ++cache)
    {
      *cache = alpha * *src->current + (b[1]*cache[-1] + b[2]*cache[-2] + b[3]*cache[-3]);
      src->current += src->offset;
    }
  /* second mirror forward */
  for(count = 0; count < s; ++count, ++cache)
    {
      *cache = alpha * *src->current + (b[1]*cache[-1] + b[2]*cache[-2] + b[3]*cache[-3]);
      src->current -= src->offset;
    }
  /* first mirror backward */
  --cache;
  *cache = alpha * *cache;
  --cache;
  *cache = alpha * *cache + (b[1]*cache[1]);
  --cache;
  *cache = alpha * *cache + (b[1]*cache[1] + b[2]*cache[2]);
  --cache;
  for(count = c->N; count < s; ++count, --cache)
    {
      *cache = alpha * *cache + (b[1]*cache[1] + b[2]*cache[2] + b[3]*cache[3]);
    }    
  /* second pass backward */
  dst->current += dst->offset*(s-1);
  for(count = 0; count < s; ++count, --cache)
    {
      *cache = alpha * *cache + (b[1]*cache[1] + b[2]*cache[2] + b[3]*cache[3]);
      *dst->current = *cache;
      dst->current -= dst->offset;
    }	  
}

static void GaussG0mirror(float* src, float* dst, int w, int h, gauss3_coefs* c)
{
  int count;
  float* cache;
  float* it;
  float* ot;
  oiterator vit;
  oiterator vot;

  cache = (float*) malloc(3*(w+h) * sizeof(float));
  /* X */
  it = src;
  ot = dst;
  for(count = h; (count); --count, it += w, ot += w)
    {
      gausssmoothmirror_X(it,ot,cache,w,c);
    }
  /* Y */
  vit.current = dst;
  vit.offset = w;
  vot.current = dst;
  vot.offset = w;
  for(count = 0; count < w; ++count)
    {
      gausssmoothmirror_Y(&vit,&vot,cache,h,c);
      vit.current = dst + count;
      vot.current = dst + count;
    }
  free(cache);
}
/*
  Calcul de la moyenne et de la variance en une passe.
 */
static void compute_mean_var(float* src, float* mean, float* var, int s)
{
  float vsquared;
  int count;
  vsquared = 0;
  count = s;
  *mean = 0;
  do
    {
      *mean += *src;
      vsquared += *src * *src;
      ++src;
    } while(--count);
  *mean /= (float) s; /* mean */
  vsquared /= (float) s; /* mean (x^2) */
  *var = ( vsquared - (*mean * *mean) );
  *var = sqrt(*var); /* var */
}

