Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quellcode-Bibliothek gradient-flare.c   Sprache: C

 

/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * GFlare plug-in -- lense flare effect by using custom gradients
 * Copyright (C) 1997 Eiichi Takamori <taka@ma1.sekyou.ne.jp>
 *
 * 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/>.
 *
 *
 * A fair proportion of this code was taken from GIMP & Script-fu
 * copyrighted by Spencer Kimball and Peter Mattis, and from Gradient
 * Editor copyrighted by Federico Mena Quintero. (See copyright notice
 * below) Thanks for senior GIMP hackers!!
 *
 * GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * Gradient editor module copyight (C) 1996-1997 Federico Mena Quintero
 * federico@nuclecu.unam.mx
 */


#include "config.h"

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

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <glib/gstdio.h>

#include <glib-object.h>

#include "libgimpcolor/gimpcolor-private.h"

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

#include "libgimp/stdplugins-intl.h"

/* #define DEBUG */

#ifdef DEBUG
#define DEBUG_PRINT(X) g_print X
#else
#define DEBUG_PRINT(X)
#endif

#define LUMINOSITY(PIX) (GIMP_RGB_LUMINANCE (PIX[0], PIX[1], PIX[2]) + 0.5)

#define RESPONSE_RESCAN     1

#define PLUG_IN_PROC        "plug-in-gflare"
#define PLUG_IN_BINARY      "gradient-flare"
#define PLUG_IN_ROLE        "gimp-gradient-flare"

#define GRADIENT_NAME_MAX   256
#define GRADIENT_RESOLUTION 360

#define GFLARE_NAME_MAX     256
#define GFLARE_FILE_HEADER  "GIMP GFlare 0.25"
#define SFLARE_NUM           30

#define DLG_PREVIEW_WIDTH   256
#define DLG_PREVIEW_HEIGHT  256
#define DLG_PREVIEW_MASK    GDK_EXPOSURE_MASK | \
                            GDK_BUTTON_PRESS_MASK
#define DLG_LISTBOX_WIDTH   80
#define DLG_LISTBOX_HEIGHT  40

#define ED_PREVIEW_WIDTH    256
#define ED_PREVIEW_HEIGHT   256

#define GM_PREVIEW_WIDTH    80
#define GM_PREVIEW_HEIGHT   16

#ifndef OPAQUE
#define OPAQUE              255
#endif
#define GRAY50              128

#define GRADIENT_CACHE_SIZE 32

#define CALC_GLOW   0x01
#define CALC_RAYS   0x02
#define CALC_SFLARE 0x04

typedef struct _Preview Preview;

typedef gchar   GradientName[GRADIENT_NAME_MAX];

typedef enum
{
  GF_NORMAL = 0,
  GF_ADDITION,
  GF_OVERLAY,
  GF_SCREEN,
  GF_NUM_MODES
} GFlareMode;

typedef enum
{
  GF_CIRCLE = 0,
  GF_POLYGON,
  GF_NUM_SHAPES
} GFlareShape;

typedef struct
{
  gchar        *name;
  gchar        *filename;
  gdouble       glow_opacity;
  GFlareMode    glow_mode;
  gdouble       rays_opacity;
  GFlareMode    rays_mode;
  gdouble       sflare_opacity;
  GFlareMode    sflare_mode;
  GradientName  glow_radial;
  GradientName  glow_angular;
  GradientName  glow_angular_size;
  gdouble       glow_size;
  gdouble       glow_rotation;
  gdouble       glow_hue;
  GradientName  rays_radial;
  GradientName  rays_angular;
  GradientName  rays_angular_size;
  gdouble       rays_size;
  gdouble       rays_rotation;
  gdouble       rays_hue;
  gint          rays_nspikes;
  gdouble       rays_thickness;
  GradientName  sflare_radial;
  GradientName  sflare_sizefac;
  GradientName  sflare_probability;
  gdouble       sflare_size;
  gdouble       sflare_rotation;
  gdouble       sflare_hue;
  GFlareShape   sflare_shape;
  gint          sflare_nverts;
  guint32       sflare_seed;
  gboolean      random_seed;
} GFlare;

typedef struct
{
  FILE *fp;
  gint  error;
} GFlareFile;


typedef enum
{
  PAGE_SETTINGS,
  PAGE_SELECTOR,
  PAGE_GENERAL,
  PAGE_GLOW,
  PAGE_RAYS,
  PAGE_SFLARE
} PageNum;


typedef struct
{
  gint       init;
  GFlare    *gflare;
  GtkWidget *shell;
  Preview   *preview;
  struct
  {
    gdouble x0, y0, x1, y1;
  } pwin;
  gboolean          update_preview;
  GtkWidget        *notebook;
  GtkWidget        *sizeentry;
  GtkWidget        *asupsample_frame;
  GtkListStore     *selector_list;
  GtkTreeSelection *selection;
  gint              init_params_done;
} GFlareDialog;

typedef void (* GFlareEditorCallback) (gint updated, gpointer data);

typedef struct
{
  gint                  init;
  gint                  run;
  GFlareEditorCallback  callback;
  gpointer              calldata;
  GFlare               *target_gflare;
  GFlare               *gflare;
  GtkWidget            *shell;
  Preview              *preview;
  GtkWidget            *notebook;
  PageNum               cur_page;
  GtkWidget            *polygon_entry;
  GtkWidget            *polygon_toggle;
  gint                  init_params_done;
} GFlareEditor;

typedef struct
{
  gdouble x0;
  gdouble y0;
  gdouble x1;
  gdouble y1;
} CalcBounds;

typedef struct
{
  gint     init;
  gint     type;
  GFlare  *gflare;
  gdouble  xcenter;
  gdouble  ycenter;
  gdouble  radius;
  gdouble  rotation;
  gdouble  hue;
  gdouble  vangle;
  gdouble  vlength;

  gint        glow_opacity;
  CalcBounds  glow_bounds;
  guchar     *glow_radial;
  guchar     *glow_angular;
  guchar     *glow_angular_size;
  gdouble     glow_radius;
  gdouble     glow_rotation;

  gint        rays_opacity;
  CalcBounds  rays_bounds;
  guchar     *rays_radial;
  guchar     *rays_angular;
  guchar     *rays_angular_size;
  gdouble     rays_radius;
  gdouble     rays_rotation;
  gdouble     rays_spike_mod;
  gdouble     rays_thinness;

  gint         sflare_opacity;
  GList       *sflare_list;
  guchar      *sflare_radial;
  guchar      *sflare_sizefac;
  guchar      *sflare_probability;
  gdouble      sflare_radius;
  gdouble      sflare_rotation;
  GFlareShape  sflare_shape;
  gdouble      sflare_angle;
  gdouble      sflare_factor;
} CalcParams;

/*
 * What's the difference between (structure) CalcParams and GFlare ?
 * well, radius and lengths are actual length for CalcParams where
 * they are typically 0 to 100 for GFlares, and angles are G_PI based
 * (radian) for CalcParams where they are degree for GFlares. et cetra.
 * This is because convenience for dialog processing and for calculating.
 * these conversion is taken place in calc init routines. see below.
 */


typedef struct
{
  gdouble    xcenter;
  gdouble    ycenter;
  gdouble    radius;
  CalcBounds bounds;
} CalcSFlare;

typedef struct
{
  const Babl *format;
  gint        bpp;
  gint        is_color;
  gint        has_alpha;
  gint        x, y, w, h; /* mask bounds */
} DrawableInfo;

typedef struct _GradientMenu GradientMenu;
typedef void (* GradientMenuCallback) (const gchar *gradient_name,
                                       gpointer     data);
struct _GradientMenu
{
  GtkWidget            *preview;
  GtkWidget            *combo;
  GradientMenuCallback  callback;
  gpointer              callback_data;
  GradientName          gradient_name;
};

typedef gint (* PreviewInitFunc)   (Preview  *preview,
                                    gpointer  data);
typedef void (* PreviewRenderFunc) (Preview  *preview,
                                    guchar   *buffer,
                                    gint      y,
                                    gpointer  data);
typedef void (* PreviewDeinitFunc) (Preview  *preview,
                                    gpointer  data);

struct _Preview
{
  GtkWidget         *widget;
  gint               width;
  gint               height;
  PreviewInitFunc    init_func;
  gpointer           init_data;
  PreviewRenderFunc  render_func;
  gpointer           render_data;
  PreviewDeinitFunc  deinit_func;
  gpointer           deinit_data;
  guint              timeout_tag;
  guint              idle_tag;
  gint               init_done;
  gint               current_y;
  gint               drawn_y;
  guchar            *buffer;
  guchar            *full_image_buffer;
};

typedef struct
{
  gint               tag;
  gint               got_gradients;
  gint               current_y;
  gint               drawn_y;
  PreviewRenderFunc  render_func;
  guchar            *buffer;
} PreviewIdle;

typedef struct _GradientCacheItem  GradientCacheItem;

struct _GradientCacheItem
{
  GradientCacheItem *next;
  GradientCacheItem *prev;
  GradientName       name;
  guchar             values[4 * GRADIENT_RESOLUTION];
};


typedef void (* QueryFunc) (GtkWidget *,
                            gpointer,
                            gpointer);


typedef struct _Gflare      Gflare;
typedef struct _GflareClass GflareClass;

struct _Gflare
{
  GimpPlugIn parent_instance;
};

struct _GflareClass
{
  GimpPlugInClass parent_class;
};


#define GFLARE_TYPE  (gflare_get_type ())
#define GFLARE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GFLARE_TYPE, Gflare))

GType                   gflare_get_type         (void) G_GNUC_CONST;

static GList          * gflare_query_procedures (GimpPlugIn           *plug_in);
static GimpProcedure  * gflare_create_procedure (GimpPlugIn           *plug_in,
                                                 const gchar          *name);

static GimpValueArray * gflare_run              (GimpProcedure        *procedure,
                                                 GimpRunMode           run_mode,
                                                 GimpImage            *image,
                                                 GimpDrawable        **drawables,
                                                 GimpProcedureConfig  *config,
                                                 gpointer              run_data);

static GFlare * gflare_new_with_default (const gchar *new_name);
static GFlare * gflare_dup              (const GFlare      *src,
                                         const gchar *new_name);
static void     gflare_copy             (GFlare      *dest,
                                         const GFlare      *src);
static GFlare * gflare_load             (const gchar *filename,
                                         const gchar *name);
static void     gflare_save             (GFlare      *gflare);

static gint     gflares_list_insert     (GFlare      *gflare);
static GFlare * gflares_list_lookup     (const gchar *name);
static gint     gflares_list_index      (GFlare      *gflare);
static gint     gflares_list_remove     (GFlare      *gflare);
static void     gflares_list_load_all   (void);
static void     gflares_list_free_all   (void);

static void     calc_init_params   (GFlare  *gflare,
                                    gint     calc_type,
                                    gint     xcenter,
                                    gint     ycenter,
                                    gdouble  radius,
                                    gdouble  rotation,
                                    gdouble  hue,
                                    gdouble  vangle,
                                    gdouble  vlength);
static gint     calc_init_progress (void);
static void     calc_deinit        (void);
static void     calc_glow_pix      (guchar  *dest_pix,
                                    gdouble  x,
                                    gdouble  y);
static void     calc_rays_pix      (guchar  *dest_pix,
                                    gdouble  x,
                                    gdouble  y);
static void     calc_sflare_pix    (guchar  *dest_pix,
                                    gdouble  x,
                                    gdouble  y,
                                    guchar  *src_pix);
static void     calc_gflare_pix    (guchar  *dest_pix,
                                    gdouble  x,
                                    gdouble  y,
                                    guchar  *src_pix);

static gboolean    dlg_run                 (GimpProcedure       *procedure,
                                            GimpProcedureConfig *config);
static void        dlg_preview_calc_window (void);
static void        ed_preview_calc_window  (void);
static GtkWidget * ed_mode_menu_new        (GFlareMode *mode_var);

static Preview   * preview_new          (gint               width,
                                         gint               height,
                                         PreviewInitFunc    init_func,
                                         gpointer           init_data,
                                         PreviewRenderFunc  render_func,
                                         gpointer           render_data,
                                         PreviewDeinitFunc  deinit_func,
                                         gpointer           deinit_data);
static void        preview_free         (Preview           *preview);
static void        preview_render_start (Preview           *preview);
static void        preview_render_end   (Preview           *preview);
static void        preview_rgba_to_rgb  (guchar            *dest,
                                         gint               x,
                                         gint               y,
                                         guchar            *src);

static void             gradient_menu_init    (void);
static void             gradient_menu_rescan  (void);
static GradientMenu   * gradient_menu_new     (GradientMenuCallback callback,
                                               gpointer             callback_data,
                                               const gchar         *default_gradient_name);
static void             gradient_name_copy    (gchar               *dest,
                                               const gchar         *src);
static void             gradient_name_encode  (gchar               *dest,
                                               const gchar         *src);
static void             gradient_name_decode  (gchar               *dest,
                                               const gchar         *src);
static void             gradient_init         (void);
static void             gradient_free         (void);
static gchar         ** gradient_get_list     (gint                *num_gradients);
static void             gradient_get_values   (const gchar         *gradient_name,
                                               guchar              *values,
                                               gint                 nvalues);
static void             gradient_cache_flush  (void);

/**
***     +++ Static Functions Prototypes
**/


static void plugin_do                    (GimpProcedureConfig *config);

static void plugin_do_non_asupsample     (GeglBuffer          *src_buffer,
                                          GeglBuffer          *dest_buffer);
static void plugin_do_asupsample         (GeglBuffer          *src_buffer,
                                          GeglBuffer          *dest_buffer,
                                          gint                 asupsample_max_depth,
                                          gdouble              asupsample_threshold);
static void plugin_render_func           (gdouble              x,
                                          gdouble              y,
                                          gdouble             *color,
                                          gpointer             data);
static void plugin_put_pixel_func        (gint                 ix,
                                          gint                 iy,
                                          gdouble             *color,
                                          gpointer             data);
static void plugin_progress_func         (gint                 y1,
                                          gint                 y2,
                                          gint                 curr_y,
                                          gpointer             data);

static GFlare * gflare_new               (void);
static void gflare_free                  (GFlare              *gflare);
static void gflare_read_int              (gint                *intvar,
                                          GFlareFile          *gf);
static void gflare_read_double           (gdouble             *dblvar,
                                          GFlareFile          *gf);
static void gflare_read_gradient_name    (GradientName         name,
                                          GFlareFile          *gf);
static void gflare_read_shape            (GFlareShape         *shape,
                                          GFlareFile          *gf);
static void gflare_read_mode             (GFlareMode          *mode,
                                          GFlareFile          *gf);
static void gflare_write_gradient_name   (GradientName         name,
                                          FILE                *fp);

static gint calc_sample_one_gradient     (void);
static void calc_place_sflare            (void);
static void calc_get_gradient            (guchar              *pix,
                                          guchar              *gradient,
                                          gdouble              pos);
static gdouble fmod_positive             (gdouble              x,
                                          gdouble              m);
static void calc_paint_func              (guchar              *dest,
                                          guchar              *src1,
                                          guchar              *src2,
                                          gint                 opacity,
                                          GFlareMode           mode);
static void calc_combine                 (guchar              *dest,
                                          guchar              *src1,
                                          guchar              *src2,
                                          gint                 opacity);
static void calc_addition                (guchar              *dest,
                                          guchar              *src1,
                                          guchar              *src2);
static void calc_screen                  (guchar              *dest,
                                          guchar              *src1,
                                          guchar              *src2);
static void calc_overlay                 (guchar              *dest,
                                          guchar              *src1,
                                          guchar              *src2);

static void dlg_setup_gflare             (const gchar         *gflare_name);
static void dlg_preview_realize          (GtkWidget           *widget);
static gboolean dlg_preview_handle_event (GtkWidget           *widget,
                                          GdkEvent            *event,
                                          GimpProcedureConfig *config);
static void dlg_preview_update           (void);
static gint dlg_preview_init_func        (Preview             *preview,
                                          gpointer             data);
static void dlg_preview_render_func      (Preview             *preview,
                                          guchar              *dest,
                                          gint                 y,
                                          gpointer             data);
static void dlg_preview_deinit_func      (Preview             *preview,
                                          gpointer             data);
static void dlg_make_page_settings       (GFlareDialog        *dlg,
                                          GtkWidget           *notebook,
                                          GimpProcedureConfig *config);
static void dlg_position_entry_callback  (GtkWidget           *widget,
                                          GimpProcedureConfig *config);
static void dlg_update_preview_callback  (GtkWidget           *widget,
                                          gpointer             data);
static void dlg_make_page_selector       (GFlareDialog        *dlg,
                                          GtkWidget           *notebook,
                                          GimpProcedureConfig *config);

static void dlg_selector_setup_listbox      (void);
static void dlg_selector_list_item_callback (GtkTreeSelection *selection);

static void dlg_selector_new_callback       (GtkWidget        *widget,
                                             gpointer          data);
static void dlg_selector_new_ok_callback    (GtkWidget        *widget,
                                             const gchar      *new_name,
                                             gpointer          data);

static void dlg_selector_edit_callback      (GtkWidget        *widget,
                                             gpointer          data);
static void dlg_selector_edit_done_callback (gint              updated,
                                             gpointer          data);

static void dlg_selector_copy_callback      (GtkWidget        *widget,
                                             gpointer         data);
static void dlg_selector_copy_ok_callback   (GtkWidget        *widget,
                                             const gchar      *copy_name,
                                             gpointer          data);

static void dlg_selector_delete_callback    (GtkWidget        *widget,
                                             gpointer          data);
static void dlg_selector_do_delete_callback (GtkWidget        *widget,
                                             gboolean          delete,
                                             gpointer          data);

static void ed_run                (GtkWindow            *parent,
                                   GFlare               *target_gflare,
                                   GFlareEditorCallback  callback,
                                   gpointer              calldata);
static void ed_destroy_callback   (GtkWidget            *widget,
                                   GFlareEditor         *ed);
static void ed_response           (GtkWidget            *widget,
                                   gint                  response_id,
                                   GFlareEditor         *ed);
static void ed_make_page_general  (GFlareEditor         *ed,
                                   GtkWidget            *notebook);
static void ed_make_page_glow     (GFlareEditor         *ed,
                                   GtkWidget            *notebook);
static void ed_make_page_rays     (GFlareEditor         *ed,
                                   GtkWidget            *notebook);
static void ed_make_page_sflare   (GFlareEditor         *ed,
                                   GtkWidget            *notebook);
static void ed_put_gradient_menu  (GtkWidget            *grid,
                                   gint                  x,
                                   gint                  y,
                                   const gchar          *caption,
                                   GradientMenu         *gm);
static void ed_mode_menu_callback (GtkWidget            *widget,
                                   gpointer              data);
static void ed_gradient_menu_callback (const gchar *gradient_name,
                                       gpointer     data);
static void ed_shape_radio_callback   (GtkWidget   *widget,
                                       gpointer     data);
static void ed_ientry_callback        (GtkWidget   *widget,
                                       gpointer     data);
static void ed_page_map_callback      (GtkWidget   *widget,
                                       gpointer     data);
static void ed_preview_update         (void);
static gint ed_preview_init_func      (Preview     *preview,
                                       gpointer     data);
static void ed_preview_deinit_func    (Preview     *preview,
                                       gpointer     data);
static void ed_preview_render_func    (Preview     *preview,
                                       guchar      *buffer,
                                       gint         y,
                                       gpointer     data);
static void ed_preview_render_general (guchar      *buffer,
                                       gint         y);
static void ed_preview_render_glow    (guchar      *buffer,
                                       gint         y);
static void ed_preview_render_rays    (guchar      *buffer,
                                       gint         y);
static void ed_preview_render_sflare  (guchar      *buffer,
                                       gint         y);

static gint preview_render_start_2    (Preview *preview);
static gint preview_handle_idle       (Preview *preview);

static void gm_gradient_get_list                (void);
static void gm_gradient_combo_fill              (GradientMenu   *gm,
                                                 const gchar    *default_gradient);
static void gm_gradient_combo_callback          (GtkWidget      *widget,
                                                 gpointer        data);
static void gm_preview_draw                     (GtkWidget      *preview,
                                                 const gchar    *gradient_name);
static void gm_combo_destroy_callback           (GtkWidget      *widget,
                                                 gpointer        data);

static void gradient_get_values_internal        (const gchar    *gradient_name,
                                                 guchar         *values,
                                                 gint            nvalues);
static void gradient_get_blend                  (const guchar   *fg,
                                                 const guchar   *bg,
                                                 guchar         *values,
                                                 gint            nvalues);
static void gradient_get_random                 (guchar         *values,
                                                 gint            nvalues);
static void gradient_get_default                (const gchar    *name,
                                                 guchar         *values,
                                                 gint            nvalues);
static void gradient_get_values_external        (const gchar    *gradient_name,
                                                 guchar         *values,
                                                 gint            nvalues);
static void gradient_get_values_real_external   (const gchar    *gradient_name,
                                                 guchar         *values,
                                                 gint            nvalues,
                                                 gboolean        reverse);
static GradientCacheItem *gradient_cache_lookup (const gchar    *name,
                                                 gboolean       *found);
static void gradient_cache_zorch                (void);
static void gradient_scale_entry_update_double  (GimpLabelSpin  *entry,
                                                 gdouble        *value);
static void gradient_scale_entry_update_int     (GimpLabelSpin  *entry,
                                                 gint           *value);


G_DEFINE_TYPE (Gflare, gflare, GIMP_TYPE_PLUG_IN)

GIMP_MAIN (GFLARE_TYPE)
DEFINE_STD_SET_I18N


GFlare default_gflare =
{
  NULL,         /* name */
  NULL,         /* filename */
  100,          /* glow_opacity */
  GF_NORMAL,    /* glow_mode */
  100,          /* rays_opacity */
  GF_NORMAL,    /* rays_mode */
  100,          /* sflare_opacity */
  GF_NORMAL,    /* sflare_mode */
  "%red_grad",  /* glow_radial */
  "%white",     /* glow_angular */
  "%white",     /* glow_angular_size */
  100.0,        /* glow_size */
  0.0,          /* glow_rotation */
  0.0,          /* glow_hue */
  "%white_grad",/* rays_radial */
  "%random",    /* rays_angular */
  "%random",    /* rays_angular_size */
  100.0,        /* rays_size */
  0.0,          /* rays_rotation */
  0.0,          /* rays_hue */
  40,           /* rays_nspikes */
  20.0,         /* rays_thickness */
  "%white_grad",/* sflare_radial */
  "%random",    /* sflare_sizefac */
  "%random",    /* sflare_probability */
  40.0,         /* sflare_size */
  0.0,          /* sflare_rotation */
  0.0,          /* sflare_hue */
  GF_CIRCLE,    /* sflare_shape */
  6,            /* sflare_nverts */
  0,            /* sflare_seed */
  TRUE,         /* random_seed */
};

/* These are keywords to be written to disk files specifying flares. */
/* They are not translated since we want gflare files to be compatible
   across languages. */

static const gchar *gflare_modes[] =
{
  "NORMAL",
  "ADDITION",
  "OVERLAY",
  "SCREEN"
};

static const gchar *gflare_shapes[] =
{
  "CIRCLE",
  "POLYGON"
};

/* These are for menu entries, so they are translated. */
static const gchar *gflare_menu_modes[] =
{
  N_("Normal"),
  N_("Addition"),
  N_("Overlay"),
  N_("Screen")
};

static GimpImage          *image;
static GimpDrawable       *drawable;
static DrawableInfo        dinfo;
static GFlareDialog       *dlg = NULL;
static GFlareEditor       *ed = NULL;
static GList              *gflares_list = NULL;
static gint                num_gflares  = 0;
static gchar              *gflare_path  = NULL;
static CalcParams          calc;
static GList              *gradient_menus;
static gchar             **gradient_names = NULL;
static gint                num_gradient_names = 0;
static GradientCacheItem  *gradient_cache_head  = NULL;
static gint                gradient_cache_count = 0;


static const gchar *internal_gradients[] =
{
  "%white", "%white_grad", "%red_grad", "%blue_grad", "%yellow_grad", "%random"
};

#ifdef DEBUG
static gint     get_values_external_count = 0;
static clock_t  get_values_external_clock = 0;
#endif


static void
gflare_class_init (GflareClass *klass)
{
  GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);

  plug_in_class->query_procedures = gflare_query_procedures;
  plug_in_class->create_procedure = gflare_create_procedure;
  plug_in_class->set_i18n         = STD_SET_I18N;
}

static void
gflare_init (Gflare *gflare)
{
}

static GList *
gflare_query_procedures (GimpPlugIn *plug_in)
{
  return g_list_append (NULL, g_strdup (PLUG_IN_PROC));
}

static GimpProcedure *
gflare_create_procedure (GimpPlugIn  *plug_in,
                         const gchar *name)
{
  GimpProcedure *procedure = NULL;

  if (! strcmp (name, PLUG_IN_PROC))
    {
      procedure = gimp_image_procedure_new (plug_in, name,
                                            GIMP_PDB_PROC_TYPE_PLUGIN,
                                            gflare_run, NULL, NULL);

      gimp_procedure_set_image_types (procedure, "RGB*, GRAY*");
      gimp_procedure_set_sensitivity_mask (procedure,
                                           GIMP_PROCEDURE_SENSITIVE_DRAWABLE);

      gimp_procedure_set_menu_label (procedure, _("_Gradient Flare..."));
      gimp_procedure_add_menu_path (procedure,
                                    "<Image>/Filters/Light and Shadow/[Light]");

      gimp_procedure_set_documentation (procedure,
                                        _("Produce a lense flare effect "
                                          "using gradients"),
                                        "This plug-in produces a lense flare "
                                        "effect using custom gradients. In "
                                        "interactive call, the user can edit "
                                        "their own favorite lense flare "
                                        "(GFlare) and render it. Edited "
                                        "gflare is saved automatically to "
                                        "the folder in gflare-path, if it is "
                                        "defined in gimprc. In "
                                        "non-interactive call, the user can "
                                        "only render one of GFlare "
                                        "which has been stored in "
                                        "gflare-path already.",
                                        name);
      gimp_procedure_set_attribution (procedure,
                                      "Eiichi Takamori",
                                      "Eiichi Takamori, and a lot of GIMP people",
                                      "1997");

      gimp_procedure_add_string_argument (procedure, "gflare-name",
                                          "GFlare name",
                                          "Name of the GFlare to render",
                                          "Default",
                                          G_PARAM_READWRITE);

      gimp_procedure_add_int_argument (procedure, "center-x",
                                       "Center X",
                                       "X coordinate of center of GFlare",
                                       -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, 128,
                                       G_PARAM_READWRITE);

      gimp_procedure_add_int_argument (procedure, "center-y",
                                       "Center Y",
                                       "Y coordinate of center of GFlare",
                                       -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE, 128,
                                       G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "radius",
                                          _("Radi_us"),
                                          _("Radius of GFlare (pixel)"),
                                          1, GIMP_MAX_IMAGE_SIZE, 100,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "rotation",
                                          _("Ro_tation"),
                                          _("Rotation of GFlare (degree)"),
                                          0, 360, 0,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "hue",
                                          _("_Hue rotation"),
                                          _("Hue rotation of GFlare (degree)"),
                                          0, 360, 0,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "vector-angle",
                                          _("Vector _angle"),
                                          _("Vector angle for second flares (degree)"),
                                          0, 360, 60,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "vector-length",
                                          _("Vector len_gth"),
                                          _("Vector length for second flares "
                                          "(percentage of Radius)"),
                                          0, 10000, 400,
                                          G_PARAM_READWRITE);

      gimp_procedure_add_boolean_argument (procedure, "use-asupsample",
                                           _("Ada_ptive supersampling"),
                                           _("Use adaptive supersampling while rendering"),
                                           FALSE,
                                           G_PARAM_READWRITE);

      gimp_procedure_add_int_argument (procedure, "asupsample-max-depth",
                                       _("_Max depth"),
                                       _("Max depth for adaptive supersampling"),
                                       0, 10, 3,
                                       G_PARAM_READWRITE);

      gimp_procedure_add_double_argument (procedure, "asupsample-threshold",
                                          _("Threshol_d"),
                                          _("Threshold for adaptive supersampling"),
                                          0.0, 1.0, 0.2,
                                          G_PARAM_READWRITE);
    }

  return procedure;
}

static GimpValueArray *
gflare_run (GimpProcedure        *procedure,
            GimpRunMode           run_mode,
            GimpImage            *_image,
            GimpDrawable        **drawables,
            GimpProcedureConfig  *config,
            gpointer              run_data)
{
  gchar *path;

  gegl_init (NULL, NULL);

  image = _image;

  if (gimp_core_object_array_get_length ((GObject **) drawables) != 1)
    {
      GError *error = NULL;

      g_set_error (&error, GIMP_PLUG_IN_ERROR, 0,
                   _("Procedure '%s' only works with one drawable."),
                   gimp_procedure_get_name (procedure));

      return gimp_procedure_new_return_values (procedure,
                                               GIMP_PDB_CALLING_ERROR,
                                               error);
    }
  else
    {
      drawable = drawables[0];
    }

  dinfo.is_color  = gimp_drawable_is_rgb (drawable);
  dinfo.has_alpha = gimp_drawable_has_alpha (drawable);

  if (dinfo.is_color)
    {
      if (dinfo.has_alpha)
        dinfo.format = babl_format ("R'G'B'A u8");
      else
        dinfo.format = babl_format ("R'G'B' u8");
    }
  else
    {
      if (dinfo.has_alpha)
        dinfo.format = babl_format ("Y'A u8");
      else
        dinfo.format = babl_format ("Y u8");
    }

  dinfo.bpp = babl_format_get_bytes_per_pixel (dinfo.format);

  if (! gimp_drawable_mask_intersect (drawable,
                                      &dinfo.x, &dinfo.y, &dinfo.w, &dinfo.h))
    {
      return gimp_procedure_new_return_values (procedure,
                                               GIMP_PDB_SUCCESS,
                                               NULL);
    }

  /*
   *    Start gradient caching
   */


  gradient_init ();

  /*
   *    Parse gflare path from gimprc and load gflares
   */


  path = gimp_gimprc_query ("gflare-path");
  if (path)
    {
      gflare_path = g_filename_from_utf8 (path, -1, NULL, NULL, NULL);
      g_free (path);
    }
  else
    {
      GFile *gimprc    = gimp_directory_file ("gimprc", NULL);
      gchar *full_path = gimp_config_build_data_path ("gflare");
      gchar *esc_path  = g_strescape (full_path, NULL);
      g_free (full_path);

      g_message (_("No %s in gimprc:\n"
                   "You need to add an entry like\n"
                   "(%s \"%s\")\n"
                   "to your %s file."),
                 "gflare-path", "gflare-path",
                 esc_path, gimp_file_get_utf8_name (gimprc));

      g_object_unref (gimprc);
      g_free (esc_path);
    }

  gflares_list_load_all ();

  if (run_mode == GIMP_RUN_INTERACTIVE && ! dlg_run (procedure, config))
    return gimp_procedure_new_return_values (procedure,
                                             GIMP_PDB_CANCEL,
                                             NULL);

  if (gimp_drawable_is_rgb  (drawable) ||
      gimp_drawable_is_gray (drawable))
    {
      gimp_progress_init (_("Gradient Flare"));

      plugin_do (config);

      if (run_mode != GIMP_RUN_NONINTERACTIVE)
        gimp_displays_flush ();
    }
  else
    {
      GError *error =
        g_error_new_literal (0, 0,
                             _("Cannot operate on indexed color images."));

      return gimp_procedure_new_return_values (procedure,
                                               GIMP_PDB_EXECUTION_ERROR,
                                               error);
    }

  /*
   *    Deinitialization
   */

  gradient_free ();

  return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL);
}

static void
plugin_do (GimpProcedureConfig *config)
{
  GeglBuffer *src_buffer;
  GeglBuffer *dest_buffer;
  GFlare     *gflare;
  gchar      *gflare_name;
  gint        xcenter;
  gint        ycenter;
  gdouble     radius;
  gdouble     rotation;
  gdouble     hue;
  gdouble     vangle;
  gdouble     vlength;
  gboolean    use_asupsample;
  gint        asupsample_max_depth;
  gdouble     asupsample_threshold;

  g_object_get (config,
                "gflare-name",          &gflare_name,
                "center-x",             &xcenter,
                "center-y",             &ycenter,
                "radius",               &radius,
                "rotation",             &rotation,
                "hue",                  &hue,
                "vector-angle",         &vangle,
                "vector-length",        &vlength,
                "use-asupsample",       &use_asupsample,
                "asupsample-max-depth", &asupsample_max_depth,
                "asupsample-threshold", &asupsample_threshold,
                NULL);

  gflare = gflares_list_lookup (gflare_name);
  if (gflare == NULL)
    {
      /* FIXME */
      g_warning ("Not found %s\n", gflare_name);
      g_free (gflare_name);
      return;
    }
  g_free (gflare_name);

  /* Initialize calc params and gradients */
  calc_init_params (gflare, CALC_GLOW | CALC_RAYS | CALC_SFLARE,
                    xcenter, ycenter,
                    radius, rotation, hue,
                    vangle, vlength);
  while (calc_init_progress ()) ;

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

  /* Render it ! */
  if (use_asupsample)
    plugin_do_asupsample (src_buffer, dest_buffer, asupsample_max_depth, asupsample_threshold);
  else
    plugin_do_non_asupsample (src_buffer, dest_buffer);

  g_object_unref (src_buffer);
  g_object_unref (dest_buffer);

  gimp_progress_update (1.0);

  /* Clean up */
  calc_deinit ();

  gimp_drawable_merge_shadow (drawable, TRUE);
  gimp_drawable_update (drawable, dinfo.x, dinfo.y, dinfo.w, dinfo.h);
}

/* these routines should be almost rewritten anyway */

static void
plugin_do_non_asupsample (GeglBuffer   *src_buffer,
                          GeglBuffer   *dest_buffer)
{
  GeglBufferIterator *iter;
  gint                progress;
  gint                max_progress;

  progress = 0;
  max_progress = dinfo.w * dinfo.h;

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

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

  while (gegl_buffer_iterator_next (iter))
    {
      const guchar *src_row  = iter->items[0].data;
      guchar       *dest_row = iter->items[1].data;
      gint          row, y;

      for (row = 0, y = iter->items[0].roi.y;
           row < iter->items[0].roi.height;
           row++, y++)
        {
          const guchar *src  = src_row;
          guchar       *dest = dest_row;
          gint          col, x;

          for (col = 0, x = iter->items[0].roi.x;
               col < iter->items[0].roi.width;
               col++, x++)
            {
              guchar  src_pix[4];
              guchar  dest_pix[4];
              gint    b;

              for (b = 0; b < 3; b++)
                src_pix[b] = dinfo.is_color ? src[b] : src[0];

              src_pix[3] = dinfo.has_alpha ? src[dinfo.bpp - 1] : OPAQUE;

              calc_gflare_pix (dest_pix, x, y, src_pix);

              if (dinfo.is_color)
                {
                  for (b = 0; b < 3; b++)
                    dest[b] = dest_pix[b];
                }
              else
                {
                  dest[0] = LUMINOSITY (dest_pix);
                }

              if (dinfo.has_alpha)
                dest[dinfo.bpp - 1] = dest_pix[3];

              src  += dinfo.bpp;
              dest += dinfo.bpp;
            }

          src_row  += dinfo.bpp * iter->items[0].roi.width;
          dest_row += dinfo.bpp * iter->items[1].roi.width;
        }

      /* Update progress */
      progress += iter->items[0].roi.width * iter->items[0].roi.height;
      gimp_progress_update ((double) progress / (double) max_progress);
    }
}

static void
plugin_do_asupsample (GeglBuffer *src_buffer,
                      GeglBuffer *dest_buffer,
                      gint        asupsample_max_depth,
                      gdouble     asupsample_threshold)
{
  gimp_adaptive_supersample_area (dinfo.x, dinfo.y,
                                  dinfo.x + dinfo.w - 1, dinfo.y + dinfo.h - 1,
                                  asupsample_max_depth,
                                  asupsample_threshold,
                                  plugin_render_func,
                                  src_buffer,
                                  plugin_put_pixel_func,
                                  dest_buffer,
                                  plugin_progress_func,
                                  NULL);
}

/*
  Adaptive supersampling callback functions

  These routines may look messy, since adaptive supersampling needs
  pixel values in `double' (from 0.0 to 1.0) but calc_*_pix () returns
  guchar values. */


static void
plugin_render_func (gdouble   x,
                    gdouble   y,
                    gdouble  *color,
                    gpointer  data)
{
  GeglBuffer *src_buffer = data;
  guchar      src_pix[4];
  guchar      flare_pix[4];
  guchar      src[4];
  gint        b;
  gint        ix, iy;

  /* translate (0.5, 0.5) before convert to `int' so that it can surely
     point the center of pixel */

  ix = floor (x + 0.5);
  iy = floor (y + 0.5);

  gegl_buffer_sample (src_buffer, ix, iy, NULL, src, dinfo.format,
                      GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);

  for (b = 0; b < 3; b++)
    src_pix[b] = dinfo.is_color ? src[b] : src[0];
  src_pix[3] = dinfo.has_alpha ? src[dinfo.bpp - 1] : OPAQUE;

  calc_gflare_pix (flare_pix, x, y, src_pix);

  color[0] = flare_pix[0] / 255.0;
  color[1] = flare_pix[1] / 255.0;
  color[2] = flare_pix[2] / 255.0;
  color[3] = flare_pix[3] / 255.0;
}

static void
plugin_put_pixel_func (gint      ix,
                       gint      iy,
                       gdouble  *color,
                       gpointer  data)
{
  GeglBuffer *dest_buffer = data;
  guchar      dest[4];

  if (dinfo.is_color)
    {
      dest[0] = color[0] * 255;
      dest[1] = color[1] * 255;
      dest[2] = color[2] * 255;
    }
  else
    {
      guchar rgb[3] = {color[0] * 255, color[2] * 255, color[3] * 255};

      dest[0] = GIMP_RGB_LUMINANCE (rgb[0], rgb[1], rgb[2]);
    }

  if (dinfo.has_alpha)
    dest[dinfo.bpp - 1] = color[3] * 255;

  gegl_buffer_set (dest_buffer, GEGL_RECTANGLE (ix, iy, 1, 1), 0,
                   dinfo.format, dest, GEGL_AUTO_ROWSTRIDE);
}

static void
plugin_progress_func (gint     y1,
                      gint     y2,
                      gint     curr_y,
                      gpointer data)
{
  gimp_progress_update ((double) curr_y / (double) (y2 - y1));
}

/*************************************************************************/
/**                                                                     **/
/**             +++ GFlare Routines                                     **/
/**                                                                     **/
/*************************************************************************/

/*
 *      These code are more or less based on Quartic's gradient.c,
 *      other gimp sources, and script-fu.
 */


static GFlare *
gflare_new (void)
{
  GFlare *gflare = g_new0 (GFlare, 1);

  gflare->name     = NULL;
  gflare->filename = NULL;

  return gflare;
}

static GFlare *
gflare_new_with_default (const gchar *new_name)
{
  return gflare_dup (&default_gflare, new_name);
}

static GFlare *
gflare_dup (const GFlare *src,
            const gchar  *new_name)
{
  GFlare *dest = g_new0 (GFlare, 1);

  *dest = *src;

  dest->name = g_strdup (new_name);
  dest->filename = NULL;

  return dest;
}

static void
gflare_copy (GFlare       *dest,
             const GFlare *src)
{
  gchar *name, *filename;

  name = dest->name;
  filename = dest->filename;

  *dest = *src;

  dest->name = name;
  dest->filename =filename;
}


static void
gflare_free (GFlare *gflare)
{
  g_return_if_fail (gflare != NULL);

  g_free (gflare->name);
  g_free (gflare->filename);
  g_free (gflare);
}

GFlare *
gflare_load (const gchar *filename,
             const gchar *name)
{
  FILE          *fp;
  GFlareFile    *gf;
  GFlare        *gflare;
  gchar         header[256];

  g_return_val_if_fail (filename != NULL, NULL);

  fp = g_fopen (filename, "rb");
  if (!fp)
    {
      g_message (_("Failed to open GFlare file '%s': %s"),
                  gimp_filename_to_utf8 (filename), g_strerror (errno));
      return NULL;
    }

  if (fgets (header, sizeof(header), fp) == NULL ||
      ! g_str_has_prefix (header, GFLARE_FILE_HEADER))
    {
      g_warning (_("'%s' is not a valid GFlare file."),
                  gimp_filename_to_utf8 (filename));
      fclose (fp);
      return NULL;
    }

  gf = g_new (GFlareFile, 1);
  gf->fp = fp;
  gf->error = FALSE;

  gflare = gflare_new ();
  gflare->name = g_strdup (name);
  gflare->filename = g_strdup (filename);

  gflare_read_double   (&gflare->glow_opacity, gf);
  gflare_read_mode     (&gflare->glow_mode, gf);
  gflare_read_double   (&gflare->rays_opacity, gf);
  gflare_read_mode     (&gflare->rays_mode, gf);
  gflare_read_double   (&gflare->sflare_opacity, gf);
  gflare_read_mode     (&gflare->sflare_mode, gf);

  gflare_read_gradient_name (gflare->glow_radial, gf);
  gflare_read_gradient_name (gflare->glow_angular, gf);
  gflare_read_gradient_name (gflare->glow_angular_size, gf);
  gflare_read_double   (&gflare->glow_size, gf);
  gflare_read_double   (&gflare->glow_rotation, gf);
  gflare_read_double   (&gflare->glow_hue, gf);

  gflare_read_gradient_name (gflare->rays_radial, gf);
  gflare_read_gradient_name (gflare->rays_angular, gf);
  gflare_read_gradient_name (gflare->rays_angular_size, gf);
  gflare_read_double   (&gflare->rays_size, gf);
  gflare_read_double   (&gflare->rays_rotation, gf);
  gflare_read_double   (&gflare->rays_hue, gf);
  gflare_read_int      (&gflare->rays_nspikes, gf);
  gflare_read_double   (&gflare->rays_thickness, gf);

  gflare_read_gradient_name (gflare->sflare_radial, gf);
  gflare_read_gradient_name (gflare->sflare_sizefac, gf);
  gflare_read_gradient_name (gflare->sflare_probability, gf);
  gflare_read_double   (&gflare->sflare_size, gf);
  gflare_read_double   (&gflare->sflare_hue, gf);
  gflare_read_double   (&gflare->sflare_rotation, gf);
  gflare_read_shape    (&gflare->sflare_shape, gf);
  gflare_read_int      (&gflare->sflare_nverts, gf);
  gflare_read_int      ((gint *) &gflare->sflare_seed, gf);

  if (gflare->sflare_seed == 0)
    gflare->sflare_seed = g_random_int();

  fclose (gf->fp);

  if (gf->error)
    {
      g_warning (_("invalid formatted GFlare file: %s\n"), filename);
      g_free (gflare);
      g_free (gf);
      return NULL;
    }

  g_free (gf);

  return gflare;
}

static void
gflare_read_int (gint       *intvar,
                 GFlareFile *gf)
{
  if (gf->error)
    return;

  if (fscanf (gf->fp, "%d", intvar) != 1)
    gf->error = TRUE;
}

static void
gflare_read_double (gdouble    *dblvar,
                    GFlareFile *gf)
{
  gchar buf[31];

  if (gf->error)
    return;

  if (fscanf (gf->fp, "%30s", buf) == 1)
    *dblvar = g_ascii_strtod (buf, NULL);
  else
    gf->error = TRUE;
}

static void
gflare_read_gradient_name (GradientName  name,
                           GFlareFile   *gf)
{
  gchar tmp[1024], dec[1024];

  if (gf->error)
    return;

  /* FIXME: this is buggy */

  if (fscanf (gf->fp, "%1023s", tmp) == 1)
    {
      /* @GRADIENT_NAME */
      gradient_name_decode (dec, tmp);
      gradient_name_copy (name, dec);
    }
  else
    gf->error = TRUE;
}

static void
gflare_read_shape (GFlareShape *shape,
                   GFlareFile  *gf)
{
  gchar tmp[1024];
  gint  i;

  if (gf->error)
    return;

  if (fscanf (gf->fp, "%1023s", tmp) == 1)
    {
      for (i = 0; i < GF_NUM_SHAPES; i++)
        if (strcmp (tmp, gflare_shapes[i]) == 0)
          {
            *shape = i;
            return;
          }
    }
  gf->error = TRUE;
}

static void
gflare_read_mode (GFlareMode *mode,
                  GFlareFile *gf)
{
  gchar tmp[1024];
  gint  i;

  if (gf->error)
    return;

  if (fscanf (gf->fp, "%1023s", tmp) == 1)
    {
      for (i = 0; i < GF_NUM_MODES; i++)
        if (strcmp (tmp, gflare_modes[i]) == 0)
          {
            *mode = i;
            return;
          }
    }
  gf->error = TRUE;
}

static void
gflare_save (GFlare *gflare)
{
  FILE  *fp;
  gchar *path;
  gchar  buf[3][G_ASCII_DTOSTR_BUF_SIZE];
  static gboolean message_ok = FALSE;

  if (gflare->filename == NULL)
    {
      GList *list;

      if (gflare_path == NULL)
        {
          if (! message_ok)
            {
              GFile *gimprc = gimp_directory_file ("gimprc", NULL);
              GFile *dir    = gimp_directory_file ("gflare", NULL);
              gchar *gflare_dir;

              gflare_dir =
                g_strescape ("${gimp_dir}" G_DIR_SEPARATOR_S "gflare", NULL);

              g_message (_("GFlare '%s' is not saved. If you add a new entry "
                           "in '%s', like:\n"
                           "(gflare-path \"%s\")\n"
                           "and make a folder '%s', then you can save "
                           "your own GFlares into that folder."),
                         gflare->name,
                         gimp_file_get_utf8_name (gimprc),
                         gflare_dir,
                         gimp_file_get_utf8_name (dir));

              g_object_unref (gimprc);
              g_object_unref (dir);
              g_free (gflare_dir);

              message_ok = TRUE;
            }

          return;
        }

      list = gimp_path_parse (gflare_path, 256, FALSE, NULL);
      path = gimp_path_get_user_writable_dir (list);
      gimp_path_free (list);

      if (! path)
        path = g_strdup (gimp_directory ());

      gflare->filename = g_build_filename (path, gflare->name, NULL);

      g_free (path);
    }

  fp = g_fopen (gflare->filename, "wb");
  if (!fp)
    {
      g_message (_("Failed to write GFlare file '%s': %s"),
                  gimp_filename_to_utf8 (gflare->filename), g_strerror (errno));
      return;
    }

  fprintf (fp, "%s\n", GFLARE_FILE_HEADER);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->glow_opacity);
  fprintf (fp, "%s %s\n", buf[0], gflare_modes[gflare->glow_mode]);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_opacity);
  fprintf (fp, "%s %s\n", buf[0], gflare_modes[gflare->rays_mode]);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->sflare_opacity);
  fprintf (fp, "%s %s\n", buf[0], gflare_modes[gflare->sflare_mode]);

  gflare_write_gradient_name (gflare->glow_radial, fp);
  gflare_write_gradient_name (gflare->glow_angular, fp);
  gflare_write_gradient_name (gflare->glow_angular_size, fp);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->glow_size);
  g_ascii_dtostr (buf[1],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->glow_rotation);
  g_ascii_dtostr (buf[2],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->glow_hue);
  fprintf (fp, "%s %s %s\n", buf[0], buf[1], buf[2]);

  gflare_write_gradient_name (gflare->rays_radial, fp);
  gflare_write_gradient_name (gflare->rays_angular, fp);
  gflare_write_gradient_name (gflare->rays_angular_size, fp);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_size);
  g_ascii_dtostr (buf[1],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_rotation);
  g_ascii_dtostr (buf[2],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_hue);
  fprintf (fp, "%s %s %s\n", buf[0], buf[1], buf[2]);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->rays_thickness);
  fprintf (fp, "%d %s\n", gflare->rays_nspikes, buf[0]);

  gflare_write_gradient_name (gflare->sflare_radial, fp);
  gflare_write_gradient_name (gflare->sflare_sizefac, fp);
  gflare_write_gradient_name (gflare->sflare_probability, fp);
  g_ascii_dtostr (buf[0],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->sflare_size);
  g_ascii_dtostr (buf[1],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->sflare_rotation);
  g_ascii_dtostr (buf[2],
                  G_ASCII_DTOSTR_BUF_SIZE, gflare->sflare_hue);
  fprintf (fp, "%s %s %s\n", buf[0], buf[1], buf[2]);
  fprintf (fp, "%s %d %d\n",
           gflare_shapes[gflare->sflare_shape],
           gflare->sflare_nverts, gflare->sflare_seed);

  fclose (fp);
}

static void
gflare_write_gradient_name (GradientName  name,
                            FILE         *fp)
{
  gchar enc[1024];

  /* @GRADIENT_NAME */

  /* encode white spaces and control characters (if any) */
  gradient_name_encode (enc, name);

  fprintf (fp, "%s\n", enc);
}

/*************************************************************************/
/**                                                                     **/
/**             +++ GFlares List                                        **/
/**                                                                     **/
/*************************************************************************/

static gint
gflare_compare (const GFlare *flare1,
                const GFlare *flare2)
{
  return strcmp (flare1->name, flare2->name);
}

static gint
gflares_list_insert (GFlare *gflare)
{
  num_gflares++;
  gflares_list = g_list_insert_sorted (gflares_list, gflare,
                                       (GCompareFunc) gflare_compare);
  return gflares_list_index (gflare);
}

static gint
gflare_compare_name (const GFlare *flare,
                     const gchar  *name)
{
  return strcmp (flare->name, name);
}

static GFlare *
gflares_list_lookup (const gchar *name)
{
  GList *llink;
  llink = g_list_find_custom (gflares_list, name,
                              (GCompareFunc) gflare_compare_name);
  return (llink) ? llink->data : NULL;
}

static gint
gflares_list_index (GFlare *gflare)
{
  return g_list_index (gflares_list, gflare);
}

static gint
gflares_list_remove (GFlare *gflare)
{
  GList         *tmp;
  gint          n;

  n = 0;
  tmp = gflares_list;
  while (tmp)
    {
      if (tmp->data == gflare)
        {
          /* Found! */
          num_gflares--;
          gflares_list = g_list_remove (gflares_list, gflare);
          return n;
        }
      tmp = tmp->next;
      n++;
    }
  return -1;
}

/*
 * Load all gflares, which are founded in gflare-path-list, into gflares_list.
 */

static void
gflares_list_load_all (void)
{
  GList *path;
  GList *list;

  /*  Make sure to clear any existing gflares  */
  gflares_list_free_all ();

  path = gimp_config_path_expand_to_files (gflare_path, NULL);

  for (list = path; list; list = g_list_next (list))
    {
      GFileEnumerator *enumerator;

      enumerator = g_file_enumerate_children (list->data,
                                              G_FILE_ATTRIBUTE_STANDARD_NAME ","
                                              G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
                                              G_FILE_ATTRIBUTE_STANDARD_TYPE,
                                              G_FILE_QUERY_INFO_NONE,
                                              NULL, NULL);

      if (enumerator)
        {
          GFileInfo *info;

          while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)))
            {
              GFileType file_type = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE);

              if (file_type == G_FILE_TYPE_REGULAR &&
                  ! g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN))
                {
                  GFlare *gflare;
                  GFile  *child;
                  gchar  *filename;
                  gchar  *basename;

                  child = g_file_enumerator_get_child (enumerator, info);

                  filename = g_file_get_path (child);
                  basename = g_file_get_basename (child);

                  gflare = gflare_load (filename, basename);

                  g_free (filename);
                  g_free (basename);

                  if (gflare)
                    gflares_list_insert (gflare);

                  g_object_unref (child);
                }

              g_object_unref (info);
            }

          g_object_unref (enumerator);
        }
    }

  g_list_free_full (path, (GDestroyNotify) g_object_unref);
}

static void
gflares_list_free_all (void)
{
  g_list_free_full (gflares_list, (GDestroyNotify) gflare_free);
  gflares_list = NULL;
}

/*************************************************************************/
/**                                                                     **/
/**             +++ Calculator                                          **/
/**                                                                     **/
/*************************************************************************/


/*
 * These routines calculates pixel values of particular gflare, at
 * specified point.  The client which wants to get benefit from these
 * calculation routines must call calc_init_params() first, and
 * iterate calling calc_init_progress() until it returns FALSE. and
 * must call calc_deinit() when job is done.
 */


static void
calc_init_params (GFlare  *gflare,
                  gint     calc_type,
                  gint     xcenter,
                  gint     ycenter,
                  gdouble  radius,
                  gdouble  rotation,
                  gdouble  hue,
                  gdouble  vangle,
                  gdouble  vlength)
{
  calc.type            = calc_type;
  calc.gflare          = gflare;
  calc.xcenter         = xcenter;
  calc.ycenter         = ycenter;
  calc.radius          = radius;
  calc.rotation        = rotation * G_PI / 180.0;
  calc.hue             = hue;
  calc.vangle          = vangle * G_PI / 180.0;
  calc.vlength         = radius * vlength / 100.0;
  calc.glow_radius     = radius * gflare->glow_size / 100.0;
  calc.rays_radius     = radius * gflare->rays_size / 100.0;
  calc.sflare_radius   = radius * gflare->sflare_size / 100.0;
  calc.glow_rotation   = (rotation + gflare->glow_rotation) * G_PI / 180.0;
  calc.rays_rotation   = (rotation + gflare->rays_rotation) * G_PI / 180.0;
  calc.sflare_rotation = (rotation + gflare->sflare_rotation) * G_PI / 180.0;
  calc.glow_opacity    = gflare->glow_opacity * 255 / 100.0;
  calc.rays_opacity    = gflare->rays_opacity * 255 / 100.0;
  calc.sflare_opacity  = gflare->sflare_opacity * 255 / 100.0;

  calc.glow_bounds.x0 = calc.xcenter - calc.glow_radius - 0.1;
  calc.glow_bounds.x1 = calc.xcenter + calc.glow_radius + 0.1;
  calc.glow_bounds.y0 = calc.ycenter - calc.glow_radius - 0.1;
  calc.glow_bounds.y1 = calc.ycenter + calc.glow_radius + 0.1;
  calc.rays_bounds.x0 = calc.xcenter - calc.rays_radius - 0.1;
  calc.rays_bounds.x1 = calc.xcenter + calc.rays_radius + 0.1;
  calc.rays_bounds.y0 = calc.ycenter - calc.rays_radius - 0.1;
  calc.rays_bounds.y1 = calc.ycenter + calc.rays_radius + 0.1;

  /* Thanks to Marcelo Malheiros for this algorithm */
  calc.rays_thinness  = log (gflare->rays_thickness / 100.0) / log(0.8);

  calc.rays_spike_mod = 1.0 / (2 * gflare->rays_nspikes);

  /*
    Initialize part of sflare
    The rest will be initialized in calc_sflare()
   */

  calc.sflare_list = NULL;
  calc.sflare_shape = gflare->sflare_shape;
  if (calc.sflare_shape == GF_POLYGON)
    {
      calc.sflare_angle = 2 * G_PI / (2 * gflare->sflare_nverts);
      calc.sflare_factor = 1.0 / cos (calc.sflare_angle);
    }

  calc.glow_radial        = NULL;
  calc.glow_angular       = NULL;
  calc.glow_angular_size  = NULL;
  calc.rays_radial        = NULL;
  calc.rays_angular       = NULL;
  calc.rays_angular_size  = NULL;
  calc.sflare_radial      = NULL;
  calc.sflare_sizefac     = NULL;
  calc.sflare_probability = NULL;

  calc.init = TRUE;
}

static int
calc_init_progress (void)
{
  if (calc_sample_one_gradient ())
    return TRUE;
  calc_place_sflare ();
  return FALSE;
}

/*
   Store samples of gradient into an array
   this routine is called during Calc initialization
   this code is very messy... :( */

static int
calc_sample_one_gradient (void)
{
  static struct
  {
    guchar **values;
    gint     name_offset;
    gint     hue_offset;
    gint     gray;
  } table[] = {
    {
      &calc.glow_radial,
      G_STRUCT_OFFSET (GFlare, glow_radial),
      G_STRUCT_OFFSET (GFlare, glow_hue),
      FALSE
    },
    {
      &calc.glow_angular,
      G_STRUCT_OFFSET (GFlare, glow_angular),
      0,
      FALSE
    },
    {
      &calc.glow_angular_size,
      G_STRUCT_OFFSET (GFlare, glow_angular_size),
      0,
      TRUE
    },
    {
      &calc.rays_radial,
      G_STRUCT_OFFSET (GFlare, rays_radial),
      G_STRUCT_OFFSET (GFlare, rays_hue),
      FALSE
    },
    {
      &calc.rays_angular,
      G_STRUCT_OFFSET (GFlare, rays_angular),
      0,
      FALSE
    },
    {
      &calc.rays_angular_size,
      G_STRUCT_OFFSET (GFlare, rays_angular_size),
      0,
      TRUE
    },
    {
      &calc.sflare_radial,
      G_STRUCT_OFFSET (GFlare, sflare_radial),
      G_STRUCT_OFFSET (GFlare, sflare_hue),
      FALSE
    },
    {
      &calc.sflare_sizefac,
      G_STRUCT_OFFSET (GFlare, sflare_sizefac),
      0,
      TRUE
    },
    {
      &calc.sflare_probability,
      G_STRUCT_OFFSET (GFlare, sflare_probability),
      0,
      TRUE
    }
  };
  GFlare        *gflare = calc.gflare;
  GradientName  *grad_name;
  guchar        *gradient;
  gdouble        hue_deg;
  gint           i, j, hue;
  const Babl    *to_hsv = babl_fish ("R'G'B'A u8", "HSVA float");
  const Babl    *to_rgb = babl_fish ("HSVA float", "R'G'B'A u8");

  for (i = 0; i < G_N_ELEMENTS (table); i++)
    {
      if (*(table[i].values) == NULL)
        {
          /* @GRADIENT_NAME */
          grad_name = (GradientName *) ((char*) gflare + table[i].name_offset);
          gradient = *(table[i].values) = g_new (guchar, 4 * GRADIENT_RESOLUTION);
          gradient_get_values (*grad_name, gradient, GRADIENT_RESOLUTION);

          /*
           * Do hue rotation, if needed
           */


          if (table[i].hue_offset != 0)
            {
              hue_deg = calc.hue + *(gdouble *) ((char*) gflare + table[i].hue_offset);
              hue = (gint) (hue_deg / 360.0 * 256.0) % 256;
              if (hue < 0)
                hue += 256;
              g_assert (0 <= hue && hue < 256);

              if (hue > 0)
                {
                  gfloat *hsva;

                  hsva = g_new0 (gfloat, sizeof (gfloat) * (GRADIENT_RESOLUTION * 4));
                  babl_process (to_hsv, gradient, hsva, GRADIENT_RESOLUTION);

                  for (j = 0; j < GRADIENT_RESOLUTION; j++)
                    {
                      hsva[j * 4] = (hsva[j * 4] + ((gdouble) hue / 255.0));

                      if (hsva[j * 4] > 1.0)
                        hsva[j * 4] -= 1.0;
                    }

                  babl_process (to_rgb, hsva, gradient, GRADIENT_RESOLUTION);

                  g_free (hsva);
                }
            }

          /*
           *    Grayfy gradient, if needed
           */


          if (table[i].gray)
            {
              for (j = 0; j < GRADIENT_RESOLUTION; j++)
                /* the first byte is enough */
                gradient[j*4] = LUMINOSITY ((gradient + j*4));

            }

          /* sampling of one gradient is done */
          return TRUE;
        }
    }
  return FALSE;
}

static void
calc_place_sflare (void)
{
  GFlare        *gflare;
  CalcSFlare    *sflare;
  gdouble       prob[GRADIENT_RESOLUTION];
  gdouble       sum, sum2;
  gdouble       pos;
  gdouble       rnd, sizefac;
  int           n;
  int           i;
  GRand        *gr;

  gr = g_rand_new ();
  if ((calc.type & CALC_SFLARE) == 0)
    return;

  gflare = calc.gflare;

  /*
    Calc cumulative probability
    */


  sum = 0.0;
  for (i = 0; i < GRADIENT_RESOLUTION; i++)
    {
      /* probability gradient was grayfied already */
      prob[i] = calc.sflare_probability[i*4];
      sum += prob[i];
    }

  if (sum == 0.0)
    sum = 1.0;

  sum2 = 0;
  for (i = 0; i < GRADIENT_RESOLUTION; i++)
    {
      sum2 += prob[i];          /* cumulation */
      prob[i] = sum2 / sum;
    }

  g_rand_set_seed (gr, gflare->sflare_seed);

  for (n = 0; n < SFLARE_NUM; n++)
    {
      sflare = g_new (CalcSFlare, 1);
      rnd = g_rand_double (gr);
      for (i = 0; i < GRADIENT_RESOLUTION; i++)
        if (prob[i] >= rnd)
          break;
      if (i >= GRADIENT_RESOLUTION)
        i = GRADIENT_RESOLUTION - 1;

      /* sizefac gradient was grayfied already */
      sizefac = calc.sflare_sizefac[i*4] / 255.0;
      sizefac = pow (sizefac, 5.0);

      pos = (double) (i - GRADIENT_RESOLUTION / 2) / GRADIENT_RESOLUTION;
      sflare->xcenter = calc.xcenter + cos (calc.vangle) * calc.vlength * pos;
      sflare->ycenter = calc.ycenter - sin (calc.vangle) * calc.vlength * pos;
      sflare->radius = sizefac * calc.sflare_radius; /* FIXME */
      sflare->bounds.x0 = sflare->xcenter - sflare->radius - 1;
      sflare->bounds.x1 = sflare->xcenter + sflare->radius + 1;
      sflare->bounds.y0 = sflare->ycenter - sflare->radius - 1;
      sflare->bounds.y1 = sflare->ycenter + sflare->radius + 1;
      calc.sflare_list = g_list_append (calc.sflare_list, sflare);
    }

  g_rand_free (gr);
}

static void
calc_deinit (void)
{
  if (!calc.init)
    {
      g_warning("calc_deinit: not initialized");
      return;
    }

  g_list_free_full (g_steal_pointer (&calc.sflare_list), (GDestroyNotify) g_free);
  calc.sflare_list = NULL;

  g_free (calc.glow_radial);
  g_free (calc.glow_angular);
  g_free (calc.glow_angular_size);
  g_free (calc.rays_radial);
  g_free (calc.rays_angular);
  g_free (calc.rays_angular_size);
  g_free (calc.sflare_radial);
  g_free (calc.sflare_sizefac);
  g_free (calc.sflare_probability);

  calc.init = FALSE;
}

/*
 *  Get sample value at specified position of a gradient
 *
 *  gradient samples are stored into array at the time of
 *  calc_sample_one_gradients (), and it is now linear interpolated.
 *
 *  INPUT:
 *      guchar  gradient[4*GRADIENT_RESOLUTION]         gradient array(RGBA)
 *      gdouble pos                                     position (0<=pos<=1)
 *  OUTPUT:
 *      guchar  pix[4]
 */

static void
calc_get_gradient (guchar *pix, guchar *gradient, gdouble pos)
{
  gint          ipos;
  gdouble       frac;
  gint          i;

  if (isnan (pos) || pos < 0.0001 || pos > 1)
    {
      pix[0] = pix[1] = pix[2] = pix[3] = 0;
      return;
    }
  pos *= GRADIENT_RESOLUTION - 1.0001;
  ipos = (gint) pos;    frac = pos - ipos;
  gradient += ipos * 4;

  for (i = 0; i < 4; i++)
    {
      pix[i] = gradient[i] * (1 - frac) + gradient[i+4] * frac;
    }
}

/* I need fmod to return always positive value */
static gdouble
fmod_positive (gdouble x, gdouble m)
{
  return x - floor (x/m) * m;
}

/*
 *  Calc glow's pixel (RGBA) value
 *  INPUT:
 *      gdouble x, y                    image coordinates
 *  OUTPUT:
 *      guchar  pix[4]
 */

static void
calc_glow_pix (guchar *dest_pix, gdouble x, gdouble y)
{
  gdouble radius, angle;
  gdouble angular_size;
  guchar  radial_pix[4], angular_pix[4], size_pix[4];
  gint    i;

  if ((calc.type & CALC_GLOW) == 0
      || x < calc.glow_bounds.x0 || x > calc.glow_bounds.x1
      || y < calc.glow_bounds.y0 || y > calc.glow_bounds.y1)
    {
      memset (dest_pix, 0, 4);
      return;
    }

  x -= calc.xcenter;
  y -= calc.ycenter;
  radius = sqrt (x*x + y*y) / calc.glow_radius;
  angle = (atan2 (-y, x) + calc.glow_rotation ) / (2 * G_PI);
  angle = fmod_positive (angle, 1.0);

  calc_get_gradient (size_pix, calc.glow_angular_size, angle);
  /* angular_size gradient was grayfied already */
  angular_size = size_pix[0] / 255.0;
  radius /= (angular_size+0.0001);      /* in case angular_size == 0.0 */
  if (radius < 0 || radius > 1)
    {
      memset (dest_pix, 0, 4);
      return;
    }

  calc_get_gradient (radial_pix, calc.glow_radial, radius);
  calc_get_gradient (angular_pix, calc.glow_angular, angle);

  for (i = 0; i < 4; i++)
    dest_pix[i] = radial_pix[i] * angular_pix[i] / 255;
}

/*
 *  Calc rays's pixel (RGBA) value
 *
 */

static void
calc_rays_pix (guchar *dest_pix, gdouble x, gdouble y)
{
  gdouble radius, angle;
  gdouble angular_size;
  gdouble spike_frac, spike_inten, spike_angle;
  guchar  radial_pix[4], angular_pix[4], size_pix[4];
  gint    i;

  if ((calc.type & CALC_RAYS) == 0
      || x < calc.rays_bounds.x0 || x > calc.rays_bounds.x1
      || y < calc.rays_bounds.y0 || y > calc.rays_bounds.y1)
    {
      memset (dest_pix, 0, 4);
      return;
    }

  x -= calc.xcenter;
  y -= calc.ycenter;
  radius = sqrt (x*x + y*y) / calc.rays_radius;
  angle = (atan2 (-y, x) + calc.rays_rotation ) / (2 * G_PI);
  angle = fmod_positive (angle, 1.0);   /* make sure 0 <= angle < 1.0 */
  spike_frac = fmod (angle, calc.rays_spike_mod * 2);
  spike_angle = angle - spike_frac + calc.rays_spike_mod;
  spike_frac = (angle - spike_angle) / calc.rays_spike_mod;
  /* spike_frac is between -1.0 and 1.0 here (except round error...) */

  spike_inten = pow (1.0 - fabs (spike_frac), calc.rays_thinness);

  calc_get_gradient (size_pix, calc.rays_angular_size, spike_angle);
  /* angular_size gradient was grayfied already */
  angular_size = size_pix[0] / 255.0;
  radius /= (angular_size+0.0001);      /* in case angular_size == 0.0 */
  if(radius < 0 || radius > 1)
    {
      memset (dest_pix, 0, 4);
      return;
    }

  calc_get_gradient (radial_pix, calc.rays_radial, radius);
  calc_get_gradient (angular_pix, calc.rays_angular, spike_angle);

  for (i = 0; i < 3; i++)
    dest_pix[i] =  radial_pix[i] * angular_pix[i] / 255;
  dest_pix[3] = spike_inten * radial_pix[3] * angular_pix[3] / 255;
}

/*
 *  Calc sflare's pixel (RGBA) value
 *
 *  the sflare (second flares) are needed to be rendered one each
 *  sequentially, onto the source image, such as like usual layer
 *  operations. So the function takes src_pix as argument.  glow, rays
 *  routines don't have src_pix as argument, because of convenience.
 *
 *  @JAPANESE
 *  sflare $B$OJ#?t$N%U%l%"$r=g$K(B($B%l%$%dE*$K(B)$B$+$V$;$J$,$iIA2h$9$kI,MW$,(B
 *  $B$"$k$N$G!"$3$l$@$1(B src_pix $B$r0z?t$K$H$C$F(B paint_func $B$rE,MQ$9$k!#(B
 *  glow, rays $B$O4J0W2=$N$?$a$K$J$7!#(B
 */

void
calc_sflare_pix (guchar  *dest_pix,
                 gdouble  x,
                 gdouble  y,
                 guchar  *src_pix)
{
  GList         *list;
  CalcSFlare    *sflare;
  gdouble       sx, sy, th;
  gdouble       radius, angle;
  guchar        radial_pix[4], tmp_pix[4];

  memcpy (dest_pix, src_pix, 4);

  if ((calc.type & CALC_SFLARE) == 0)
    return;

  list = calc.sflare_list;

  while (list)
    {
      sflare = list->data;
      list = list->next;

      if (! sflare)
        break;

      if (x < sflare->bounds.x0 || x > sflare->bounds.x1
          || y < sflare->bounds.y0 || y > sflare->bounds.y1)
        continue;
      sx = x - sflare->xcenter;
      sy = y - sflare->ycenter;
      radius = sqrt (sx * sx + sy * sy) / sflare->radius;
      if (calc.sflare_shape == GF_POLYGON)
        {
          angle = atan2 (-sy, sx) - calc.vangle + calc.sflare_rotation;
          th = fmod_positive (angle, calc.sflare_angle * 2) - calc.sflare_angle;
          radius *= cos (th) * calc.sflare_factor;
        }
      if (radius < 0 || radius > 1)
        continue;

      calc_get_gradient (radial_pix, calc.sflare_radial, radius);
      memcpy (tmp_pix, dest_pix, 4);
      calc_paint_func (dest_pix, tmp_pix, radial_pix,
                       calc.sflare_opacity, calc.gflare->sflare_mode);
    }
}

static void
calc_gflare_pix (guchar *dest_pix, gdouble x, gdouble y, guchar *src_pix)
{
  GFlare        *gflare = calc.gflare;
  guchar        glow_pix[4], rays_pix[4];
  guchar        tmp_pix[4];

  memcpy (dest_pix, src_pix, 4);

  if (calc.type & CALC_GLOW)
    {
      memcpy (tmp_pix, dest_pix, 4);
      calc_glow_pix (glow_pix, x, y);
      calc_paint_func (dest_pix, tmp_pix, glow_pix,
                       calc.glow_opacity, gflare->glow_mode);
    }
  if (calc.type & CALC_RAYS)
    {
      memcpy (tmp_pix, dest_pix, 4);
      calc_rays_pix (rays_pix, x, y);
      calc_paint_func (dest_pix, tmp_pix, rays_pix,
                       calc.rays_opacity, gflare->rays_mode);
    }
  if (calc.type & CALC_SFLARE)
    {
      memcpy (tmp_pix, dest_pix, 4);
      calc_sflare_pix (dest_pix, x, y, tmp_pix);
    }
}

/*
        Paint func routines, such as Normal, Addition, ...
 */

static void
calc_paint_func (guchar *dest, guchar *src1, guchar *src2, gint opacity,
                 GFlareMode mode)
{
  guchar        buf[4], *s=buf;

  if (src2[3] == 0 || opacity <= 0)
    {
      memcpy (dest, src1, 4);
      return;
    }

  switch (mode)
    {
    case GF_NORMAL:
      s = src2;
      break;
    case GF_ADDITION:
      calc_addition (s, src1, src2);
      break;
    case GF_OVERLAY:
      calc_overlay (s, src1, src2);
      break;
    case GF_SCREEN:
      calc_screen (s, src1, src2);
      break;
    default:
      s = src2;
      break;
    }
  calc_combine (dest, src1, s, opacity);
}

static void
calc_combine (guchar *dest, guchar *src1, guchar *src2, gint opacity)
{
  gdouble       s1_a, s2_a, new_a;
  gdouble       ratio, compl_ratio;
  gint          i;

  s1_a = src1[3] / 255.0;
  s2_a = src2[3] * opacity / 65025.0;
  new_a  = s1_a + (1.0 - s1_a) * s2_a;

  if (new_a != 0.0)
    ratio = s2_a / new_a;
  else
    ratio = 0.0;

  compl_ratio = 1.0 - ratio;

  for (i = 0; i < 3; i++)
    dest[i] = src1[i] * compl_ratio + src2[i] * ratio;

  dest[3] = new_a * 255.0;
}

static void
calc_addition (guchar *dest, guchar *src1, guchar *src2)
{
  gint          tmp, i;

  for (i = 0; i < 3; i++)
    {
      tmp = src1[i] + src2[i];
      dest[i] = tmp <= 255 ? tmp: 255;
    }
  dest[3] = MIN (src1[3], src2[3]);
}

static void
calc_screen (guchar *dest, guchar *src1, guchar *src2)
{
  gint          i;

  for (i = 0; i < 3; i++)
    {
      dest[i] = 255 - ((255 - src1[i]) * (255 - src2[i])) / 255;
    }
  dest[3] = MIN (src1[3], src2[3]);
}

static void
calc_overlay (guchar *dest, guchar *src1, guchar *src2)
{
  gint          screen, mult, i;

  for (i = 0; i < 3; i++)
    {
      screen = 255 - ((255 - src1[i]) * (255 - src2[i])) / 255;
      mult = (src1[i] * src2[i]) / 255;
      dest[i] = (screen * src1[i] + mult * (255 - src1[i])) / 255;
    }
  dest[3] = MIN (src1[3], src2[3]);
}

/*************************************************************************/
/**                                                                     **/
/**                     Main Dialog                                     **/
/**                     +++ dlg                                         **/
/**                                                                     **/
/*************************************************************************/

/*
        This is gflare main dialog, one which opens in first.
 */


static gboolean
dlg_run (GimpProcedure       *procedure,
         GimpProcedureConfig *config)
{
  GeglBuffer *src_buffer;
  GtkWidget  *shell;
  GtkWidget  *hbox;
  GtkWidget  *vbox;
  GtkWidget  *frame;
  GtkWidget  *button;
  GtkWidget  *notebook;
  gboolean    run = FALSE;
  gchar      *gflare_name;

  gimp_ui_init (PLUG_IN_BINARY);

  /*
   *    Init Main Dialog
   */


  dlg = g_new0 (GFlareDialog, 1);
  dlg->init           = TRUE;
  dlg->update_preview = TRUE;

  gradient_menu_init (); /* FIXME: this should go elsewhere  */
  g_object_get (config, "gflare-name", &gflare_name, NULL);
  dlg_setup_gflare (gflare_name);
  g_free (gflare_name);

  g_assert (gflares_list != NULL);
  g_assert (dlg->gflare != NULL);
  g_assert (dlg->gflare->name != NULL);

  /*
   *    Dialog Shell
   */


  shell = dlg->shell = gimp_procedure_dialog_new (procedure,
                                                  GIMP_PROCEDURE_CONFIG (config),
                                                  _("Gradient Flare"));

  /*
   *    main hbox
   */


  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (shell))),
                      hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  /*
   *    Preview
   */


  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
  gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
  gtk_widget_show (vbox);

  frame = gtk_frame_new (NULL);
  gtk_widget_set_valign (frame, GTK_ALIGN_START);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
  gtk_widget_show (frame);

  src_buffer = gimp_drawable_get_buffer (drawable);

  dlg->preview = preview_new (DLG_PREVIEW_WIDTH, DLG_PREVIEW_HEIGHT,
                              dlg_preview_init_func, config,
                              dlg_preview_render_func, src_buffer,
                              dlg_preview_deinit_func, NULL);
  gtk_widget_set_events (GTK_WIDGET (dlg->preview->widget), DLG_PREVIEW_MASK);
  gtk_container_add (GTK_CONTAINER (frame), dlg->preview->widget);

  g_signal_connect (dlg->preview->widget, "realize",
                    G_CALLBACK (dlg_preview_realize),
                    NULL);
  g_signal_connect (dlg->preview->widget, "event",
                    G_CALLBACK (dlg_preview_handle_event),
                    config);

  dlg_preview_calc_window ();

  button = gtk_check_button_new_with_mnemonic (_("A_uto update preview"));
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
                                dlg->update_preview);
  gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

  g_signal_connect (button, "toggled",
                    G_CALLBACK (dlg_update_preview_callback),
                    &dlg->update_preview);

  /*
   *    Notebook
   */


  notebook = dlg->notebook = gtk_notebook_new ();
  gtk_box_pack_start (GTK_BOX (hbox), notebook, TRUE, TRUE, 0);
  gtk_widget_show (notebook);

  dlg_make_page_settings (dlg, notebook, config);
  dlg_make_page_selector (dlg, notebook, config);

  gtk_widget_show (shell);

  /*
   *    Initialization done
   */

  dlg->init = FALSE;
  dlg_preview_update ();

  run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (shell));

  if (! dlg->gflare)
    run = FALSE;

  if (run)
    {
      g_object_set (config,
                    "gflare-name", dlg->gflare->name,
                    NULL);
    }

  g_object_unref (src_buffer);

  gtk_widget_destroy (shell);

  return run;
}

static void
dlg_setup_gflare (const gchar *gflare_name)
{
  dlg->gflare = gflares_list_lookup (gflare_name);

  if (!dlg->gflare)
    {
      dlg->gflare = gflares_list_lookup ("Default");
      if (!dlg->gflare)
        {
          g_warning (_("'Default' is created."));
          dlg->gflare = gflare_new_with_default (_("Default"));
          gflares_list_insert (dlg->gflare);
        }
    }
}

/***********************************/
/**     Main Dialog / Preview     **/
/***********************************/

/*
 *      Calculate preview's window, ie. translation of preview widget and
 *      drawable.
 *
 *      x0, x1, y0, y1 are drawable coord, corresponding with top left
 *      corner of preview widget, etc.
 */

void
dlg_preview_calc_window (void)
{
  gint     width  = gimp_drawable_get_width  (drawable);
  gint     height = gimp_drawable_get_height (drawable);
  gint     is_wide;
  gdouble  offx, offy;

  is_wide = ((double) DLG_PREVIEW_HEIGHT * width >=
             (double) DLG_PREVIEW_WIDTH  * height);

  if (is_wide)
    {
      offy = ((double) width * DLG_PREVIEW_HEIGHT / DLG_PREVIEW_WIDTH) / 2.0;

      dlg->pwin.x0 = 0;
      dlg->pwin.x1 = width;
      dlg->pwin.y0 = height / 2.0 - offy;
      dlg->pwin.y1 = height / 2.0 + offy;
    }
  else
    {
      offx = ((double) height * DLG_PREVIEW_WIDTH / DLG_PREVIEW_HEIGHT) / 2.0;

      dlg->pwin.x0 = width / 2.0 - offx;
      dlg->pwin.x1 = width / 2.0 + offx;
      dlg->pwin.y0 = 0;
      dlg->pwin.y1 = height;
    }
}

void
ed_preview_calc_window (void)
{
  gint     width  = gimp_drawable_get_width  (drawable);
  gint     height = gimp_drawable_get_height (drawable);
  gint     is_wide;
  gdouble  offx, offy;

  is_wide = ((double) DLG_PREVIEW_HEIGHT * width >=
             (double) DLG_PREVIEW_WIDTH  * height);

  if (is_wide)
    {
      offy = ((double) width * DLG_PREVIEW_HEIGHT / DLG_PREVIEW_WIDTH) / 2.0;

      dlg->pwin.x0 = 0;
      dlg->pwin.x1 = width;
      dlg->pwin.y0 = height / 2.0 - offy;
      dlg->pwin.y1 = height / 2.0 + offy;
    }
  else
    {
      offx = ((double) height * DLG_PREVIEW_WIDTH / DLG_PREVIEW_HEIGHT) / 2.0;

      dlg->pwin.x0 = width / 2.0 - offx;      dlg->pwin.x1 = width / 2.0 + offx;
      dlg->pwin.y0 = 0;
      dlg->pwin.y1 = height;
    }
}

static void
dlg_preview_realize (GtkWidget *widget)
{
  GdkDisplay *display = gtk_widget_get_display (widget);
  GdkCursor  *cursor  = gdk_cursor_new_for_display (display, GDK_CROSSHAIR);

  gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
  g_object_unref (cursor);
}

static gboolean
dlg_preview_handle_event (GtkWidget           *widget,
                          GdkEvent            *event,
                          GimpProcedureConfig *config)
{
  GdkEventButton *bevent;
  gint            bx, by, x, y;
  gint         xcenter;
  gint         ycenter;
  gboolean        handled = FALSE;

  g_object_get (config,
                "center-x", &xcenter,
                "center-y", &ycenter,
                NULL);

  switch (event->type)
    {
    case GDK_BUTTON_PRESS:
      bevent = (GdkEventButton *) event;
      bx = bevent->x;
      by = bevent->y;

      /* convert widget coord to drawable coord */
      x = dlg->pwin.x0 + (double) (dlg->pwin.x1 - dlg->pwin.x0)
                                        * bx / DLG_PREVIEW_WIDTH;
      y = dlg->pwin.y0 + (double) (dlg->pwin.y1 - dlg->pwin.y0)
                                        * by / DLG_PREVIEW_HEIGHT;

      if ((x != xcenter || y != ycenter))
        {
          if (x != xcenter)
            {
              xcenter = x;
              gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (dlg->sizeentry),
                                          0, x);
            }
          if (y != ycenter)
            {
              ycenter = y;
              gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (dlg->sizeentry),
                                          1, y);
            }
          dlg_preview_update ();
        }
      handled = TRUE;
      break;
    default:
      break;
    }

  g_object_set (config,
                "center-x", xcenter,
                "center-y", ycenter,
                NULL);

  return handled;
}

static void
dlg_preview_update (void)
{
  if (!dlg->init && dlg->update_preview)
    {
      dlg->init_params_done = FALSE;
      preview_render_start (dlg->preview);
    }
}

/*      preview callbacks       */
static gint
dlg_preview_init_func (Preview *preview,
                       gpointer data)
{
  GimpProcedureConfig *config = GIMP_PROCEDURE_CONFIG (data);

  /* call init_params first, and iterate init_progress while
     it returns true */

  if (dlg->init_params_done == FALSE && dlg->gflare)
    {
      gint    xcenter;
      gint    ycenter;
      gdouble radius;
      gdouble rotation;
      gdouble hue;
      gdouble vangle;
      gdouble vlength;

      g_object_get (config,
                    "center-x",      &xcenter,
                    "center-y",      &ycenter,
                    "radius",        &radius,
                    "rotation",      &rotation,
                    "hue",           &hue,
                    "vector-angle",  &vangle,
                    "vector-length", &vlength,
                    NULL);

      calc_init_params (dlg->gflare,
                        CALC_GLOW | CALC_RAYS | CALC_SFLARE,
                        xcenter, ycenter,
                        radius, rotation, hue,
                        vangle, vlength);
      dlg->init_params_done = TRUE;
      return TRUE;
    }
  return calc_init_progress ();
}

/* render preview
   do what "preview" means, ie. render lense flare effect onto drawable */

static void
dlg_preview_render_func (Preview  *preview,
                         guchar   *dest,
                         gint      y,
                         gpointer  data)
{
  GeglBuffer *src_buffer = data;
  gint        width      = gimp_drawable_get_width  (drawable);
  gint        height     = gimp_drawable_get_height (drawable);
  gint        x;
  gint        dx, dy;         /* drawable x, y */
  guchar     *src_row, *src;
  guchar      src_pix[4], dest_pix[4];
  gint        b;

  dy = (dlg->pwin.y0 +
        (gdouble) (dlg->pwin.y1 - dlg->pwin.y0) * y / DLG_PREVIEW_HEIGHT);

  if (dy < 0 || dy >= height)
    {
      memset (dest, GRAY50, 3 * DLG_PREVIEW_WIDTH);
      return;
    }

  src_row = g_new (guchar, dinfo.bpp * width);

  gegl_buffer_get (src_buffer, GEGL_RECTANGLE (0, dy, width, 1), 1.0,
                   dinfo.format, src_row,
                   GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);

  for (x = 0; x < DLG_PREVIEW_HEIGHT; x++)
    {
      dx = (dlg->pwin.x0 +
            (double) (dlg->pwin.x1 - dlg->pwin.x0) * x / DLG_PREVIEW_WIDTH);

      if (dx < 0 || dx >= width)
        {
          for (b = 0; b < 3; b++)
            *dest++ = GRAY50;
          continue;
        }

      /* Get drawable pix value */
      src = &src_row[dx * dinfo.bpp];

      for (b = 0; b < 3; b++)
        src_pix[b] = dinfo.is_color ? src[b] : src[0];
      src_pix[3] = dinfo.has_alpha ? src[dinfo.bpp - 1] : OPAQUE;

      /* Get GFlare pix value */

      calc_gflare_pix (dest_pix, dx, dy, src_pix);

      /* Draw gray check if needed */
      preview_rgba_to_rgb (dest, x, y, dest_pix);
      dest += 3;
    }

  g_free (src_row);
}

static void
dlg_preview_deinit_func (Preview *preview, gpointer data)
{
  if (dlg->init_params_done)
    {
      calc_deinit ();
      dlg->init_params_done = FALSE;
    }
}

/*****************************************/
/**     Main Dialog / Settings Page     **/
/*****************************************/

static void
dlg_make_page_settings (GFlareDialog        *dlg,
                        GtkWidget           *notebook,
                        GimpProcedureConfig *config)
{
  GtkWidget *main_vbox;
  GtkWidget *frame;
  GtkWidget *center;
  GtkWidget *chain;
  GtkWidget *scale;
  gdouble    xres, yres;
  gint       xcenter;
  gint       ycenter;

  g_object_get (config,
                "center-x", &xcenter,
                "center-y", &ycenter,
                NULL);
  gimp_image_get_resolution (image, &xres, &yres);

  center = dlg->sizeentry =
    gimp_coordinates_new (gimp_image_get_unit (image), "%a",
                          TRUE, TRUE, 75, GIMP_SIZE_ENTRY_UPDATE_SIZE,

                          FALSE, FALSE,

                          _("_X:"), xcenter, xres,
                          -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE,
                          0, gimp_drawable_get_width (drawable),

                          _("_Y:"), ycenter, yres,
                          -GIMP_MAX_IMAGE_SIZE, GIMP_MAX_IMAGE_SIZE,
                          0, gimp_drawable_get_height (drawable));

  chain = GTK_WIDGET (GIMP_COORDINATES_CHAINBUTTON (center));

  g_signal_connect (center, "value-changed",
                    G_CALLBACK (dlg_position_entry_callback),
                    config);
  g_signal_connect (center, "refval-changed",
                    G_CALLBACK (dlg_position_entry_callback),
                    config);
  gtk_widget_hide (chain);
  gtk_widget_show (center);

  gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                   "center-title", _("Center"),
                                   FALSE, FALSE);
  frame = gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                            "center-frame", "center-title",
                                            FALSE, NULL);
  gtk_container_add (GTK_CONTAINER (frame), center);

  scale = gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell), "radius", 1.0);
  gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 1.0,
                                    gimp_drawable_get_width (drawable) / 2);

  gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell), "rotation", 1.0);
  gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell), "hue", 1.0);
  gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell), "vector-angle", 1.0);

  scale = gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell), "vector-length", 1.0);
  gimp_spin_scale_set_scale_limits (GIMP_SPIN_SCALE (scale), 1.0, 1000);

  gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                  "parameters-box",
                                  "radius", "rotation", "hue",
                                  "vector-angle", "vector-length",
                                  NULL);
  gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                   "parameters-title", _("Parameters"),
                                   FALSE, FALSE);
  gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                    "parameters-frame", "parameters-title",
                                    FALSE, "parameters-box");

  g_signal_connect (config, "notify",
                    G_CALLBACK (dlg_preview_update),
                    NULL);

  /**
  ***   Asupsample settings
  ***   This code is stolen from gimp-0.99.x/app/blend.c
  **/


  gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                        "asupsample-max-depth", 1.0);

  gimp_procedure_dialog_get_spin_scale (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                        "asupsample-threshold", 1.0);

  gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                  "asupsample-box",
                                  "asupsample-max-depth", "asupsample-threshold",
                                  NULL);
  gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                    "asupsample-frame", "use-asupsample",
                                    FALSE, "asupsample-box");

  main_vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dlg->shell),
                                              "settings-page",
                                              "center-frame", "parameters-frame", "asupsample-frame",
                                              NULL);
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), main_vbox,
                            gtk_label_new_with_mnemonic (_("_Settings")));
  gtk_widget_show (main_vbox);
}

static void
dlg_position_entry_callback (GtkWidget           *widget,
                             GimpProcedureConfig *config)
{
  gint xcenter;
  gint ycenter;
  gint x, y;

  x = RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 0));
  y = RINT (gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (widget), 1));

  DEBUG_PRINT (("dlg_position_entry_callback\n"));

  g_object_get (config,
                "center-x", &xcenter,
                "center-y", &ycenter,
                NULL);

  if (xcenter != x || ycenter != y)
    {
      g_object_set (config,
                    "center-x", x,
                    "center-y", y,
                    NULL);

      dlg_preview_update ();
    }
}

static void
dlg_update_preview_callback (GtkWidget *widget,
                             gpointer   data)
{
  gimp_toggle_button_update (widget, data);

  dlg_preview_update ();
}

/*****************************************/
/**     Main Dialog / Selector Page     **/
/*****************************************/

static void
dlg_make_page_selector (GFlareDialog        *dlg,
                        GtkWidget           *notebook,
                        GimpProcedureConfig *config)
{
  GtkWidget         *vbox;
  GtkWidget         *hbox;
  GtkWidget         *listbox;
  GtkListStore      *list;
  GtkWidget         *listview;
  GtkCellRenderer   *renderer;
  GtkTreeViewColumn *column;
  GtkWidget         *button;
  gint               i;

  static struct
  {
    const gchar *label;
    GCallback    callback;
  }
  buttons[] =
  {
    { N_("_New"),    G_CALLBACK (dlg_selector_new_callback)    },
    { N_("_Edit"),   G_CALLBACK (dlg_selector_edit_callback)   },
    { N_("_Copy"),   G_CALLBACK (dlg_selector_copy_callback)   },
    { N_("_Delete"), G_CALLBACK (dlg_selector_delete_callback) }
  };

  DEBUG_PRINT (("dlg_make_page_selector\n"));

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);

  /*
   *    List Box
   */


  listbox = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (listbox),
                                  GTK_POLICY_AUTOMATIC,
                                  GTK_POLICY_AUTOMATIC);

  gtk_widget_set_size_request (listbox, DLG_LISTBOX_WIDTH, DLG_LISTBOX_HEIGHT);
  gtk_box_pack_start (GTK_BOX (vbox), listbox, TRUE, TRUE, 0);

  list = dlg->selector_list = gtk_list_store_new (2,
                                                  G_TYPE_STRING,
                                                  G_TYPE_POINTER);
  listview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (list));
  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (listview), FALSE);

  gtk_container_add (GTK_CONTAINER (listbox), listview);
  gtk_widget_show (listbox);
  dlg->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (listview));
  gtk_tree_selection_set_mode (dlg->selection, GTK_SELECTION_BROWSE);
  gtk_widget_show (listview);
  g_signal_connect (dlg->selection, "changed",
                    G_CALLBACK (dlg_selector_list_item_callback),
                    NULL);

  renderer = gtk_cell_renderer_text_new ();

  /* Note: the title isn't shown, so it doesn't need to be translated. */
  column = gtk_tree_view_column_new_with_attributes ("GFlare", renderer,
                                                      "text", 0,
                                                      NULL);
  gtk_tree_view_append_column (GTK_TREE_VIEW (listview), column);

  dlg_selector_setup_listbox ();

  /*
   *    The buttons for the possible listbox operations
   */


  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  for (i = 0; i < G_N_ELEMENTS (buttons); i++)
    {
      button = gtk_button_new_with_mnemonic (gettext (buttons[i].label));
      gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
      gtk_widget_show (button);

      g_signal_connect (button, "clicked",
                        buttons[i].callback,
                        config);
    }

  gtk_widget_show (vbox);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox,
                            gtk_label_new_with_mnemonic (_("S_elector")));
  gtk_widget_show (vbox);
}

/*
 *      Set up selector's listbox, according to gflares_list
 */

static void
dlg_selector_setup_listbox (void)
{
  GList  *list;
  GFlare *gflare;
  gint    n;

  list = gflares_list;
  n = 0;

  while (list)
    {
      GtkTreeIter   iter;

      gflare = list->data;

      /*
        dlg->gflare should be valid (ie. not NULL) here.
        */

      gtk_list_store_append (dlg->selector_list, &iter);

      gtk_list_store_set (dlg->selector_list, &iter,
                          0, gflare->name,
                          1, gflare,
                          -1);
      if (gflare == dlg->gflare)
         gtk_tree_selection_select_iter (dlg->selection,
                                         &iter);

      list = list->next;
      n++;
    }
}

static void
dlg_selector_list_item_callback (GtkTreeSelection *selection)
{
  GtkTreeIter iter;

  if (gtk_tree_selection_get_selected (selection, NULL, &iter))
    {
      gtk_tree_model_get (GTK_TREE_MODEL (dlg->selector_list), &iter,
                          1, &dlg->gflare, -1);
    }

  dlg_preview_update ();
}

/*
 *      "New" button in Selector page
 */

static void
dlg_selector_new_callback (GtkWidget *widget,
                           gpointer   data)
{
  GtkWidget *query_box;

  query_box = gimp_query_string_box (_("New Gradient Flare"),
                                     gtk_widget_get_toplevel (widget),
                                     gimp_standard_help_func, PLUG_IN_PROC,
                                     _("Enter a name for the new GFlare"),
                                     _("Unnamed"),
                                     NULL, NULL,
                                     dlg_selector_new_ok_callback,
                                     dlg, NULL);
  gtk_widget_show (query_box);
}

static void
dlg_selector_new_ok_callback (GtkWidget   *widget,
                              const gchar *new_name,
                              gpointer     data)
{
  GFlare      *gflare;
  GtkTreeIter  iter;
  gint         pos;

  g_return_if_fail (new_name != NULL);

  if (gflares_list_lookup (new_name))
    {
      g_message (_("The name '%s' is used already!"), new_name);
      return;
    }

  gflare = gflare_new_with_default (new_name);

  pos = gflares_list_insert (gflare);

  gtk_list_store_insert (dlg->selector_list, &iter, pos);
  gtk_list_store_set (dlg->selector_list, &iter,
                      0, gflare->name,
                      1, gflare,
                      -1);
  gtk_tree_selection_select_iter (dlg->selection, &iter);

  dlg->gflare = gflare;
  dlg_preview_update ();
}

/*
 *  "Edit" button in Selector page
 */

static void
dlg_selector_edit_callback (GtkWidget *widget,
                            gpointer   data)
{
  preview_render_end (dlg->preview);
  gtk_widget_set_sensitive (dlg->shell, FALSE);
  ed_run (GTK_WINDOW (dlg->shell),
          dlg->gflare, dlg_selector_edit_done_callback, data);
}

static void
dlg_selector_edit_done_callback (gint     updated,
                                 gpointer data)
{
  gtk_widget_set_sensitive (dlg->shell, TRUE);
  if (updated)
    {
      gflare_save (dlg->gflare);
    }
  dlg_preview_update ();
}

/*
 *  "Copy" button in Selector page
 */

static void
dlg_selector_copy_callback (GtkWidget *widget,
                            gpointer   data)
{
  GtkWidget *query_box;
  gchar     *name;

  name = g_strdup_printf ("%s copy", dlg->gflare->name);

  query_box = gimp_query_string_box (_("Copy Gradient Flare"),
                                     gtk_widget_get_toplevel (widget),
                                     gimp_standard_help_func, PLUG_IN_PROC,
                                     _("Enter a name for the copied GFlare"),
                                     name,
                                     NULL, NULL,
                                     dlg_selector_copy_ok_callback,
                                     dlg, NULL);
  g_free (name);

  gtk_widget_show (query_box);
}

static void
dlg_selector_copy_ok_callback (GtkWidget   *widget,
                               const gchar *copy_name,
                               gpointer     data)
{
  GFlare      *gflare;
  GtkTreeIter  iter;
  gint         pos;

  g_return_if_fail (copy_name != NULL);

  if (gflares_list_lookup (copy_name))
    {
      g_warning (_("The name '%s' is used already!"), copy_name);
      return;
    }

  gflare = gflare_dup (dlg->gflare, copy_name);

  pos = gflares_list_insert (gflare);
  gtk_list_store_insert (dlg->selector_list, &iter, pos);
  gtk_list_store_set (dlg->selector_list, &iter,
                      0, gflare->name,
                      1, gflare,
                      -1);
  gtk_tree_selection_select_iter (dlg->selection, &iter);

  dlg->gflare = gflare;
  gflare_save (dlg->gflare);
  dlg_preview_update ();
}

/*
 *      "Delete" button in Selector page
 */

static void
dlg_selector_delete_callback (GtkWidget *widget,
                              gpointer   data)
{
  GtkWidget *dialog;
  gchar     *str;

  if (num_gflares <= 1)
    {
      g_message (_("Cannot delete!! There must be at least one GFlare."));
      return;
    }

  gtk_widget_set_sensitive (dlg->shell, FALSE);

  str = g_strdup_printf (_("Are you sure you want to delete "
                           "\"%s\" from the list and from disk?"),
                         dlg->gflare->name);

  dialog = gimp_query_boolean_box (_("Delete Gradient Flare"),
                                   dlg->shell,
                                   gimp_standard_help_func, PLUG_IN_PROC,
                                   GIMP_ICON_DIALOG_QUESTION,
                                   str,
                                   _("_Delete"), _("_Cancel"),
                                   NULL, NULL,
                                   dlg_selector_do_delete_callback,
                                   NULL, NULL);

  g_free (str);

  gtk_widget_show (dialog);
}

static void
dlg_selector_do_delete_callback (GtkWidget *widget,
                                 gboolean   delete,
                                 gpointer   data)
{
  GFlare      *old_gflare;
  GList       *tmp;
  gint         i, new_i;
  GtkTreeIter  iter;

  gtk_widget_set_sensitive (dlg->shell, TRUE);

  if (!delete)
    return;

  i = gflares_list_index (dlg->gflare);

  if (i >= 0)
    {
      /* Remove current gflare from gflares_list and free it */
      old_gflare = dlg->gflare;
      gflares_list_remove (dlg->gflare);
      dlg->gflare = NULL;

      /* Remove from listbox */
      if (! gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (dlg->selector_list),
                                           &iter, NULL, i))
        {
          g_warning ("Unsynchronized lists. Bad things will happen!");
          return;
        }
      gtk_list_store_remove (dlg->selector_list, &iter);

      /* Calculate new position of gflare and select it */
      new_i = (i < num_gflares) ? i : num_gflares - 1;
      if ((tmp = g_list_nth (gflares_list, new_i)))
        dlg->gflare = tmp->data;
      if (!gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (dlg->selector_list),
                                          &iter, NULL, i))
        {
          g_warning ("Unsynchronized lists. Bad things will happen!");
          return;
        }
       gtk_tree_selection_select_iter (dlg->selection,
                                       &iter);

      /* Delete old one from disk and memory */
      if (old_gflare->filename)
        g_unlink (old_gflare->filename);

      gflare_free (old_gflare);

      /* Update */
      dlg_preview_update ();
    }
  else
    {
      g_warning (_("not found %s in gflares_list"), dlg->gflare->name);
    }
}

/*************************************************************************/
/**                                                                     **/
/**                     GFlare Editor                                   **/
/**                     +++ ed                                          **/
/**                                                                     **/
/*************************************************************************/

/*
        This is gflare editor dialog, one which opens by clicking
        "Edit" button on the selector page in the main dialog.
 */


static void
ed_run (GtkWindow            *parent,
        GFlare               *target_gflare,
        GFlareEditorCallback  callback,
        gpointer              calldata)
{
  GtkWidget *shell;
  GtkWidget *hbox;
  GtkWidget *frame;
  GtkWidget *notebook;

  g_return_if_fail (GIMP_IS_PROCEDURE_CONFIG (calldata));

  if (!ed)
    ed = g_new0 (GFlareEditor, 1);
  ed->init          = TRUE;
  ed->run           = FALSE;
  ed->target_gflare = target_gflare;
  ed->gflare        = gflare_dup (target_gflare, target_gflare->name);
  ed->callback      = callback;
  ed->calldata      = calldata;

  /*
   *    Dialog Shell
   */

  ed->shell =
    shell = gimp_dialog_new (_("Gradient Flare Editor"), PLUG_IN_ROLE,
                             GTK_WIDGET (parent), 0,
                             gimp_standard_help_func, PLUG_IN_PROC,

                             _("_Rescan Gradients"), RESPONSE_RESCAN,
                             _("_Cancel"),           GTK_RESPONSE_CANCEL,
                             _("_OK"),               GTK_RESPONSE_OK,

                             NULL);

  gimp_dialog_set_alternative_button_order (GTK_DIALOG (shell),
                                            RESPONSE_RESCAN,
                                            GTK_RESPONSE_OK,
                                            GTK_RESPONSE_CANCEL,
                                            -1);

  g_signal_connect (shell, "response",
                    G_CALLBACK (ed_response),
                    ed);
  g_signal_connect (shell, "destroy",
                    G_CALLBACK (ed_destroy_callback),
                    ed);

  /*
   *    main hbox
   */


  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (shell))),
                      hbox, FALSE, FALSE, 0);
  gtk_widget_set_visible (hbox, TRUE);

  /*
   *    Preview
   */


  frame = gtk_frame_new (NULL);
  gtk_widget_set_valign (frame, GTK_ALIGN_START);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
  gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0);
  gtk_widget_set_visible (frame, TRUE);

  ed->preview = preview_new (ED_PREVIEW_WIDTH, ED_PREVIEW_HEIGHT,
                             ed_preview_init_func, calldata,
                             ed_preview_render_func, NULL,
                             ed_preview_deinit_func, NULL);
  gtk_widget_set_events (GTK_WIDGET (ed->preview->widget), DLG_PREVIEW_MASK);
  gtk_container_add (GTK_CONTAINER (frame), ed->preview->widget);
  ed_preview_calc_window ();

  /*
   *    Notebook
   */

  notebook = ed->notebook = gtk_notebook_new ();
  gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP);
  gtk_box_pack_start (GTK_BOX (hbox), notebook, TRUE, TRUE, 0);
  gtk_widget_set_visible (notebook, TRUE);

  ed_make_page_general (ed, notebook);
  ed_make_page_glow (ed, notebook);
  ed_make_page_rays (ed, notebook);
  ed_make_page_sflare (ed, notebook);

  gtk_widget_set_visible (shell, TRUE);

  ed->init = FALSE;
  ed_preview_update ();
}

static void
ed_destroy_callback (GtkWidget    *widget,
                     GFlareEditor *ed)
{
  preview_free (ed->preview);
  gflare_free (ed->gflare);
  if (ed->callback)
    (*ed->callback) (ed->run, ed->calldata);
}

static void
ed_response (GtkWidget    *widget,
             gint          response_id,
             GFlareEditor *ed)
{
  switch (response_id)
    {
    case RESPONSE_RESCAN:
      ed->init = TRUE;
      gradient_menu_rescan ();
      ed->init = FALSE;
      ed_preview_update ();
      gtk_widget_queue_draw (ed->notebook);
      break;

    case GTK_RESPONSE_OK:
      ed->run = TRUE;
      gflare_copy (ed->target_gflare, ed->gflare);
      gtk_widget_destroy (ed->shell);
      break;

    default:
      gtk_widget_destroy (ed->shell);
      break;
    }
}

static void
ed_make_page_general (GFlareEditor *ed,
                      GtkWidget    *notebook)
{
  GFlare    *gflare = ed->gflare;
  GtkWidget *vbox;
  GtkWidget *frame;
  GtkWidget *grid;
  GtkWidget *combo;
  GtkWidget *scale;

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);

  /*  Glow  */

  frame = gimp_frame_new (_("Glow Paint Options"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);
  gtk_widget_show (grid);

  scale = gimp_scale_entry_new (_("Opacity:"), gflare->glow_opacity, 0.0, 100.0, 1);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->glow_opacity);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, 0, 3, 1);
  gtk_widget_show (scale);

  combo = ed_mode_menu_new (&gflare->glow_mode);
  gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
                            _("Paint mode:"), 0.0, 0.5, combo, 1);

  /*  Rays  */

  frame = gimp_frame_new (_("Rays Paint Options"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);
  gtk_widget_show (grid);

  scale = gimp_scale_entry_new (_("Opacity:"), gflare->rays_opacity, 0.0, 100.0, 1);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->rays_opacity);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, 0, 3, 1);
  gtk_widget_show (scale);

  combo = ed_mode_menu_new (&gflare->rays_mode);
  gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
                            _("Paint mode:"), 0.0, 0.5, combo, 1);

  /*  Rays  */

  frame = gimp_frame_new (_("Second Flares Paint Options"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);
  gtk_widget_show (grid);

  scale = gimp_scale_entry_new (_("Opacity:"), gflare->sflare_opacity, 0.0, 100.0, 1);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->sflare_opacity);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, 0, 3, 1);
  gtk_widget_show (scale);

  combo = ed_mode_menu_new (&gflare->sflare_mode);
  gimp_grid_attach_aligned (GTK_GRID (grid), 0, 1,
                            _("Paint mode:"), 0.0, 0.5, combo, 1);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox,
                            gtk_label_new_with_mnemonic (_("_General")));
  g_signal_connect (vbox, "map",
                    G_CALLBACK (ed_page_map_callback),
                    (gpointer) PAGE_GENERAL);
  gtk_widget_show (vbox);
}

static void
ed_make_page_glow (GFlareEditor *ed,
                   GtkWidget    *notebook)
{
  GFlare       *gflare = ed->gflare;
  GradientMenu *gm;
  GtkWidget    *vbox;
  GtkWidget    *frame;
  GtkWidget    *grid;
  GtkWidget    *scale;
  gint          row;

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);

  /*
   *  Gradient Menus
   */


  frame = gimp_frame_new (_("Gradients"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->glow_radial, gflare->glow_radial);
  ed_put_gradient_menu (grid, 0, 0, _("Radial gradient:"), gm);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->glow_angular, gflare->glow_angular);
  ed_put_gradient_menu (grid, 0, 1, _("Angular gradient:"), gm);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->glow_angular_size, gflare->glow_angular_size);
  ed_put_gradient_menu (grid, 0, 2, _("Angular size gradient:"), gm);

  gtk_widget_show (grid);

  /*
   *  Scales
   */


  frame = gimp_frame_new (_("Parameters"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);

  row = 0;

  scale = gimp_scale_entry_new (_("Size (%):"), gflare->glow_size, 0.0, G_MAXINT, 1);
  gimp_scale_entry_set_bounds (GIMP_SCALE_ENTRY (scale), 0.0, 200.0, TRUE);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->glow_size);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Rotation:"), gflare->glow_rotation, -180.0, 180.0, 1);
  gimp_label_spin_set_increments (GIMP_LABEL_SPIN (scale), 1.0, 15.0);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->glow_rotation);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Hue rotation:"), gflare->glow_hue, -180.0, 180.0, 1);
  gimp_label_spin_set_increments (GIMP_LABEL_SPIN (scale), 1.0, 15.0);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->glow_hue);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  gtk_widget_show (grid);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox,
                            gtk_label_new_with_mnemonic (_("G_low")));
  g_signal_connect (vbox, "map",
                    G_CALLBACK (ed_page_map_callback),
                    (gpointer) PAGE_GLOW);
  gtk_widget_show (vbox);
}

static void
ed_make_page_rays (GFlareEditor *ed,
                   GtkWidget    *notebook)
{
  GFlare       *gflare = ed->gflare;
  GradientMenu *gm;
  GtkWidget    *vbox;
  GtkWidget    *frame;
  GtkWidget    *grid;
  GtkWidget    *scale;
  gint          row;

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);

  /*
   *  Gradient Menus
   */


  frame = gimp_frame_new (_("Gradients"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);

  row = 0;

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->rays_radial, gflare->rays_radial);
  ed_put_gradient_menu (grid, 0, row++, _("Radial gradient:"), gm);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->rays_angular, gflare->rays_angular);
  ed_put_gradient_menu (grid, 0, row++, _("Angular gradient:"), gm);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->rays_angular_size, gflare->rays_angular_size);
  ed_put_gradient_menu (grid, 0, row++, _("Angular size gradient:"), gm);

  gtk_widget_show (grid);

  /*
   *    Scales
   */


  frame = gimp_frame_new (_("Parameters"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);

  row = 0;

  scale = gimp_scale_entry_new (_("Size (%):"), gflare->rays_size, 0.0, G_MAXINT, 1);
  gimp_scale_entry_set_bounds (GIMP_SCALE_ENTRY (scale), 0.0, 200.0, TRUE);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->rays_size);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Rotation:"), gflare->rays_rotation, -180.0, 180.0, 1);
  gimp_label_spin_set_increments (GIMP_LABEL_SPIN (scale), 1.0, 15.0);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->rays_rotation);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Hue rotation:"), gflare->rays_hue, -180.0, 180.0, 1);
  gimp_label_spin_set_increments (GIMP_LABEL_SPIN (scale), 1.0, 15.0);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->rays_hue);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("# of Spikes:"), gflare->rays_nspikes, 1, G_MAXINT, 0);
  gimp_scale_entry_set_bounds (GIMP_SCALE_ENTRY (scale), 1.0, 300.0, TRUE);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_int),
                    &gflare->rays_nspikes);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Spike thickness:"), gflare->rays_thickness, 1.0, GIMP_MAX_IMAGE_SIZE, 1);
  gimp_scale_entry_set_bounds (GIMP_SCALE_ENTRY (scale), 1.0, 100.0, TRUE);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->rays_thickness);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  gtk_widget_show (grid);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox,
                            gtk_label_new_with_mnemonic (_("_Rays")));
  g_signal_connect (vbox, "map",
                    G_CALLBACK (ed_page_map_callback),
                    (gpointer) PAGE_RAYS);
  gtk_widget_show (vbox);
}

static void
ed_make_page_sflare (GFlareEditor *ed,
                     GtkWidget    *notebook)
{
  GFlare       *gflare = ed->gflare;
  GradientMenu *gm;
  GtkWidget    *vbox;
  GtkWidget    *grid;
  GtkWidget    *frame;
  GtkWidget    *shape_vbox;
  GSList       *shape_group = NULL;
  GtkWidget    *polygon_hbox;
  GtkWidget    *seed_hbox;
  GtkWidget    *toggle;
  GtkWidget    *label;
  GtkWidget    *seed;
  GtkWidget    *entry;
  GtkWidget    *scale;
  gchar         buf[256];
  gint          row;

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);

  /*
   *  Gradient Menus
   */


  frame = gimp_frame_new (_("Gradients"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->sflare_radial, gflare->sflare_radial);
  ed_put_gradient_menu (grid, 0, 0, _("Radial gradient:"), gm);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->sflare_sizefac, gflare->sflare_sizefac);
  ed_put_gradient_menu (grid, 0, 1, _("Size factor gradient:"), gm);

  gm = gradient_menu_new ((GradientMenuCallback) &ed_gradient_menu_callback,
                          gflare->sflare_probability, gflare->sflare_probability);
  ed_put_gradient_menu (grid, 0, 2, _("Probability gradient:"), gm);

  gtk_widget_show (grid);

  /*
   *    Scales
   */


  frame = gimp_frame_new (_("Parameters"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  grid = gtk_grid_new ();
  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
  gtk_container_add (GTK_CONTAINER (frame), grid);

  row = 0;

  scale = gimp_scale_entry_new (_("Size (%):"), gflare->sflare_size, 0.0, G_MAXINT, 1);
  gimp_scale_entry_set_bounds (GIMP_SCALE_ENTRY (scale), 1.0, 200.0, TRUE);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->sflare_size);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Rotation:"), gflare->sflare_rotation, -180.0, 180.0, 1);
  gimp_label_spin_set_increments (GIMP_LABEL_SPIN (scale), 1.0, 15.0);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->sflare_rotation);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  scale = gimp_scale_entry_new (_("Hue rotation:"), gflare->sflare_hue, -180.0, 180.0, 1);
  gimp_label_spin_set_increments (GIMP_LABEL_SPIN (scale), 1.0, 15.0);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (gradient_scale_entry_update_double),
                    &gflare->sflare_hue);
  g_signal_connect (scale, "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);
  gtk_grid_attach (GTK_GRID (grid), scale, 0, row++, 3, 1);
  gtk_widget_show (scale);

  gtk_widget_show (grid);

  /*
   *    Shape Radio Button Frame
   */


  frame = gimp_frame_new (_("Shape of Second Flares"));
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  shape_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
  gtk_container_add (GTK_CONTAINER (frame), shape_vbox);
  gtk_widget_show (shape_vbox);

  toggle = gtk_radio_button_new_with_label (shape_group, _("Circle"));
  shape_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle));
  g_object_set_data (G_OBJECT (toggle), "gimp-item-data",
                     GINT_TO_POINTER (GF_CIRCLE));
  g_signal_connect (toggle, "toggled",
                    G_CALLBACK (ed_shape_radio_callback),
                    &gflare->sflare_shape);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
                                gflare->sflare_shape == GF_CIRCLE);
  gtk_box_pack_start (GTK_BOX (shape_vbox), toggle, FALSE, FALSE, 0);
  gtk_widget_show (toggle);

  polygon_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  gtk_box_pack_start (GTK_BOX (shape_vbox), polygon_hbox, FALSE, FALSE, 0);
  gtk_widget_show (polygon_hbox);

  toggle = ed->polygon_toggle =
    gtk_radio_button_new_with_label (shape_group, _("Polygon"));
  shape_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle));
  g_object_set_data (G_OBJECT (toggle), "gimp-item-data",
                     GINT_TO_POINTER (GF_POLYGON));
  g_signal_connect (toggle, "toggled",
                    G_CALLBACK (ed_shape_radio_callback),
                    &gflare->sflare_shape);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
                                gflare->sflare_shape == GF_POLYGON);
  gtk_box_pack_start (GTK_BOX (polygon_hbox), toggle, FALSE, FALSE, 0);
  gtk_widget_show (toggle);

  entry = ed->polygon_entry = gtk_entry_new ();
  gtk_entry_set_width_chars (GTK_ENTRY (entry), 4);
  g_snprintf (buf, sizeof (buf), "%d", gflare->sflare_nverts);
  gtk_entry_set_text (GTK_ENTRY (entry), buf);
  g_signal_connect (entry, "changed",
                    G_CALLBACK (ed_ientry_callback),
                    &gflare->sflare_nverts);
  gtk_box_pack_start (GTK_BOX (polygon_hbox), entry, FALSE, FALSE, 0);
  gtk_widget_show (entry);

  g_object_bind_property (toggle, "active",
                          entry,  "sensitive",
                          G_BINDING_SYNC_CREATE);

  /*
   *    Random Seed Entry
   */


  seed_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
  gtk_box_pack_start (GTK_BOX (vbox), seed_hbox, FALSE, FALSE, 0);
  gtk_widget_show (seed_hbox);

  label = gtk_label_new (_("Random seed:"));
  gtk_label_set_xalign (GTK_LABEL (label), 0.0);
  gtk_box_pack_start (GTK_BOX (seed_hbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  seed = gimp_random_seed_new (&gflare->sflare_seed, &gflare->random_seed);
  gtk_box_pack_start (GTK_BOX (seed_hbox), seed, FALSE, TRUE, 0);
  gtk_widget_show (seed);

  g_signal_connect (GIMP_RANDOM_SEED_SPINBUTTON_ADJ (seed), "value-changed",
                    G_CALLBACK (ed_preview_update),
                    NULL);

  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), vbox,
                            gtk_label_new_with_mnemonic (_("_Second Flares")));
  g_signal_connect (vbox, "map",
                    G_CALLBACK (ed_page_map_callback),
                    (gpointer) PAGE_SFLARE);
  gtk_widget_show (vbox);
}

GtkWidget *
ed_mode_menu_new (GFlareMode *mode_var)
{
  GtkWidget *combo = gimp_int_combo_box_new_array (GF_NUM_MODES,
                                                   gflare_menu_modes);

  gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), *mode_var,
                              G_CALLBACK (ed_mode_menu_callback),
                              mode_var, NULL);

  return combo;
}

/*
  puts gradient menu with caption into grid
  occupies 1 row and 3 cols in grid
 */

static void
ed_put_gradient_menu (GtkWidget    *grid,
                      gint          x,
                      gint          y,
                      const gchar  *caption,
                      GradientMenu *gm)
{
  GtkWidget *label;

  label = gtk_label_new (caption);
  gtk_label_set_xalign (GTK_LABEL (label), 1.0);
  gtk_widget_show (label);

  gtk_grid_attach (GTK_GRID (grid), label, x, y, 1, 1);
  gtk_grid_attach (GTK_GRID (grid), gm->preview, x + 1, y, 1, 1);
  gtk_grid_attach (GTK_GRID (grid), gm->combo, x + 2, y, 1, 1);
}

static void
ed_mode_menu_callback (GtkWidget *widget,
                       gpointer   data)
{
  gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), (gint *) data);

  ed_preview_update ();
}

static void
ed_gradient_menu_callback (const gchar *gradient_name,
                           gpointer     data)
{
  gchar *dest_string = data;

  /* @GRADIENT_NAME */
  gradient_name_copy (dest_string, gradient_name);
  ed_preview_update ();
}

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

  ed_preview_update ();
}

static void
ed_ientry_callback (GtkWidget *widget,
                    gpointer   data)
{
  gint new_val;

  new_val = atoi (gtk_entry_get_text (GTK_ENTRY (widget)));
  *(gint *)data = new_val;

  ed_preview_update ();
}

/*
  NOTE: This is hack, because this code depends on internal "map"
  signal of changing pages of gtknotebook.
 */

static void
ed_page_map_callback (GtkWidget *widget,
                      gpointer   data)
{
  gint page_num = GPOINTER_TO_INT (data);

  DEBUG_PRINT(("ed_page_map_callback\n"));

  ed->cur_page = page_num;
  ed_preview_update ();
}


static void
ed_preview_update (void)
{
  if (ed->init)
    return;

  ed->init_params_done = FALSE;
  preview_render_start (ed->preview);
}

static gint
ed_preview_init_func (Preview *preview, gpointer data)
{
  GimpProcedureConfig *config = GIMP_PROCEDURE_CONFIG (data);
  int                  type   = 0;

  if (ed->init_params_done == FALSE)
    {
      gdouble vangle;
      gdouble vlength;

      g_object_get (config,
                    "vector-angle",  &vangle,
                    "vector-length", &vlength,
                    NULL);
      switch (ed->cur_page)
        {
        case PAGE_GENERAL:
          type = (CALC_GLOW | CALC_RAYS | CALC_SFLARE);
          break;
        case PAGE_GLOW:
          type = CALC_GLOW;
          break;
        case PAGE_RAYS:
          type = CALC_RAYS;
          break;
        case PAGE_SFLARE:
          type = CALC_SFLARE;
          break;
        default:
          g_warning ("ed_preview_edit_func: bad page");
          break;
        }
      calc_init_params (ed->gflare, type,
                        ED_PREVIEW_WIDTH/2, ED_PREVIEW_HEIGHT/2,
                        ED_PREVIEW_WIDTH/2, 0.0, 0.0,
                        vangle, vlength);

      ed->init_params_done = TRUE;
      return TRUE;
    }
  return calc_init_progress ();
}

static void
ed_preview_deinit_func (Preview *preview, gpointer data)
{
  if (ed->init_params_done)
    {
      calc_deinit ();
      ed->init_params_done = FALSE;
    }
}

static void
ed_preview_render_func (Preview *preview,
                        guchar  *buffer,
                        gint     y,
                        gpointer data)
{
  switch (ed->cur_page)
    {
    case PAGE_GENERAL:
      ed_preview_render_general (buffer, y);
      break;
    case PAGE_GLOW:
      ed_preview_render_glow (buffer, y);
      break;
    case PAGE_RAYS:
      ed_preview_render_rays (buffer, y);
      break;
    case PAGE_SFLARE:
      ed_preview_render_sflare (buffer, y);
      break;
    default:
      g_warning ("hmm, bad page in ed_preview_render_func ()");
      break;
    }
}

static void
ed_preview_render_general (guchar *buffer, gint y)
{
  int           x, i;
  guchar        gflare_pix[4];
  static guchar src_pix[4] = {0, 0, 0, OPAQUE};
  int           gflare_a;

  for (x = 0; x < ED_PREVIEW_WIDTH; x++)
    {
      calc_gflare_pix (gflare_pix, x, y, src_pix);
      gflare_a = gflare_pix[3];

      for (i = 0; i < 3; i++)
        {
          *buffer++ = gflare_pix[i] * gflare_a / 255;
        }
    }
}

static void
ed_preview_render_glow (guchar *buffer, gint y)
{
  int           x, i;
  guchar        pix[4];

  for (x = 0; x < ED_PREVIEW_WIDTH; x++)
    {
      calc_glow_pix (pix, x, y);
      for (i = 0; i < 3; i++)
        *buffer++ = pix[i] * pix[3] / 255;
    }
}

static void
ed_preview_render_rays (guchar *buffer, gint y)
{
  int           x, i;
  guchar        pix[4];

  for (x = 0; x < ED_PREVIEW_WIDTH; x++)
    {
      calc_rays_pix (pix, x, y);
      for (i = 0; i < 3; i++)
        *buffer++ = pix[i] * pix[3] / 255;
    }
}

static void
ed_preview_render_sflare (guchar *buffer, gint y)
{
  int           x, i;
  guchar        pix[4];
  static guchar src_pix[4] = {0, 0, 0, OPAQUE};

  for (x = 0; x < ED_PREVIEW_WIDTH; x++)
    {
      calc_sflare_pix (pix, x, y, src_pix);
      for (i = 0; i < 3; i++)
        *buffer++ = pix[i] * pix[3] / 255;
    }
}

/*************************************************************************/
/**                                                                     **/
/**                     +++ Preview                                     **/
/**                                                                     **/
/*************************************************************************/

/*
        this is generic preview routines.
 */



/*
    Routines to render the preview in background
 */

static Preview *
preview_new (gint                  width,
             gint                  height,
             PreviewInitFunc       init_func,
             gpointer              init_data,
             PreviewRenderFunc     render_func,
             gpointer              render_data,
             PreviewDeinitFunc     deinit_func,
             gpointer              deinit_data)
{
  Preview *preview;

  preview = g_new0 (Preview, 1);

  preview->widget = gimp_preview_area_new ();
  gtk_widget_set_size_request (preview->widget, width, height);
  gtk_widget_show (preview->widget);

  preview->width           = width;
  preview->height          = height;
  preview->init_func       = init_func;
  preview->init_data       = init_data;
  preview->render_func     = render_func;
  preview->render_data     = render_data;
  preview->deinit_func     = deinit_func;
  preview->deinit_data     = deinit_data;
  preview->idle_tag        = 0;
  preview->buffer          = g_new (guchar, width * 3);
  preview->full_image_buffer = g_new (guchar, width * height * 3);

  return preview;
}

static void
preview_free (Preview *preview)
{
  preview_render_end (preview);
  /* not destroy preview->widget */
  g_free (preview->buffer);
  g_free (preview->full_image_buffer);
  g_free (preview);
}

/*
  Start rendering of the preview in background using an idle event.
  If already started and not yet finished, stop it first.
 */

static void
preview_render_start (Preview *preview)
{
  preview_render_end (preview);

  preview->init_done = FALSE;
  preview->current_y = 0;
  preview->drawn_y = 0;
  preview->timeout_tag = g_timeout_add (100,
                                        (GSourceFunc) preview_render_start_2,
                                        preview);
}

static gint
preview_render_start_2 (Preview *preview)
{
  preview->timeout_tag = 0;
  preview->idle_tag = g_idle_add ((GSourceFunc) preview_handle_idle, preview);
  return FALSE;
}

static void
preview_render_end (Preview *preview)
{
  if (preview->timeout_tag > 0)
    {
      g_source_remove (preview->timeout_tag);
      preview->timeout_tag = 0;
    }
  if (preview->idle_tag > 0)
    {
      if (preview->deinit_func)
        (*preview->deinit_func) (preview, preview->deinit_data);

      g_source_remove (preview->idle_tag);
      preview->idle_tag = 0;
    }
}

/*
  Handle an idle event.
  Return FALSE if done, TRUE otherwise.
 */

static gboolean
preview_handle_idle (Preview *preview)
{
  gboolean done = FALSE;

  if (preview->init_done == FALSE)
    {
      if (preview->init_func &&
          (*preview->init_func) (preview, preview->init_data))
        return TRUE;

      preview->init_done = TRUE;
    }

  if (preview->render_func)
    (*preview->render_func) (preview, preview->buffer, preview->current_y,
                             preview->render_data);
  else
    memset (preview->buffer, 0, preview->width * 3);

  memcpy (preview->full_image_buffer + preview->width * 3 * preview->current_y,
          preview->buffer,
          preview->width * 3);

  if (++preview->current_y >= preview->height)
    done = TRUE;

  if (done)
    {
      gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview->widget),
                              0, 0, preview->width, preview->height,
                              GIMP_RGB_IMAGE,
                              preview->full_image_buffer,
                              preview->width * 3);

      preview->drawn_y = preview->current_y;
      preview_render_end (preview);
      return FALSE;
    }

  return TRUE;
}

/*
  Convert RGBA to RGB with rendering gray check if needed.
        (from nova.c)
  input:  guchar src[4]         RGBA pixel
  output: guchar dest[3]        RGB pixel
 */


static void
preview_rgba_to_rgb (guchar *dest,
                     gint    x,
                     gint    y,
                     guchar *src)
{
  gint src_a;
  gint check;
  gint b;

  src_a = src[3];

  if (src_a == OPAQUE)  /* full opaque */
    {
      for (b = 0; b < 3; b++)
        dest[b] = src[b];
    }
  else
    {
      if ((x % (GIMP_CHECK_SIZE) < GIMP_CHECK_SIZE_SM) ^
          (y % (GIMP_CHECK_SIZE) < GIMP_CHECK_SIZE_SM))
        check = GIMP_CHECK_LIGHT * 255;
      else
        check = GIMP_CHECK_DARK * 255;

      if (src_a == 0)   /* full transparent */
        {
          for (b = 0; b < 3; b++)
            dest[b] = check;
        }
      else
        {
          for (b = 0; b < 3; b++)
            dest[b] = (src[b] * src_a + check * (OPAQUE-src_a)) / OPAQUE;
        }
    }
}

/*************************************************************************/
/**                                                                     **/
/**                     +++ Gradient Menu                               **/
/**                     +++ gm                                          **/
/**                                                                     **/
/*************************************************************************/

static void
gradient_menu_init (void)
{
  gm_gradient_get_list ();
  gradient_menus = NULL;
}

static void
gradient_menu_rescan (void)
{
  GList         *tmp;
  GradientMenu  *gm;
  GtkTreeModel  *model;

  /* Detach and destroy menus first */
  tmp = gradient_menus;
  while (tmp)
    {
      gm  = tmp->data;
      tmp = tmp->next;

      model = gtk_combo_box_get_model (GTK_COMBO_BOX (gm->combo));
      gtk_list_store_clear (GTK_LIST_STORE (model));
    }

  gm_gradient_get_list ();

  tmp = gradient_menus;
  while (tmp)
    {
      gm  = tmp->data;
      tmp = tmp->next;

      gm_gradient_combo_fill (gm, gm->gradient_name);
    }
}

static GradientMenu *
gradient_menu_new (GradientMenuCallback callback,
                   gpointer             callback_data,
                   const gchar         *default_gradient_name)
{
  GradientMenu *gm = g_new (GradientMenu, 1);

  gm->callback = callback;
  gm->callback_data = callback_data;

  gm->preview = gimp_preview_area_new ();
  gtk_widget_set_size_request (gm->preview,
                               GM_PREVIEW_WIDTH, GM_PREVIEW_HEIGHT);

  gm->combo = g_object_new (GIMP_TYPE_INT_COMBO_BOX, NULL);

  g_signal_connect (gm->combo, "changed",
                    G_CALLBACK (gm_gradient_combo_callback),
                    gm);

  gm_gradient_combo_fill (gm, default_gradient_name);

  gtk_widget_show (gm->preview);
  gtk_widget_show (gm->combo);

  g_signal_connect (gm->combo, "destroy",
                    G_CALLBACK (gm_combo_destroy_callback),
                    gm);

  gradient_menus = g_list_append (gradient_menus, gm);

  return gm;
}

/* Local Functions */

static void
gm_gradient_get_list (void)
{
  int   i;

  if (gradient_names)
    {
      for (i = 0; i < num_gradient_names; i++)
        g_free (gradient_names[i]);
      g_free (gradient_names);
    }

  gradient_cache_flush ();      /* to make sure */
  gradient_names = gradient_get_list (&num_gradient_names);
}

static void
gm_gradient_combo_fill (GradientMenu *gm,
                        const gchar  *default_gradient)
{
  gint active = 0;
  gint i;

  for (i = 0; i < num_gradient_names; i++)
    {
      const gchar *name = gradient_names[i];

      if (strcmp (name, default_gradient) == 0)
        active = i;

      gimp_int_combo_box_append (GIMP_INT_COMBO_BOX (gm->combo),
                                 GIMP_INT_STORE_VALUE, i,
                                 GIMP_INT_STORE_LABEL, name,
                                 -1);
    }

  gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (gm->combo), active);
}

static void
gm_gradient_combo_callback (GtkWidget *widget,
                            gpointer   data)
{
  GradientMenu *gm = data;
  const gchar  *gradient_name;
  gint          index;

  if (! gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &index) ||
      index < 0 ||
      index >= num_gradient_names)
    return;

  gradient_name = gradient_names[index];

  DEBUG_PRINT(("gm_combo_callback\n"));

  gradient_name_copy (gm->gradient_name, gradient_name);

  gm_preview_draw (gm->preview, gradient_name);

  if (gm->callback)
    (* gm->callback) (gradient_name, gm->callback_data);
}

static void
gm_preview_draw (GtkWidget   *preview,
                 const gchar *gradient_name)
{
  guchar      values[GM_PREVIEW_WIDTH][4];
  gint        nvalues = GM_PREVIEW_WIDTH;
  gint        row, irow, col;
  guchar      dest_row[GM_PREVIEW_WIDTH][3];
  guchar     *dest;
  guchar     *src;
  gint        check, b;
  guchar     *dest_total_preview_buffer;
  const gint  alpha = 3;

  gradient_get_values (gradient_name, (guchar *)values, nvalues);

  dest_total_preview_buffer = g_new (guchar,
                                     GM_PREVIEW_HEIGHT * GM_PREVIEW_WIDTH * 3);

  for (row = 0; row < GM_PREVIEW_HEIGHT; row += GIMP_CHECK_SIZE_SM)
    {
      for (col = 0; col < GM_PREVIEW_WIDTH; col++)
        {
          dest = dest_row[col];
          src  = values[col];

          if (src[alpha] == OPAQUE)
            {
              /* no alpha channel or opaque -- simple way */
              for (b = 0; b < alpha; b++)
                dest[b] = src[b];
            }
          else
            {
              /* more or less transparent */
              if((col % (GIMP_CHECK_SIZE) < GIMP_CHECK_SIZE_SM) ^
                  (row % (GIMP_CHECK_SIZE) < GIMP_CHECK_SIZE_SM))
                {
                  check = GIMP_CHECK_LIGHT * 255;
                }
              else
                {
                  check = GIMP_CHECK_DARK * 255;
                }

              if (src[alpha] == 0)
                {
                  /* full transparent -- check */
                  for (b = 0; b < alpha; b++)
                    dest[b] = check;
                }
              else
                {
                  /* middlemost transparent -- mix check and src */
                  for (b = 0; b < alpha; b++)
                    dest[b] = (src[b] * src[alpha] +
                               check * (OPAQUE - src[alpha])) / OPAQUE;
                }
            }
        }

      for (irow = 0;
           irow < GIMP_CHECK_SIZE_SM && row + irow < GM_PREVIEW_HEIGHT;
           irow++)
        {
          memcpy (dest_total_preview_buffer + (row+irow) * 3 * GM_PREVIEW_WIDTH,
                  dest_row,
                  GM_PREVIEW_WIDTH * 3);
        }
    }

    gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview),
                            0, 0, GM_PREVIEW_WIDTH, GM_PREVIEW_HEIGHT,
                            GIMP_RGB_IMAGE,
                            (const guchar *) dest_total_preview_buffer,
                            GM_PREVIEW_WIDTH * 3);

    g_free (dest_total_preview_buffer);
}

static void
gm_combo_destroy_callback (GtkWidget *w,
                           gpointer   data)
{
  GradientMenu *gm = data;
  gradient_menus = g_list_remove (gradient_menus, gm);
}

/*************************************************************************/
/**                                                                     **/
/**                     +++ Gradients                                   **/
/**                                                                     **/
/*************************************************************************/

/*
    Manage both internal and external gradients: list up, cache,
    sampling, etc.

    External gradients are cached.
 */



static void
gradient_name_copy (gchar       *dest,
                    const gchar *src)
{
  g_strlcpy (dest, src, GRADIENT_NAME_MAX);
}

/*
  Translate SPACE to "\\040", etc.
 */

static void
gradient_name_encode (gchar       *dest,
                      const gchar *src)
{
  gint cnt = GRADIENT_NAME_MAX - 1;

  while (*src && cnt--)
    {
      if (g_ascii_iscntrl (*src) || g_ascii_isspace (*src) || *src == '\\')
        {
          sprintf (dest, "\\%03o", *src++);
          dest += 4;
        }
      else
        *dest++ = *src++;
    }
  *dest = '\0';
}

/*
  Translate "\\040" to SPACE, etc.
 */

static void
gradient_name_decode (gchar       *dest,
                      const gchar *src)
{
  gint  cnt = GRADIENT_NAME_MAX - 1;
  guint tmp;

  while (*src && cnt--)
    {
      if (*src == '\\' && *(src+1) && *(src+2) && *(src+3))
        {
          sscanf (src+1, "%3o", &tmp);
          *dest++ = tmp;
          src += 4;
        }
      else
        *dest++ = *src++;
    }
  *dest = '\0';
}

static void
gradient_init (void)
{
  gradient_cache_head = NULL;
  gradient_cache_count = 0;
}

static void
gradient_free (void)
{
  gradient_cache_flush ();
}

static gchar **
gradient_get_list (gint *num_gradients)
{
  gchar        **gradients;
  GimpGradient **external_gradients  = NULL;
  gint           external_ngradients = 0;
  gint           i, n;

  gradient_cache_flush ();
  external_gradients = gimp_gradients_get_list (NULL);
  external_ngradients = gimp_core_object_array_get_length ((GObject **) external_gradients);

  *num_gradients = G_N_ELEMENTS (internal_gradients) + external_ngradients;
  gradients = g_new (gchar *, *num_gradients);

  n = 0;
  for (i = 0; i < G_N_ELEMENTS (internal_gradients); i++)
    {
      gradients[n++] = g_strdup (internal_gradients[i]);
    }
  for (i = 0; i < external_ngradients; i++)
    {
      gradients[n++] = gimp_resource_get_name ((GimpResource *) external_gradients[i]);
    }

  g_free (external_gradients);

  return gradients;
}

static void
gradient_get_values (const gchar *gradient_name,
                     guchar      *values,
                     gint         nvalues)
{
  /*
    Criteria to distinguish internal and external is rather simple here.
    It should be fixed later.
   */

  if (gradient_name[0] == '%')
    gradient_get_values_internal (gradient_name, values, nvalues);
  else
    gradient_get_values_external (gradient_name, values, nvalues);
}

static void
gradient_get_values_internal (const gchar *gradient_name,
                              guchar      *values,
                              gint         nvalues)
{
  const guchar white[4]        = { 255, 255, 255, 255 };
  const guchar white_trans[4]  = { 255, 255, 255, 0   };
  const guchar red_trans[4]    = { 255, 0,   0,   0   };
  const guchar blue_trans[4]   = { 0,   0,   255, 0   };
  const guchar yellow_trans[4] = { 255, 255, 0,   0   };

  /*
    The internal gradients here are example --
    What kind of internals would be useful ?
   */

  if(!strcmp(gradient_name, "%white"))
    {
      gradient_get_blend (white, white, values, nvalues);
    }
  else if(!strcmp(gradient_name, "%white_grad"))
    {
      gradient_get_blend (white, white_trans, values, nvalues);
    }
  else if (!strcmp (gradient_name, "%red_grad"))
    {
      gradient_get_blend (white, red_trans, values, nvalues);
    }
  else if (!strcmp (gradient_name, "%blue_grad"))
    {
      gradient_get_blend (white, blue_trans, values, nvalues);
    }
  else if (!strcmp (gradient_name, "%yellow_grad"))
    {
      gradient_get_blend (white, yellow_trans, values, nvalues);
    }
  else if (!strcmp (gradient_name, "%random"))
    {
      gradient_get_random (values, nvalues);
    }
  else
    {
      gradient_get_default (gradient_name, values, nvalues);
    }
}

static void
gradient_get_blend (const guchar *fg,
                    const guchar *bg,
                    guchar       *values,
                    gint          nvalues)
{
  gdouble  x;
  gint     i;
  gint     j;
  guchar  *v = values;

  for (i = 0; i < nvalues; i++)
    {
      x = (double) i / nvalues;
      for (j = 0; j < 4; j++)
        *v++ = fg[j] * (1 - x) + bg[j] * x;
    }
}

static void
gradient_get_random (guchar *values,
                     gint    nvalues)
{
  gint    i;
  gint    j;
  gint    inten;
  guchar *v = values;

  /*
    This is really simple  -- gaussian noise might be better
   */

  for (i = 0; i < nvalues; i++)
    {
      inten = g_random_int_range (0, 256);
      for (j = 0; j < 3; j++)
        *v++ = inten;
      *v++ = 255;
    }
}

static void
gradient_get_default (const gchar *name,
                      guchar      *values,
                      gint         nvalues)
{
  gdouble  e[3];
  gdouble  x;
  gint     i;
  gint     j;
  guchar  *v = values;

  /*
    Create gradient by name
   */

  name++;
  for (j = 0; j < 3; j++)
    e[j] = name[j] / 255.0;

  for (i = 0; i < nvalues; i++)
    {
      x = (double) i / nvalues;
      for (j = 0; j < 3; j++)
        *v++ = 255 * pow (x, e[j]);
      *v++ = 255;
    }
}

/*
  Caching gradients is really needed. It really takes 0.2 seconds each
  time to resample an external gradient. (And this plug-in has
  currently 6 gradient menus.)

  However, this caching routine is not too good. It picks up just
  GRADIENT_RESOLUTION samples every time, and rescales it later.  And
  cached values are stored in guchar array. No accuracy.
 */

static void
gradient_get_values_external (const gchar *gradient_name,
                              guchar      *values,
                              gint         nvalues)
{
  GradientCacheItem *ci;
  gboolean           found;
#ifdef DEBUG
  clock_t            clk = clock ();
#endif

  g_return_if_fail (nvalues >= 2);

  ci = gradient_cache_lookup (gradient_name, &found);
  if (!found)
    {
      /* FIXME: "reverse" hardcoded to FALSE. */
      gradient_get_values_real_external (gradient_name, ci->values,
                                         GRADIENT_RESOLUTION, FALSE);
    }
  if (nvalues == GRADIENT_RESOLUTION)
    {
      memcpy (values, ci->values, 4 * GRADIENT_RESOLUTION);
    }
  else
    {
      double    pos, frac;
      int       ipos;
      int       i, j;

      for (i = 0; i < nvalues; i++)
        {
          pos = ((double) i / (nvalues - 1)) * (GRADIENT_RESOLUTION - 1);
          g_assert (0 <= pos && pos <= GRADIENT_RESOLUTION - 1);
          ipos = (int) pos; frac = pos - ipos;
          if (frac == 0.0)
            {
              memcpy (&values[4 * i], &ci->values[4 * ipos], 4);
            }
          else
            for (j = 0; j < 4; j++)
              values[4 * i + j] = ci->values[4 * ipos + j] * (1 - frac)
                                + ci->values[4 * (ipos + 1) + j] * frac;
        }
    }

#ifdef DEBUG
  get_values_external_clock += clock () - clk;
  get_values_external_count ++;
#endif

}

static void
gradient_get_values_real_external (const gchar *gradient_name,
                                   guchar      *values,
                                   gint         nvalues,
                                   gboolean     reverse)
{
  GimpGradient  *gradient;
  GeglColor    **colors;
  const Babl    *format = babl_format ("R'G'B'A u8");
  gint           i;

  gradient = gimp_gradient_get_by_name (gradient_name);

  colors = gimp_gradient_get_uniform_samples (gradient, nvalues, reverse);

  for (i = 0; i < nvalues; i++)
    gegl_color_get_pixel (colors[i], format, (void *) &values[4 * i]);

  gimp_color_array_free (colors);
}

void
gradient_cache_flush (void)
{
  GradientCacheItem *ci;
  GradientCacheItem *tmp;

  ci = gradient_cache_head;
  while (ci)
    {
      tmp = ci->next;
      g_free (ci);
      ci = tmp;
    }
  gradient_cache_head = NULL;
  gradient_cache_count = 0;
}

static GradientCacheItem *
gradient_cache_lookup (const gchar *name,
                       gboolean    *found)
{
  GradientCacheItem *ci;

  ci = gradient_cache_head;
  while (ci)
    {
      if (!strcmp (ci->name, name))
        break;
      ci = ci->next;
    }
  if (ci)
    {
      *found = TRUE;
      if (!ci->prev)
        {
          g_assert (ci == gradient_cache_head);
          return ci;
        }
      ci->prev->next = ci->next;
      if (ci->next)
        ci->next->prev = ci->prev;
      ci->next = gradient_cache_head;
      gradient_cache_head->prev = ci;
      gradient_cache_head = ci;
      ci->prev = NULL;
      return ci;
    }
  else
    {
      *found = FALSE;
      while (gradient_cache_count >= GRADIENT_CACHE_SIZE)
        gradient_cache_zorch();
      ci = g_new (GradientCacheItem, 1);
      g_strlcpy (ci->name, name, GRADIENT_NAME_MAX);
      ci->next = gradient_cache_head;
      ci->prev = NULL;
      if (gradient_cache_head)
        gradient_cache_head->prev = ci;
      gradient_cache_head = ci;
      ++gradient_cache_count;
      return ci;
    }
}

static void
gradient_cache_zorch (void)
{
  GradientCacheItem *ci = gradient_cache_head;

  while (ci && ci->next)
    {
      ci = ci->next;
    }
  if (ci)
    {
      g_assert (ci->next == NULL);
      if (ci->prev)
        ci->prev->next = NULL;
      else
        gradient_cache_head = NULL;
      g_free (ci);
      --gradient_cache_count;
    }
}

static void
gradient_scale_entry_update_double (GimpLabelSpin *entry,
                                    gdouble       *value)
{
  *value = gimp_label_spin_get_value (entry);
}

static void
gradient_scale_entry_update_int (GimpLabelSpin *entry,
                                 gint          *value)
{
  *value = (gint) gimp_label_spin_get_value (entry);
}

#ifdef DEBUG
void
gradient_report (void)
{
  double total = (double) get_values_external_clock / CLOCKS_PER_SEC;

  g_printerr ("gradient_get_values_external "
              "%.2f sec. / %d times (ave %.2f sec.)\n",
              total,
              get_values_external_count,
              total / get_values_external_count);
}
#endif

Messung
CHZG
839610093

¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.133Bemerkung:  (vorverarbeitet am  2026-11-04) ¤

*Bot Zugriff






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104851
#Domains=417127