Logo Search packages:      
Sourcecode: gimp version File versions  Download package

fp.c

/*
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This is a plug-in for the GIMP.
 *
 * Copyright (C) Pavel Grinfeld (pavel@ml.com)
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <stdlib.h>

#include <gtk/gtk.h>

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

#include "libgimp/stdplugins-intl.h"

#define MAX_PREVIEW_SIZE   125
#define MAX_ROUGHNESS      128
#define RANGE_HEIGHT       15
#define PR_BX_BRDR         4
#define ALL                255
#define MARGIN             4

#define HELP_ID            "plug-in-filter-pack"

#define RANGE_ADJUST_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


typedef struct
{
  gint run;
} fpInterface;

typedef struct
{
  gint       width;
  gint       height;
  guchar    *rgb;
  gdouble   *hsv;
  guchar    *mask;
} ReducedImage;

typedef enum
{
  SHADOWS,
  MIDTONES,
  HIGHLIGHTS,
  INTENSITIES
}FPIntensity;

enum
{
  NONEATALL  = 0,
  CURRENT    = 1,
  HUE        = 2,
  SATURATION = 4,
  VALUE      = 8
};

enum
{
  BY_HUE,
  BY_SAT,
  BY_VAL,
  JUDGE_BY
};

enum
{
  RED,
  GREEN,
  BLUE,
  CYAN,
  YELLOW,
  MAGENTA,
  ALL_PRIMARY
};

enum
{
  DOWN = -1,
  UP   =  1
};

typedef struct
{
  GtkWidget *window;
  GtkWidget *range_preview;
  GtkWidget *aliasing_preview;
  GtkWidget *aliasing_graph;
} AdvancedWindow;


typedef struct
{
  gdouble       roughness;
  gdouble       aliasing;
  gdouble       preview_size;
  FPIntensity   intensity_range;
  gint          value_by;
  gint          selection_only;
  gboolean      real_time;
  guchar        offset;
  guchar        visible_frames;
  guchar        cutoff[INTENSITIES];
  gint          touched[JUDGE_BY];
  gint          red_adjust[JUDGE_BY][256];
  gint          blue_adjust[JUDGE_BY][256];
  gint          green_adjust[JUDGE_BY][256];
  gint          sat_adjust[JUDGE_BY][256];
} FPValues;

typedef struct
{
  GtkWidget    *roughness_scale;
  GtkWidget    *aliasing_scale;
  GtkWidget    *preview_size_scale;
  GtkWidget    *range_label[12];
} FPWidgets;

static void           fp_show_hide_frame          (GtkWidget     *button,
                                                   GtkWidget     *frame);

static ReducedImage * fp_reduce_image             (GimpDrawable  *drawable,
                                                   GimpDrawable  *mask,
                                                   gint           longer_size,
                                                   gint           selection);

static void           fp_render_preview           (GtkWidget     *preview,
                                                   gint           change_what,
                                                   gint           change_which);

static void           update_current_fp           (gint           change_what,
                                                   gint           change_which);

static void           fp_create_nudge             (gint          *adj_array);

static gboolean       fp_dialog                   (void);
static void           fp_advanced_dialog          (void);

static void           fp_selection_made           (GtkWidget     *widget,
                                                   gpointer       data);
static void           fp_scale_update             (GtkAdjustment *adjustment,
                                                   gdouble        *scale_val);
static void           fp_reset_filter_packs       (void);

static void           fp_create_smoothness_graph  (GtkWidget     *preview);

static void           fp_range_preview_spill      (GtkWidget     *preview,
                                                   gint           type);
static void           fp_adjust_preview_sizes     (gint           width,
                                                   gint           height);
static void           fp_redraw_all_windows       (void);
static void           fp_refresh_previews         (gint           which);
static void           fp_init_filter_packs        (void);

static void           fp_drag                     (GtkWidget     *button);
static void           fp_preview_scale_update     (GtkAdjustment *adjustment,
                                                   gdouble        *scale_val);

static void           fp                          (GimpDrawable  *drawable);
static GtkWidget *    fp_create_bna               (void);
static GtkWidget *    fp_create_rough             (void);
static GtkWidget *    fp_create_range             (void);
static GtkWidget *    fp_create_circle_palette    (void);
static GtkWidget *    fp_create_lnd               (void);
static GtkWidget *    fp_create_show              (void);
static GtkWidget *    fp_create_msnls             (void);
static GtkWidget *    fp_create_pixels_select_by  (void);
static void           update_range_labels         (void);
static gboolean       fp_range_change_events      (GtkWidget     *widget,
                                                   GdkEvent      *event,
                                                   FPValues     *current);

static void           fp_create_preview           (GtkWidget    **preview,
                                                   GtkWidget    **frame,
                                                   gint           preview_width,
                                                   gint           preview_height);

static void           fp_create_table_entry      (GtkWidget     **box,
                                                  GtkWidget      *smaller_frame,
                                                  const gchar    *description);

static void           fp_checkbutton_in_box      (GtkWidget      *vbox,
                                                  const gchar    *label,
                                                  GtkSignalFunc   func,
                                                  gpointer        data,
                                                  gboolean        clicked);

static void         fp_frames_checkbutton_in_box (GtkWidget      *vbox,
                                                  const gchar    *label,
                                                  GtkSignalFunc   func,
                                                  GtkWidget      *frame,
                                                  gboolean        clicked);

static void             fp_preview_size_allocate (GtkWidget      *widget,
                                                  GtkAllocation  *allocation);

#define RESPONSE_RESET 1

/* These values are translated for the GUI but also used internally
   to figure out which button the user pushed, etc.
   Not my design, please don't blame me -- njl */

static const gchar *hue_red     = N_("Red:");
static const gchar *hue_green   = N_("Green:");
static const gchar *hue_blue    = N_("Blue:");
static const gchar *hue_cyan    = N_("Cyan:");
static const gchar *hue_yellow  = N_("Yellow:");
static const gchar *hue_magenta = N_("Magenta:");

static const gchar *val_darker  = N_("Darker:");
static const gchar *val_lighter = N_("Lighter:");

static const gchar *sat_more    = N_("More Sat:");
static const gchar *sat_less    = N_("Less Sat:");

static const gchar *current_val = N_("Current:");

static gint colorSign[3][ALL_PRIMARY]=
{{1,-1,-1,-1,1,1},{-1,1,-1,1,1,-1},{-1,-1,1,1,-1,1}};

static AdvancedWindow AW = { NULL, NULL, NULL, NULL };

static FPWidgets fp_widgets = { NULL, NULL, NULL };

static gint nudgeArray[256];

static GtkWidget *origPreview, *curPreview;
static GtkWidget *rPreview, *gPreview, *bPreview;
static GtkWidget *cPreview, *yPreview, *mPreview;
static GtkWidget *centerPreview;
static GtkWidget *darkerPreview, *lighterPreview, *middlePreview;
static GtkWidget *dlg;
static GtkWidget *plusSatPreview, *SatPreview, *minusSatPreview;

static struct
{
  GtkWidget *bna;
  GtkWidget *palette;
  GtkWidget *rough;
  GtkWidget *range;
  GtkWidget *show;
  GtkWidget *lnd;
  GtkWidget *pixelsBy;
  GtkWidget *frameSelect;
  GtkWidget *satur;
} fp_frames;

static fpInterface FPint =
{
  FALSE   /*  run  */
};

static ReducedImage *reduced;

static FPValues fpvals =
{
  .25,                /* Initial Roughness */
  .6,                 /* Initial Degree of Aliasing */
  80,                 /* Initial preview size */
  MIDTONES,           /* Initial Range */
  BY_VAL,             /* Initial God knows what */
  TRUE,               /* Selection Only */
  TRUE,               /* Real Time */
  0,                  /* Offset */
  0,                  /* Visible frames */
  {32,224,255},       /* cutoffs */
  {0,0,0}             /* touched */
};

static GimpDrawable *drawable, *mask;

static void      query  (void);
static void      run    (const gchar      *name,
                         gint              nparams,
                         const GimpParam  *param,
                         gint             *nreturn_vals,
                         GimpParam       **return_vals);

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

MAIN()

static void
query (void)
{
  GimpParamDef args[] =
  {
    { GIMP_PDB_INT32,    "run_mode", "Interactive, non-interactive" },
    { GIMP_PDB_IMAGE,    "image",    "Input image (used for indexed images)" },
    { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
  };

  gimp_install_procedure ("plug_in_filter_pack",
                          "Allows the user to change H, S, or C with many previews",
                          "No help available",
                          "Pavel Grinfeld (pavel@ml.com)",
                          "Pavel Grinfeld (pavel@ml.com)",
                          "27th March 1997",
                          N_("_Filter Pack..."),
                          "RGB*",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (args), 0,
                          args, NULL);

  gimp_plugin_menu_register ("plug_in_filter_pack", "<Image>/Filters/Colors");
}

/********************************STANDARD RUN*************************/

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

  *nreturn_vals = 1;
  *return_vals = values;

  run_mode = param[0].data.d_int32;

  INIT_I18N ();

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

  fp_init_filter_packs();

  drawable = gimp_drawable_get (param[2].data.d_drawable);
  mask = gimp_drawable_get (gimp_image_get_selection (param[1].data.d_image));

  switch (run_mode)
    {
    case GIMP_RUN_INTERACTIVE:
      /*  Possibly retrieve data  */
      gimp_get_data ("plug_in_filter_pack", &fpvals);

      if (gimp_drawable_is_indexed (drawable->drawable_id) ||
          gimp_drawable_is_gray (drawable->drawable_id) )
        {
          gimp_message (_("FP can only be used on RGB images."));
          status = GIMP_PDB_EXECUTION_ERROR;
        }
      else if (! fp_dialog())
        {
          status = GIMP_PDB_CANCEL;
        }
      break;

    case GIMP_RUN_NONINTERACTIVE:
      gimp_message (_("FP can only be run interactively."));
      status = GIMP_PDB_CALLING_ERROR;
      break;

    case GIMP_RUN_WITH_LAST_VALS:
      /*  Possibly retrieve data  */
      gimp_get_data ("plug_in_filter_pack", &fpvals);
      break;

    default:
      break;
    }

  if (status == GIMP_PDB_SUCCESS)
    {
      /*  Make sure that the drawable is gray or RGB color  */
      if (gimp_drawable_is_rgb (drawable->drawable_id))
        {
          gimp_progress_init (_("Applying Filter Pack..."));
          gimp_tile_cache_ntiles (2 * (drawable->width / gimp_tile_width () + 1));
          fp (drawable);

          /*  Store data  */
          if (run_mode == GIMP_RUN_INTERACTIVE)
            gimp_set_data ("plug_in_filter_pack",
                           &fpvals, sizeof (FPValues));

          gimp_displays_flush ();
        }
      else status = GIMP_PDB_EXECUTION_ERROR;
    }

  values[0].data.d_status = status;

  if (status == GIMP_PDB_SUCCESS)
    gimp_drawable_detach (drawable);
}

static void
fp_func (const guchar *src,
         guchar       *dest,
         gint          bpp,
         gpointer      data)
{
  gint    bytenum, k;
  gint    JudgeBy, Intensity = 0, P[3];
  GimpRGB rgb;
  GimpHSV hsv;
  gint    M, m, middle;

  P[0] = src[0];
  P[1] = src[1];
  P[2] = src[2];

  gimp_rgb_set_uchar (&rgb, (guchar) P[0], (guchar) P[1], (guchar) P[2]);
  gimp_rgb_to_hsv (&rgb, &hsv);

  for (JudgeBy = BY_HUE; JudgeBy < JUDGE_BY; JudgeBy++)
    {
      if (!fpvals.touched[JudgeBy])
        continue;

      switch (JudgeBy)
        {
        case BY_HUE:
          Intensity = 255 * hsv.h;
          break;

        case BY_SAT:
          Intensity = 255 * hsv.s;
          break;

        case BY_VAL:
          Intensity = 255 * hsv.v;
          break;
        }


      /* It's important to take care of Saturation first!!! */

      m = MIN (MIN (P[0], P[1]), P[2]);
      M = MAX (MAX (P[0], P[1]), P[2]);
      middle = (M + m) / 2;

      for (k = 0; k < 3; k++)
        if (P[k] != m && P[k] != M)
          middle = P[k];

      for (k = 0; k < 3; k++)
        if (M != m)
          {
            if (P[k] == M)
              P[k] = MAX (P[k] + fpvals.sat_adjust[JudgeBy][Intensity], middle);
            else if (P[k] == m)
              P[k] = MIN (P[k] - fpvals.sat_adjust[JudgeBy][Intensity], middle);
          }

      P[0] += fpvals.red_adjust[JudgeBy][Intensity];
      P[1] += fpvals.green_adjust[JudgeBy][Intensity];
      P[2] += fpvals.blue_adjust[JudgeBy][Intensity];

      P[0]  = CLAMP0255(P[0]);
      P[1]  = CLAMP0255(P[1]);
      P[2]  = CLAMP0255(P[2]);
    }

  dest[0] = P[0];
  dest[1] = P[1];
  dest[2] = P[2];

  for (bytenum = 3; bytenum < bpp; bytenum++)
    dest[bytenum] = src[bytenum];
}

static void
fp (GimpDrawable *drawable)
{
  gimp_rgn_iterate2 (drawable, 0 /* unused */, fp_func, NULL);
}

/***********************************************************/
/************   Main Dialog Window   ******************/
/***********************************************************/

static GtkWidget *
fp_create_bna (void)
{
  GtkWidget *table;
  GtkWidget *label;
  GtkWidget *bframe, *aframe;

  fp_create_preview (&origPreview, &bframe, reduced->width, reduced->height);
  fp_create_preview (&curPreview, &aframe, reduced->width, reduced->height);

  table = gtk_table_new (2, 2, FALSE);
  gtk_table_set_row_spacings (GTK_TABLE (table), 6);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);

  label = gtk_label_new (_("Original:"));
  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
                    GTK_SHRINK, GTK_SHRINK, 0, 0);
  gtk_widget_show (label);

  gtk_table_attach (GTK_TABLE (table), bframe, 0, 1, 1, 2,
                    GTK_EXPAND, 0, 0, 0);

  label = gtk_label_new (_("Current:"));
  gtk_table_attach (GTK_TABLE (table), label, 1, 2, 0, 1,
                    GTK_SHRINK, GTK_SHRINK, 0, 0);
  gtk_widget_show (label);

  gtk_table_attach (GTK_TABLE (table), aframe, 1, 2, 1, 2,
                    GTK_EXPAND, 0, 0, 0);

  gtk_widget_show (table);

  return table;
}

/* close a sub dialog (from window manager) by simulating toggle click */
static gboolean
sub_dialog_destroy (GtkWidget *dialog,
                    GdkEvent  *ev,
                    gpointer   dummy)
{
  GtkWidget *button =
    GTK_WIDGET (g_object_get_data (G_OBJECT (dialog), "ctrlButton"));

  gtk_button_clicked (GTK_BUTTON (button));

  return TRUE;
}

static GtkWidget *
fp_create_circle_palette (void)
{
  GtkWidget *table;
  GtkWidget *rVbox, *rFrame;
  GtkWidget *gVbox, *gFrame;
  GtkWidget *bVbox, *bFrame;
  GtkWidget *cVbox, *cFrame;
  GtkWidget *yVbox, *yFrame;
  GtkWidget *mVbox, *mFrame;
  GtkWidget *centerVbox, *centerFrame;
  GtkWidget *win;

  win = gtk_window_new (GTK_WINDOW_TOPLEVEL);

  gimp_help_connect (win, gimp_standard_help_func, HELP_ID, NULL);

  gtk_window_set_title (GTK_WINDOW (win), _("Hue Variations"));

  g_signal_connect (win, "delete_event",
                    G_CALLBACK (sub_dialog_destroy),
                    NULL);

  table = gtk_table_new (11, 11, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_table_set_row_spacings (GTK_TABLE (table), 6);
  gtk_container_set_border_width (GTK_CONTAINER (table), 12);
  gtk_container_add (GTK_CONTAINER (win), table);
  gtk_widget_show (table);

  fp_create_preview (&rPreview, &rFrame, reduced->width, reduced->height);
  fp_create_preview (&gPreview, &gFrame, reduced->width, reduced->height);
  fp_create_preview (&bPreview, &bFrame, reduced->width, reduced->height);
  fp_create_preview (&cPreview, &cFrame, reduced->width, reduced->height);
  fp_create_preview (&yPreview, &yFrame, reduced->width, reduced->height);
  fp_create_preview (&mPreview, &mFrame, reduced->width, reduced->height);
  fp_create_preview (&centerPreview, &centerFrame,
                    reduced->width, reduced->height);

  fp_create_table_entry (&rVbox, rFrame, hue_red);
  fp_create_table_entry (&gVbox, gFrame, hue_green);
  fp_create_table_entry (&bVbox, bFrame, hue_blue);
  fp_create_table_entry (&cVbox, cFrame, hue_cyan);
  fp_create_table_entry (&yVbox, yFrame, hue_yellow);
  fp_create_table_entry (&mVbox, mFrame, hue_magenta);
  fp_create_table_entry (&centerVbox, centerFrame, current_val);

  gtk_table_attach (GTK_TABLE (table), rVbox, 8, 11 ,4 , 7,
                    GTK_EXPAND , GTK_EXPAND, 0 ,0);
  gtk_table_attach (GTK_TABLE (table), gVbox, 2, 5, 0, 3,
                    GTK_EXPAND, GTK_EXPAND, 0, 0);
  gtk_table_attach (GTK_TABLE (table), bVbox, 2, 5, 8, 11,
                    GTK_EXPAND, GTK_EXPAND,0,0);
  gtk_table_attach (GTK_TABLE (table), cVbox, 0, 3, 4, 7,
                    GTK_EXPAND, GTK_EXPAND, 0 ,0);
  gtk_table_attach (GTK_TABLE (table), yVbox, 6, 9, 0, 3,
                    GTK_EXPAND, GTK_EXPAND, 0 ,0);
  gtk_table_attach (GTK_TABLE (table), mVbox, 6, 9, 8, 11,
                    GTK_EXPAND, GTK_EXPAND, 0 ,0);
  gtk_table_attach (GTK_TABLE (table), centerVbox, 4, 7, 4, 7,
                    GTK_EXPAND, GTK_EXPAND, 0 ,0);

  return win;
}

static GtkWidget *
fp_create_rough (void)
{
  GtkWidget *frame, *vbox, *scale;
  GtkObject *data;

  frame = gimp_frame_new (_("Roughness"));
  gtk_widget_show (frame);

  vbox = gtk_vbox_new (FALSE, 6);
  gtk_container_add (GTK_CONTAINER (frame), vbox);
  gtk_widget_show (vbox);

  data = gtk_adjustment_new (fpvals.roughness, 0, 1.0, 0.05, 0.01, 0.0);
  fp_widgets.roughness_scale = scale = gtk_hscale_new (GTK_ADJUSTMENT (data));

  gtk_widget_set_size_request (scale, 60, -1);
  gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP);
  gtk_scale_set_digits (GTK_SCALE (scale), 2);
  gtk_widget_show (scale);

  g_signal_connect (data, "value_changed",
                    G_CALLBACK (fp_scale_update),
                    &fpvals.roughness);

  gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);

  return frame;
}

static void
fp_change_current_range (GtkWidget *widget,
                         gpointer   data)
{
  gimp_radio_button_update (widget, data);

  if (GTK_TOGGLE_BUTTON (widget)->active)
    {
      fp_refresh_previews (fpvals.visible_frames);
      if (AW.window && GTK_WIDGET_VISIBLE (AW.window))
        fp_create_smoothness_graph (AW.aliasing_preview);
    }
}

static GtkWidget *
fp_create_range (void)
{
  GtkWidget *frame;

  frame = gimp_int_radio_group_new (TRUE, _("Affected Range"),
                                    G_CALLBACK (fp_change_current_range),
                                    &fpvals.intensity_range, fpvals.intensity_range,

                                    _("Sha_dows"),  SHADOWS, NULL,
                                    _("_Midtones"), MIDTONES, NULL,
                                    _("H_ighlights"), HIGHLIGHTS, NULL,

                                    NULL);

  gtk_widget_show (frame);

  return frame;
}

static GtkWidget *
fp_create_control (void)
{
  GtkWidget *frame, *box;

  frame = gimp_frame_new (_("Windows"));

  box = gtk_vbox_new (FALSE, 6);
  gtk_container_add (GTK_CONTAINER (frame), box);
  gtk_widget_show (box);

  fp_frames_checkbutton_in_box (box, _("_Hue"),
                                GTK_SIGNAL_FUNC (fp_show_hide_frame),
                                fp_frames.palette,
                                fpvals.visible_frames & HUE);
  fp_frames_checkbutton_in_box (box, _("_Saturation"),
                                GTK_SIGNAL_FUNC (fp_show_hide_frame),
                                fp_frames.satur,
                                fpvals.visible_frames & SATURATION);
  fp_frames_checkbutton_in_box (box, _("_Value"),
                                GTK_SIGNAL_FUNC (fp_show_hide_frame),
                                fp_frames.lnd,
                                fpvals.visible_frames & VALUE);
  fp_frames_checkbutton_in_box (box, _("A_dvanced"),
                                GTK_SIGNAL_FUNC (fp_show_hide_frame),
                                AW.window,
                                FALSE);
  gtk_widget_show (frame);

  return frame;
}

static GtkWidget *
fp_create_lnd (void)
{
  GtkWidget *table, *lighterFrame, *middleFrame, *darkerFrame;
  GtkWidget *lighterVbox, *middleVbox, *darkerVbox;
  GtkWidget *win;

  win = gtk_window_new (GTK_WINDOW_TOPLEVEL);

  gimp_help_connect (win, gimp_standard_help_func, HELP_ID, NULL);

  gtk_window_set_title (GTK_WINDOW (win), _("Value Variations"));

  g_signal_connect (win, "delete_event",
                    G_CALLBACK (sub_dialog_destroy),
                    NULL);

  fp_create_preview (&lighterPreview, &lighterFrame,
                    reduced->width, reduced->height);
  fp_create_preview (&middlePreview, &middleFrame,
                    reduced->width, reduced->height);
  fp_create_preview (&darkerPreview, &darkerFrame,
                    reduced->width, reduced->height);

  fp_create_table_entry (&lighterVbox, lighterFrame, val_lighter);
  fp_create_table_entry (&middleVbox, middleFrame, current_val);
  fp_create_table_entry (&darkerVbox, darkerFrame, val_darker);

  table = gtk_table_new (1, 11, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_container_set_border_width (GTK_CONTAINER (table), 12);
  gtk_container_add (GTK_CONTAINER (win), table);
  gtk_widget_show (table);

  gtk_table_attach (GTK_TABLE (table), lighterVbox, 0, 3, 0, 1,
                    GTK_EXPAND , GTK_EXPAND, 0, 0);
  gtk_table_attach (GTK_TABLE (table), middleVbox, 4, 7, 0, 1,
                    GTK_EXPAND, GTK_EXPAND, 0, 0);
  gtk_table_attach (GTK_TABLE (table), darkerVbox, 8, 11, 0, 1,
                    GTK_EXPAND, GTK_EXPAND, 0, 0);

  return win;
}

static GtkWidget *
fp_create_msnls (void)
{
  GtkWidget *table, *lessFrame, *middleFrame, *moreFrame;
  GtkWidget *lessVbox, *middleVbox, *moreVbox;
  GtkWidget *win;

  win = gtk_window_new (GTK_WINDOW_TOPLEVEL);

  gimp_help_connect (win, gimp_standard_help_func, HELP_ID, NULL);

  gtk_window_set_title (GTK_WINDOW (win), _("Saturation Variations"));

  g_signal_connect (win, "delete_event",
                    G_CALLBACK (sub_dialog_destroy),
                    NULL);

  fp_create_preview (&minusSatPreview, &lessFrame,
                    reduced->width, reduced->height);
  fp_create_preview (&SatPreview, &middleFrame,
                    reduced->width, reduced->height);
  fp_create_preview (&plusSatPreview, &moreFrame,
                    reduced->width, reduced->height);

  fp_create_table_entry (&moreVbox, moreFrame, sat_more);
  fp_create_table_entry (&middleVbox, middleFrame, current_val);
  fp_create_table_entry (&lessVbox, lessFrame, sat_less);

  table = gtk_table_new (1, 11, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_container_set_border_width (GTK_CONTAINER (table), 12);
  gtk_container_add (GTK_CONTAINER (win), table);
  gtk_widget_show (table);

  gtk_table_attach (GTK_TABLE (table), moreVbox, 0, 3, 0, 1,
                    GTK_EXPAND, GTK_EXPAND, 0, 0);
  gtk_table_attach (GTK_TABLE (table), middleVbox, 4, 7, 0, 1,
                    GTK_EXPAND, GTK_EXPAND, 0, 0);
  gtk_table_attach (GTK_TABLE (table), lessVbox, 8, 11, 0, 1,
                    GTK_EXPAND, GTK_EXPAND, 0, 0);

  return win;
}

static void
fp_change_current_pixels_by (GtkWidget *widget,
                             gpointer   data)
{
  gimp_radio_button_update (widget, data);

  if (GTK_TOGGLE_BUTTON (widget)->active)
    {
      fp_refresh_previews (fpvals.visible_frames);
      if (AW.window && GTK_WIDGET_VISIBLE (AW.window) && AW.range_preview)
        fp_range_preview_spill (AW.range_preview,fpvals.value_by);
    }
}

static GtkWidget *
fp_create_pixels_select_by (void)
{
  GtkWidget *frame;

  frame = gimp_int_radio_group_new (TRUE, _("Select Pixels by"),
                                    G_CALLBACK (fp_change_current_pixels_by),
                                    &fpvals.value_by,
                                    fpvals.value_by,

                                    _("H_ue"),  0, NULL,
                                    _("Satu_ration"), 1, NULL,
                                    _("V_alue"), 2, NULL,

                                    NULL);

  gtk_widget_show (frame);

  return frame;
}

static void
fp_change_selection (GtkWidget *widget,
                     gpointer   data)
{
  gimp_radio_button_update (widget, data);

  if (GTK_TOGGLE_BUTTON (widget)->active)
    {
      fp_redraw_all_windows ();
    }
}

static GtkWidget *
fp_create_show (void)
{
  GtkWidget *frame;

  frame = gimp_int_radio_group_new (TRUE, _("Show"),
                                    G_CALLBACK (fp_change_selection),
                                    &fpvals.selection_only,
                                    fpvals.selection_only,

                                    _("_Entire Image"),  0, NULL,
                                    _("Se_lection Only"), 1, NULL,
                                    _("Selec_tion In Context"), 2, NULL,

                                    NULL);

  gtk_widget_show (frame);

  return frame;
}

static void
fp_create_preview (GtkWidget **preview,
                  GtkWidget  **frame,
                  gint         preview_width,
                  gint         preview_height)
{
  *frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (*frame), GTK_SHADOW_IN);
  gtk_widget_show (*frame);

  *preview = gimp_preview_area_new ();
  gtk_widget_set_size_request (*preview, preview_width, preview_height);
  g_signal_connect (*preview, "size_allocate",
                    G_CALLBACK (fp_preview_size_allocate), NULL);
  gtk_widget_show (*preview);
  gtk_container_add (GTK_CONTAINER (*frame), *preview);
}

static void
fp_checkbutton_in_box (GtkWidget     *vbox,
                       const gchar   *label,
                       GtkSignalFunc  function,
                       gpointer       data,
                       gboolean       clicked)
{
  GtkWidget *button;

  button = gtk_check_button_new_with_mnemonic (label);
  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

  g_signal_connect (button, "clicked",
                    G_CALLBACK (function),
                    data);

  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), clicked);
}

static void
fp_frames_checkbutton_in_box (GtkWidget     *vbox,
                              const gchar   *label,
                              GtkSignalFunc  function,
                              GtkWidget     *frame,
                              gboolean       clicked)
{
  GtkWidget *button;

  button = gtk_check_button_new_with_mnemonic (label);
  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
  g_object_set_data (G_OBJECT (frame), "ctrlButton", (gpointer) button);
  gtk_widget_show (button);

  g_signal_connect (button, "clicked",
                    G_CALLBACK (function),
                    frame);

  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), clicked);
}

static void
fp_create_table_entry (GtkWidget   **box,
                      GtkWidget    *smaller_frame,
                      const gchar  *description)
{
  GtkWidget *label, *button, *table;

  *box = gtk_vbox_new (FALSE, 1);
  gtk_container_set_border_width (GTK_CONTAINER (*box), PR_BX_BRDR);
  gtk_widget_show (*box);

  /* Delayed translation applied here */
  label = gtk_label_new (gettext (description));

  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_widget_show (label);

  table = gtk_table_new (2, 1, FALSE);
  gtk_widget_show (table);

  gtk_box_pack_start (GTK_BOX (*box), table, TRUE, TRUE, 0);

  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
                    0, 0, 0, 0);

  if (description != current_val)
    {
      button = gtk_button_new ();
      gtk_table_attach (GTK_TABLE (table), button, 0, 1, 1, 2,
                        0, 0, 0, 4);
      gtk_widget_show (button);

      gtk_container_add (GTK_CONTAINER (button), smaller_frame);

      g_signal_connect (button, "clicked",
                        G_CALLBACK (fp_selection_made),
                        (gchar *) description);
    }
  else
    {
      gtk_table_attach (GTK_TABLE (table), smaller_frame, 0, 1, 1, 2,
                        0, 0, 0, 4);
    }
}

static void
fp_redraw_all_windows (void)
{
  reduced = fp_reduce_image (drawable,mask,
                              fpvals.preview_size,
                              fpvals.selection_only);
  fp_adjust_preview_sizes (reduced->width, reduced->height);

  gtk_widget_queue_draw (fp_frames.palette);
  gtk_widget_queue_draw (fp_frames.satur);
  gtk_widget_queue_draw (fp_frames.lnd);
  gtk_widget_queue_draw (dlg);

  fp_refresh_previews (fpvals.visible_frames);
}

static void
fp_show_hide_frame (GtkWidget *button,
                    GtkWidget *frame)
{
  gint prev = fpvals.visible_frames;

  if (frame == NULL)
    return;

  if (GTK_TOGGLE_BUTTON (button)->active)
    {
      if (!GTK_WIDGET_VISIBLE (frame))
        {
          gtk_widget_show (frame);

          if (frame==fp_frames.palette)
            fpvals.visible_frames |= HUE;
          else if (frame==fp_frames.satur)
            fpvals.visible_frames |= SATURATION;
          else if (frame==fp_frames.lnd)
            fpvals.visible_frames |= VALUE;

          fp_refresh_previews (fpvals.visible_frames & ~prev);
          fp_create_smoothness_graph (AW.aliasing_preview);
          fp_range_preview_spill (AW.range_preview,fpvals.value_by);
        }
    }
  else
    {
      if (GTK_WIDGET_VISIBLE (frame))
        {
          gtk_widget_hide (frame);

          if (frame==fp_frames.palette)
            fpvals.visible_frames &= ~HUE;
          else if (frame==fp_frames.satur)
            fpvals.visible_frames &= ~SATURATION;
          else if (frame==fp_frames.lnd)
            fpvals.visible_frames &= ~VALUE;
        }
    }
}

static void
fp_adjust_preview_sizes (gint width,
                         gint height)
{
  gtk_widget_set_size_request (origPreview,     width, height);
  gtk_widget_set_size_request (curPreview,      width, height);
  gtk_widget_set_size_request (rPreview,        width, height);
  gtk_widget_set_size_request (gPreview,        width, height);
  gtk_widget_set_size_request (bPreview,        width, height);
  gtk_widget_set_size_request (cPreview,        width, height);
  gtk_widget_set_size_request (yPreview,        width, height);
  gtk_widget_set_size_request (mPreview,        width, height);
  gtk_widget_set_size_request (centerPreview,   width, height);
  gtk_widget_set_size_request (lighterPreview,  width, height);
  gtk_widget_set_size_request (darkerPreview,   width, height);
  gtk_widget_set_size_request (middlePreview,   width, height);
  gtk_widget_set_size_request (minusSatPreview, width, height);
  gtk_widget_set_size_request (SatPreview,      width, height);
  gtk_widget_set_size_request (plusSatPreview,  width, height);

}

static void
fp_selection_made (GtkWidget *widget,
                   gpointer   data)
{
  fpvals.touched[fpvals.value_by] = 1;

  if (data == (gpointer) hue_red) {
    update_current_fp (HUE, RED);
  } else if (data == (gpointer) hue_green) {
    update_current_fp (HUE, GREEN);
  } else if (data == (gpointer) hue_blue) {
    update_current_fp (HUE, BLUE);
  } else if (data == (gpointer) hue_cyan) {
    update_current_fp (HUE, CYAN);
  } else if (data == (gpointer) hue_yellow) {
    update_current_fp (HUE, YELLOW);
  } else if (data == (gpointer) hue_magenta) {
    update_current_fp (HUE, MAGENTA);
  } else if (data == (gpointer) val_darker) {
    update_current_fp (VALUE, DOWN);
  } else if (data == (gpointer) val_lighter) {
    update_current_fp (VALUE, UP);
  } else if (data == (gpointer) sat_more) {
    update_current_fp (SATURATION, UP);
  } else if (data == (gpointer) sat_less) {
    update_current_fp (SATURATION, DOWN);
  }

  fp_refresh_previews (fpvals.visible_frames);
}

static void
fp_refresh_previews (gint which)
{
  fp_create_nudge (nudgeArray);
  fp_render_preview (origPreview, NONEATALL, 0);
  fp_render_preview (curPreview, CURRENT, 0);
  if (which & HUE)
    {
      fp_render_preview (rPreview,        HUE,        RED);
      fp_render_preview (gPreview,        HUE,        GREEN);
      fp_render_preview (bPreview,        HUE,        BLUE);
      fp_render_preview (cPreview,        HUE,        CYAN);
      fp_render_preview (yPreview,        HUE,        YELLOW);
      fp_render_preview (mPreview,        HUE,        MAGENTA);
      fp_render_preview (centerPreview,   CURRENT,    0);
    }
  if (which & VALUE)
    {
      fp_render_preview (lighterPreview,  VALUE,      UP);
      fp_render_preview (middlePreview,   CURRENT,    0);
      fp_render_preview (darkerPreview,   VALUE,      DOWN);
    }
  if (which & SATURATION)
    {
      fp_render_preview (plusSatPreview,  SATURATION, UP);
      fp_render_preview (SatPreview,      CURRENT,    0);
      fp_render_preview (minusSatPreview, SATURATION, DOWN);
    }
}

static void
fp_response (GtkWidget *widget,
             gint       response_id,
             gpointer   data)
{
  switch (response_id)
    {
    case RESPONSE_RESET:
      fp_reset_filter_packs ();
      break;

    case GTK_RESPONSE_OK:
      FPint.run = TRUE;
      gtk_widget_destroy (widget);
      break;

    default:
      gtk_widget_destroy (widget);
      break;
    }
}

static void
fp_scale_update (GtkAdjustment *adjustment,
                 gdouble        *scale_val)
{
  static gdouble prevValue = 0.25;

  *scale_val = adjustment->value;

  if (prevValue != adjustment->value)
    {
      fp_create_nudge (nudgeArray);
      fp_refresh_previews (fpvals.visible_frames);
      if (AW.window != NULL && GTK_WIDGET_VISIBLE (AW.window))
        fp_create_smoothness_graph (AW.aliasing_preview);
      prevValue = adjustment->value;
    }
}

static gboolean
fp_dialog (void)
{
  GtkWidget *bna;
  GtkWidget *palette;
  GtkWidget *lnd;
  GtkWidget *show;
  GtkWidget *rough;
  GtkWidget *range;
  GtkWidget *pixelsBy;
  GtkWidget *satur;
  GtkWidget *control;
  GtkWidget *table;

  reduced = fp_reduce_image (drawable,mask,
                              fpvals.preview_size,
                              fpvals.selection_only);

  gimp_ui_init ("fp", TRUE);

  dlg = gimp_dialog_new (_("Filter Pack Simulation"), "fp",
                         NULL, 0,
                         gimp_standard_help_func, HELP_ID,

                         GIMP_STOCK_RESET, RESPONSE_RESET,
                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                         GTK_STOCK_OK,     GTK_RESPONSE_OK,

                         NULL);

  g_signal_connect (dlg, "response",
                    G_CALLBACK (fp_response),
                    dlg);

  g_signal_connect (dlg, "destroy",
                    G_CALLBACK (gtk_main_quit),
                    NULL);

  fp_advanced_dialog ();

  fp_frames.bna          = bna          = fp_create_bna();
  fp_frames.rough        = rough        = fp_create_rough();
  fp_frames.range        = range        = fp_create_range();
  fp_frames.palette      = palette      = fp_create_circle_palette();
  fp_frames.lnd          = lnd          = fp_create_lnd();
  fp_frames.show         = show         = fp_create_show();
  fp_frames.satur        = satur        = fp_create_msnls();
  fp_frames.pixelsBy     = pixelsBy     = fp_create_pixels_select_by();
                          control      = fp_create_control();
  /********************************************************************/
  /********************   PUT EVERYTHING TOGETHER    ******************/

  table = gtk_table_new (4, 2, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_table_set_row_spacings (GTK_TABLE (table), 6);
  gtk_container_set_border_width (GTK_CONTAINER (table), 12);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), table, TRUE, TRUE, 0);
  gtk_widget_show (table);

  gtk_table_attach (GTK_TABLE (table), bna, 0, 2, 0, 1,
                    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);

  gtk_table_attach (GTK_TABLE (table), control, 1, 2, 1, 3,
                    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);

  gtk_table_attach (GTK_TABLE (table), rough, 1, 2, 3, 4,
                    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);

  gtk_table_attach (GTK_TABLE (table), show, 0, 1, 1, 2,
                    GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);

  gtk_table_attach (GTK_TABLE (table), range, 0, 1, 2, 3,
                    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);

  gtk_table_attach (GTK_TABLE (table), pixelsBy, 0, 1, 3, 4,
                    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);

  gtk_widget_show (dlg);

  fp_refresh_previews (fpvals.visible_frames);

  gtk_main ();

  return FPint.run;
}

/***********************************************************/
/************   Advanced Options Window   ******************/
/***********************************************************/

static void
fp_drag (GtkWidget *button)
{
  static gboolean notFirstTime = FALSE;

  if (! notFirstTime)
    return;

  notFirstTime = TRUE;

  if (GTK_TOGGLE_BUTTON (button)->active)
    {
      fpvals.real_time=TRUE;
      gtk_range_set_update_policy (GTK_RANGE (fp_widgets.roughness_scale),0);
      gtk_range_set_update_policy (GTK_RANGE (fp_widgets.aliasing_scale),0);
      gtk_range_set_update_policy (GTK_RANGE (fp_widgets.preview_size_scale),0);
    }
  else
    {
      fpvals.real_time=FALSE;
      gtk_range_set_update_policy (GTK_RANGE (fp_widgets.roughness_scale),
                                   GTK_UPDATE_DELAYED);
      gtk_range_set_update_policy (GTK_RANGE (fp_widgets.aliasing_scale),
                                   GTK_UPDATE_DELAYED);
      gtk_range_set_update_policy (GTK_RANGE (fp_widgets.preview_size_scale),
                                   GTK_UPDATE_DELAYED);
    }
}

static void
fp_preview_scale_update (GtkAdjustment *adjustment,
                         gdouble        *scale_val)
{
  fpvals.preview_size = adjustment->value;
  fp_redraw_all_windows();
}

static void
fp_advanced_dialog (void)
{
  gchar     *rangeNames[] = { N_("Shadows:"),
                              N_("Midtones:"),
                              N_("Highlights:") };
  GtkWidget *frame, *mainvbox;
  GtkObject *smoothnessData;
  GtkWidget *graphFrame, *table, *scale;
  GtkWidget *vbox, *label, *labelTable, *alignment, *inner_vbox;
  gint i;

  AW.window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

  gimp_help_connect (AW.window, gimp_standard_help_func, HELP_ID, NULL);

  gtk_window_set_title (GTK_WINDOW (AW.window),
                        _("Advanced Filter Pack Options"));

  g_signal_connect (AW.window, "delete_event",
                    G_CALLBACK (sub_dialog_destroy),
                    NULL);

  mainvbox = gtk_hbox_new (FALSE, 12);
  gtk_container_set_border_width (GTK_CONTAINER (mainvbox), 12);
  gtk_container_add (GTK_CONTAINER (AW.window), mainvbox);
  gtk_widget_show (mainvbox);

  frame = gimp_frame_new (_("Smoothness of Aliasing"));
  gtk_box_pack_start (GTK_BOX (mainvbox), frame, TRUE, TRUE, 0);
  gtk_widget_show (frame);

  table = gtk_table_new (3, 1, FALSE);
  gtk_table_set_row_spacings (GTK_TABLE (table), 6);
  gtk_container_add (GTK_CONTAINER (frame), table);
  gtk_widget_show (table);

  graphFrame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1, TRUE);
  gtk_frame_set_shadow_type (GTK_FRAME (graphFrame), GTK_SHADOW_IN);
  gtk_container_set_border_width (GTK_CONTAINER (graphFrame),0);
  gtk_widget_show (graphFrame);
  gtk_table_attach (GTK_TABLE (table), graphFrame, 0, 1, 0, 1,
                    GTK_EXPAND, 0, 0, 0);

  vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_add (GTK_CONTAINER (graphFrame), vbox);
  gtk_widget_show (vbox);

  alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
  gtk_box_pack_start_defaults (GTK_BOX (vbox), alignment);
  gtk_widget_show (alignment);

  inner_vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_add (GTK_CONTAINER (alignment), inner_vbox);
  gtk_widget_show (inner_vbox);

  AW.aliasing_preview = gimp_preview_area_new ();
  gtk_widget_set_size_request (AW.aliasing_preview, 256, MAX_ROUGHNESS);
  gtk_box_pack_start (GTK_BOX (inner_vbox), AW.aliasing_preview, TRUE, TRUE, 0);
  gtk_widget_show (AW.aliasing_preview);

  fp_create_smoothness_graph (AW.aliasing_preview);

  AW.range_preview = gimp_preview_area_new ();
  gtk_widget_set_size_request (AW.range_preview, 256, RANGE_HEIGHT);
  gtk_box_pack_start(GTK_BOX (inner_vbox), AW.range_preview, TRUE, TRUE, 0);
  gtk_widget_show (AW.range_preview);

  fp_range_preview_spill (AW.range_preview, fpvals.value_by);

  labelTable = gtk_table_new (3, 4, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (labelTable), 6);
  gtk_table_set_row_spacings (GTK_TABLE (labelTable), 6);
  gtk_widget_show (labelTable);
  gtk_table_attach (GTK_TABLE (table), labelTable, 0, 1, 1, 2,
                    GTK_EXPAND, 0, 0, 0);

  for (i = 0; i < 12; i++)
    {
      label = fp_widgets.range_label[i] = gtk_label_new ("-");
      if (!(i % 4))
        {
          gtk_label_set_text (GTK_LABEL(label), gettext (rangeNames[i/4]));
          gtk_misc_set_alignment (GTK_MISC (label), 1.0, 1.0);
        }
      gtk_widget_show (label);
      gtk_table_attach (GTK_TABLE (labelTable), label, i%4, i%4+1, i/4, i/4+1,
                        GTK_EXPAND | GTK_FILL, 0, 0, 0);
    }

  /************************************************************/

  AW.aliasing_graph = gtk_drawing_area_new ();
  gtk_widget_set_size_request (AW.aliasing_graph,
                               2 * MARGIN + 256,
                               RANGE_HEIGHT);
  gtk_box_pack_start (GTK_BOX (vbox), AW.aliasing_graph, TRUE, TRUE, 0);
  gtk_widget_show (AW.aliasing_graph);
  gtk_widget_set_events (AW.aliasing_graph, RANGE_ADJUST_MASK);

  g_signal_connect (AW.aliasing_graph, "event",
                    G_CALLBACK (fp_range_change_events),
                    &fpvals);

  /************************************************************/

  smoothnessData = gtk_adjustment_new (fpvals.aliasing, 0, 1.0, 0.05, 0.01, 0.0);

  fp_widgets.aliasing_scale = scale =
    gtk_hscale_new (GTK_ADJUSTMENT (smoothnessData));
  gtk_widget_set_size_request (scale, 200, -1);
  gtk_scale_set_digits (GTK_SCALE (scale), 2);
  gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP);
  gtk_range_set_update_policy (GTK_RANGE (scale), 0);
  gtk_table_attach (GTK_TABLE (table), scale, 0, 1, 2, 3,
                    0, 0, 0, 0);
  gtk_widget_show (scale);

  g_signal_connect (smoothnessData, "value_changed",
                    G_CALLBACK (fp_scale_update),
                    &fpvals.aliasing);

  /******************* MISC OPTIONS ***************************/

  vbox = gtk_vbox_new (FALSE, 6);
  gtk_box_pack_start (GTK_BOX (mainvbox), vbox, TRUE, TRUE, 0);
  gtk_widget_show (vbox);

  fp_checkbutton_in_box (vbox, _("Preview as You Drag"),
                         GTK_SIGNAL_FUNC (fp_drag),
                         NULL, TRUE);

  frame = gimp_frame_new (_("Preview Size"));
  gtk_widget_show (frame);

  smoothnessData = gtk_adjustment_new (fpvals.preview_size,
                                       50, MAX_PREVIEW_SIZE,
                                       5, 5, 0.0);

  fp_widgets.preview_size_scale = scale =
    gtk_hscale_new (GTK_ADJUSTMENT (smoothnessData));
  gtk_container_add (GTK_CONTAINER (frame), scale);
  gtk_widget_set_size_request (scale, 100, -1);
  gtk_scale_set_digits (GTK_SCALE (scale), 0);
  gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP);
  gtk_range_set_update_policy (GTK_RANGE (scale), 0);
  gtk_widget_show (scale);

  g_signal_connect (smoothnessData, "value_changed",
                    G_CALLBACK (fp_preview_scale_update),
                    &fpvals.preview_size);

  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
}

static void
slider_erase (GdkWindow *window,
              int        xpos)
{
  gdk_window_clear_area (window, MARGIN + xpos - (RANGE_HEIGHT - 1) / 2, 0,
                         RANGE_HEIGHT, RANGE_HEIGHT);
}

static void
draw_slider (GdkWindow *window,
             GdkGC     *border_gc,
             GdkGC     *fill_gc,
             gint       xpos)
{
  gint i;

  for (i = 0; i < RANGE_HEIGHT; i++)
    gdk_draw_line (window, fill_gc, MARGIN + xpos-i/2, i, MARGIN + xpos+i/2,i);

  gdk_draw_line (window, border_gc, MARGIN + xpos, 0,
                 MARGIN + xpos - (RANGE_HEIGHT - 1) / 2, RANGE_HEIGHT - 1);

  gdk_draw_line (window, border_gc, MARGIN + xpos, 0,
                 MARGIN + xpos + (RANGE_HEIGHT - 1) / 2, RANGE_HEIGHT - 1);

  gdk_draw_line (window, border_gc, MARGIN + xpos- (RANGE_HEIGHT - 1)/2,
                 RANGE_HEIGHT-1, MARGIN + xpos + (RANGE_HEIGHT-1)/2,
                 RANGE_HEIGHT - 1);
}

static void
draw_it (GtkWidget *widget)
{
  draw_slider (AW.aliasing_graph->window,
               AW.aliasing_graph->style->black_gc,
               AW.aliasing_graph->style->dark_gc[GTK_STATE_NORMAL],
               fpvals.cutoff[SHADOWS]);

  draw_slider (AW.aliasing_graph->window,
               AW.aliasing_graph->style->black_gc,
               AW.aliasing_graph->style->dark_gc[GTK_STATE_NORMAL],
               fpvals.cutoff[MIDTONES]);

  draw_slider (AW.aliasing_graph->window,
               AW.aliasing_graph->style->black_gc,
               AW.aliasing_graph->style->dark_gc[GTK_STATE_SELECTED],
               fpvals.offset);
}

static gboolean
fp_range_change_events (GtkWidget *widget,
                        GdkEvent  *event,
                        FPValues *current)
{
  GdkEventButton *bevent;
  GdkEventMotion *mevent;
  gint shad, mid, offset, min;
  static guchar  *new;
  gint  x;

  switch (event->type)
    {
    case GDK_EXPOSE:
      draw_it (NULL);
      break;

    case GDK_BUTTON_PRESS:
      bevent= (GdkEventButton *) event;

      shad   =  abs (bevent->x - fpvals.cutoff[SHADOWS]);
      mid    =  abs (bevent->x - fpvals.cutoff[MIDTONES]);
      offset =  abs (bevent->x - fpvals.offset);

      min = MIN (MIN (shad, mid), offset);

      if (bevent->x >0 && bevent->x<256)
        {
          if (min == shad)
            new = &fpvals.cutoff[SHADOWS];
          else if (min == mid)
            new = &fpvals.cutoff[MIDTONES];
          else
            new = &fpvals.offset;

          slider_erase (AW.aliasing_graph->window, *new);
          *new = bevent->x;
        }

      draw_it (NULL);

      if (fpvals.real_time)
        {
          fp_range_preview_spill (AW.range_preview, fpvals.value_by);
          update_range_labels ();
          fp_create_smoothness_graph (AW.aliasing_preview);
          fp_refresh_previews (fpvals.visible_frames);
        }
      break;

    case GDK_BUTTON_RELEASE:
      if (!fpvals.real_time)
        {
          fp_range_preview_spill (AW.range_preview, fpvals.value_by);
          update_range_labels ();
          fp_create_smoothness_graph (AW.aliasing_preview);
          fp_refresh_previews (fpvals.visible_frames);
        }
      break;

    case GDK_MOTION_NOTIFY:
      mevent = (GdkEventMotion *) event;
      gdk_window_get_pointer (widget->window, &x, NULL, NULL);

      if (x >= 0 && x < 256)
        {
          slider_erase (AW.aliasing_graph->window, *new);
          *new = x;
          draw_it (NULL);
          if (fpvals.real_time)
            {
              fp_range_preview_spill (AW.range_preview, fpvals.value_by);
              update_range_labels ();
              fp_create_smoothness_graph (AW.aliasing_preview);
              fp_refresh_previews (fpvals.visible_frames);
            }
        }
      break;

    default:
      break;
    }

  return FALSE;
}

static void
update_range_labels (void)
{
  gchar buffer[3];

  gtk_label_set_text (GTK_LABEL(fp_widgets.range_label[1]),"0");

  g_snprintf (buffer, sizeof (buffer), "%d", fpvals.cutoff[SHADOWS]);
  gtk_label_set_text (GTK_LABEL (fp_widgets.range_label[3]), buffer);
  gtk_label_set_text (GTK_LABEL (fp_widgets.range_label[5]), buffer);

  g_snprintf (buffer, sizeof (buffer), "%d", fpvals.cutoff[MIDTONES]);
  gtk_label_set_text (GTK_LABEL (fp_widgets.range_label[7]), buffer);
  gtk_label_set_text (GTK_LABEL (fp_widgets.range_label[9]), buffer);

  gtk_label_set_text (GTK_LABEL(fp_widgets.range_label[11]), "255");
}

static void
fp_init_filter_packs (void)
{
  gint i, j;
  for (i = 0; i < 256; i++)
    for (j = BY_HUE; j < JUDGE_BY; j++)
      {
        fpvals.red_adjust   [j][i] = 0;
        fpvals.green_adjust [j][i] = 0;
        fpvals.blue_adjust  [j][i] = 0;
        fpvals.sat_adjust   [j][i] = 0;
      }
}

static void
fp_reset_filter_packs (void)
{
  fp_init_filter_packs ();
  fp_refresh_previews (fpvals.visible_frames);
}

static ReducedImage *
fp_reduce_image (GimpDrawable  *drawable,
                 GimpDrawable *mask,
                 gint          longer_size,
                 gint          selection)
{
  gint          RH, RW, width, height, bytes=drawable->bpp;
  ReducedImage *temp = (ReducedImage *) malloc (sizeof (ReducedImage));
  guchar       *tempRGB, *src_row, *tempmask, *src_mask_row, R, G, B;
  gint          i, j, whichcol, whichrow, x1, x2, y1, y2;
  GimpPixelRgn  srcPR, srcMask;
  gboolean      NoSelectionMade = TRUE;
  gdouble      *tempHSV;
  GimpRGB       rgb;
  GimpHSV       hsv;

  gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2);
  width  = x2 - x1;
  height = y2 - y1;

  if (width != drawable->width && height != drawable->height)
    NoSelectionMade = FALSE;

  if (selection == 0)
    {
      x1 = 0;
      x2 = drawable->width;
      y1 = 0;
      y2 = drawable->height;
    }
  else if (selection == 2)
    {
      x1 = MAX (0,                x1 - width  / 2.0);
      x2 = MIN (drawable->width,  x2 + width  / 2.0);
      y1 = MAX (0,                y1 - height / 2.0);
      y2 = MIN (drawable->height, y2 + height / 2.0);
    }

  width  = x2 - x1;
  height = y2 - y1;

  if (width > height)
    {
      RW = longer_size;
      RH = (gdouble) height * (gdouble) longer_size / (gdouble) width;
    }
  else
    {
      RH = longer_size;
      RW = (gdouble) width * (gdouble) longer_size / (gdouble) height;
    }

  tempRGB  = (guchar *)  malloc (RW * RH * bytes);
  tempHSV  = (gdouble *) malloc (RW * RH * bytes * sizeof (gdouble));
  tempmask = (guchar *)  malloc (RW * RH);

  gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE);
  gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE);

  src_row      = (guchar *) malloc (width * bytes);
  src_mask_row = (guchar *) malloc (width * bytes);

  for (i = 0; i < RH; i++)
    {
      whichrow = (gdouble) i * (gdouble) height / (gdouble) RH;

      gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1 + whichrow, width);
      gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1 + whichrow, width);

      for (j = 0; j < RW; j++)
        {
          whichcol = (gdouble) j * (gdouble) width / (gdouble) RW;

          if (NoSelectionMade)
            tempmask[i * RW + j] = 255;
          else
            tempmask[i * RW + j] = src_mask_row[whichcol];

          R = src_row[whichcol * bytes + 0];
          G = src_row[whichcol * bytes + 1];
          B = src_row[whichcol * bytes + 2];

          gimp_rgb_set_uchar (&rgb, R, G, B);
          gimp_rgb_to_hsv (&rgb, &hsv);

          tempRGB[i * RW * bytes + j * bytes + 0] = R;
          tempRGB[i * RW * bytes + j * bytes + 1] = G;
          tempRGB[i * RW * bytes + j * bytes + 2] = B;

          tempHSV[i * RW * bytes + j * bytes + 0] = hsv.h;
          tempHSV[i * RW * bytes + j * bytes + 1] = hsv.s;
          tempHSV[i * RW * bytes + j * bytes + 2] = hsv.v;

          if (bytes == 4)
            tempRGB[i * RW * bytes + j * bytes + 3] =
              src_row[whichcol * bytes + 3];
        }
    }

  temp->width  = RW;
  temp->height = RH;
  temp->rgb    = tempRGB;
  temp->hsv    = tempHSV;
  temp->mask   = tempmask;

  return temp;
}

static void
fp_render_preview(GtkWidget *preview,
                  gint       change_what,
                  gint       change_which)
{
  guchar *a;
  gint    Inten;
  gint    bytes = drawable->bpp;
  gint    i, j, k, nudge, M, m, middle, JudgeBy;
  gdouble partial;
  gint    RW = reduced->width;
  gint    RH = reduced->height;
  gint    backupP[3];
  gint    P[3];
  gint    tempSat[JUDGE_BY][256];

  a = g_new (guchar, 4*RW*RH);

  if (change_what==SATURATION)
    for (k = 0; k < 256; k++)
      {
        for (JudgeBy = BY_HUE; JudgeBy < JUDGE_BY; JudgeBy++)
          tempSat[JudgeBy][k] = 0;
        tempSat[fpvals.value_by][k] += change_which * nudgeArray[(k + fpvals.offset) % 256];
      }

  for (i = 0; i < RH; i++)
    {
      for (j = 0; j < RW; j++)
        {
          backupP[0] = P[0]  = (int) reduced->rgb[i * RW * bytes + j * bytes + 0];
          backupP[1] = P[1]  = (int) reduced->rgb[i * RW * bytes + j * bytes + 1];
          backupP[2] = P[2]  = (int) reduced->rgb[i * RW * bytes + j * bytes + 2];

      m = MIN (MIN (P[0], P[1]), P[2]);
      M = MAX (MAX (P[0], P[1]), P[2]);

      middle = (M + m) / 2;

      for (k = 0; k < 3; k++)
        if (P[k] != m && P[k] != M) middle = P[k];

      partial = reduced->mask[i * RW + j] / 255.0;

      for (JudgeBy = BY_HUE; JudgeBy < JUDGE_BY; JudgeBy++)
        {
         if (!fpvals.touched[JudgeBy]) continue;

         Inten   = reduced->hsv[i * RW * bytes + j * bytes + JudgeBy] * 255.0;

         /*DO SATURATION FIRST*/
         if (change_what != NONEATALL)
           {
             if (M != m)
               {
                 for (k = 0; k < 3; k++)
                   if (backupP[k] == M)
                     P[k] = MAX (P[k] + partial * fpvals.sat_adjust[JudgeBy][Inten],
                                 middle);
                   else if (backupP[k] == m)
                     P[k] = MIN (P[k] - partial * fpvals.sat_adjust[JudgeBy][Inten],
                                 middle);
               }

             P[0]  += partial * fpvals.red_adjust[JudgeBy][Inten];
             P[1]  += partial * fpvals.green_adjust[JudgeBy][Inten];
             P[2]  += partial * fpvals.blue_adjust[JudgeBy][Inten];
           }
        }

      Inten   = reduced->hsv[i * RW * bytes + j * bytes + fpvals.value_by] * 255.0;
      nudge   = partial * nudgeArray[(Inten + fpvals.offset) % 256];

      switch (change_what)
        {
        case HUE:
          P[0]  += colorSign[RED][change_which]   * nudge;
          P[1]  += colorSign[GREEN][change_which] * nudge;
          P[2]  += colorSign[BLUE][change_which]  * nudge;
          break;

        case SATURATION:
          for (JudgeBy = BY_HUE; JudgeBy < JUDGE_BY; JudgeBy++)
            for (k = 0; k < 3; k++)
              if (M != m)
                {
                  if (backupP[k] == M)
                    P[k] = MAX (P[k] + partial * tempSat[JudgeBy][Inten],
                                middle);
                  else if (backupP[k] == m)
                    P[k] = MIN (P[k]- partial * tempSat[JudgeBy][Inten],
                                middle);
                }
          break;

        case VALUE:
          P[0]  += change_which * nudge;
          P[1]  += change_which * nudge;
          P[2]  += change_which * nudge;
          break;

        default:
          break;
        }

      a[(i * RW + j) * 4 + 0] = CLAMP0255(P[0]);
      a[(i * RW + j) * 4 + 1] = CLAMP0255(P[1]);
      a[(i * RW + j) * 4 + 2] = CLAMP0255(P[2]);

      if (bytes == 4)
        a[(i * RW + j) * 4 + 3] = reduced->rgb[i * RW * bytes + j * bytes + 3];
      else
        a[(i * RW + j) * 4 + 3] = 255;
        }
    }

  gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview),
                          0, 0, RW, RH,
                          GIMP_RGBA_IMAGE,
                          a,
                          RW * 4);
  g_free (a);
}

static void
update_current_fp (gint change_what,
                   gint change_which)
{
  gint i;

  for (i = 0; i < 256; i++)
    {
      gint nudge;

      fp_create_nudge (nudgeArray);
      nudge = nudgeArray[(i + fpvals.offset) % 256];

      switch (change_what) {
      case HUE:
        fpvals.red_adjust[fpvals.value_by][i] +=
          colorSign[RED][change_which] * nudge;

        fpvals.green_adjust[fpvals.value_by][i] +=
          colorSign[GREEN][change_which] * nudge;

        fpvals.blue_adjust[fpvals.value_by][i] +=
          colorSign[BLUE][change_which]  * nudge;
        break;

      case SATURATION:
        fpvals.sat_adjust[fpvals.value_by][i] += change_which * nudge;
        break;

      case VALUE:
        fpvals.red_adjust[fpvals.value_by][i]   += change_which * nudge;
        fpvals.green_adjust[fpvals.value_by][i] += change_which * nudge;
        fpvals.blue_adjust[fpvals.value_by][i]  += change_which * nudge;
        break;

      default:
        break;
      }
    }
}

static void
fp_create_smoothness_graph (GtkWidget *preview)
{
  guchar data[256 * MAX_ROUGHNESS * 3];
  gint nArray[256];
  gint i, j;
  gboolean toBeBlack;

  fp_create_nudge(nArray);

  for (i = 0; i < MAX_ROUGHNESS; i++)
    {
      gint coor = MAX_ROUGHNESS - i;
      for (j = 0; j < 256; j++) {
        data[3 * (i * 256 + j) + 0] = 255;
        data[3 * (i * 256 + j) + 1] = 255;
        data[3 * (i * 256 + j) + 2] = 255;
        if (!(i % (MAX_ROUGHNESS / 4))) {
          data[3 * (i * 256 + j) + 0] = 255;
          data[3 * (i * 256 + j) + 1] = 128;
          data[3 * (i * 256 + j) + 2] = 128;
        }
        if (!((j + 1) % 32)) {
          data[3 * (i * 256 + j) + 0] = 255;
          data[3 * (i * 256 + j) + 1] = 128;
          data[3 * (i * 256 + j) + 2] = 128;
        }
        toBeBlack = FALSE;
        if (nArray[j] == coor)
          toBeBlack = TRUE;

        if (j < 255) {
          gint jump = abs (nArray[j] - nArray[j+1]);
          if (abs (coor - nArray[j]) < jump  &&
              abs (coor - nArray[j + 1]) < jump)
            toBeBlack = TRUE;
        }
        if (toBeBlack) {
          data[3 * (i * 256 + j) + 0] = 0;
          data[3 * (i * 256 + j) + 1] = 0;
          data[3 * (i * 256 + j) + 2] = 0;
        }
      }
    }
  gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview),
                          0, 0, 256, MAX_ROUGHNESS,
                          GIMP_RGB_IMAGE,
                          data,
                          256 * 3);
}

static void
fp_range_preview_spill (GtkWidget *preview,
                        gint       type)
{
  gint   i, j;
  guchar data[256 * RANGE_HEIGHT * 3];

  for (i = 0; i < RANGE_HEIGHT; i++)
    {
      for (j = 0; j < 256; j++)
        {
          GimpRGB rgb;
          GimpHSV hsv;

          if (! ((j + 1) % 32))
            {
              data[3 * (i * 256 + j) + 0] = 255;
              data[3 * (i * 256 + j) + 1] = 128;
              data[3 * (i * 256 + j) + 2] = 128;
            }
          else
            {
              switch (type)
                {
                case BY_VAL:
                  data[3 * (i * 256 + j) + 0] = j - fpvals.offset;
                  data[3 * (i * 256 + j) + 1] = j - fpvals.offset;
                  data[3 * (i * 256 + j) + 2] = j - fpvals.offset;
                  break;

                case BY_HUE:
                  gimp_hsv_set (&hsv,
                                ((j - fpvals.offset + 256) % 256) / 255.0,
                                1.0,
                                0.5);
                  gimp_hsv_to_rgb (&hsv, &rgb);
                  gimp_rgb_get_uchar (&rgb,
                                      &data[3 * (i * 256 + j) + 0],
                                      &data[3 * (i * 256 + j) + 1],
                                      &data[3 * (i * 256 + j) + 2]);
                  break;

                case BY_SAT:
                  gimp_hsv_set (&hsv,
                                0.5,
                                ((j-(gint)fpvals.offset+256)%256) / 255.0,
                                0.5);
                  gimp_hsv_to_rgb (&hsv, &rgb);
                  gimp_rgb_get_uchar (&rgb,
                                      &data[3 * (i * 256 + j) + 0],
                                      &data[3 * (i * 256 + j) + 1],
                                      &data[3 * (i * 256 + j) + 2]);
                  break;
                }
            }
        }
    }
  gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview),
                          0, 0, 256, RANGE_HEIGHT,
                          GIMP_RGB_IMAGE,
                          data,
                          256 * 3);
}

static void
fp_create_nudge (gint *adj_array)
{
  gint left, right, middle,i;
  /* The following function was determined by trial and error */
  gdouble Steepness = pow (1 - fpvals.aliasing, 4) * .8;

  left = (fpvals.intensity_range == SHADOWS) ? 0 : fpvals.cutoff[fpvals.intensity_range - 1];
  right = fpvals.cutoff[fpvals.intensity_range];
  middle = (left + right)/2;

  if (fpvals.aliasing)
    for (i = 0; i < 256; i++)
      if (i <= middle)
        adj_array[i] = MAX_ROUGHNESS *
          fpvals.roughness * (1 + tanh (Steepness * (i - left))) / 2;
      else
        adj_array[i] = MAX_ROUGHNESS *
          fpvals.roughness * (1 + tanh (Steepness * (right - i))) / 2;
  else
    for (i = 0; i < 256; i++)
      adj_array[i] = (left <= i && i <= right)
        ? MAX_ROUGHNESS * fpvals.roughness : 0;
}

static void
fp_preview_size_allocate (GtkWidget     *widget,
                          GtkAllocation *allocation)
{
  gint  which   = fpvals.visible_frames;

  if (widget == origPreview)
    fp_render_preview (origPreview, NONEATALL, 0);
  else if (widget == curPreview)
    fp_render_preview (curPreview, CURRENT, 0);

  if (which & HUE)
    {
      if (widget == rPreview)
        fp_render_preview (rPreview,        HUE,        RED);
      else if (widget == gPreview)
        fp_render_preview (gPreview,        HUE,        GREEN);
      else if (widget == bPreview)
        fp_render_preview (bPreview,        HUE,        BLUE);
      else if (widget == cPreview)
        fp_render_preview (cPreview,        HUE,        CYAN);
      else if (widget == yPreview)
        fp_render_preview (yPreview,        HUE,        YELLOW);
      else if (widget == mPreview)
        fp_render_preview (mPreview,        HUE,        MAGENTA);
      else if (widget == centerPreview)
        fp_render_preview (centerPreview,   CURRENT,    0);
    }

  if (which & VALUE)
    {
      if (widget == lighterPreview)
        fp_render_preview (lighterPreview,  VALUE,      UP);
      else if (widget == middlePreview)
        fp_render_preview (middlePreview,   CURRENT,    0);
      else if (widget == darkerPreview)
        fp_render_preview (darkerPreview,   VALUE,      DOWN);
    }

  if (which & SATURATION)
    {
      if (widget == plusSatPreview)
        fp_render_preview (plusSatPreview,  SATURATION, UP);
      else if (widget == SatPreview)
        fp_render_preview (SatPreview,      CURRENT,    0);
      else if (widget == minusSatPreview)
        fp_render_preview (minusSatPreview, SATURATION, DOWN);
    }
}

Generated by  Doxygen 1.6.0   Back to index