/* sample_colorize.c
 *      A GIMP Plug-In by Wolfgang Hofer
 */

/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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 3 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, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

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

#include <glib/gstdio.h>

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

#include "libgimp/stdplugins-intl.h"


/* Some useful macros */
#define RESPONSE_RESET      1
#define RESPONSE_GET_COLORS 2

#define PLUG_IN_PROC        "plug-in-sample-colorize"
#define PLUG_IN_BINARY      "sample-colorize"
#define PLUG_IN_ROLE        "gimp-sample-colorize"
#define NUMBER_IN_ARGS      13

#define LUMINOSITY_0(X)      ((X[0] * 30 + X[1] * 59 + X[2] * 11))
#define LUMINOSITY_1(X)      ((X[0] * 30 + X[1] * 59 + X[2] * 11) / 100)
#define MIX_CHANNEL(a, b, m) (((a * m) + (b * (255 - m))) / 255)

#define SMP_GRADIENT         -444
#define SMP_INV_GRADIENT     -445


#define PREVIEW_BPP 3
#define PREVIEW_SIZE_X  256
#define PREVIEW_SIZE_Y  256
#define DA_WIDTH         256
#define DA_HEIGHT        25
#define GRADIENT_HEIGHT  15
#define CONTROL_HEIGHT   DA_HEIGHT - GRADIENT_HEIGHT
#define LEVELS_DA_MASK  (GDK_EXPOSURE_MASK       | \
                         GDK_ENTER_NOTIFY_MASK   | \
                         GDK_BUTTON_PRESS_MASK   | \
                         GDK_BUTTON_RELEASE_MASK | \
                         GDK_BUTTON1_MOTION_MASK | \
                         GDK_POINTER_MOTION_HINT_MASK)

#define LOW_INPUT          0x1
#define GAMMA              0x2
#define HIGH_INPUT         0x4
#define LOW_OUTPUT         0x8
#define HIGH_OUTPUT        0x10
#define INPUT_LEVELS       0x20
#define OUTPUT_LEVELS      0x40
#define INPUT_SLIDERS      0x80
#define OUTPUT_SLIDERS     0x100
#define DRAW               0x200
#define REFRESH_DST        0x400
#define ALL                0xFFF

#define MC_GET_SAMPLE_COLORS 1
#define MC_DST_REMAP         2
#define MC_ALL               (MC_GET_SAMPLE_COLORS | MC_DST_REMAP)

typedef struct
{
  gint32  dst_id;
  gint32  sample_id;

  gint32 hold_inten;       /* TRUE or FALSE */
  gint32 orig_inten;       /* TRUE or FALSE */
  gint32 rnd_subcolors;    /* TRUE or FALSE */
  gint32 guess_missing;    /* TRUE or FALSE */
  gint32 lvl_in_min;       /* 0 up to 254 */
  gint32 lvl_in_max;       /* 1 up to 255 */
  float  lvl_in_gamma;     /* 0.1 up to 10.0  (1.0 == linear) */
  gint32 lvl_out_min;      /* 0 up to 254 */
  gint32 lvl_out_max;      /* 1 up to 255 */

  float  tol_col_err;      /* 0.0% up to 100.0%
                            * this is used to find out colors of the same
                            * colortone, while analyzing sample colors,
                            * It does not make much sense for the user to adjust this
                            * value. (I used a param file to find out a suitable value)
                            */
} t_values;

typedef struct
{
  GtkWidget     *dialog;
  GtkWidget     *sample_preview;
  GtkWidget     *dst_preview;
  GtkWidget     *sample_colortab_preview;
  GtkWidget     *sample_drawarea;
  GtkWidget     *in_lvl_gray_preview;
  GtkWidget     *in_lvl_drawarea;
  GtkAdjustment *adj_lvl_in_min;
  GtkAdjustment *adj_lvl_in_max;
  GtkAdjustment *adj_lvl_in_gamma;
  GtkAdjustment *adj_lvl_out_min;
  GtkAdjustment *adj_lvl_out_max;
  GtkWidget     *orig_inten_button;
  gint           active_slider;
  gint           slider_pos[5];  /*  positions for the five sliders  */

  gboolean       enable_preview_update;
  gboolean       sample_show_selection;
  gboolean       dst_show_selection;
  gboolean       sample_show_color;
  gboolean       dst_show_color;
} t_samp_interface;



typedef struct
{
  guchar  color[4];     /* R,G,B,A */
  gint32  sum_color;    /* nr. of sourcepixels with (nearly the same) color */
  void   *next;
} t_samp_color_elem;



typedef struct
{
  gint32              all_samples;  /* number of all source pixels with this luminosity */
  gint                from_sample;  /* TRUE: color found in sample, FALSE: interpolated color added */
  t_samp_color_elem  *col_ptr;       /* List of sample colors at same luminosity */
} t_samp_table_elem;


typedef struct
{
  gint32        drawable_id;
  GeglBuffer   *buffer;
  const Babl   *format;
  gint          width;
  gint          height;
  void         *sel_gdrw;
  gint          x1;
  gint          y1;
  gint          x2;
  gint          y2;
  gint          index_alpha;   /* 0 == no alpha, 1 == GREYA, 3 == RGBA */
  gint          bpp;
  gint          tile_width;
  gint          tile_height;
  gint          shadow;
  gint32        seldeltax;
  gint32        seldeltay;
} t_GDRW;

/*
 * Some globals
 */

static t_samp_interface  g_di;  /* global dialog interface variables */
static t_values          g_values = { -1, -1, 1, 1, 0, 1, 0, 255, 1.0, 0, 255, 5.5 };
static t_samp_table_elem g_lum_tab[256];
static guchar            g_lvl_trans_tab[256];
static guchar            g_out_trans_tab[256];
static guchar            g_sample_color_tab[256 * 3];
static guchar            g_dst_preview_buffer[PREVIEW_SIZE_X * PREVIEW_SIZE_Y * 4 ];  /* color copy with mask of dst in previewsize */

static gint32  g_tol_col_err;
static gint32  g_max_col_err;
static gint    g_Sdebug = FALSE;
static gint    g_show_progress = FALSE;

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

static gint   main_colorize             (gint          mc_flags);
static void   get_filevalues            (void);
static void   smp_dialog                (void);
static void   refresh_dst_preview       (GtkWidget    *preview,
                                         guchar       *src_buffer);
static void   update_preview            (gint32       *id_ptr);
static void   clear_tables              (void);
static void   free_colors               (void);
static void   levels_update             (gint          update);
static gint   level_in_events           (GtkWidget    *widget,
                                         GdkEvent     *event);
static gint   level_out_events          (GtkWidget    *widget,
                                         GdkEvent     *event);
static void   calculate_level_transfers (void);
static void   get_pixel                 (t_GDRW       *gdrw,
                                         gint32        x,
                                         gint32        y,
                                         guchar       *pixel);
static void   init_gdrw                 (t_GDRW       *gdrw,
                                         gint32        drawable_id,
                                         gboolean      shadow);
static void   end_gdrw                  (t_GDRW       *gdrw);
static gint32 is_layer_alive            (gint32        drawable_id);
static void   remap_pixel               (guchar       *pixel,
                                         const guchar *original,
                                         gint          bpp2);
static void   guess_missing_colors      (void);
static void   fill_missing_colors       (void);
static void   smp_get_colors            (GtkWidget *dialog);
static void   get_gradient              (gint mode);
static void   clear_preview             (GtkWidget *preview);

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

MAIN ()

static void
query (void)
{
  static const GimpParamDef args[]=
  {
    { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
    { GIMP_PDB_IMAGE, "image", "Input image (unused)" },
    { GIMP_PDB_DRAWABLE, "dst-drawable", "The drawable to be colorized (Type GRAY* or RGB*)" },
    { GIMP_PDB_DRAWABLE, "sample-drawable", "Sample drawable (should be of Type RGB or RGBA)" },
    { GIMP_PDB_INT32, "hold-inten", "hold brightness intensity levels (TRUE, FALSE)" },
    { GIMP_PDB_INT32, "orig-inten", "TRUE: hold brightness of original intensity levels. FALSE: Hold Intensity of input levels" },
    { GIMP_PDB_INT32, "rnd-subcolors", "TRUE: Use all subcolors of same intensity, FALSE: use only one color per intensity" },
    { GIMP_PDB_INT32, "guess-missing", "TRUE: guess samplecolors for the missing intensity values FALSE: use only colors found in the sample" },
    { GIMP_PDB_INT32, "in-low",   "intensity of lowest input (0 <= in_low <= 254)" },
    { GIMP_PDB_INT32, "in-high",  "intensity of highest input (1 <= in_high <= 255)" },
    { GIMP_PDB_FLOAT, "gamma",  "gamma adjustment factor (0.1 <= gamma <= 10) where 1.0 is linear" },
    { GIMP_PDB_INT32, "out-low",   "lowest sample color intensity (0 <= out_low <= 254)" },
    { GIMP_PDB_INT32, "out-high",  "highest sample color intensity (1 <= out_high <= 255)" }
  };

  static gchar *help_string =
    "This plug-in colorizes the contents of the specified (gray) layer"
    " with the help of a  sample (color) layer."
    " It analyzes all colors in the sample layer."
    " The sample colors are sorted by brightness (== intentisty) and amount"
    " and stored in a sample colortable (where brightness is the index)"
    " The pixels of the destination layer are remapped with the help of the"
    " sample colortable. If use_subcolors is TRUE, the remapping process uses"
    " all sample colors of the corresponding brightness-intensity and"
    " distributes the subcolors according to their amount in the sample"
    " (If the sample has 5 green, 3 yellow, and 1 red pixel of the "
    " intensity value 105, the destination pixels at intensity value 105"
    " are randomly painted in green, yellow and red in a relation of 5:3:1"
    " If use_subcolors is FALSE only one sample color per intensity is used."
    " (green will be used in this example)"
    " The brightness intensity value is transformed at the remapping process"
    " according to the levels: out_lo, out_hi, in_lo, in_high and gamma"
    " The in_low / in_high levels specify an initial mapping of the intensity."
    " The gamma value determines how intensities are interpolated between"
    " the in_lo and in_high levels. A gamma value of 1.0 results in linear"
    " interpolation. Higher gamma values results in more high-level intensities"
    " Lower gamma values results in more low-level intensities"
    " The out_low/out_high levels constrain the resulting intensity index"
    " The intensity index is used to pick the corresponding color"
    " in the sample colortable. If hold_inten is FALSE the picked color"
    " is used 1:1 as resulting remap_color."
    " If hold_inten is TRUE The brightness of the picked color is adjusted"
    " back to the origial intensity value (only hue and saturation are"
    " taken from the picked sample color)"
    " (or to the input level, if orig_inten is set FALSE)"
    " Works on both Grayscale and RGB image with/without alpha channel."
    " (the image with the dst_drawable is converted to RGB if necessary)"
    " The sample_drawable should be of type RGB or RGBA";

  gimp_install_procedure (PLUG_IN_PROC,
                          N_("Colorize image using a sample image as a guide"),
                          help_string,
                          "Wolfgang Hofer",
                          "hof@hotbot.com",
                          "02/2000",
                          N_("_Sample Colorize..."),
                          "RGB*, GRAY*",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (args), 0,
                          args, NULL);

  gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Colors/Map");
}

static void
run (const gchar      *name,
     gint              nparams,
     const GimpParam  *param,
     gint             *nreturn_vals,
     GimpParam       **return_vals)
{
  static GimpParam   values[1];
  GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
  const gchar       *env;
  GimpRunMode        run_mode;
  gint32             drawable_id;

  INIT_I18N ();
  gegl_init (NULL, NULL);

  env = g_getenv ("SAMPLE_COLORIZE_DEBUG");
  if (env != NULL && (*env != 'n') && (*env != 'N'))
    g_Sdebug = TRUE;

  if (g_Sdebug)
    g_printf ("sample colorize run\n");
  g_show_progress = FALSE;

  run_mode    = param[0].data.d_int32;
  drawable_id = param[2].data.d_drawable;

  *nreturn_vals = 1;
  *return_vals = values;

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

  g_values.lvl_out_min = 0;
  g_values.lvl_out_max = 255;
  g_values.lvl_in_min = 0;
  g_values.lvl_in_max = 255;
  g_values.lvl_in_gamma = 1.0;

  /* Possibly retrieve data from a previous run */
  gimp_get_data (PLUG_IN_PROC, &g_values);
  if (g_values.sample_id == SMP_GRADIENT ||
      g_values.sample_id == SMP_INV_GRADIENT)
    g_values.sample_id = -1;

  /* fix value */
  g_values.tol_col_err = 5.5;

  /*  Get the specified dst_drawable  */
  g_values.dst_id = drawable_id;

  clear_tables ();

  /*  Make sure that the drawable is gray or RGB color        */
  if (gimp_drawable_is_rgb (drawable_id) ||
      gimp_drawable_is_gray (drawable_id))
    {
      switch (run_mode)
        {
        case GIMP_RUN_INTERACTIVE:
          smp_dialog ();
          free_colors ();
          gimp_set_data (PLUG_IN_PROC, &g_values, sizeof (t_values));
          gimp_displays_flush ();
          break;

        case GIMP_RUN_NONINTERACTIVE:
          if (nparams == NUMBER_IN_ARGS)
            {
              g_values.sample_id     = param[3].data.d_drawable;
              g_values.hold_inten    = param[4].data.d_int32;
              g_values.orig_inten    = param[5].data.d_int32;
              g_values.rnd_subcolors = param[6].data.d_int32;
              g_values.guess_missing = param[7].data.d_int32;
              g_values.lvl_in_min    = param[8].data.d_int32;
              g_values.lvl_in_max    = param[9].data.d_int32;
              g_values.lvl_in_gamma  = param[10].data.d_float;
              g_values.lvl_out_min   = param[11].data.d_int32;
              g_values.lvl_out_max   = param[12].data.d_int32;
              if (main_colorize (MC_GET_SAMPLE_COLORS) >= 0)
                {
                  main_colorize (MC_DST_REMAP);
                  status = GIMP_PDB_SUCCESS;
                }
              else
                {
                  status = GIMP_PDB_EXECUTION_ERROR;
                }
            }
          else
            {
              status = GIMP_PDB_CALLING_ERROR;
            }
          break;

        case GIMP_RUN_WITH_LAST_VALS:
          break;
        }
    }
  else
    {
      status = GIMP_PDB_EXECUTION_ERROR;
    }

  values[0].data.d_status = status;
}

/* ============================================================================
 * callback and constraint procedures for the dialog
 * ============================================================================
 */

static void
smp_response_callback (GtkWidget *widget,
                       gint       response_id)
{
  switch (response_id)
    {
    case RESPONSE_RESET:
      g_values.lvl_in_min    = 0;
      g_values.lvl_in_max    = 255;
      g_values.lvl_in_gamma  = 1.0;
      g_values.lvl_out_min   = 0;
      g_values.lvl_out_max   = 255;

      levels_update (ALL);
      break;

    case RESPONSE_GET_COLORS:
      smp_get_colors (widget);
      break;

    case GTK_RESPONSE_APPLY:
      g_show_progress = TRUE;
      if (main_colorize (MC_DST_REMAP) == 0)
        {
          gimp_displays_flush ();
          g_show_progress = FALSE;
          return;
        }
      gtk_dialog_set_response_sensitive (GTK_DIALOG (widget),
                                         GTK_RESPONSE_APPLY, FALSE);
      break;

    default:
      gtk_widget_destroy (widget);
      gtk_main_quit ();
      break;
    }
}

static void
smp_toggle_callback (GtkWidget *widget,
                     gpointer   data)
{
  gboolean *toggle_val = (gboolean *)data;

  *toggle_val = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));

  if ((data == &g_di.sample_show_selection) ||
      (data == &g_di.sample_show_color))
    {
      update_preview (&g_values.sample_id);
      return;
    }

  if ((data == &g_di.dst_show_selection) ||
      (data == &g_di.dst_show_color))
    {
       update_preview (&g_values.dst_id);
       return;
    }

  if ((data == &g_values.hold_inten) ||
      (data == &g_values.orig_inten) ||
      (data == &g_values.rnd_subcolors))
    {
      if (g_di.orig_inten_button)
        gtk_widget_set_sensitive (g_di.orig_inten_button,g_values.hold_inten);
      refresh_dst_preview (g_di.dst_preview,  &g_dst_preview_buffer[0]);
    }

  if (data == &g_values.guess_missing)
    {
      if (g_values.guess_missing)
        guess_missing_colors ();
      else
        fill_missing_colors ();
      smp_get_colors (NULL);
    }
}

static void
smp_sample_combo_callback (GtkWidget *widget)
{
  gint value;

  gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &value);

  g_values.sample_id = value;

  if (value == SMP_GRADIENT || value == SMP_INV_GRADIENT)
    {
      get_gradient (value);
      smp_get_colors (NULL);

      if (g_di.sample_preview)
        clear_preview (g_di.sample_preview);

      gtk_dialog_set_response_sensitive (GTK_DIALOG (g_di.dialog),
                                         GTK_RESPONSE_APPLY, TRUE);
      gtk_dialog_set_response_sensitive (GTK_DIALOG (g_di.dialog),
                                         RESPONSE_GET_COLORS, FALSE);
    }
  else
    {
      update_preview (&g_values.sample_id);

      gtk_dialog_set_response_sensitive (GTK_DIALOG (g_di.dialog),
                                         RESPONSE_GET_COLORS, TRUE);
    }
}

static void
smp_dest_combo_callback (GtkWidget *widget)
{
  gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget),
                                 &g_values.dst_id);

  update_preview (&g_values.dst_id);

  gtk_dialog_set_response_sensitive (GTK_DIALOG (g_di.dialog),
                                     RESPONSE_GET_COLORS, TRUE);
}

static gint
smp_constrain (gint32   image_id,
               gint32   drawable_id,
               gpointer data)
{
  if (image_id < 0)
    return FALSE;

  /* don't accept layers from indexed images */
  if (gimp_drawable_is_indexed (drawable_id))
    return FALSE;

  return TRUE;
}


static void
smp_adj_lvl_in_max_upd_callback (GtkAdjustment *adjustment)
{
  gint32 value;
  gint   upd_flags;

  value = CLAMP ((gtk_adjustment_get_value (adjustment)), 1, 255);

  if (value != g_values.lvl_in_max)
    {
      g_values.lvl_in_max = value;
      upd_flags = INPUT_SLIDERS | INPUT_LEVELS | DRAW | REFRESH_DST;
      if (g_values.lvl_in_max < g_values.lvl_in_min)
        {
          g_values.lvl_in_min = g_values.lvl_in_max;
          upd_flags |= LOW_INPUT;
        }
      levels_update (upd_flags);
    }
}

static void
smp_adj_lvl_in_min_upd_callback (GtkAdjustment *adjustment)
{
  double value;
  gint   upd_flags;

  value = CLAMP ((gtk_adjustment_get_value (adjustment)), 0, 254);

  if (value != g_values.lvl_in_min)
    {
      g_values.lvl_in_min = value;
      upd_flags = INPUT_SLIDERS | INPUT_LEVELS | DRAW | REFRESH_DST;
      if (g_values.lvl_in_min > g_values.lvl_in_max)
        {
          g_values.lvl_in_max = g_values.lvl_in_min;
          upd_flags |= HIGH_INPUT;
        }
      levels_update (upd_flags);
    }
}

static void
smp_text_gamma_upd_callback (GtkAdjustment *adjustment)
{
  double value;

  value = CLAMP ((gtk_adjustment_get_value (adjustment)), 0.1, 10.0);

  if (value != g_values.lvl_in_gamma)
    {
      g_values.lvl_in_gamma = value;
      levels_update (INPUT_SLIDERS | INPUT_LEVELS | DRAW | REFRESH_DST);
    }
}

static void
smp_adj_lvl_out_max_upd_callback (GtkAdjustment *adjustment)
{
  gint32 value;
  gint   upd_flags;

  value = CLAMP ((gtk_adjustment_get_value (adjustment)), 1, 255);

  if (value != g_values.lvl_out_max)
    {
      g_values.lvl_out_max = value;
      upd_flags = OUTPUT_SLIDERS | OUTPUT_LEVELS | DRAW | REFRESH_DST;
      if (g_values.lvl_out_max < g_values.lvl_out_min)
        {
          g_values.lvl_out_min = g_values.lvl_out_max;
          upd_flags |= LOW_OUTPUT;
        }
      levels_update (upd_flags);
    }
}

static void
smp_adj_lvl_out_min_upd_callback (GtkAdjustment *adjustment)
{
  double value;
  gint   upd_flags;

  value = CLAMP ((gtk_adjustment_get_value (adjustment)), 0, 254);

  if (value != g_values.lvl_out_min)
    {
      g_values.lvl_out_min = value;
      upd_flags = OUTPUT_SLIDERS | OUTPUT_LEVELS | DRAW  | REFRESH_DST;
      if (g_values.lvl_out_min > g_values.lvl_out_max)
        {
          g_values.lvl_out_max = g_values.lvl_out_min;
          upd_flags |= HIGH_OUTPUT;
        }
      levels_update (upd_flags);
    }
}

/* ============================================================================
 * DIALOG helper procedures
 *    (workers for the updates on the preview widgets)
 * ============================================================================
 */

static void
refresh_dst_preview (GtkWidget *preview,
                     guchar    *src_buffer)
{
  guchar  allrowsbuf[3 * PREVIEW_SIZE_X * PREVIEW_SIZE_Y];
  guchar *ptr;
  guchar *src_ptr;
  guchar  lum;
  guchar  maskbyte;
  gint    x, y;
  gint    preview_bpp;
  gint    src_bpp;

  preview_bpp = PREVIEW_BPP;
  src_bpp = PREVIEW_BPP +1;   /* 3 colors + 1 maskbyte */
  src_ptr = src_buffer;

  ptr = allrowsbuf;
  for (y = 0; y < PREVIEW_SIZE_Y; y++)
    {
      for (x = 0; x < PREVIEW_SIZE_X; x++)
        {
          if ((maskbyte = src_ptr[3]) == 0)
            {
              ptr[0] = src_ptr[0];
              ptr[1] = src_ptr[1];
              ptr[2] = src_ptr[2];
            }
          else
            {
              if (g_di.dst_show_color)
                {
                  remap_pixel (ptr, src_ptr, 3);
                }
              else
                {
                  /* lum = g_out_trans_tab[g_lvl_trans_tab[LUMINOSITY_1(src_ptr)]]; */
                  /* get brightness from (uncolorized) original */

                  lum = g_lvl_trans_tab[LUMINOSITY_1 (src_ptr)];
                  /* get brightness from (uncolorized) original */

                  ptr[0] = lum;
                  ptr[1] = lum;
                  ptr[2] = lum;
                }

              if (maskbyte < 255)
                {
                  ptr[0] = MIX_CHANNEL (ptr[0], src_ptr[0], maskbyte);
                  ptr[1] = MIX_CHANNEL (ptr[1], src_ptr[1], maskbyte);
                  ptr[2] = MIX_CHANNEL (ptr[2], src_ptr[2], maskbyte);
                }
            }
          ptr += preview_bpp;
          src_ptr += src_bpp;
        }
    }
  gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview),
                          0, 0, PREVIEW_SIZE_X, PREVIEW_SIZE_Y,
                          GIMP_RGB_IMAGE,
                          allrowsbuf,
                          PREVIEW_SIZE_X * 3);
}

static void
clear_preview (GtkWidget *preview)
{
  gimp_preview_area_fill (GIMP_PREVIEW_AREA (preview),
                          0, 0, PREVIEW_SIZE_X, PREVIEW_SIZE_Y,
                          170, 170, 170);
}

static void
update_pv (GtkWidget *preview,
           gboolean   show_selection,
           t_GDRW    *gdrw,
           guchar    *dst_buffer,
           gboolean   is_color)
{
  guchar  allrowsbuf[4 * PREVIEW_SIZE_X * PREVIEW_SIZE_Y];
  guchar  pixel[4];
  guchar *ptr;
  gint    x, y;
  gint    x2, y2;
  gint    ofx, ofy;
  gint    sel_width, sel_height;
  double  scale_x, scale_y;
  guchar *buf_ptr;
  guchar  dummy[4];
  guchar  maskbytes[4];
  gint    dstep;
  guchar  alpha;


  if (!preview)
    return;

  /* init gray pixel (if we are called without a sourceimage (gdwr == NULL) */
  pixel[0] = pixel[1] =pixel[2] = pixel[3] = 127;

  /* calculate scale factors and offsets */
  if (show_selection)
    {
      sel_width  = gdrw->x2 - gdrw->x1;
      sel_height = gdrw->y2 - gdrw->y1;

      if (sel_height > sel_width)
        {
          scale_y = (gfloat) sel_height / PREVIEW_SIZE_Y;
          scale_x = scale_y;
          ofx = (gdrw->x1 +
                   ((sel_width - (PREVIEW_SIZE_X * scale_x)) / 2));
          ofy = gdrw->y1;
        }
      else
        {
          scale_x = (gfloat) sel_width / PREVIEW_SIZE_X;
          scale_y = scale_x;
          ofx = gdrw->x1;
          ofy = (gdrw->y1 +
                   ((sel_height - (PREVIEW_SIZE_Y * scale_y)) / 2));
        }
    }
  else
    {
      if (gdrw->height > gdrw->width)
        {
          scale_y = (gfloat) gdrw->height / PREVIEW_SIZE_Y;
          scale_x = scale_y;
          ofx = (gdrw->width - (PREVIEW_SIZE_X * scale_x)) / 2;
          ofy = 0;
        }
      else
        {
          scale_x = (gfloat) gdrw->width / PREVIEW_SIZE_X;
          scale_y = scale_x;
          ofx = 0;
          ofy = (gdrw->height - (PREVIEW_SIZE_Y * scale_y)) / 2;
        }
    }

  /* check if output goes to previw widget or to dst_buffer */
  if (dst_buffer)
    {
      buf_ptr = dst_buffer;
      dstep = PREVIEW_BPP +1;
    }
  else
    {
      buf_ptr = &dummy[0];
      dstep = 0;
    }


  /* render preview */
  ptr = allrowsbuf;
  for (y = 0; y < PREVIEW_SIZE_Y; y++)
    {
      for (x = 0; x < PREVIEW_SIZE_X; x++)
        {
          if (gdrw->drawable_id > 0)
            {
              x2 = ofx + (x * scale_x);
              y2 = ofy + (y * scale_y);
              get_pixel (gdrw, x2, y2, &pixel[0]);
              if (gdrw->sel_gdrw)
                {
                  get_pixel (gdrw->sel_gdrw,
                             x2 + gdrw->seldeltax,
                             y2 + gdrw->seldeltay,
                             &maskbytes[0]);
                }
              else
                {
                  maskbytes[0] = 255;
                }
            }

          alpha = pixel[gdrw->index_alpha];
          if (is_color && (gdrw->bpp > 2))
            {
              buf_ptr[0] = ptr[0] = pixel[0];
              buf_ptr[1] = ptr[1] = pixel[1];
              buf_ptr[2] = ptr[2] = pixel[2];
            }
          else
            {
              if (gdrw->bpp > 2)
                *ptr = LUMINOSITY_1 (pixel);
              else
                *ptr = pixel[0];

              *buf_ptr   = *ptr;
              buf_ptr[1] = ptr[1] = *ptr;
              buf_ptr[2] = ptr[2] = *ptr;
            }
          if (gdrw->index_alpha == 0)             /* has no alpha channel */
            buf_ptr[3] = ptr[3] = 255;
          else
            buf_ptr[3] = ptr[3] = MIN (maskbytes[0], alpha);
          buf_ptr += dstep;   /* advance (or stay at dummy byte) */
          ptr += 4;
        }

    }

  if (dst_buffer == NULL)
    {
      gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview),
                              0, 0, PREVIEW_SIZE_X, PREVIEW_SIZE_Y,
                              GIMP_RGBA_IMAGE,
                              allrowsbuf,
                              PREVIEW_SIZE_X * 4);
      gtk_widget_queue_draw (preview);
    }
}

static void
update_preview (gint32 *id_ptr)
{
  t_GDRW   gdrw;
  gboolean drawable = FALSE;

  if (g_Sdebug)
    g_printf ("UPD PREVIEWS   ID:%d ENABLE_UPD:%d\n",
              id_ptr ? (int) *id_ptr : -1, (int)g_di.enable_preview_update);

  if (id_ptr == NULL || !g_di.enable_preview_update)
    return;
  if (is_layer_alive (*id_ptr) < 0)
    {
      /* clear preview on invalid drawable id
       * (SMP_GRADIENT and SMP_INV_GRADIENT)
       */
      if (id_ptr == &g_values.sample_id)
          clear_preview (g_di.sample_preview);
      if (id_ptr == &g_values.dst_id)
        clear_preview (g_di.dst_preview);
      return;
    }

  if (id_ptr == &g_values.sample_id)
    {
      drawable = TRUE;

      init_gdrw (&gdrw, *id_ptr, FALSE);
      update_pv (g_di.sample_preview, g_di.sample_show_selection, &gdrw,
                 NULL, g_di.sample_show_color);
    }
  else if (id_ptr == &g_values.dst_id)
    {
      drawable = TRUE;

      init_gdrw (&gdrw, *id_ptr, FALSE);
      update_pv (g_di.dst_preview, g_di.dst_show_selection, &gdrw,
                 &g_dst_preview_buffer[0], g_di.dst_show_color);
      refresh_dst_preview (g_di.dst_preview,  &g_dst_preview_buffer[0]);
    }

  if (drawable)
    end_gdrw (&gdrw);
}

static void
levels_draw_slider (cairo_t  *cr,
                    GdkColor *border_color,
                    GdkColor *fill_color,
                    gint      xpos)
{
  cairo_move_to (cr, xpos, 0);
  cairo_line_to (cr, xpos - (CONTROL_HEIGHT - 1) / 2, CONTROL_HEIGHT - 1);
  cairo_line_to (cr, xpos + (CONTROL_HEIGHT - 1) / 2, CONTROL_HEIGHT - 1);
  cairo_line_to (cr, xpos, 0);

  gdk_cairo_set_source_color (cr, fill_color);
  cairo_fill_preserve (cr);

  gdk_cairo_set_source_color (cr, border_color);
  cairo_stroke (cr);
}

static void
smp_get_colors (GtkWidget *dialog)
{
  gint   i;
  guchar buffer[3 * DA_WIDTH * GRADIENT_HEIGHT];

  update_preview (&g_values.sample_id);

  if (dialog && main_colorize (MC_GET_SAMPLE_COLORS) >= 0)  /* do not colorize, just analyze sample colors */
    gtk_dialog_set_response_sensitive (GTK_DIALOG (g_di.dialog),
                                       GTK_RESPONSE_APPLY, TRUE);
  for (i = 0; i < GRADIENT_HEIGHT; i++)
    memcpy (buffer + i * 3 * DA_WIDTH, g_sample_color_tab, 3 * DA_WIDTH);

  update_preview (&g_values.dst_id);

  gimp_preview_area_draw (GIMP_PREVIEW_AREA (g_di.sample_colortab_preview),
                          0, 0, DA_WIDTH, GRADIENT_HEIGHT,
                          GIMP_RGB_IMAGE,
                          buffer,
                          DA_WIDTH * 3);
}


static void
levels_update (gint update)
{
  gint i;

  if (g_Sdebug)
    g_printf ("levels_update: update reques %x\n", update);

  /*  Recalculate the transfer array  */
  calculate_level_transfers ();
  if (update & REFRESH_DST)
    refresh_dst_preview (g_di.dst_preview,  &g_dst_preview_buffer[0]);

  /* update the spinbutton entry widgets */
  if (update & LOW_INPUT)
    gtk_adjustment_set_value (g_di.adj_lvl_in_min,
                              g_values.lvl_in_min);
  if (update & GAMMA)
    gtk_adjustment_set_value (g_di.adj_lvl_in_gamma,
                              g_values.lvl_in_gamma);
  if (update & HIGH_INPUT)
    gtk_adjustment_set_value (g_di.adj_lvl_in_max,
                              g_values.lvl_in_max);
  if (update & LOW_OUTPUT)
    gtk_adjustment_set_value (g_di.adj_lvl_out_min,
                              g_values.lvl_out_min);
  if (update & HIGH_OUTPUT)
    gtk_adjustment_set_value (g_di.adj_lvl_out_max,
                              g_values.lvl_out_max);
  if (update & INPUT_LEVELS)
    {
      guchar buffer[DA_WIDTH * GRADIENT_HEIGHT];
      for (i = 0; i < GRADIENT_HEIGHT; i++)
        memcpy (buffer + DA_WIDTH * i, g_lvl_trans_tab, DA_WIDTH);
      gimp_preview_area_draw (GIMP_PREVIEW_AREA (g_di.in_lvl_gray_preview),
                              0, 0, DA_WIDTH, GRADIENT_HEIGHT,
                              GIMP_GRAY_IMAGE,
                              buffer,
                              DA_WIDTH);
    }

  if (update & INPUT_SLIDERS)
    {
      GtkStyle *style = gtk_widget_get_style (g_di.in_lvl_drawarea);
      cairo_t  *cr    = gdk_cairo_create (gtk_widget_get_window (g_di.in_lvl_drawarea));
      gdouble   width, mid, tmp;

      gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]);
      cairo_paint (cr);

      cairo_translate (cr, 0.5, 0.5);
      cairo_set_line_width (cr, 1.0);

      g_di.slider_pos[0] = DA_WIDTH * ((double) g_values.lvl_in_min / 255.0);
      g_di.slider_pos[2] = DA_WIDTH * ((double) g_values.lvl_in_max / 255.0);

      width = (double) (g_di.slider_pos[2] - g_di.slider_pos[0]) / 2.0;
      mid = g_di.slider_pos[0] + width;
      tmp = log10 (1.0 / g_values.lvl_in_gamma);
      g_di.slider_pos[1] = (int) (mid + width * tmp + 0.5);

      levels_draw_slider (cr,
                          &style->black,
                          &style->dark[GTK_STATE_NORMAL],
                          g_di.slider_pos[1]);
      levels_draw_slider (cr,
                          &style->black,
                          &style->black,
                          g_di.slider_pos[0]);
      levels_draw_slider (cr,
                          &style->black,
                          &style->white,
                          g_di.slider_pos[2]);

      cairo_destroy (cr);
    }

  if (update & OUTPUT_SLIDERS)
    {
      GtkStyle *style = gtk_widget_get_style (g_di.sample_drawarea);
      cairo_t  *cr    = gdk_cairo_create (gtk_widget_get_window (g_di.sample_drawarea));

      gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]);
      cairo_paint (cr);

      cairo_translate (cr, 0.5, 0.5);
      cairo_set_line_width (cr, 1.0);

      g_di.slider_pos[3] = DA_WIDTH * ((double) g_values.lvl_out_min / 255.0);
      g_di.slider_pos[4] = DA_WIDTH * ((double) g_values.lvl_out_max / 255.0);

      levels_draw_slider (cr,
                          &style->black,
                          &style->black,
                          g_di.slider_pos[3]);
      levels_draw_slider (cr,
                          &style->black,
                          &style->black,
                          g_di.slider_pos[4]);

      cairo_destroy (cr);
    }
}

static gboolean
level_in_events (GtkWidget *widget,
                 GdkEvent  *event)
{
  GdkEventButton *bevent;
  GdkEventMotion *mevent;
  gdouble         width, mid, tmp;
  gint            x, distance;
  gint            i;
  gint            update = FALSE;

  switch (event->type)
    {
    case GDK_EXPOSE:
      if (g_Sdebug)
        g_printf ("EVENT: GDK_EXPOSE\n");
      if (widget == g_di.in_lvl_drawarea)
        levels_update (INPUT_SLIDERS);
      break;

    case GDK_BUTTON_PRESS:
      if (g_Sdebug)
        g_printf ("EVENT: GDK_BUTTON_PRESS\n");
      gtk_grab_add (widget);
      bevent = (GdkEventButton *) event;

      distance = G_MAXINT;
      for (i = 0; i < 3; i++)
        {
          if (fabs (bevent->x - g_di.slider_pos[i]) < distance)
            {
              g_di.active_slider = i;
              distance = fabs (bevent->x - g_di.slider_pos[i]);
            }
        }
      x = bevent->x;
      update = TRUE;
      break;

    case GDK_BUTTON_RELEASE:
      if (g_Sdebug)
        g_printf ("EVENT: GDK_BUTTON_RELEASE\n");
      gtk_grab_remove (widget);
      switch (g_di.active_slider)
        {
        case 0:  /*  low input  */
          levels_update (LOW_INPUT | GAMMA | DRAW);
          break;

        case 1:  /*  gamma  */
          levels_update (GAMMA);
          break;

        case 2:  /*  high input  */
          levels_update (HIGH_INPUT | GAMMA | DRAW);
          break;
        }

      refresh_dst_preview (g_di.dst_preview,  &g_dst_preview_buffer[0]);
      break;

    case GDK_MOTION_NOTIFY:
      if (g_Sdebug)
        g_printf ("EVENT: GDK_MOTION_NOTIFY\n");
      mevent = (GdkEventMotion *) event;
      x = mevent->x;
      gdk_event_request_motions (mevent);
      update = TRUE;
      break;

    default:
      if (g_Sdebug)
        g_printf ("EVENT: default\n");
      break;
    }

  if (update)
    {
      if (g_Sdebug)
        g_printf ("EVENT: ** update **\n");
      switch (g_di.active_slider)
        {
        case 0:  /*  low input  */
          g_values.lvl_in_min = ((double) x / (double) DA_WIDTH) * 255.0;
          g_values.lvl_in_min = CLAMP (g_values.lvl_in_min,
                                       0, g_values.lvl_in_max);
          break;

        case 1:  /*  gamma  */
          width = (double) (g_di.slider_pos[2] - g_di.slider_pos[0]) / 2.0;
          mid = g_di.slider_pos[0] + width;

          x = CLAMP (x, g_di.slider_pos[0], g_di.slider_pos[2]);
          tmp = (double) (x - mid) / width;
          g_values.lvl_in_gamma = 1.0 / pow (10, tmp);

          /*  round the gamma value to the nearest 1/100th  */
          g_values.lvl_in_gamma =
            floor (g_values.lvl_in_gamma * 100 + 0.5) / 100.0;
          break;

        case 2:  /*  high input  */
          g_values.lvl_in_max = ((double) x / (double) DA_WIDTH) * 255.0;
          g_values.lvl_in_max = CLAMP (g_values.lvl_in_max,
                                       g_values.lvl_in_min, 255);
          break;
        }

      levels_update (INPUT_SLIDERS | INPUT_LEVELS | DRAW);
    }

  return FALSE;
}

static gboolean
level_out_events (GtkWidget *widget,
                  GdkEvent  *event)
{
  GdkEventButton *bevent;
  GdkEventMotion *mevent;
  gint            x, distance;
  gint            i;
  gint            update = FALSE;

  switch (event->type)
    {
    case GDK_EXPOSE:
      if (g_Sdebug)
        g_printf ("OUT_EVENT: GDK_EXPOSE\n");
      if (widget == g_di.sample_drawarea)
        levels_update (OUTPUT_SLIDERS);
      break;

    case GDK_BUTTON_PRESS:
      if (g_Sdebug)
        g_printf ("OUT_EVENT: GDK_BUTTON_PRESS\n");
      bevent = (GdkEventButton *) event;

      distance = G_MAXINT;
      for (i = 3; i < 5; i++)
        {
          if (fabs (bevent->x - g_di.slider_pos[i]) < distance)
            {
              g_di.active_slider = i;
              distance = fabs (bevent->x - g_di.slider_pos[i]);
            }
        }

      x = bevent->x;
      update = TRUE;
      break;

    case GDK_BUTTON_RELEASE:
      if (g_Sdebug)
        g_printf ("OUT_EVENT: GDK_BUTTON_RELEASE\n");
      switch (g_di.active_slider)
        {
        case 3:  /*  low output  */
          levels_update (LOW_OUTPUT | DRAW);
          break;

        case 4:  /*  high output  */
          levels_update (HIGH_OUTPUT | DRAW);
          break;
        }

      refresh_dst_preview (g_di.dst_preview,  &g_dst_preview_buffer[0]);
      break;

    case GDK_MOTION_NOTIFY:
      if (g_Sdebug)
        g_printf ("OUT_EVENT: GDK_MOTION_NOTIFY\n");
      mevent = (GdkEventMotion *) event;
      x = mevent->x;
      gdk_event_request_motions (mevent);
      update = TRUE;
      break;

    default:
      if (g_Sdebug)
        g_printf ("OUT_EVENT: default\n");
      break;
    }

  if (update)
    {
      if (g_Sdebug)
        g_printf ("OUT_EVENT: ** update **\n");
      switch (g_di.active_slider)
        {
        case 3:  /*  low output  */
          g_values.lvl_out_min = ((double) x / (double) DA_WIDTH) * 255.0;
          g_values.lvl_out_min = CLAMP (g_values.lvl_out_min,
                                        0, g_values.lvl_out_max);
          break;

        case 4:  /*  high output  */
          g_values.lvl_out_max = ((double) x / (double) DA_WIDTH) * 255.0;
          g_values.lvl_out_max = CLAMP (g_values.lvl_out_max,
                                        g_values.lvl_out_min, 255);
          break;
        }

      levels_update (OUTPUT_SLIDERS | OUTPUT_LEVELS | DRAW);
    }

  return FALSE;
}


/* ============================================================================
 * smp_dialog
 *        The Interactive Dialog
 * ============================================================================
 */
static void
smp_dialog (void)
{
  GtkWidget *dialog;
  GtkWidget *hbox;
  GtkWidget *vbox2;
  GtkWidget *frame;
  GtkWidget *table;
  GtkWidget *check_button;
  GtkWidget *label;
  GtkWidget *combo;
  GtkWidget *spinbutton;
  GtkObject *data;
  gint       ty;

  /* set flags for check buttons from mode value bits */
  if (g_Sdebug)
    g_print ("smp_dialog START\n");

  /* init some dialog variables */
  g_di.enable_preview_update = FALSE;
  g_di.sample_show_selection = TRUE;
  g_di.dst_show_selection    = TRUE;
  g_di.dst_show_color        = TRUE;
  g_di.sample_show_color     = TRUE;
  g_di.orig_inten_button     = NULL;

  /* Init GTK  */
  gimp_ui_init (PLUG_IN_BINARY, TRUE);

  /* Main Dialog */
  g_di.dialog = dialog =
    gimp_dialog_new (_("Sample Colorize"), PLUG_IN_ROLE,
                     NULL, 0,
                     gimp_standard_help_func, PLUG_IN_PROC,

                     _("_Reset"),             RESPONSE_RESET,
                     _("Get _Sample Colors"), RESPONSE_GET_COLORS,
                     _("_Close"),             GTK_RESPONSE_CLOSE,
                     _("_Apply"),             GTK_RESPONSE_APPLY,

                     NULL);

  gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
                                           RESPONSE_GET_COLORS,
                                           RESPONSE_RESET,
                                           GTK_RESPONSE_APPLY,
                                           GTK_RESPONSE_CLOSE,
                                           -1);

  gimp_window_set_transient (GTK_WINDOW (dialog));

  g_signal_connect (dialog, "response",
                    G_CALLBACK (smp_response_callback),
                    NULL);

  /* table for values */
  table = gtk_table_new (7, 5, FALSE);
  gtk_table_set_row_spacings (GTK_TABLE (table), 12);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_container_set_border_width (GTK_CONTAINER (table), 12);
  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
                      table, TRUE, TRUE, 0);

  ty = 0;
  /* layer combo_box (Dst) */
  label = gtk_label_new (_("Destination:"));
  gtk_label_set_xalign (GTK_LABEL (label), 1.0);
  gtk_table_attach (GTK_TABLE (table), label, 0, 1, ty, ty + 1,
                    GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show (label);

  combo = gimp_layer_combo_box_new (smp_constrain, NULL);
  gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), g_values.dst_id,
                              G_CALLBACK (smp_dest_combo_callback),
                              NULL);

  gtk_table_attach (GTK_TABLE (table), combo, 1, 2, ty, ty + 1,
                    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
  gtk_widget_show (combo);

  /* layer combo_box (Sample) */
  label = gtk_label_new (_("Sample:"));
  gtk_label_set_xalign (GTK_LABEL (label), 1.0);
  gtk_table_attach (GTK_TABLE (table), label, 3, 4, ty, ty + 1,
                    GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show (label);

  combo = gimp_layer_combo_box_new (smp_constrain, NULL);

  gimp_int_combo_box_prepend (GIMP_INT_COMBO_BOX (combo),
                              GIMP_INT_STORE_VALUE,     SMP_INV_GRADIENT,
                              GIMP_INT_STORE_LABEL,     _("From reverse gradient"),
                              GIMP_INT_STORE_ICON_NAME, GIMP_ICON_GRADIENT,
                              -1);
  gimp_int_combo_box_prepend (GIMP_INT_COMBO_BOX (combo),
                              GIMP_INT_STORE_VALUE,     SMP_GRADIENT,
                              GIMP_INT_STORE_LABEL,     _("From gradient"),
                              GIMP_INT_STORE_ICON_NAME, GIMP_ICON_GRADIENT,
                              -1);

  gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), g_values.sample_id,
                              G_CALLBACK (smp_sample_combo_callback),
                              NULL);

  gtk_table_attach (GTK_TABLE (table), combo, 4, 5, ty, ty + 1,
                    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
  gtk_widget_show (combo);

  ty++;


  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  gtk_table_attach (GTK_TABLE (table), hbox, 0, 2, ty, ty + 1,
                    GTK_FILL, 0, 0, 0);
  gtk_widget_show (hbox);

  /* check button */
  check_button = gtk_check_button_new_with_mnemonic (_("Sho_w selection"));
  gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0);
  gtk_widget_show (check_button);

  g_signal_connect (check_button, "toggled",
                    G_CALLBACK (smp_toggle_callback),
                    &g_di.dst_show_selection);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
                                g_di.dst_show_selection);

  /* check button */
  check_button = gtk_check_button_new_with_mnemonic (_("Show co_lor"));
  gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0);
  gtk_widget_show (check_button);

  g_signal_connect (check_button, "toggled",
                    G_CALLBACK (smp_toggle_callback),
                    &g_di.dst_show_color);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
                                g_di.dst_show_color);

  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  gtk_table_attach (GTK_TABLE (table), hbox, 3, 5, ty, ty + 1,
                    GTK_FILL, 0, 0, 0);
  gtk_widget_show (hbox);

  /* check button */
  check_button = gtk_check_button_new_with_mnemonic (_("Show selec_tion"));
  gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0);
  gtk_widget_show (check_button);

  g_signal_connect (check_button, "toggled",
                    G_CALLBACK (smp_toggle_callback),
                    &g_di.sample_show_selection);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
                                g_di.sample_show_selection);

  /* check button */
  check_button = gtk_check_button_new_with_mnemonic (_("Show c_olor"));
  gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0);
  gtk_widget_show (check_button);

  g_signal_connect (check_button, "toggled",
                    G_CALLBACK (smp_toggle_callback),
                    &g_di.sample_show_color);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
                                g_di.sample_show_color);

  ty++;

  /* Preview (Dst) */

  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_table_attach (GTK_TABLE (table),
                    frame, 0, 2, ty, ty + 1, 0, 0, 0, 0);
  gtk_widget_show (frame);

  g_di.dst_preview = gimp_preview_area_new ();
  gtk_widget_set_size_request (g_di.dst_preview,
                               PREVIEW_SIZE_X, PREVIEW_SIZE_Y);
  gtk_container_add (GTK_CONTAINER (frame), g_di.dst_preview);
  gtk_widget_show (g_di.dst_preview);

  /* Preview (Sample)*/

  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_table_attach (GTK_TABLE (table),
                    frame, 3, 5, ty, ty + 1, 0, 0, 0, 0);
  gtk_widget_show (frame);

  g_di.sample_preview = gimp_preview_area_new ();
  gtk_widget_set_size_request (g_di.sample_preview,
                               PREVIEW_SIZE_X, PREVIEW_SIZE_Y);
  gtk_container_add (GTK_CONTAINER (frame), g_di.sample_preview);
  gtk_widget_show (g_di.sample_preview);

  ty++;

  /*  The levels graylevel prevev  */
  frame = gtk_frame_new (NULL);

  vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
  gtk_container_add (GTK_CONTAINER (frame), vbox2);
  gtk_table_attach (GTK_TABLE (table),
                    frame, 0, 2, ty, ty + 1, 0, 0, 0, 0);

  g_di.in_lvl_gray_preview = gimp_preview_area_new ();
  gtk_widget_set_size_request (g_di.in_lvl_gray_preview,
                               DA_WIDTH, GRADIENT_HEIGHT);
  gtk_widget_set_events (g_di.in_lvl_gray_preview, LEVELS_DA_MASK);
  gtk_box_pack_start (GTK_BOX (vbox2), g_di.in_lvl_gray_preview, FALSE, TRUE, 0);
  gtk_widget_show (g_di.in_lvl_gray_preview);

  g_signal_connect (g_di.in_lvl_gray_preview, "event",
                    G_CALLBACK (level_in_events),
                    NULL);

  /*  The levels drawing area  */
  g_di.in_lvl_drawarea = gtk_drawing_area_new ();
  gtk_widget_set_size_request (g_di.in_lvl_drawarea,
                               DA_WIDTH, CONTROL_HEIGHT);
  gtk_widget_set_events (g_di.in_lvl_drawarea, LEVELS_DA_MASK);
  gtk_box_pack_start (GTK_BOX (vbox2), g_di.in_lvl_drawarea, FALSE, TRUE, 0);
  gtk_widget_show (g_di.in_lvl_drawarea);

  g_signal_connect (g_di.in_lvl_drawarea, "event",
                    G_CALLBACK (level_in_events),
                    NULL);

  gtk_widget_show (vbox2);
  gtk_widget_show (frame);

  /*  The sample_colortable prevev  */
  frame = gtk_frame_new (NULL);

  vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
  gtk_container_add (GTK_CONTAINER (frame), vbox2);
  gtk_table_attach (GTK_TABLE (table),
                    frame, 3, 5, ty, ty + 1, 0, 0, 0, 0);

  g_di.sample_colortab_preview = gimp_preview_area_new ();
  gtk_widget_set_size_request (g_di.sample_colortab_preview,
                               DA_WIDTH, GRADIENT_HEIGHT);
  gtk_box_pack_start (GTK_BOX (vbox2), g_di.sample_colortab_preview, FALSE, TRUE, 0);
  gtk_widget_show (g_di.sample_colortab_preview);

  /*  The levels drawing area  */
  g_di.sample_drawarea = gtk_drawing_area_new ();
  gtk_widget_set_size_request (g_di.sample_drawarea,
                               DA_WIDTH, CONTROL_HEIGHT);
  gtk_widget_set_events (g_di.sample_drawarea, LEVELS_DA_MASK);
  gtk_box_pack_start (GTK_BOX (vbox2), g_di.sample_drawarea, FALSE, TRUE, 0);
  gtk_widget_show (g_di.sample_drawarea);

  g_signal_connect (g_di.sample_drawarea, "event",
                    G_CALLBACK (level_out_events),
                    NULL);

  gtk_widget_show (vbox2);
  gtk_widget_show (frame);


  ty++;

  /*  Horizontal box for INPUT levels text widget  */
  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE);
  gtk_table_attach (GTK_TABLE (table), hbox, 0, 2, ty, ty+1,
                    GTK_FILL, 0, 0, 0);

  label = gtk_label_new (_("Input levels:"));
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  /* min input spinbutton */
  data = gtk_adjustment_new ((gfloat)g_values.lvl_in_min, 0.0, 254.0, 1, 10, 0);
  g_di.adj_lvl_in_min = GTK_ADJUSTMENT (data);

  spinbutton = gimp_spin_button_new (g_di.adj_lvl_in_min, 0.5, 0);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
  gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0);
  gtk_widget_show (spinbutton);

  g_signal_connect (g_di.adj_lvl_in_min, "value-changed",
                    G_CALLBACK (smp_adj_lvl_in_min_upd_callback),
                    &g_di);

  /* input gamma spinbutton */
  data = gtk_adjustment_new ((gfloat)g_values.lvl_in_gamma, 0.1, 10.0, 0.02, 0.2, 0);
  g_di.adj_lvl_in_gamma = GTK_ADJUSTMENT (data);

  spinbutton = gimp_spin_button_new (g_di.adj_lvl_in_gamma, 0.5, 2);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
  gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0);
  gtk_widget_show (spinbutton);

  g_signal_connect (g_di.adj_lvl_in_gamma, "value-changed",
                    G_CALLBACK (smp_text_gamma_upd_callback),
                    &g_di);

  /* high input spinbutton */
  data = gtk_adjustment_new ((gfloat)g_values.lvl_in_max, 1.0, 255.0, 1, 10, 0);
  g_di.adj_lvl_in_max = GTK_ADJUSTMENT (data);

  spinbutton = gimp_spin_button_new (g_di.adj_lvl_in_max, 0.5, 0);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
  gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0);
  gtk_widget_show (spinbutton);

  g_signal_connect (g_di.adj_lvl_in_max, "value-changed",
                    G_CALLBACK (smp_adj_lvl_in_max_upd_callback),
                    &g_di);

  gtk_widget_show (hbox);

  /*  Horizontal box for OUTPUT levels text widget  */
  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
  gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE);
  gtk_table_attach (GTK_TABLE (table), hbox, 3, 5, ty, ty+1,
                    GTK_FILL, 0, 0, 0);

  label = gtk_label_new (_("Output levels:"));
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  /*  min output spinbutton */
  data = gtk_adjustment_new ((gfloat)g_values.lvl_out_min, 0.0, 254.0, 1, 10, 0);
  g_di.adj_lvl_out_min = GTK_ADJUSTMENT (data);

  spinbutton = gimp_spin_button_new (g_di.adj_lvl_out_min, 0.5, 0);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
  gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0);
  gtk_widget_show (spinbutton);

  g_signal_connect (g_di.adj_lvl_out_min, "value-changed",
                    G_CALLBACK (smp_adj_lvl_out_min_upd_callback),
                    &g_di);

  /* high output spinbutton */
  data = gtk_adjustment_new ((gfloat)g_values.lvl_out_max, 0.0, 255.0, 1, 10, 0);
  g_di.adj_lvl_out_max = GTK_ADJUSTMENT (data);

  spinbutton = gimp_spin_button_new (g_di.adj_lvl_out_max, 0.5, 0);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
  gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0);
  gtk_widget_show (spinbutton);

  g_signal_connect (g_di.adj_lvl_out_max, "value-changed",
                    G_CALLBACK (smp_adj_lvl_out_max_upd_callback),
                    &g_di);

  gtk_widget_show (hbox);

  ty++;

  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  gtk_table_attach (GTK_TABLE (table), hbox, 0, 2, ty, ty+1,
                    GTK_FILL, 0, 0, 0);
  gtk_widget_show (hbox);

  /* check button */
  check_button = gtk_check_button_new_with_mnemonic (_("Hold _intensity"));
  gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0);
  gtk_widget_show (check_button);

  g_signal_connect (check_button, "toggled",
                    G_CALLBACK (smp_toggle_callback),
                    &g_values.hold_inten);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
                                g_values.hold_inten);

  /* check button */
  check_button = gtk_check_button_new_with_mnemonic (_("Original i_ntensity"));
  g_di.orig_inten_button = check_button;
  gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0);
  gtk_widget_set_sensitive (g_di.orig_inten_button, g_values.hold_inten);
  gtk_widget_show (check_button);

  g_signal_connect (check_button, "toggled",
                    G_CALLBACK (smp_toggle_callback),
                    &g_values.orig_inten);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
                                g_values.orig_inten);

  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
  gtk_table_attach (GTK_TABLE (table), hbox, 3, 5, ty, ty+1,
                    GTK_FILL, 0, 0, 0);
  gtk_widget_show (hbox);

  /* check button */
  check_button = gtk_check_button_new_with_mnemonic (_("Us_e subcolors"));
  gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0);
  gtk_widget_show (check_button);

  g_signal_connect (check_button, "toggled",
                    G_CALLBACK (smp_toggle_callback),
                    &g_values.rnd_subcolors);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
                                g_values.rnd_subcolors);

  /* check button */
  check_button = gtk_check_button_new_with_mnemonic (_("S_mooth samples"));
  gtk_box_pack_start (GTK_BOX (hbox), check_button, FALSE, FALSE, 0);
  gtk_widget_show (check_button);

  g_signal_connect (check_button, "toggled",
                    G_CALLBACK (smp_toggle_callback),
                    &g_values.guess_missing);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check_button),
                                g_values.guess_missing);

  ty++;

  gtk_widget_show (table);
  gtk_widget_show (frame);

  gtk_widget_show (dialog);

  /* set old_id's different (to force updates of the previews) */
  g_di.enable_preview_update = TRUE;
  smp_get_colors (NULL);
  update_preview (&g_values.dst_id);
  levels_update (INPUT_SLIDERS | INPUT_LEVELS | DRAW);

  gtk_main ();
}

/* -----------------------------
 * DEBUG print procedures START
 * -----------------------------
 */

static void
print_ppm (const gchar *ppm_name)
{
  FILE              *fp;
  gint               idx;
  gint               cnt;
  gint               r;
  gint               g;
  gint               b;
  t_samp_color_elem *col_ptr;

  if (ppm_name == NULL)
    return;

  fp = g_fopen (ppm_name, "w");
  if (fp)
    {
      fprintf (fp, "P3\n# CREATOR: Gimp sample coloros\n256 256\n255\n");
      for (idx = 0; idx < 256; idx++)
        {
          col_ptr = g_lum_tab[idx].col_ptr;

          for (cnt = 0; cnt < 256; cnt++)
            {
              r = g = b = 0;
              if (col_ptr)
                {
                  if ((col_ptr->sum_color > 0) && (cnt != 20))
                    {
                      r = (gint) col_ptr->color[0];
                      g = (gint) col_ptr->color[1];
                      b = (gint) col_ptr->color[2];
                    }

                  if (cnt > 20)
                    col_ptr = col_ptr->next;
                }
              fprintf (fp, "%d %d %d\n", r, g, b);
            }
        }
      fclose (fp);
    }
}

static void
print_color_list (FILE              *fp,
                  t_samp_color_elem *col_ptr)
{
  if (fp == NULL)
    return;

  while (col_ptr)
    {
      fprintf (fp, "  RGBA: %03d %03d %03d %03d  sum: [%d]\n",
               (gint)col_ptr->color[0],
               (gint)col_ptr->color[1],
               (gint)col_ptr->color[2],
               (gint)col_ptr->color[3],
               (gint)col_ptr->sum_color);

      col_ptr = col_ptr->next;
    }
}

static void
print_table (FILE *fp)
{
  gint idx;

  if (fp == NULL)
    return;

  fprintf (fp, "---------------------------\n");
  fprintf (fp, "print_table\n");
  fprintf (fp, "---------------------------\n");

  for (idx = 0; idx < 256; idx++)
    {
      fprintf (fp, "LUM [%03d]  pixcount:%d\n",
               idx, (int)g_lum_tab[idx].all_samples);
      print_color_list (fp, g_lum_tab[idx].col_ptr);
    }
}

static void
print_transtable (FILE *fp)
{
  gint idx;

  if (fp == NULL)
    return;

  fprintf (fp, "---------------------------\n");
  fprintf (fp, "print_transtable\n");
  fprintf (fp, "---------------------------\n");

  for (idx = 0; idx < 256; idx++)
    {
      fprintf (fp, "LVL_TRANS [%03d]  in_lvl: %3d  out_lvl: %3d\n",
               idx, (int)g_lvl_trans_tab[idx], (int)g_out_trans_tab[idx]);
    }
}

static void
print_values (FILE *fp)
{
  if (fp == NULL)
    return;

  fprintf (fp, "sample_colorize: params\n");
  fprintf (fp, "g_values.hold_inten     :%d\n", (int)g_values.hold_inten);
  fprintf (fp, "g_values.orig_inten     :%d\n", (int)g_values.orig_inten);
  fprintf (fp, "g_values.rnd_subcolors  :%d\n", (int)g_values.rnd_subcolors);
  fprintf (fp, "g_values.guess_missing  :%d\n", (int)g_values.guess_missing);
  fprintf (fp, "g_values.lvl_in_min     :%d\n", (int)g_values.lvl_in_min);
  fprintf (fp, "g_values.lvl_in_max     :%d\n", (int)g_values.lvl_in_max);
  fprintf (fp, "g_values.lvl_in_gamma   :%f\n", g_values.lvl_in_gamma);
  fprintf (fp, "g_values.lvl_out_min    :%d\n", (int)g_values.lvl_out_min);
  fprintf (fp, "g_values.lvl_out_max    :%d\n", (int)g_values.lvl_out_max);

  fprintf (fp, "g_values.tol_col_err    :%f\n", g_values.tol_col_err);
}

/* -----------------------------
 * DEBUG print procedures END
 * -----------------------------
 */

/* DEBUG: read values from file */
static void
get_filevalues (void)
{
  FILE  *fp;
  gchar  buf[1000];

/*
  g_values.lvl_out_min = 0;
  g_values.lvl_out_max = 255;
  g_values.lvl_in_min = 0;
  g_values.lvl_in_max = 255;
  g_values.lvl_in_gamma = 1.0;
*/
  g_values.tol_col_err = 5.5;

  fp = g_fopen ("sample_colorize.values", "r");
  if (fp != NULL)
    {
      fgets (buf, 999, fp);
      sscanf (buf, "%f", &g_values.tol_col_err);
      fclose (fp);
    }

  g_printf ("g_values.tol_col_err    :%f\n", g_values.tol_col_err);
}

static gint32
color_error (guchar ref_red, guchar ref_green, guchar ref_blue,
             guchar cmp_red, guchar cmp_green, guchar cmp_blue)
{
  glong ff;
  glong fs;
  glong cmp_h, ref_h;

  /* 1. Brightness differences */
  cmp_h = (3 * cmp_red + 6 * cmp_green + cmp_blue) / 10;
  ref_h = (3 * ref_red + 6 * ref_green + ref_blue) / 10;

  fs = labs (ref_h - cmp_h);
  ff = fs * fs;

  /* 2. add Red Color differences */
  fs = abs (ref_red - cmp_red);
  ff += (fs * fs);

  /* 3. add  Green Color differences */
  fs = abs (ref_green - cmp_green);
  ff += (fs * fs);

  /* 4. add  Blue Color differences */
  fs = abs (ref_blue - cmp_blue);
  ff += (fs * fs);

  return ((gint32)(ff));
}

/* get pixel value
 *   return light gray transparent pixel if out of bounds
 *   (should occur in the previews only)
 */
static void
get_pixel (t_GDRW *gdrw,
           gint32  x,
           gint32  y,
           guchar *pixel)
{
  gegl_buffer_sample (gdrw->buffer, x, y, NULL, pixel, gdrw->format,
                      GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
}

/* clear table */
static void
clear_tables (void)
{
  guint i;

  for (i = 0; i < 256; i++)
    {
      g_lum_tab[i].col_ptr = NULL;
      g_lum_tab[i].all_samples = 0;
      g_lvl_trans_tab[i] = i;
      g_out_trans_tab[i] = i;
      g_sample_color_tab[3 * i + 0] = i;
      g_sample_color_tab[3 * i + 1] = i;
      g_sample_color_tab[3 * i + 2] = i;
    }
}

/* free all allocated sample colors in table g_lum_tab */
static void
free_colors (void)
{
  gint               lum;
  t_samp_color_elem *col_ptr;
  t_samp_color_elem *next_ptr;

  for (lum = 0; lum < 256; lum++)
    {
      for (col_ptr = g_lum_tab[lum].col_ptr;
           col_ptr != NULL;
           col_ptr = next_ptr)
        {
          next_ptr = (t_samp_color_elem *)col_ptr->next;
          g_free (col_ptr);
        }

      g_lum_tab[lum].col_ptr = NULL;
      g_lum_tab[lum].all_samples = 0;
    }
}

/* setup lum transformer table according to input_levels, gamma and output levels
 * (uses sam algorithm as GIMP Level Tool)
 */
static void
calculate_level_transfers (void)
{
  double inten;
  gint   i;
  gint   in_min, in_max;
  gint   out_min, out_max;

  if (g_values.lvl_in_max >= g_values.lvl_in_min)
    {
      in_max = g_values.lvl_in_max;
      in_min = g_values.lvl_in_min;
    }
  else
    {
      in_max = g_values.lvl_in_min;
      in_min = g_values.lvl_in_max;
    }
  if (g_values.lvl_out_max >= g_values.lvl_out_min)
    {
      out_max = g_values.lvl_out_max;
      out_min = g_values.lvl_out_min;
    }
  else
    {
      out_max = g_values.lvl_out_min;
      out_min = g_values.lvl_out_max;
    }

  /*  Recalculate the levels arrays  */
  for (i = 0; i < 256; i++)
    {
      /*  determine input intensity  */
      inten = (double) i / 255.0;
      if (g_values.lvl_in_gamma != 0.0)
        {
          inten = pow (inten, (1.0 / g_values.lvl_in_gamma));
        }
      inten = (double) (inten * (in_max - in_min) + in_min);
      inten = CLAMP (inten, 0.0, 255.0);
      g_lvl_trans_tab[i] = (guchar) (inten + 0.5);

      /*  determine the output intensity  */
      inten = (double) i / 255.0;
      inten = (double) (inten * (out_max - out_min) + out_min);
      inten = CLAMP (inten, 0.0, 255.0);
      g_out_trans_tab[i] = (guchar) (inten + 0.5);
    }
}



/* alloc and init new col Element */
static t_samp_color_elem *
new_samp_color (const guchar *color)
{
  t_samp_color_elem *col_ptr;

  col_ptr = g_new0 (t_samp_color_elem, 1);

  memcpy (&col_ptr->color[0], color, 4);

  col_ptr->sum_color = 1;
  col_ptr->next = NULL;

  return col_ptr;
}


/* store color in g_lum_tab  */
static void
add_color (const guchar *color)
{
  gint32             lum;
  t_samp_color_elem *col_ptr;

  lum = LUMINOSITY_1(color);

  g_lum_tab[lum].all_samples++;
  g_lum_tab[lum].from_sample = TRUE;

  /* check if exactly the same color is already in the list */
  for (col_ptr = g_lum_tab[lum].col_ptr;
       col_ptr != NULL;
       col_ptr = (t_samp_color_elem *)col_ptr->next)
    {
      if ((color[0] == col_ptr->color[0]) &&
          (color[1] == col_ptr->color[1]) &&
          (color[2] == col_ptr->color[2]))
        {
          col_ptr->sum_color++;
          return;
        }
    }

  /* alloc and init element for the new color  */
  col_ptr = new_samp_color (color);

  if (col_ptr != NULL)
    {
      /* add new color element as 1.st of the list */
      col_ptr->next = g_lum_tab[lum].col_ptr;
      g_lum_tab[lum].col_ptr = col_ptr;
    }
}

/* sort Sublists (color) by descending sum_color in g_lum_tab
 */
static void
sort_color (gint32 lum)
{
  t_samp_color_elem *col_ptr;
  t_samp_color_elem *next_ptr;
  t_samp_color_elem *prev_ptr;
  t_samp_color_elem *sorted_col_ptr;
  gint32             min;
  gint32             min_next;

  sorted_col_ptr = NULL;
  min_next           = 0;

  while (g_lum_tab[lum].col_ptr != NULL)
    {
      min = min_next;
      next_ptr = NULL;
      prev_ptr = NULL;

      for (col_ptr = g_lum_tab[lum].col_ptr;
           col_ptr != NULL;
           col_ptr = next_ptr)
        {
          next_ptr = col_ptr->next;
          if (col_ptr->sum_color > min)
            {
              /* check min value for next loop */
              if ((col_ptr->sum_color < min_next) || (min == min_next))
                {
                  min_next = col_ptr->sum_color;
                }
              prev_ptr = col_ptr;
            }
          else
            {
              /* add element at head of sorted list */
              col_ptr->next = sorted_col_ptr;
              sorted_col_ptr = col_ptr;

              /* remove element from list */
              if (prev_ptr == NULL)
                {
                  g_lum_tab[lum].col_ptr = next_ptr;  /* remove 1.st element */
                }
              else
                {
                  prev_ptr->next = next_ptr;
                }
            }
        }
    }

  g_lum_tab[lum].col_ptr = sorted_col_ptr;
}

static void
cnt_same_sample_colortones (t_samp_color_elem *ref_ptr,
                            guchar            *prev_color,
                            guchar            *color_tone,
                            gint              *csum)
{
  gint32             col_error, ref_error;
  t_samp_color_elem *col_ptr;

  ref_error = 0;
  if (prev_color != NULL)
    {
      ref_error = color_error (ref_ptr->color[0], ref_ptr->color[1], ref_ptr->color[2],
                                 prev_color[0],     prev_color[1],     prev_color[2]);
    }

  /* collect colors that are (nearly) the same */
  for (col_ptr = ref_ptr->next;
       col_ptr != NULL;
       col_ptr = (t_samp_color_elem *)col_ptr->next)
    {
      if (col_ptr->sum_color < 1)
        continue;
      col_error = color_error (ref_ptr->color[0], ref_ptr->color[1], ref_ptr->color[2],
                               col_ptr->color[0], col_ptr->color[1], col_ptr->color[2]);

      if (col_error <= g_tol_col_err)
        {
          /* cout color of the same colortone */
          *csum += col_ptr->sum_color;
          /* mark the already checked color with negative sum_color value */
          col_ptr->sum_color = 0 - col_ptr->sum_color;

          if (prev_color != NULL)
            {
              col_error = color_error (col_ptr->color[0], col_ptr->color[1], col_ptr->color[2],
                                       prev_color[0],     prev_color[1],     prev_color[2]);
              if (col_error < ref_error)
                {
                  /* use the color that is closest to prev_color */
                  memcpy (color_tone, &col_ptr->color[0], 3);
                  ref_error = col_error;
                }
            }
        }
    }
}

/* find the dominant colortones (out of all sample colors)
 * for each available brightness intensity value.
 * and store them in g_sample_color_tab
 */
static void
ideal_samples (void)
{
  gint32             lum;
  t_samp_color_elem *col_ptr;
  guchar            *color;

  guchar             color_tone[4];
  guchar             color_ideal[4];
  gint               csum, maxsum;

  color = NULL;
  for (lum = 0; lum < 256; lum++)
    {
      if (g_lum_tab[lum].col_ptr == NULL)
        continue;

      sort_color (lum);
      col_ptr = g_lum_tab[lum].col_ptr;
      memcpy (&color_ideal[0], &col_ptr->color[0], 3);

      maxsum = 0;

      /* collect colors that are (nearly) the same */
      for (;
           col_ptr != NULL;
           col_ptr = (t_samp_color_elem *)col_ptr->next)
        {
          csum = 0;
          if (col_ptr->sum_color > 0)
            {
              memcpy (&color_tone[0], &col_ptr->color[0], 3);
              cnt_same_sample_colortones (col_ptr, color,
                                          &color_tone[0], &csum);
              if (csum > maxsum)
                {
                  maxsum = csum;
                  memcpy (&color_ideal[0], &color_tone[0], 3);
                }
            }
          else
            col_ptr->sum_color = abs (col_ptr->sum_color);
        }

      /* store ideal color and keep track of the color */
      color = &g_sample_color_tab[3 * lum];
      memcpy (color, &color_ideal[0], 3);
    }
}

static void
guess_missing_colors (void)
{
  gint32  lum;
  gint32  idx;
  gfloat  div;

  guchar  lo_color[4];
  guchar  hi_color[4];
  guchar  new_color[4];

  lo_color[0] = 0;
  lo_color[1] = 0;
  lo_color[2] = 0;
  lo_color[3] = 255;

  hi_color[0] = 255;
  hi_color[1] = 255;
  hi_color[2] = 255;
  hi_color[3] = 255;

  new_color[0] = 0;
  new_color[1] = 0;
  new_color[2] = 0;
  new_color[3] = 255;

  for (lum = 0; lum < 256; lum++)
    {
      if ((g_lum_tab[lum].col_ptr == NULL) ||
          (g_lum_tab[lum].from_sample == FALSE))
        {
          if (lum > 0)
            {
              for (idx = lum; idx < 256; idx++)
                {
                  if ((g_lum_tab[idx].col_ptr != NULL) &&
                      g_lum_tab[idx].from_sample)
                    {
                      memcpy (&hi_color[0],
                              &g_sample_color_tab[3 * idx], 3);
                      break;
                    }
                  if (idx == 255)
                    {
                      hi_color[0] = 255;
                      hi_color[1] = 255;
                      hi_color[2] = 255;
                      break;
                    }
                }

              div = idx - (lum -1);
              new_color[0] = lo_color[0] + ((float)(hi_color[0] - lo_color[0]) / div);
              new_color[1] = lo_color[1] + ((float)(hi_color[1] - lo_color[1]) / div);
              new_color[2] = lo_color[2] + ((float)(hi_color[2] - lo_color[2]) / div);

/*
 *          printf ("LO: %03d %03d %03d HI: %03d %03d %03d   NEW: %03d %03d %03d\n",
 *                  (int)lo_color[0],  (int)lo_color[1],  (int)lo_color[2],
 *                  (int)hi_color[0],  (int)hi_color[1],  (int)hi_color[2],
 *                  (int)new_color[0], (int)new_color[1], (int)new_color[2]);
 */

            }
          g_lum_tab[lum].col_ptr = new_samp_color (&new_color[0]);
          g_lum_tab[lum].from_sample = FALSE;
          memcpy (&g_sample_color_tab [3 * lum], &new_color[0], 3);
        }
      memcpy (&lo_color[0], &g_sample_color_tab [3 * lum], 3);
    }
}

static void
fill_missing_colors (void)
{
  gint32 lum;
  gint32 idx;
  gint32 lo_idx;

  guchar lo_color[4];
  guchar hi_color[4];
  guchar new_color[4];

  lo_color[0] = 0;
  lo_color[1] = 0;
  lo_color[2] = 0;
  lo_color[3] = 255;

  hi_color[0] = 255;
  hi_color[1] = 255;
  hi_color[2] = 255;
  hi_color[3] = 255;

  new_color[0] = 0;
  new_color[1] = 0;
  new_color[2] = 0;
  new_color[3] = 255;

  lo_idx = 0;
  for (lum = 0; lum < 256; lum++)
    {
      if ((g_lum_tab[lum].col_ptr == NULL) ||
          (g_lum_tab[lum].from_sample == FALSE))
        {
          if (lum > 0)
            {
              for (idx = lum; idx < 256; idx++)
                {
                  if ((g_lum_tab[idx].col_ptr != NULL) &&
                      (g_lum_tab[idx].from_sample))
                    {
                      memcpy (&hi_color[0],
                              &g_sample_color_tab[3 * idx], 3);
                      break;
                    }

                  if (idx == 255)
                    {
/*
 *               hi_color[0] = 255;
 *               hi_color[1] = 255;
 *               hi_color[2] = 255;
 */
                      memcpy (&hi_color[0], &lo_color[0], 3);
                      break;
                    }
                }

              if ((lum > (lo_idx + ((idx - lo_idx ) / 2))) ||
                  (lo_idx == 0))
                {
                  new_color[0] = hi_color[0];
                  new_color[1] = hi_color[1];
                  new_color[2] = hi_color[2];
                }
              else
                {
                  new_color[0] = lo_color[0];
                  new_color[1] = lo_color[1];
                  new_color[2] = lo_color[2];
                }
            }

          g_lum_tab[lum].col_ptr = new_samp_color (&new_color[0]);
          g_lum_tab[lum].from_sample = FALSE;
          memcpy (&g_sample_color_tab[3 * lum], &new_color[0], 3);
        }
      else
        {
          lo_idx = lum;
          memcpy (&lo_color[0], &g_sample_color_tab[3 * lum], 3);
        }
    }
}

/* get 256 samples of active gradient (optional in inverse order) */
static void
get_gradient (gint mode)
{
  gchar   *name;
  gint     n_f_samples;
  gdouble *f_samples;
  gdouble *f_samp;        /* float samples */
  gint     lum;

  free_colors ();

  name = gimp_context_get_gradient ();

  gimp_gradient_get_uniform_samples (name, 256 /* n_samples */,
                                     mode == SMP_INV_GRADIENT,
                                     &n_f_samples, &f_samples);

  g_free (name);

  for (lum = 0; lum < 256; lum++)
    {
      f_samp = &f_samples[lum * 4];

      g_sample_color_tab[3 * lum + 0] = f_samp[0] * 255;
      g_sample_color_tab[3 * lum + 1] = f_samp[1] * 255;
      g_sample_color_tab[3 * lum + 2] = f_samp[2] * 255;

      g_lum_tab[lum].col_ptr =
        new_samp_color (&g_sample_color_tab[3 * lum]);
      g_lum_tab[lum].from_sample = TRUE;
      g_lum_tab[lum].all_samples = 1;
    }

  g_free (f_samples);
}

static gint32
is_layer_alive (gint32 drawable_id)
{
  /* return -1 if layer has become invalid */
  if (drawable_id < 0)
    return -1;

  if (gimp_item_get_image (drawable_id) < 0)
    {
      g_printf ("sample colorize: unknown layer_id %d (Image closed?)\n",
                (int)drawable_id);
      return -1;
    }

  return drawable_id;
}

static void
end_gdrw (t_GDRW *gdrw)
{
  t_GDRW *sel_gdrw = (t_GDRW *) gdrw->sel_gdrw;

  if (sel_gdrw && sel_gdrw->buffer)
    {
      g_object_unref (sel_gdrw->buffer);
      sel_gdrw->buffer = NULL;
    }

  g_object_unref (gdrw->buffer);
  gdrw->buffer = NULL;
}

static void
init_gdrw (t_GDRW   *gdrw,
           gint32    drawable_id,
           gboolean  shadow)
{
  gint32  image_id;
  gint32  sel_channel_id;
  gint32  x1, x2, y1, y2;
  gint    offsetx, offsety;
  gint    w, h;
  gint    sel_offsetx, sel_offsety;
  t_GDRW *sel_gdrw;
  gint32  non_empty;

  if (g_Sdebug)
    g_printf ("\np_init_gdrw: drawable_ID: %d\n", drawable_id);

  gdrw->drawable_id = drawable_id;

  if (shadow)
    gdrw->buffer = gimp_drawable_get_shadow_buffer (drawable_id);
  else
    gdrw->buffer = gimp_drawable_get_buffer (drawable_id);

  gdrw->width = gimp_drawable_width (drawable_id);
  gdrw->height = gimp_drawable_height (drawable_id);
  gdrw->tile_width = gimp_tile_width ();
  gdrw->tile_height = gimp_tile_height ();
  gdrw->shadow = shadow;
  gdrw->seldeltax = 0;
  gdrw->seldeltay = 0;
  /* get offsets within the image */
  gimp_drawable_offsets (gdrw->drawable_id, &offsetx, &offsety);

  if (! gimp_drawable_mask_intersect (gdrw->drawable_id,
                                      &gdrw->x1, &gdrw->y1, &w, &h))
    return;

  gdrw->x2 = gdrw->x1 + w;
  gdrw->y2 = gdrw->y1 + h;

  if (gimp_drawable_has_alpha (drawable_id))
    gdrw->format = babl_format ("R'G'B'A u8");
  else
    gdrw->format = babl_format ("R'G'B' u8");

  gdrw->bpp = babl_format_get_bytes_per_pixel (gdrw->format);

  if (gimp_drawable_has_alpha (drawable_id))
    {
      /* index of the alpha channelbyte {1|3} */
      gdrw->index_alpha = gdrw->bpp -1;
    }
  else
    {
      gdrw->index_alpha = 0;      /* there is no alpha channel */
    }

  image_id = gimp_item_get_image (gdrw->drawable_id);

  /* check and see if we have a selection mask */
  sel_channel_id  = gimp_image_get_selection (image_id);

  if (g_Sdebug)
    {
      g_printf ("init_gdrw: image_id %d sel_channel_id: %d\n",
                (int)image_id, (int)sel_channel_id);
      g_printf ("init_gdrw: BOUNDS     x1: %d y1: %d x2:%d y2: %d\n",
                (int)gdrw->x1,  (int)gdrw->y1, (int)gdrw->x2,(int)gdrw->y2);
      g_printf ("init_gdrw: OFFS       x: %d y: %d\n",
                (int)offsetx, (int)offsety );
    }

  gimp_selection_bounds (image_id, &non_empty, &x1, &y1, &x2, &y2);

  if (non_empty && (sel_channel_id >= 0))
    {
      /* selection is TRUE */
      sel_gdrw = g_new0 (t_GDRW, 1);
      sel_gdrw->drawable_id = sel_channel_id;

      sel_gdrw->buffer = gimp_drawable_get_buffer (sel_channel_id);
      sel_gdrw->format = babl_format ("Y u8");

      sel_gdrw->width = gimp_drawable_width (sel_channel_id);
      sel_gdrw->height = gimp_drawable_height (sel_channel_id);

      sel_gdrw->tile_width = gimp_tile_width ();
      sel_gdrw->tile_height = gimp_tile_height ();
      sel_gdrw->shadow = shadow;
      sel_gdrw->x1 = x1;
      sel_gdrw->y1 = y1;
      sel_gdrw->x2 = x2;
      sel_gdrw->y2 = y2;
      sel_gdrw->seldeltax = 0;
      sel_gdrw->seldeltay = 0;
      sel_gdrw->bpp = babl_format_get_bytes_per_pixel (sel_gdrw->format);
      sel_gdrw->index_alpha = 0;   /* there is no alpha channel */
      sel_gdrw->sel_gdrw = NULL;

      /* offset delta between drawable and selection
       * (selection always has image size and should always have offsets of 0 )
       */
      gimp_drawable_offsets (sel_channel_id, &sel_offsetx, &sel_offsety);
      gdrw->seldeltax = offsetx - sel_offsetx;
      gdrw->seldeltay = offsety - sel_offsety;

      gdrw->sel_gdrw = (t_GDRW *) sel_gdrw;

      if (g_Sdebug)
        {
          g_printf ("init_gdrw: SEL_BOUNDS x1: %d y1: %d x2:%d y2: %d\n",
                    (int)sel_gdrw->x1, (int)sel_gdrw->y1,
                    (int)sel_gdrw->x2, (int)sel_gdrw->y2);
          g_printf ("init_gdrw: SEL_OFFS   x: %d y: %d\n",
                    (int)sel_offsetx, (int)sel_offsety );
          g_printf ("init_gdrw: SEL_DELTA  x: %d y: %d\n",
                    (int)gdrw->seldeltax, (int)gdrw->seldeltay );
        }
    }
  else
    gdrw->sel_gdrw = NULL;     /* selection is FALSE */
}

/* analyze the colors in the sample_drawable */
static int
sample_analyze (t_GDRW *sample_gdrw)
{
  gint32  sample_pixels;
  gint32  row, col;
  gint32  first_row, first_col, last_row, last_col;
  gint32  x, y;
  gint32  x2, y2;
  float   progress_step;
  float   progress_max;
  float   progress;
  guchar  color[4];
  FILE   *prot_fp;

  sample_pixels = 0;

  /* init progress */
  progress_max = (sample_gdrw->x2 - sample_gdrw->x1);
  progress_step = 1.0 / progress_max;
  progress = 0.0;
  if (g_show_progress)
    gimp_progress_init (_("Sample analyze"));

  prot_fp = NULL;
  if (g_Sdebug)
    prot_fp = g_fopen ("sample_colors.dump", "w");
  print_values (prot_fp);

  /* ------------------------------------------------
   * foreach pixel in the SAMPLE_drawable:
   *  calculate brightness intensity LUM
   * ------------------------------------------------
   * the inner loops (x/y) are designed to process
   * all pixels of one tile in the sample drawable, the outer loops (row/col) do step
   * to the next tiles. (this was done to reduce tile swapping)
   */

  first_row = sample_gdrw->y1 / sample_gdrw->tile_height;
  last_row  = (sample_gdrw->y2 / sample_gdrw->tile_height);
  first_col = sample_gdrw->x1 / sample_gdrw->tile_width;
  last_col  = (sample_gdrw->x2 / sample_gdrw->tile_width);

  for (row = first_row; row <= last_row; row++)
    {
      for (col = first_col; col <= last_col; col++)
        {
          if (col == first_col)
            x = sample_gdrw->x1;
          else
            x = col * sample_gdrw->tile_width;
          if (col == last_col)
            x2 = sample_gdrw->x2;
          else
            x2 = (col +1) * sample_gdrw->tile_width;

          for ( ; x < x2; x++)
            {
              if (row == first_row)
                y = sample_gdrw->y1;
              else
                y = row * sample_gdrw->tile_height;
              if (row == last_row)
                y2 = sample_gdrw->y2;
              else
                y2 = (row +1) * sample_gdrw->tile_height ;

              /* printf ("X: %4d Y:%4d Y2:%4d\n", (int)x, (int)y, (int)y2); */

              for ( ; y < y2; y++)
                {
                  /* check if the pixel is in the selection */
                  if (sample_gdrw->sel_gdrw)
                    {
                      get_pixel (sample_gdrw->sel_gdrw,
                                   (x + sample_gdrw->seldeltax),
                                   (y + sample_gdrw->seldeltay),
                                   &color[0]);

                      if (color[0] == 0)
                        continue;
                    }
                  get_pixel (sample_gdrw, x, y, &color[0]);

                  /* if this is a visible (non-transparent) pixel */
                  if ((sample_gdrw->index_alpha < 1) ||
                      (color[sample_gdrw->index_alpha] != 0))
                    {
                      /* store color in the sublists of g_lum_tab  */
                      add_color (&color[0]);
                      sample_pixels++;
                    }
                }

              if (g_show_progress)
                gimp_progress_update (progress += progress_step);
            }
        }
    }

  if (g_show_progress)
    gimp_progress_update (1.0);

  if (g_Sdebug)
    g_printf ("ROWS: %d - %d  COLS: %d - %d\n",
            (int)first_row, (int)last_row,
            (int)first_col, (int)last_col);

  print_table (prot_fp);

  if (g_Sdebug)
    print_ppm ("sample_color_all.ppm");

  /* find out ideal sample colors for each brightness intensity (lum)
   * and set g_sample_color_tab to the ideal colors.
   */
  ideal_samples ();
  calculate_level_transfers ();

  if (g_values.guess_missing)
    guess_missing_colors ();
  else
    fill_missing_colors ();

  print_table (prot_fp);
  if (g_Sdebug)
    print_ppm ("sample_color_2.ppm");
  print_transtable (prot_fp);
  if (prot_fp)
    fclose (prot_fp);

  /* check if there was at least one visible pixel */
  if (sample_pixels == 0)
    {
      g_printf ("Error: Source sample has no visible Pixel\n");
      return -1;
    }
  return 0;
}

static void
rnd_remap (gint32  lum,
           guchar *mapped_color)
{
  t_samp_color_elem *col_ptr;
  gint               rnd;
  gint               ct;
  gint               idx;

  if (g_lum_tab[lum].all_samples > 1)
    {
      rnd = g_random_int_range (0, g_lum_tab[lum].all_samples);
      ct  = 0;
      idx = 0;

      for (col_ptr = g_lum_tab[lum].col_ptr;
           col_ptr != NULL;
           col_ptr = (t_samp_color_elem *)col_ptr->next)
        {
          ct += col_ptr->sum_color;

          if (rnd < ct)
            {
              /* printf ("RND_remap: rnd: %d all:%d  ct:%d idx:%d\n",
               * rnd, (int)g_lum_tab[lum].all_samples, ct, idx);
               */
              memcpy (mapped_color, &col_ptr->color[0], 3);
              return;
            }
          idx++;
        }
    }

  memcpy (mapped_color, &g_sample_color_tab[lum + lum + lum], 3);
}

static void
remap_pixel (guchar       *pixel,
             const guchar *original,
             gint          bpp2)
{
  guchar mapped_color[4];
  gint   lum;
  double orig_lum, mapped_lum;
  double grn, red, blu;
  double mg,  mr,  mb;
  double dg,  dr,  db;
  double dlum;

  /* get brightness from (uncolorized) original */
  lum = g_out_trans_tab[g_lvl_trans_tab[LUMINOSITY_1 (original)]];
  if (g_values.rnd_subcolors)
    rnd_remap (lum, mapped_color);
  else
    memcpy (mapped_color, &g_sample_color_tab[3 * lum], 3);

  if (g_values.hold_inten)
    {
      if (g_values.orig_inten)
        orig_lum = LUMINOSITY_0(original);
      else
        orig_lum = 100.0 * g_lvl_trans_tab[LUMINOSITY_1 (original)];

      mapped_lum = LUMINOSITY_0 (mapped_color);

      if (mapped_lum == 0)
        {
          /* convert black to greylevel with desired brightness value */
          mapped_color[0] = orig_lum / 100.0;
          mapped_color[1] = mapped_color[0];
          mapped_color[2] = mapped_color[0];
        }
      else
        {
          /* Calculate theoretical RGB to reach given intensity LUM
           * value (orig_lum)
           */
          mr = mapped_color[0];
          mg = mapped_color[1];
          mb = mapped_color[2];

          if (mr > 0.0)
            {
              red =
                orig_lum / (30 + (59 * mg / mr) + (11 * mb / mr));
              grn = mg * red / mr;
              blu = mb * red / mr;
            }
          else if (mg > 0.0)
            {
              grn =
                orig_lum / ((30 * mr / mg) + 59 + (11 * mb / mg));
              red = mr * grn / mg;
              blu = mb * grn / mg;
            }
          else
            {
              blu =
                orig_lum / ((30 * mr / mb) + (59 * mg / mb) + 11);
              grn = mg * blu / mb;
              red = mr * blu / mb;
            }

          /* on overflow: Calculate real RGB values
           * (this may change the hue and saturation,
           * more and more into white)
           */

          if (red > 255)
            {
              if ((blu < 255) && (grn < 255))
                {
                  /* overflow in the red channel (compensate with green and blue)  */
                  dlum = (red - 255.0) * 30.0;
                  if (mg > 0)
                    {
                      dg = dlum / (59.0 + (11.0 * mb / mg));
                      db = dg * mb / mg;
                    }
                  else if (mb > 0)
                    {
                      db = dlum / (11.0 + (59.0 * mg / mb));
                      dg = db * mg / mb;
                    }
                  else
                    {
                      db = dlum / (11.0 + 59.0);
                      dg = dlum / (59.0 + 11.0);
                    }

                  grn += dg;
                  blu += db;
                }

              red = 255.0;

              if (grn > 255)
                {
                  grn = 255.0;
                  blu = (orig_lum - 22695) / 11;  /* 22695 =  (255 * 30) + (255 * 59)  */
                }
              if (blu > 255)
                {
                  blu = 255.0;
                  grn = (orig_lum - 10455) / 59;  /* 10455 =  (255 * 30) + (255 * 11)  */
                }
            }
          else if (grn > 255)
            {
              if ((blu < 255) && (red < 255))
                {
                  /* overflow in the green channel (compensate with red and blue)  */
                  dlum = (grn - 255.0) * 59.0;

                  if (mr > 0)
                    {
                      dr = dlum / (30.0 + (11.0 * mb / mr));
                      db = dr * mb / mr;
                    }
                  else if (mb > 0)
                    {
                      db = dlum / (11.0 + (30.0 * mr / mb));
                      dr = db * mr / mb;
                    }
                  else
                    {
                      db = dlum / (11.0 + 30.0);
                      dr = dlum / (30.0 + 11.0);
                    }

                  red += dr;
                  blu += db;
                }

              grn = 255.0;

              if (red > 255)
                {
                  red = 255.0;
                  blu = (orig_lum - 22695) / 11;  /* 22695 =  (255*59) + (255*30)  */
                }
              if (blu > 255)
                {
                  blu = 255.0;
                  red = (orig_lum - 17850) / 30;  /* 17850 =  (255*59) + (255*11)  */
                }
            }
          else if (blu > 255)
            {
              if ((red < 255) && (grn < 255))
                {
                  /* overflow in the blue channel (compensate with green and red)  */
                  dlum = (blu - 255.0) * 11.0;

                  if (mg > 0)
                    {
                      dg = dlum / (59.0 + (30.0 * mr / mg));
                      dr = dg * mr / mg;
                    }
                  else if (mr > 0)
                    {
                      dr = dlum / (30.0 + (59.0 * mg / mr));
                      dg = dr * mg / mr;
                    }
                  else
                    {
                      dr = dlum / (30.0 + 59.0);
                      dg = dlum / (59.0 + 30.0);
                    }

                  grn += dg;
                  red += dr;
                }

              blu = 255.0;

              if (grn > 255)
                {
                  grn = 255.0;
                  red = (orig_lum - 17850) / 30;  /* 17850 =  (255*11) + (255*59)  */
                }
              if (red > 255)
                {
                  red = 255.0;
                  grn = (orig_lum - 10455) / 59;  /* 10455 =  (255*11) + (255*30)  */
                }
            }

          mapped_color[0] = CLAMP0255 (red + 0.5);
          mapped_color[1] = CLAMP0255 (grn + 0.5);
          mapped_color[2] = CLAMP0255 (blu + 0.5);
        }
    }

  /* set colorized pixel in shadow pr */
  memcpy (pixel, &mapped_color[0], bpp2);
}

static void
colorize_func (const guchar *src,
               guchar       *dest,
               gint          bpp,
               gboolean      has_alpha)
{
  if (has_alpha)
    {
      bpp--;
      dest[bpp] = src[bpp];
    }

  remap_pixel (dest, src, bpp);
}

static void
colorize_drawable (gint32 drawable_id)
{
  GeglBuffer         *src_buffer;
  GeglBuffer         *dest_buffer;
  GeglBufferIterator *iter;
  const Babl         *format;
  gboolean            has_alpha;
  gint                bpp;
  gint                x, y, w, h;
  gint                total_area;
  gint                area_so_far;

  if (! gimp_drawable_mask_intersect (drawable_id, &x, &y, &w, &h))
    return;

  src_buffer  = gimp_drawable_get_buffer (drawable_id);
  dest_buffer = gimp_drawable_get_shadow_buffer (drawable_id);

  has_alpha = gimp_drawable_has_alpha (drawable_id);

  if (has_alpha)
    format = babl_format ("R'G'B'A u8");
  else
    format = babl_format ("R'G'B' u8");

  bpp = babl_format_get_bytes_per_pixel (format);

  if (g_show_progress)
    gimp_progress_init (_("Remap colorized"));


  total_area  = w * h;
  area_so_far = 0;

  if (total_area <= 0)
    goto out;

  iter = gegl_buffer_iterator_new (src_buffer,
                                   GEGL_RECTANGLE (x, y, w, h), 0, format,
                                   GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);

  gegl_buffer_iterator_add (iter, dest_buffer,
                            GEGL_RECTANGLE (x, y, w, h), 0 , format,
                            GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);

  while (gegl_buffer_iterator_next (iter))
    {
      const guchar *src  = iter->items[0].data;
      guchar       *dest = iter->items[1].data;
      gint          row;

      for (row = 0; row < iter->items[0].roi.height; row++)
        {
          const guchar *s      = src;
          guchar       *d      = dest;
          gint          pixels = iter->items[0].roi.width;

          while (pixels--)
            {
              colorize_func (s, d, bpp, has_alpha);

              s += bpp;
              d += bpp;
            }

          src  += iter->items[0].roi.width * bpp;
          dest += iter->items[1].roi.width * bpp;
        }

      area_so_far += iter->items[0].roi.width * iter->items[0].roi.height;

      gimp_progress_update ((gdouble) area_so_far / (gdouble) total_area);
    }

  g_object_unref (src_buffer);
  g_object_unref (dest_buffer);

  gimp_drawable_merge_shadow (drawable_id, TRUE);
  gimp_drawable_update (drawable_id, x, y, w, h);

  out:
  if (g_show_progress)
    gimp_progress_update (0.0);
}

/* colorize dst_drawable like sample_drawable */
static int
main_colorize (gint mc_flags)
{
  t_GDRW   sample_gdrw;
  gboolean sample_drawable = FALSE;
  gint32   max;
  gint32   id;
  gint     rc;

  if (g_Sdebug)
    get_filevalues ();  /* for debugging: read values from file */

  /* calculate value of tolerable color error */
  max = color_error (0,0, 0, 255, 255, 255); /* 260100 */
  g_tol_col_err = (((float)max  * (g_values.tol_col_err * g_values.tol_col_err)) / (100.0 *100.0));
  g_max_col_err = max;

  rc = 0;

  if (mc_flags & MC_GET_SAMPLE_COLORS)
    {
      id = g_values.sample_id;
      if ((id == SMP_GRADIENT) || (id == SMP_INV_GRADIENT))
        {
          get_gradient (id);
        }
      else
        {
          if (is_layer_alive (id) < 0)
            return -1;

          sample_drawable = TRUE;

          init_gdrw (&sample_gdrw, id, FALSE);
          free_colors ();
          rc = sample_analyze (&sample_gdrw);
        }
    }

  if ((mc_flags & MC_DST_REMAP) && (rc == 0))
    {
      if (is_layer_alive (g_values.dst_id) < 0)
        return -1;

      if (gimp_drawable_is_gray (g_values.dst_id) &&
          (mc_flags & MC_DST_REMAP))
        {
          gimp_image_convert_rgb (gimp_item_get_image (g_values.dst_id));
        }

      colorize_drawable (g_values.dst_id);
    }

  if (sample_drawable)
    end_gdrw (&sample_gdrw);

  return rc;
}
