/* redeye.c: redeye remover plugin code
 *
 * Copyright (C) 2004  Robert Merkel <robert.merkel@benambra.org> (the "Author").
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name of the Author of the
 * Software shall not be used in advertising or otherwise to promote the
 * sale, use or other dealings in this Software without prior written
 * authorization from the Author.
 */

/*
 * This plugin is used for removing the "red-eye" that occurs in flash photos.

 * INSTALLATION

 * To install this tool, you need a GIMP plugin development environment, which 
 * may not be installed by default in your distribution.  On Debian systems
 * the necessary files are in the package libgimp2.0-dev but I do not know
 * about other systems.
 *
 * You can then install the tool by downloading this file and using the 
 * following command:
 * gimptool-2.0 --install redeye.c
 *

 * USAGE

 * To use the tool, select an area containing the eye with the
 * rectangular selection tool, and then select "Red Eye Remover" under
 * Filters/Misc.  You can choose the sensitivity of the tool with the
 * dialog that appears , with higher meaning more areas are selected
 * as red-eye areas.  -255 means no change, +255 means the entire
 * selected area will be affected.  The default, 0, normally works
 * very well, removing red-eye while skin hair and other things you
 * might select by accident.  To speed the process up, you can use
 * "Auto Red Eye Remover" under Filters/Misc instead, which simply
 * applies the remover with the default sensitivity.
 * 
 *
 * Based on a GIMP 1.2 Perl plugin by Geoff Kuenning
 * Patches and enhancements by:
 * 
 * Steve Joiner <steve@daisyhill.net> - Red levels bugfix.
 *
 * This release still probably, and almost certainly has bugs despite its 
 * trivial size.  Future enhancements may include I18N support
 * and proper documentation, in an automade multi-file tarball! 
 *
 * Please contact the author if you find a bug 
 * (but check for a new release on the gimp plugin website first.)
 * If you do, please include "Redeye plugin" in the subject line
 * 
 * 
 * Version History:
 * 0.1 - initial preliminary release, single file, only documentation 
 *       in the header comments, 2004-05-26
 * 0.2 - Bugfix of red levels, improved documentation.
 */

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

/*
 * preparation for I18N in a future version
 */

#define _(string) (string)
#define  N_(string) (string)


/* Declare local functions.
 */
static void      query  (void);
static void      run    (const gchar      *name,
                         gint        nparams,
                         const GimpParam  *param,
                         gint       *nreturn_vals,
                         GimpParam **return_vals);

static void      remove_redeye         (GimpDrawable *drawable, gint threshold);

#define RED_FACTOR 0.5133333
#define GREEN_FACTOR 1
#define BLUE_FACTOR 0.1933333

#define SCALE_WIDTH        300
#define SPIN_BUTTON_WIDTH   75

#define PLUGIN_NAME "Red Eye Plugin"
#define STANDARD_PLUGIN_ID "redeye_plugin"
#define AUTO_PLUGIN_ID "auto_redeye_plugin"
#define DATA_KEY_THRESHOLD "redeye-plugin-threshold"
#define DATA_KEY_UI_VALS "redeye-plugin-ui"

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


MAIN ()

static void
query (void)
{
  static GimpParamDef args[] =
  {
    { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" },
    { GIMP_PDB_IMAGE, "image", "Input image" },
    { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
    { GIMP_PDB_INT32, "threshold", "Redeye sensitivity"}
  };


  gint32 default_threshold = 0;
  gimp_set_data(DATA_KEY_THRESHOLD, &default_threshold, sizeof(default_threshold));

  gimp_install_procedure (STANDARD_PLUGIN_ID,
                          _("Redeye Remover"),
                          _("Remove \"redeye\" caused by camera flashes.  Sensitivity is adjustable"),
                          "Robert Merkel  <robert.merkel@benambra.org>",
                          "Robert Merkel",
                          "2004",
                          N_("<Image>/Filters/Misc/Red Eye Remover"),
                          "RGB*",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS(args), 0,
                          args, NULL);

  gimp_install_procedure (AUTO_PLUGIN_ID,
                          _("Auto Redeye Remover"),
                          _("Remove \"redeye\" caused by camera flashes.  Fixed sensitivity."),
                          "Robert Merkel  <robert.merkel@benambra.org>",
                          "Robert Merkel",
                          "2004",
                          N_("<Image>/Filters/Misc/Auto Red Eye Remover"),
                          "RGB*",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS(args), 0,
                          args, NULL);
}


/*
 * pops up a dialog to select the sensitivity,
 * modifying the threshold value if changed
 */ 


static gboolean
dialog (gint32         image_id,
	GimpDrawable   *drawable,
	gint           *threshold)
{
  GtkWidget *dlg;
  GtkWidget *table;
  GtkObject *adj;
  gboolean  run = FALSE;

  gimp_ui_init(PLUGIN_NAME, TRUE);
  
  dlg = gimp_dialog_new ("Red Eye Remover", PLUGIN_NAME,
			   NULL, 0,
			 gimp_standard_help_func, "red-eye-removal",
			 
			 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
			 GTK_STOCK_OK,     GTK_RESPONSE_OK,
			 
			 NULL);   
  
  table = gtk_table_new(1, 3, FALSE);
  
  gtk_container_add(GTK_CONTAINER (GTK_DIALOG (dlg)->vbox), table);
  gtk_widget_show(table);
  adj = gimp_scale_entry_new(GTK_TABLE(table), 0, 0, 
			     "Sensitivity", 
			     SCALE_WIDTH, SPIN_BUTTON_WIDTH, 
			     *threshold,
			     -255, 255, 1,1,0, 
			     TRUE,
			     -255, 255,
			     "Sensitivity to redeye, higher is more sensitive",
			     NULL);
  
  g_signal_connect (adj, "value_changed",
		    G_CALLBACK (gimp_int_adjustment_update),
		    threshold);
  
  run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK);
  

  gtk_widget_destroy(dlg);
  return run;
}

/*
 * main driver function
 */
			      
static void
run (const gchar      *name,
     gint             nparams,
     const GimpParam  *param,
     gint             *nreturn_vals,
     GimpParam        **return_vals)
{
  static GimpParam values[1];
  GimpDrawable *drawable;
  GimpRunMode run_mode;
  GimpPDBStatusType status = GIMP_PDB_SUCCESS;
  gint32 threshold;
  gint32 image_ID;

  run_mode = param[0].data.d_int32;

  /*  Get the specified drawable  */
  drawable = gimp_drawable_get (param[2].data.d_drawable);
  image_ID = param[1].data.d_image;


  switch (run_mode)
  {
    case GIMP_RUN_NONINTERACTIVE:
      if (nparams != 4)
      {
         status = GIMP_PDB_CALLING_ERROR;
      }
      else
      {
	threshold = param[3].data.d_int32;
      }
      break;

  case GIMP_RUN_INTERACTIVE:
    gimp_get_data(DATA_KEY_THRESHOLD, &threshold);

    
    if(strncmp(name, STANDARD_PLUGIN_ID, strlen(STANDARD_PLUGIN_ID)) == 0)
    {
      if (! dialog (image_ID, drawable, &threshold))
      {
	status = GIMP_PDB_CANCEL;
      }
    }
    else
    {
      threshold = 0;
    }
    break;

  case GIMP_RUN_WITH_LAST_VALS:
    gimp_get_data(DATA_KEY_THRESHOLD, &threshold);

    break;

  default:
    break;
  }

  /*  Make sure that the drawable is RGB color  */
  if (status == GIMP_PDB_SUCCESS && gimp_drawable_is_rgb (drawable->drawable_id))
    {
      gimp_progress_init ("Removing Redeye...");
      gimp_tile_cache_ntiles (2 * (drawable->width / gimp_tile_width () + 1));
      
      remove_redeye (drawable, threshold);

      if (run_mode != GIMP_RUN_NONINTERACTIVE)
        gimp_displays_flush ();

      /*
       * if we ran in auto mode don't reset the threshold
       */

      if (run_mode == GIMP_RUN_INTERACTIVE 
	  && strncmp(name, STANDARD_PLUGIN_ID, strlen(STANDARD_PLUGIN_ID)) == 0)
      {
	gimp_set_data (DATA_KEY_THRESHOLD, &threshold, sizeof(threshold));
      }
    }
  else
    {
      status = GIMP_PDB_EXECUTION_ERROR;
    }

  *nreturn_vals = 1;
  *return_vals = values;

  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;

  gimp_drawable_detach (drawable);
}


/*
 * actual redeye remover
 */

static void
  remove_redeye (GimpDrawable *drawable, gint threshold)
{
  GimpPixelRgn src_rgn, dest_rgn;
  guchar *src, *s;
  guchar *dest, *d;
  gint    progress, max_progress;
  gint    has_alpha, red, green, blue, alpha;
  gint    x1, y1, x2, y2;
  gint    x, y;
  gpointer pr;

  /* Get selection area */
  gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2);
  has_alpha = gimp_drawable_has_alpha (drawable->drawable_id);

  red = 0; green = 1; blue = 2;

  alpha = (has_alpha) ? drawable->bpp - 1 : drawable->bpp;

  /* Initialize progress */
  progress = 0;
  max_progress = (x2 - x1) * (y2 - y1);

  /* substitute pixel vales */
  gimp_pixel_rgn_init (&src_rgn, drawable,
                       x1, y1, (x2 - x1), (y2 - y1), FALSE, FALSE);
  gimp_pixel_rgn_init (&dest_rgn, drawable,
                       x1, y1, (x2 - x1), (y2 - y1), TRUE, TRUE);

  for (pr = gimp_pixel_rgns_register (2, &src_rgn, &dest_rgn);
       pr != NULL;
       pr = gimp_pixel_rgns_process (pr))
    {
      src = src_rgn.data;
      dest = dest_rgn.data;

      for (y = 0; y < src_rgn.h; y++)
        {
          s = src;
          d = dest;

          for (x = 0; x < src_rgn.w; x++)
            {
	      int adjusted_red = s[red] * RED_FACTOR;
	      int adjusted_green = s[green] * GREEN_FACTOR;
	      int adjusted_blue = s[blue] * BLUE_FACTOR;
	      if(adjusted_red >= adjusted_green - threshold
		 && adjusted_red >= adjusted_blue - threshold)
	      {
		d[red] = (int) (((double)(adjusted_green + adjusted_blue)) / (2.0  * RED_FACTOR));
	      }
	      else
	      {
		d[red] = s[red];
	      }
	      
	      d[green] = s[green];
	      d[blue] = s[blue];
	      
              if (has_alpha)
                d[alpha] = s[alpha];

              s += src_rgn.bpp;
              d += dest_rgn.bpp;
            }

          src += src_rgn.rowstride;
          dest += dest_rgn.rowstride;
        }

      /* Update progress */
      progress += src_rgn.w * src_rgn.h;

      gimp_progress_update ((double) progress / (double) max_progress);
    }

  /*  update the region  */
  gimp_drawable_flush (drawable);
  gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
  gimp_drawable_update (drawable->drawable_id, x1, y1, (x2 - x1), (y2 - y1));
}

