/*
 * @(#) $Id: plasma2.c,v 1.20 2004/06/12 21:54:30 yeti Exp $
 * This is a plugin for the GIMP.
 *
 * Copyright (C) 1996 Stephen Norris
 * Copyright (C) 2001-2002,2004 Yeti (David Necas)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

/* XXX  The following version notes are historical and pertain to the
 * XXX  original Plasma plug-in.  See the NEWS file for Plasma2 version
 * XXX  notes.
 */

/*
 * This plug-in produces plasma fractal images. The algorithm is losely
 * based on a description of the fractint algorithm, but completely
 * re-implemented because the fractint code was too ugly to read :)
 *
 * Please send any patches or suggestions to me: srn@flibble.cs.su.oz.au.
 */

/* Version 1.01 */

/*
 * Ported to GIMP Plug-in API 1.0
 *    by Eiichi Takamori <taka@ma1.seikyou.ne.jp>
 *
 * A few functions names and their order are changed :)
 * Plasma implementation almost hasn't been changed.
 *
 * Feel free to correct my WRONG English, or to modify Plug-in Path,
 * and so on. ;-)
 *
 * Version 1.02
 *
 * May 2000
 * tim copperfield [timecop@japan.co.jp]
 * Added dynamic preview mode.
 *
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#else
   /* To include libgimp/stdplugins-intl.h */
#  define GETTEXT_PACKAGE "gimp20"
#endif /* HAVE_CONFIG_H */

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

#include <gtk/gtk.h>

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"

#define PROCEDURE_NAME   "plasma2"
#define DATA_KEY_VALS    "plug_in_plasma2"

/* we are not 100% 16-bit-ready, but partially yes (almost everything is done
   on floats anyway) */
#define CHANNEL_MAX_VALUE     255
#define BITS_PER_SAMPLE         8

#define ENTRY_WIDTH            75
#define SCALE_WIDTH           128
#define PREVIEW_SIZE          128
#define CELL_SIZE_WIDTH        84

/* XXX: we cannot store true strings as parameters, hope this is enough
 * FIXME: maybe we can now? */
#define GRADIENT_NAME_SIZE    256

/* generally, we need much more than 256
   note we do NOT do our own subsampling, just rounding */
#define GRADIENT_SAMPLE_SIZE  8192

/* just some small but definitely nonzero number (it should be several orders
   larger than machine epsilon, at least) */
#define EPS 0.0000001

enum {
  PLASMA2_RESPONSE_RESET = 1,
  PLASMA2_RESPONSE_DEFAULTS,
  PLASMA2_RESPONSE_ABOUT
};

typedef struct {
  gint     seed;
  gint     htil;
  gint     vtil;
  gint     rdt;
  gint     fsf;
  gdouble  lfwhm;
  gdouble  a;
  gint     cm;
  gchar    grad[GRADIENT_NAME_SIZE];
  gboolean random_seed;
} PlasmaValues;

typedef gdouble (*NoisyAverageFunc)(gdouble v1, gdouble v2);

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

static gint    plasma2_dialog             (void);
static void    plasma2_refresh_controls   (const PlasmaValues *new_pvals);
static void    plasma2_rdt_changed        (GtkWidget *widget,
                                           gpointer data);
static void    plasma2_fsf_changed        (GtkWidget *widget,
                                           gpointer data);
static void    plasma2_cm_changed         (GtkWidget *widget,
                                           gpointer data);
static void    plasma2_gradient_changed   (const gchar *name,
                                           gint width,
                                           const gdouble *grad_data,
                                           gboolean dialog_closing,
                                           gpointer user_data);
static void    about_dialog               (GtkWidget *parent);

static void    plasma2                    (GimpDrawable *drawable);
static void    init_plasma2               (GimpDrawable *drawable);
static void    generate_plasma_channel    (gint xs,
                                           gint ys,
                                           const gint channel);
static void    commit                     (GimpDrawable *drawable,
                                           const gint channel);
static void    commit_one_tile            (GimpPixelRgn *rgn,
                                           const gint channel,
                                           const gint gradwidth);
static void    plasma2_add_transparency   (const gint channel);
static void    end_plasma2                (GimpDrawable *drawable);
static gdouble average_with_noise_uniform (gdouble v1,
                                           gdouble v2);
static gdouble average_with_noise_cauchy  (gdouble v1,
                                           gdouble v2);
static gdouble average_with_noise_exp     (gdouble v1,
                                           gdouble v2);
static gdouble average_with_noise_pow3m   (gdouble v1,
                                           gdouble v2);

/***** Local vars *****/

typedef struct {
  GtkWidget *seed;
  GtkWidget *htil;
  GtkWidget *vtil;
  GtkWidget *rdt;
  GtkWidget *fsf;
  GtkObject *lfwhm;
  GtkObject *a;
  GtkWidget *cm;
  GtkWidget *grad;
  GtkWidget *grad_label; /* for `inactivating' the label */
} PlasmaControls;

static PlasmaControls pctrl;

typedef enum {
  PLASMA2_RDT_UNIFORM = 0,
  PLASMA2_RDT_CAUCHY = 1,
  PLASMA2_RDT_EXP = 2,
  PLASMA2_RDT_POW3M = 3,
} PlasmaRandomDistributionType;

typedef enum {
  PLASMA2_FSF_EXP = 0,
  PLASMA2_FSF_POWER = 1,
} PlasmaFWHMScaleFunction;

typedef enum {
  PLASMA2_CM_INDRGB = 0,
  PLASMA2_CM_GRAY = 1,
  PLASMA2_CM_GRADIENT = 2,
  PLASMA2_CM_GREMIX = 3,
} PlasmaColoringMethod;

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

static const PlasmaValues pvals_defaults = {
  11,    /* seed */
  FALSE, /* htil */
  FALSE, /* vtil */
  PLASMA2_RDT_UNIFORM, /* rdt */
  PLASMA2_FSF_EXP, /* fsf */
  0.0,   /* lfwhm */
  1.0,   /* a */
  PLASMA2_CM_INDRGB,   /* cm */
  "",   /* grad */
  FALSE,  /* random_seed */
};

static const NoisyAverageFunc noisy_average_funcs[] = {
  &average_with_noise_uniform,
  &average_with_noise_cauchy,
  &average_with_noise_exp,
  &average_with_noise_pow3m,
};

static PlasmaValues pvals; /* = pvals_defaults; */
static PlasmaValues pvals_old; /* for revert */

static GtkWidget *preview_image = NULL;
static GdkPixbuf *preview = NULL;
static gboolean preview_mode;
static gboolean is_rgb;
static GimpDrawable *drawable;
static gdouble *gradient = NULL;
static gint gradient_p_width = -1;
static GRand *rng = NULL;  /* random generator */

/*
 * Some globals to save passing too many paramaters that don't change.
 */

static gint     ix1, iy1, ix2, iy2;     /* Selected image size. */
static gint     bpp, has_alpha, alpha;
static glong    max_progress, progress;
static gfloat   **plasma; /* plasma channel buffer */
static gdouble  fwhm, randomness; /* true and recomputed fwhm */
static gdouble  xscale, yscale; /* scale (for noise amplitude) */
static NoisyAverageFunc noisy_average = NULL;

/***** Functions *****/

MAIN()

static void
query(void)
{
  static GimpParamDef args[]= {
    { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" },
    { GIMP_PDB_IMAGE, "image", "Input image (unused)" },
    { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
    { GIMP_PDB_INT32, "seed", "Random seed" },
    { GIMP_PDB_INT32, "htil", "Horizontal tileability" },
    { GIMP_PDB_INT32, "vtil", "Vertical tileability" },
    { GIMP_PDB_INT32, "rdt", "Noise distribution type: { UNIFORM (0), CAUCHY (2), EXPONENTIAL (2), CUBIC_TAILS (3) }" },
    { GIMP_PDB_INT32, "fsf", "Noise scaling function: { FRACTINT_LIKE (0), NORRIS_LIKE (1) }" },
    { GIMP_PDB_FLOAT, "lfwhm", "Noise FWHM logarithm" },
    { GIMP_PDB_FLOAT, "a", "Scaling parameter a" },
    { GIMP_PDB_INT32, "cm", "Coloring method: { INDEPENDENT_RGB (0), GRAYSCALE (1), GRADIENT (2), GRADIENT_REMIX (3) }" },
    { GIMP_PDB_STRING, "grad", "Gradient used for coloring (when cm is GRADIENT)" },
  };

  gimp_install_procedure
    (DATA_KEY_VALS,
     "Create a plasma cloud like image to the specified drawable",
     "This plugin creates several types of cloud like images, "
     "mostly variations on the famous `plasma fractal'.",
     "Yeti & authors of original Plasma: "
     "Stephen Norris & (ported to 1.0 by) Eiichi Takamori",
     "Stephen Norris & Yeti",
     "Nov 2002",
     N_("<Image>/Filters/Render/Clouds/Plasma2..."),
     "RGB*, GRAY*",
     GIMP_PLUGIN,
     G_N_ELEMENTS(args), 0, args, NULL);
}

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

  run_mode = param[0].data.d_int32;

  *nreturn_vals = 1;
  *return_vals = values;

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

  /*  Get the specified drawable  */
  drawable = gimp_drawable_get(param[2].data.d_drawable);
  is_rgb = gimp_drawable_is_rgb(drawable->drawable_id);

  pvals = pvals_defaults;
  rng = g_rand_new();
  switch (run_mode) {
    case GIMP_RUN_INTERACTIVE:
      /*  Possibly retrieve data  */
      gimp_get_data(DATA_KEY_VALS, &pvals);

      /*  First acquire information with a dialog  */
      pvals_old = pvals;

      if (!plasma2_dialog()) {
        gimp_drawable_detach(drawable);
        return;
      }
      break;

    case GIMP_RUN_NONINTERACTIVE:
      /*  Make sure all the arguments are there!  */
      if (nparams != 12)
        status = GIMP_PDB_CALLING_ERROR;
      else {
        pvals.seed = (gint)param[3].data.d_int32;
        pvals.htil = (gint)param[4].data.d_int32;
        pvals.vtil = (gint)param[5].data.d_int32;
        pvals.rdt = (PlasmaRandomDistributionType)param[6].data.d_int32;
        pvals.fsf = (PlasmaFWHMScaleFunction)param[7].data.d_int32;
        pvals.lfwhm = (gdouble)param[8].data.d_float;
        pvals.a = (gdouble)param[9].data.d_float;
        pvals.cm = (PlasmaColoringMethod)param[10].data.d_int32;
        strncpy(pvals.grad, (gchar*)param[11].data.d_string,
                GRADIENT_NAME_SIZE-1);
        pvals.grad[GRADIENT_NAME_SIZE-1] = '\0';
      }
      break;

    case GIMP_RUN_WITH_LAST_VALS:
      /*  Possibly retrieve data  */
      gimp_get_data(DATA_KEY_VALS, &pvals);
      break;

    default:
      break;
  }

  if (status == GIMP_PDB_SUCCESS) {
    /*  Make sure that the drawable is gray or RGB color  */
    if (is_rgb || gimp_drawable_is_gray(drawable->drawable_id)) {
      gimp_progress_init(_("Plasma2..."));

      preview_mode = FALSE;
      plasma2(drawable);

      if (run_mode != GIMP_RUN_NONINTERACTIVE)
        gimp_displays_flush();

      /*  Store data  */
      if (run_mode == GIMP_RUN_INTERACTIVE
          || run_mode == GIMP_RUN_WITH_LAST_VALS)
        gimp_set_data(DATA_KEY_VALS, &pvals, sizeof(PlasmaValues));
    }
    else status = GIMP_PDB_EXECUTION_ERROR;
  }

  values[0].data.d_status = status;
  gimp_drawable_detach(drawable);
}

/* remove some source code redundancies {{{ */
static inline void
yeti_table_attach_to_row(GtkWidget *table,
                         GtkWidget *child,
                         const gint row)
{
  gtk_table_attach(GTK_TABLE(table), child, 0, GTK_TABLE(table)->ncols,
                   row, row+1, GTK_EXPAND|GTK_FILL, 0, 0, 0);
}

static inline GtkWidget*
yeti_label_new_in_table(const gchar *name,
                        GtkWidget *table,
                        const gint row,
                        const gboolean sensitive)
{
  GtkWidget *label;

  label = gtk_label_new_with_mnemonic(name);
  gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
  gtk_table_attach(GTK_TABLE(table), label, 0, 1, row, row+1,
                   GTK_EXPAND|GTK_FILL, 0, 0, 0);
  gtk_widget_set_sensitive(label, sensitive);

  return label;
}

static inline GtkWidget*
yeti_frame_new_in_box(const gchar *name,
                      GtkWidget *box,
                      const gboolean expand,
                      const gboolean fill)
{
  GtkWidget *frame;

  frame = gtk_frame_new(name);
  gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start(GTK_BOX(box), frame, expand, fill, 0);

  return frame;
}

static inline GtkWidget*
yeti_table_new_in_frame(const gint cols,
                        const gint rows,
                        GtkWidget *frame)
{
  GtkWidget *table;
  GtkWidget *align;

  align = gtk_alignment_new(0.5, 0.0, 1.0, 0.0);
  gtk_container_set_border_width(GTK_CONTAINER(align), 0);
  gtk_container_add(GTK_CONTAINER(frame), align);

  table = gtk_table_new(cols, rows, FALSE);
  gtk_table_set_col_spacings(GTK_TABLE(table), 4);
  gtk_table_set_row_spacings(GTK_TABLE(table), 2);
  gtk_container_set_border_width(GTK_CONTAINER(table), 4);
  gtk_container_add(GTK_CONTAINER(align), table);

  return table;
}

static inline GtkWidget*
yeti_check_button_new_with_label(const gchar *name,
                                 gint *value,
                                 GCallback cb,
                                 gpointer data)
{
  GtkWidget *check;

  check = gtk_check_button_new_with_mnemonic(name);
  g_signal_connect(check, "toggled",
                   G_CALLBACK(gimp_toggle_button_update), value);
  g_signal_connect_swapped(check, "toggled", cb, data);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), *value);

  return check;
}

static inline GtkWidget*
yeti_preview_frame_new_in_box(GtkWidget *hbox)
{
  GtkWidget *frame;
  GtkWidget *align;
  GtkWidget *image;
  GdkPixbuf *pixbuf;

  frame = yeti_frame_new_in_box(_("Preview"), hbox, FALSE, FALSE);

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

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

  pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB,
                          has_alpha,
                          BITS_PER_SAMPLE,
                          PREVIEW_SIZE,
                          PREVIEW_SIZE);
  gdk_pixbuf_fill(pixbuf, 0);

  image = gtk_image_new_from_pixbuf(pixbuf);
  g_object_unref(pixbuf);
  gtk_container_add(GTK_CONTAINER(frame), image);

  return frame;
}

static inline GtkObject*
yeti_scale_entry_new_double(const gchar *name,
                            GtkWidget *table,
                            const gint row,
                            double *value,
                            const double min,
                            const double max,
                            const double step,
                            const double page,
                            const gint digits,
                            GCallback cb,
                            gpointer data)
{
  GtkObject *adj;

  adj = gimp_scale_entry_new(GTK_TABLE(table), 0, row, name, SCALE_WIDTH, 0,
                             *value, min, max, step, page, digits,
                             TRUE, 0, 0, NULL, NULL);
  g_signal_connect(adj, "value_changed",
                   G_CALLBACK(gimp_double_adjustment_update),
                   value);
  g_signal_connect_swapped(adj, "value_changed", cb, data);

  return adj;
}

static inline void
yeti_progress_update(gint p,
                     gint max)
{
  double r;

  r = (double)p/max;
  gimp_progress_update(r);
}
/* }}} */

static gint
plasma2_dialog(void)
{
  GtkWidget *dlg;
  GtkWidget *main_vbox;
  GtkWidget *hbox;
  GtkWidget *frame;
  GtkWidget *table;
  gint run;
  gboolean ok;

  preview_mode = TRUE;
  gimp_ui_init(PROCEDURE_NAME, TRUE);

  dlg = gimp_dialog_new(_("Plasma2"), PROCEDURE_NAME,
                        NULL, 0,
                        gimp_standard_help_func, "filters/plasma2.html",

                        _("About"), PLASMA2_RESPONSE_ABOUT,
                        _("Revert"), PLASMA2_RESPONSE_RESET,
                        GIMP_STOCK_RESET, PLASMA2_RESPONSE_DEFAULTS,
                        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                        GTK_STOCK_OK, GTK_RESPONSE_OK,
                        NULL);

  main_vbox = gtk_vbox_new(FALSE, 4);
  gtk_container_set_border_width(GTK_CONTAINER(main_vbox), 6);
  gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), main_vbox, TRUE, TRUE, 0);

  /* top part with preview and common options */
  hbox = gtk_hbox_new(FALSE, 8);
  gtk_box_pack_start(GTK_BOX(main_vbox), hbox, TRUE, TRUE, 0);

  /* make a nice preview frame */
  frame = yeti_preview_frame_new_in_box(hbox);
  preview_image = GTK_BIN(frame)->child;
  preview = gtk_image_get_pixbuf(GTK_IMAGE(preview_image));

  /* graphic options */
  frame = yeti_frame_new_in_box(_("Graphic options"), hbox, TRUE, TRUE);
  table = yeti_table_new_in_frame(3, 4, frame);

  /* horizontal tileability */
  pctrl.htil = yeti_check_button_new_with_label(_("_Horizontally tileable"),
                                                &pvals.htil,
                                                G_CALLBACK(plasma2),
                                                drawable);
  yeti_table_attach_to_row(table, pctrl.htil, 0);

  /* vertical tileability */
  pctrl.vtil = yeti_check_button_new_with_label(_("_Vertically tileable"),
                                                &pvals.vtil,
                                                G_CALLBACK(plasma2),
                                                drawable);
  yeti_table_attach_to_row(table, pctrl.vtil, 1);

  /* gradient, only if rgb */
  if (!is_rgb)
    pvals.cm = PLASMA2_CM_GRAY;
  /* synchronize pvals.grad with current gradient */
  if (!strlen(pvals.grad) || !gimp_gradients_set_gradient(pvals.grad)) {
    strncpy(pvals.grad, gimp_gradients_get_gradient(), GRADIENT_NAME_SIZE-1);
    pvals.grad[GRADIENT_NAME_SIZE-1] = '\0';
  }

  pctrl.grad = gimp_gradient_select_widget_new("Plasma2 Gradient", pvals.grad,
                                               plasma2_gradient_changed, NULL);
  gtk_table_attach(GTK_TABLE(table), pctrl.grad, 1, 2, 3, 4,
                   GTK_FILL, 0, 0, 0);
  gtk_widget_set_sensitive(pctrl.grad,
                           pvals.cm == PLASMA2_CM_GRADIENT
                           || pvals.cm == PLASMA2_CM_GREMIX);

  pctrl.grad_label = yeti_label_new_in_table(_("_Gradient:"), table, 3,
                                             pvals.cm == PLASMA2_CM_GRADIENT
                                             || pvals.cm == PLASMA2_CM_GREMIX);
  gtk_label_set_mnemonic_widget(GTK_LABEL(pctrl.grad_label), pctrl.grad);

  /* coloring function */
  pctrl.cm
    = gimp_option_menu_new2(FALSE, G_CALLBACK(plasma2_cm_changed), NULL,
                            GUINT_TO_POINTER(pvals.cm), /*XXX*/
                            _("Independent RGB"),
                            GUINT_TO_POINTER(PLASMA2_CM_INDRGB), NULL,
                            _("Grayscale"),
                            GUINT_TO_POINTER(PLASMA2_CM_GRAY), NULL,
                            _("Gradient"),
                            GUINT_TO_POINTER(PLASMA2_CM_GRADIENT), NULL,
                            _("Gradient Remix"),
                            GUINT_TO_POINTER(PLASMA2_CM_GREMIX), NULL,
                            NULL);
  gimp_table_attach_aligned(GTK_TABLE(table), 0, 2,
                            _("Coloring _method:"), 1.0, 0.5,
                            pctrl.cm, 2, TRUE);
  if (!is_rgb)
    gtk_widget_set_sensitive(pctrl.cm, FALSE);

  /* generator settings  */
  frame = yeti_frame_new_in_box(_("Generator settings"), main_vbox, TRUE, TRUE);
  table = yeti_table_new_in_frame(3, 5, frame);

  /* seed/time */
  pctrl.seed = gimp_random_seed_new(&pvals.seed, &pvals.random_seed);
  gimp_table_attach_aligned(GTK_TABLE(table), 0, 0,
                            _("Random Seed:"), 1.0, 0.5,
                            pctrl.seed, 1, TRUE);
  g_signal_connect_swapped(GIMP_RANDOM_SEED_SPINBUTTON_ADJ(pctrl.seed),
                           "value_changed",
                           G_CALLBACK(plasma2),
                           drawable);

  /* random distribution type */
  pctrl.rdt
    = gimp_option_menu_new2(FALSE, G_CALLBACK(plasma2_rdt_changed), NULL,
                            GUINT_TO_POINTER(pvals.rdt),
                            _("Uniform"),
                            GUINT_TO_POINTER(PLASMA2_RDT_UNIFORM), NULL,
                            _("Cauchy"),
                            GUINT_TO_POINTER(PLASMA2_RDT_CAUCHY), NULL,
                            _("Exponential (asymmetric)"),
                            GUINT_TO_POINTER(PLASMA2_RDT_EXP), NULL,
                            _("Cubic tails"),
                            GUINT_TO_POINTER(PLASMA2_RDT_POW3M), NULL,
                            NULL);
  gimp_table_attach_aligned(GTK_TABLE(table), 0, 1,
                            _("Noise _distribution:"), 1.0, 0.5,
                            pctrl.rdt, 2, TRUE);

  /* fwhm */
  pctrl.lfwhm = yeti_scale_entry_new_double(_("Noise _amplitude (log):"),
                                            table, 2, &pvals.lfwhm,
                                            -7.0, 5.0, 0.01, 0.1, 2,
                                            G_CALLBACK(plasma2),
                                            drawable);

  /* fwhm scale function */
  pctrl.fsf
    = gimp_option_menu_new2(FALSE, G_CALLBACK(plasma2_fsf_changed), NULL,
                            GUINT_TO_POINTER(pvals.fsf),
                            _("Fractint-like,  2^(-a*depth)"),
                            GUINT_TO_POINTER(PLASMA2_FSF_EXP), NULL,
                            _("Norris-like,  1/depth^a"),
                            GUINT_TO_POINTER(PLASMA2_FSF_POWER), NULL,
                            NULL);
  gimp_table_attach_aligned(GTK_TABLE(table), 0, 3,
                            _("_Scaling function:"), 1.0, 0.5,
                            pctrl.fsf, 2, TRUE);

  /* parameter a */
  pctrl.a = yeti_scale_entry_new_double(_("Scaling _parameter a:"),
                                        table, 4, &pvals.a,
                                        0.0, 3.0, 0.01, 0.1, 2,
                                        G_CALLBACK(plasma2),
                                        drawable);

  gtk_widget_show_all(dlg);
  plasma2(drawable); /* preview image */

  ok = FALSE;
  do {
    run = gimp_dialog_run(GIMP_DIALOG(dlg));
    switch (run) {
      case PLASMA2_RESPONSE_RESET:
      plasma2_refresh_controls(&pvals_old);
      break;

      case PLASMA2_RESPONSE_DEFAULTS:
      plasma2_refresh_controls(&pvals_defaults);
      break;

      case PLASMA2_RESPONSE_ABOUT:
      about_dialog(dlg);
      break;

      default:
      ok = TRUE;
      break;
    }
  } while (!ok);

  gtk_widget_destroy(dlg);

  return run == GTK_RESPONSE_OK;
}

/*
 * The setup function.
 */

static void
plasma2(GimpDrawable *drawable)
{
  gint i, chmax;

  init_plasma2(drawable);
  chmax = bpp;

  for (i = 0; i < chmax; i++) {
    /* yet more voodoo.  to throw away the duplicate pixels so they don't
       appear on both edges in tileable images, we always generate one pixel
       larger image but pretend it has the right size */
    if (i == 0
        || pvals.cm == PLASMA2_CM_INDRGB
        || pvals.cm == PLASMA2_CM_GREMIX)
      generate_plasma_channel(ix2-ix1+1, iy2-iy1+1, i);

    commit(drawable, i);
  }

  if (has_alpha && preview_mode
      && (pvals.cm == PLASMA2_CM_GRADIENT || pvals.cm == PLASMA2_CM_GREMIX )) {
     for (i = 0; i < chmax; i++)
       plasma2_add_transparency(i);
  }

  end_plasma2(drawable);
}

static void
init_plasma2(GimpDrawable *drawable)
{
  gint x;

  /* don't use time for seed when rendering preview in full size to get the
     same image as shown in preview */

  /* map lfwhm to true FWHM */
  fwhm = exp(pvals.lfwhm);

  /* compute sizes and other stuff of this kind */
  alpha = -1000; /* to cause a segfault ;-> */
  has_alpha = gimp_drawable_has_alpha(drawable->drawable_id);
  if (preview_mode) {
    xscale = 1.0/PREVIEW_SIZE;
    yscale = 1.0/PREVIEW_SIZE;
    ix1 = iy1 = 0;
    ix2 = gdk_pixbuf_get_width(preview);
    iy2 = gdk_pixbuf_get_height(preview);
    bpp = gdk_pixbuf_get_n_channels(preview);
  }
  else {
    gimp_drawable_mask_bounds(drawable->drawable_id, &ix1, &iy1, &ix2, &iy2);
    xscale = 1.0/(ix2 - ix1);
    yscale = 1.0/(iy2 - iy1);
    bpp = gimp_drawable_bpp(drawable->drawable_id);
    if (has_alpha)
      alpha = bpp-1;
  }

  plasma = g_new(gfloat*, ix2-ix1+1);
  for (x = 0; x < ix2-ix1+1; x++)
    plasma[x] = g_new(gfloat, iy2-iy1+1);

  max_progress = (ix2 - ix1 + 1)*(iy2 - iy1 +1);
  max_progress *= bpp + (pvals.cm == PLASMA2_CM_INDRGB ? bpp : 1);
  max_progress += (has_alpha ? (ix2 - ix1)*(iy2 - iy1) : 0);
  progress = 0;

  /* generate gradient, this is the right place to do it */
  if (pvals.cm == PLASMA2_CM_GRADIENT || pvals.cm == PLASMA2_CM_GREMIX) {
    if (preview_mode){
      /* preview, done many times, don't regenerate what we already have */
      if (gradient == NULL) {
        gradient_p_width = CELL_SIZE_WIDTH;
        gradient = gimp_gradients_sample_uniform(gradient_p_width, FALSE);
      }
    }
    else {
      /* final rendering, done only once at the end */
      gimp_gradients_set_gradient(pvals.grad);
      g_free(gradient);
      gradient = gimp_gradients_sample_uniform(GRADIENT_SAMPLE_SIZE, FALSE);
    }
  }

  g_assert(pvals.rdt >= 0 && pvals.rdt < G_N_ELEMENTS(noisy_average_funcs));
  noisy_average = noisy_average_funcs[pvals.rdt];
}

static void
end_plasma2(GimpDrawable *drawable)
{
  gint x;

  if (preview_mode)
    gtk_widget_queue_draw(preview_image);
  else {
    gimp_drawable_flush(drawable);
    gimp_drawable_merge_shadow(drawable->drawable_id, TRUE);
    gimp_drawable_update(drawable->drawable_id,
                         ix1, iy1, (ix2 - ix1), (iy2 - iy1));
  }

  for (x = 0; x < ix2-ix1+1; x++)
    g_free(plasma[x]);
  g_free(plasma);
}

/* move one-channel plasma data into preview or drawable */
static void
commit(GimpDrawable *drawable, const gint channel)
{
  gpointer pr;
  GimpPixelRgn dest_rgn;

  if (preview_mode) {
    dest_rgn.x = dest_rgn.y = 0;
    dest_rgn.w = dest_rgn.h = PREVIEW_SIZE;
    dest_rgn.bpp = gdk_pixbuf_get_n_channels(preview);
    dest_rgn.rowstride = gdk_pixbuf_get_rowstride(preview);
    dest_rgn.data = gdk_pixbuf_get_pixels(preview);
    commit_one_tile(&dest_rgn, channel, gradient_p_width);
  }
  else {
    gimp_pixel_rgn_init(&dest_rgn, drawable,
                        ix1, iy1, (ix2 - ix1), (iy2 - iy1),
                        TRUE, TRUE);
    /* iterate by tiles */
    for (pr = gimp_pixel_rgns_register(1, &dest_rgn);
         pr != NULL;
         pr = gimp_pixel_rgns_process(pr)) {
      commit_one_tile(&dest_rgn, channel, GRADIENT_SAMPLE_SIZE);
      progress += dest_rgn.w * dest_rgn.h;
      yeti_progress_update(progress, max_progress);
    }
  }
}

/* move one-channel plasma data into one tile of drawable
   when channel == alpha, just fill it with CHANNEL_MAX_VALUE */
static void
commit_one_tile(GimpPixelRgn *rgn, const gint channel, const gint gradwidth)
{
  gint x, y;
  int index;
  guchar *p;
  gfloat r;

  /* XXX: hope compiler does its work good and moves invariant code out of the
     loops, otherwise this is slow */
  for (y = rgn->y; y < rgn->y + rgn->h; y++) {
    for (x = rgn->x; x < rgn->x + rgn->w; x++) {
      p = rgn->data + (y - rgn->y)*rgn->rowstride + (x - rgn->x)*rgn->bpp;
      if (pvals.cm == PLASMA2_CM_GRADIENT
          || pvals.cm == PLASMA2_CM_GREMIX) {
        index = plasma[x-ix1][y-iy1]*(gradwidth - 1);
        r = gradient[4*index + channel];
      }
      else {
        if (channel == alpha)
          r = 1.0;
        else
          r = plasma[x-ix1][y-iy1];
      }
      p[channel] = CHANNEL_MAX_VALUE*r;
    }
  }
}

static void
plasma2_add_transparency(const gint channel)
{
  static gint check_colors[] = {
    CHANNEL_MAX_VALUE*GIMP_CHECK_DARK,
    CHANNEL_MAX_VALUE*GIMP_CHECK_LIGHT,
  };

  guchar *p;
  gint x, y, i, chc;
  gdouble f;

  for (x = ix1; x < ix2; x++) {
    for (y = iy1; y < iy2; y++) {
      i = plasma[x-ix1][y-iy1]*(gradient_p_width - 1);
      f = gradient[4*i + 3];
      p = gdk_pixbuf_get_pixels(preview)
          + y*gdk_pixbuf_get_rowstride(preview) + x*bpp + channel;
      chc = (x-ix1)%(2*GIMP_CHECK_SIZE)/GIMP_CHECK_SIZE
            + (y-iy1)%(2*GIMP_CHECK_SIZE)/GIMP_CHECK_SIZE;
      *p = (*p)*f + (1-f)*check_colors[chc%2];
    }
  }
}

/* find start and end of block of zeros in flagfield (of size size), starting
   search from i1, the start and end indices are stored in i1 and i2
   returns FALSE when there are no more zeroes in flagfield, TRUE otherwise */
static inline gboolean
find_next_range(gint *i1, gint *i2, const gint *flagfield, const gint size)
{
  while (*i1 < size && flagfield[*i1])
    (*i1)++;
  if (*i1 == size)
    return FALSE;
  *i2 = (*i1)--;
  while (*i2 < size && !flagfield[*i2])
    (*i2)++;

  return (*i2) < size;
}

/* math functions */

/* recompute fwhm to distribution-specific parameter */
static inline void
recompute_randomness(const double scale, const gint w)
{
  static gdouble fwhm_to_rdparam_ratio[] = {
    1, 2, M_LN2, 1,
  };

  g_assert(pvals.rdt < G_N_ELEMENTS(fwhm_to_rdparam_ratio));
  switch (pvals.fsf) {
    case PLASMA2_FSF_EXP:
    randomness = pow(w*scale, pvals.a);
    break;

    case PLASMA2_FSF_POWER:
    randomness = pow(-log(w*scale)/M_LN2, -pvals.a);
    break;

    default:
    g_assert_not_reached();
    break;
  }
  randomness *= fwhm/fwhm_to_rdparam_ratio[pvals.rdt];
}

/* the generator */

/* compute and return mean value of v1 and v2 with some random shift
   proportional to randomness (global var) */
static gdouble
average_with_noise_uniform(gdouble v1,
                           gdouble v2)
{
  double val;

  val = 0.5*(v1 + v2);
  val += (-1.0 + 2.0*g_rand_double(rng))*randomness;

  return CLAMP(val, 0.0, 1.0);
}

static gdouble
average_with_noise_cauchy(gdouble v1,
                          gdouble v2)
{
  double val, r;

  val = 0.5*(v1 + v2);
  r = g_rand_double(rng);
  while (G_UNLIKELY(r == 0.5))
    r = g_rand_double(rng);
  val += randomness*tan(G_PI*r);

  return CLAMP(val, 0.0, 1.0);
}

static gdouble
average_with_noise_exp(gdouble v1,
                       gdouble v2)
{
  double val, r;

  val = 0.5*(v1 + v2);
  r = 1.0 - g_rand_double(rng);
  val += -randomness*(log(r) + 1);

  return CLAMP(val, 0.0, 1.0);
}

static gdouble
average_with_noise_pow3m(gdouble v1,
                         gdouble v2)
{
  double val, r, phi;

  val = 0.5*(v1 + v2);
  r = g_rand_double(rng);
  phi = 2.0*G_PI*g_rand_double(rng);
  val += randomness*sin(phi)*sqrt(tan(G_PI*r/2.0));

  return CLAMP(val, 0.0, 1.0);
}

/* one half of a recursion level, horizontal averaging */
static void
horizontal_pass(const gint xs,
                const gint ys,
                const gint depth,
                gint *col_fill,
                gint *col_todo,
                const gint *row_fill,
                const gint row_todo)
{
  gint x1, x2, xavg, y;

  for (x1 = 0; *col_todo > 0; (*col_todo)--) {
    if (!find_next_range(&x1, &x2, col_fill, xs))
      break;
    recompute_randomness(xscale, x2-x1);
    xavg = (x1 + x2)/2;
    col_fill[xavg] = 1;
    for (y = 0; y < ys; y++) {
      if (row_fill[y])
        plasma[xavg][y] = noisy_average(plasma[x1][y], plasma[x2][y]);
    }
    if (pvals.vtil)
      plasma[xavg][ys-1] = plasma[xavg][0];
    x1 = x2;
    progress += ys - row_todo;
  }
  if (!preview_mode)
    yeti_progress_update(progress, max_progress);
}

/* one half of a recursion level, vertical averaging */
static void
vertical_pass(const gint xs,
              const gint ys,
              const gint depth,
              gint *row_fill,
              gint *row_todo,
              const gint *col_fill,
              const gint col_todo)
{
  gint y1, y2, yavg, x;

  for (y1 = 0; row_todo > 0; (*row_todo)--) {
    if (!find_next_range(&y1, &y2, row_fill, ys))
      break;
    recompute_randomness(yscale, y2-y1);
    yavg = (y1 + y2)/2;
    row_fill[yavg] = 1;
    for (x = 0; x < xs; x++) {
      if (col_fill[x])
        plasma[x][yavg] = noisy_average(plasma[x][y1], plasma[x][y2]);
    }
    if (pvals.htil)
      plasma[xs-1][yavg] = plasma[0][yavg];
    y1 = y2;
    progress += xs - col_todo;
  }
  if (!preview_mode)
    yeti_progress_update(progress, max_progress);
}

/* generate single-channel plasma of size (xs,ys), and put it into plasma
   channel is used to generate the right seed value for given channel */
static void
generate_plasma_channel(gint xs,
                        gint ys,
                        const gint channel)
{
  gint *row_fill, *col_fill; /* true when slot is filled already  */
  gint col_todo = 0; /* number of empty cols in col_fill */
  gint row_todo = 0; /* number of empty rows in row_fill */
  gint depth; /* `recursion' depth */
  gint x, y;

  /* black magic.  we have to use consistent seed value for the preview and for
     the image for all channels and also consistent behaviour in noninteractive
     mode.  so we have to reset it for all channels, but obviously to different
     values. */
  g_rand_set_seed(rng, pvals.seed + 83*channel);

  /* initialize corner pixels
     note we use the same amount of random numbers regardless of tileability */
  plasma[0][0] = g_rand_double(rng);

  plasma[xs-1][0] = g_rand_double(rng);
  if (pvals.htil)
    plasma[xs-1][0] = plasma[0][0];

  plasma[0][ys-1] = g_rand_double(rng);
  if (pvals.vtil)
    plasma[0][ys-1] = plasma[0][0];

  plasma[xs-1][ys-1] = g_rand_double(rng);
  if (pvals.htil)
    plasma[xs-1][ys-1] = plasma[0][ys-1];
  else if (pvals.vtil)
    plasma[xs-1][ys-1] = plasma[xs-1][0];
  /* initialize center and edge-center pixels */
  plasma[xs/2][ys/2] = g_rand_double(rng);
  plasma[xs/2][0] = g_rand_double(rng);
  plasma[xs/2][ys-1] = g_rand_double(rng);
  if (pvals.vtil)
    plasma[xs/2][ys-1] = plasma[xs/2][0];
  plasma[0][ys/2] = g_rand_double(rng);
  plasma[xs-1][ys/2] = g_rand_double(rng);
  if (pvals.htil)
    plasma[xs-1][ys/2] = plasma[0][ys/2];

  /* initialize used-slot table */
  col_fill = g_new0(gint, xs);
  row_fill = g_new0(gint, ys);
  col_fill[0] = col_fill[xs-1] = col_fill[xs/2] = 1;
  row_fill[0] = row_fill[ys-1] = row_fill[ys/2] = 1;
  for (x = 0; x < xs; x++)
    col_todo += 1 - col_fill[x];
  for (y = 0; y < ys; y++)
    row_todo += 1 - row_fill[y];

  /* iterate while there remains something to do */
  for (depth = 1; row_todo || col_todo; depth++) {
    /* running vertical and horizontal pass always in the same order would
       create image looking `tall' or `wide', so we alternate them */
    if (depth%2) {
      vertical_pass(xs, ys, depth, row_fill, &row_todo, col_fill, col_todo);
      horizontal_pass(xs, ys, depth, col_fill, &col_todo, row_fill, row_todo);
    }
    else {
      horizontal_pass(xs, ys, depth, col_fill, &col_todo, row_fill, row_todo);
      vertical_pass(xs, ys, depth, row_fill, &row_todo, col_fill, col_todo);
    }
  }

  g_free(row_fill);
  g_free(col_fill);
}

/* option menu callbacks */
static void
plasma2_rdt_changed(GtkWidget *widget, gpointer data)
{
  PlasmaRandomDistributionType rdt;

  rdt = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(widget), "gimp-item-data"));
  if (pvals.rdt == rdt)
    return; /* no change, do nothing */
  pvals.rdt = rdt;
  plasma2(drawable);
}

static void
plasma2_fsf_changed(GtkWidget *widget, gpointer data)
{
  PlasmaFWHMScaleFunction fsf;

  fsf = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(widget), "gimp-item-data"));
  if (pvals.fsf == fsf)
    return; /* no change, do nothing */
  pvals.fsf = fsf;
  plasma2(drawable);
}

static void
plasma2_cm_changed(GtkWidget *widget, gpointer data)
{
  PlasmaColoringMethod cm;

  cm = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(widget), "gimp-item-data"));
  if (pvals.cm == cm)
    return; /* no change, do nothing */
  if (is_rgb) { /* this should be always true here, but... */
    gtk_widget_set_sensitive(pctrl.grad,
                             cm == PLASMA2_CM_GRADIENT
                             || cm == PLASMA2_CM_GREMIX);
    gtk_widget_set_sensitive(pctrl.grad_label,
                             cm == PLASMA2_CM_GRADIENT
                             || cm == PLASMA2_CM_GREMIX);
  }
  pvals.cm = cm;
  plasma2(drawable);
}

static void
plasma2_gradient_changed(const gchar *name,
                         gint width, const gdouble *grad_data,
                         gboolean dialog_closing, gpointer user_data)
{
  if (strncmp(pvals.grad, name, GRADIENT_NAME_SIZE-1) == 0)
    return;

  gimp_gradients_set_gradient(name);

  strncpy(pvals.grad, name, GRADIENT_NAME_SIZE-1);
  pvals.grad[GRADIENT_NAME_SIZE-1] = '\0';

  gradient = g_realloc(gradient, width*sizeof(gdouble));
  memcpy(gradient, grad_data, width*sizeof(gdouble));
  gradient_p_width = width/4;

  plasma2(drawable);
}


/* though this is obscure, we use it to reset/revert all values _and_ refresh
   the dialog controls */
static void
plasma2_refresh_controls(const PlasmaValues *new_pvals)
{
  /* we cannot reset some field and don't want to reset some others, so copy
     them one by one  */
  pvals.htil = new_pvals->htil;
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pctrl.htil), pvals.htil);

  pvals.vtil = new_pvals->vtil;
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pctrl.vtil), pvals.vtil);

  pvals.rdt = new_pvals->rdt;
  gimp_option_menu_set_history(GTK_OPTION_MENU(pctrl.rdt),
                               GUINT_TO_POINTER(pvals.rdt));

  pvals.fsf = new_pvals->fsf;
  gimp_option_menu_set_history(GTK_OPTION_MENU(pctrl.fsf),
                               GUINT_TO_POINTER(pvals.fsf));

  pvals.cm = new_pvals->cm;
  if (!is_rgb)
    pvals.cm = PLASMA2_CM_GRAY;
  gimp_option_menu_set_history(GTK_OPTION_MENU(pctrl.cm),
                               GUINT_TO_POINTER(pvals.cm));
  gtk_widget_set_sensitive(pctrl.grad,
                           pvals.cm == PLASMA2_CM_GRADIENT
                           || pvals.cm == PLASMA2_CM_GREMIX);
  gtk_widget_set_sensitive(pctrl.grad_label,
                           pvals.cm == PLASMA2_CM_GRADIENT
                           || pvals.cm == PLASMA2_CM_GREMIX);

  pvals.lfwhm = new_pvals->lfwhm;
  gtk_adjustment_set_value(GIMP_SCALE_ENTRY_SCALE_ADJ(pctrl.lfwhm),
                           pvals.lfwhm);

  pvals.a = new_pvals->a;
  gtk_adjustment_set_value(GIMP_SCALE_ENTRY_SCALE_ADJ(pctrl.a),
                           pvals.a);

  pvals.seed = new_pvals->seed;
  gtk_adjustment_set_value(GIMP_RANDOM_SEED_SPINBUTTON_ADJ(pctrl.seed),
                           pvals.seed);

  /* unable to update: grad -- FIXME */
  plasma2(drawable);
}


/* GdkPixbuf RGB C-Source image dump {{{ */

#ifdef __SUNPRO_C
#pragma align 4 (plasma2_about_image)
#endif
#ifdef __GNUC__
static const guint8 plasma2_about_image[] __attribute__ ((__aligned__ (4))) =
#else
static const guint8 plasma2_about_image[] =
#endif
{ ""
  /* Pixbuf magic (0x47646b50) */
  "GdkP"
  /* length: header (24) + pixel_data (10800) */
  "\0\0*H"
  /* pixdata_type (0x1010001) */
  "\1\1\0\1"
  /* rowstride (180) */
  "\0\0\0\264"
  /* width (60) */
  "\0\0\0<"
  /* height (60) */
  "\0\0\0<"
  /* pixel_data: */
  "!>U\37""8R\35""1W\32+W\35*P!&O\"(O%&Q&\34V(\30^&\30\\\40\24a!\27f%\30"
  "i0\31w5\25\1774\30\1773\36~/\"|$-w\32""4v\27""8{\22>\177\25A\177\23E"
  "\177\26L\177\22P~\23Qy\33Uv\32Ry\36Mw\30G~\27B{\26\77|\26>z\26B\177\26"
  "@~\30\77\177\30G\177\30O}\27Rx\33Wp\30Rt\34Rv&Ts*Vp+Zc1Xe7Wa3Rc7Pc6F"
  "a1D\\1MV/PV&TY#\\\\&Z]&Wb&Y_!\77U*{\2504i\2351[\230,]\2220R\2302F\241"
  "*=\236;4\245=4\264D,\274=-\275F%\277L&\322S-\350U*\366[+\367X9\372QE"
  "\372>Z\366+f\360\36\200\372\22\214\377\31\222\377&\227\377-\220\372/"
  "\214\351/\212\3420\212\323={\333\77z\3420x\351/v\354+v\364*\204\374\""
  "\203\377&\211\376)\212\3734\211\3758\225\372;\231\3629\241\341:\225\337"
  "3\223\3470\224\345;\231\354N\246\322b\243\317j\236\312d\242\314g\233"
  "\327q\230\315r\216\315h\227\307a\242\313c\244\300Y\250\274^\251\272`"
  "\244\264/VZ\34DV+z\237.n\236,]\222&T\216/T\205.P\214(B\2349E\247@;\266"
  "H9\270K=\305^:\312a6\316Y*\332[,\351X@\357QF\364QX\364Fj\364.{\366\40"
  "\202\375\22\216\373!\215\373\"\222\370*~\3504z\3403}\3237p\325:l\325"
  "Ko\323@t\346Dr\353;u\3606{\3740w\3729u\3708\200\3778\200\377E}\365A\177"
  "\354:\217\337=\215\3467\231\344;\216\3432\225\354T\237\327c\232\327h"
  "\230\321q\226\315n\213\327}\221\337\177\217\333w\212\334s\226\336o\232"
  "\323k\246\301t\256\267~\246\256<ZU\35DS)\204\224,u\225)p\210/a\203)W"
  "~\"P\220(T\224/Q\250AE\262PK\271SF\307Y@\310g6\327\\8\341Z4\336SI\353"
  "NT\356Ca\366=m\367*\203\370%\203\372\22\220\377\27\230\370%\221\3625"
  "\202\360@p\342;k\333<c\314Dh\305Hl\310No\346Bw\351D|\367@u\3677o\364"
  "<{\3749\200\377Ew\366Ow\364K\201\351H\215\345@\223\355/\226\3500\220"
  "\353%\203\337P\222\326\\\213\322m\224\332t\211\330l\206\325}\224\332"
  "\205\221\340\203\220\346x\224\344y\234\337z\231\315}\241\300\216\236"
  "\254FUP\37GP'~\213+n\2050n~'g\202!^|%\\~0f\2063]\220@X\242FL\261UO\276"
  "`A\311h>\313f7\322[9\330Sa\345Ha\355Cn\366-q\370%~\375\37\215\367\30"
  "\215\373\35\200\372)\202\360>}\343F\177\3378z\3250e\311=f\300Rd\275S"
  "k\333Br\337Ap\341\77o\346@a\354=y\3730\211\377@\212\372Uz\360J~\3470"
  "\217\3525\206\337/\212\332.\200\331!\204\327N~\322`w\327g}\325l\201\330"
  "~\206\323\203\212\334\231\206\341\212\225\346\201\226\350\205\227\351"
  "\211\237\341\223\236\315\216\222\260JPS&BM/{\220=w\216>x\2030q\203&d"
  "\2125n\2177m\211;g\2218m\216I\\\222WO\246fL\274iF\315^H\312WG\320Xj\350"
  "Nh\367Bs\372,t\377\"z\377\36\201\376\"\210\372%\202\3723t\364=w\362Q"
  "\202\352Bv\333/d\314;g\305Og\274[b\316Rg\325Jm\325Gb\337Jb\343@y\370"
  "0\200\377>v\365Kx\364;x\360\36\201\355$\203\344/\205\320(\203\330#\204"
  "\323No\316Yr\317^x\324v{\326\202\204\315\230\205\337\254|\353\234\200"
  "\357\225\217\364\234\214\356\245\230\347\235\227\332\221\221\302JIZ("
  "H@=v\205<s~Ev}4pu0bz4hzAg\177Lu{Gz\204Ro\233WW\243dO\261eU\304WZ\322"
  "U[\332Si\360Eq\367Eq\374*|\366\34\200\372\35\202\370\30|\366$\177\360"
  ";t\364@t\352Es\3539c\3403b\312\77f\312U_\272U]\276Oh\312>h\316=m\330"
  "Aj\346@t\3608y\372:\201\366A\200\364-w\360\33x\341\"q\337\40y\321$x\313"
  "\32y\311=d\321Nk\305Xx\310p{\315\200u\316\230n\332\270o\354\246t\357"
  "\244x\351\251}\342\245\217\346\234\224\326\223\227\311GM]&H:Dpn=neBd"
  "gFXs<\\{\77boCloEqsFz\200Um\224ek\237^e\253dj\270fm\312\\v\326Ds\357"
  "Kt\355B~\3704w\370\36\200\362\25r\364\25s\372$k\3646h\363Bq\356Js\354"
  "Jo\333>[\301BO\260SL\250QU\270I\\\277;o\275Aj\314Gv\341Aw\362=z\3771"
  "\204\376,\201\372\37r\353\22i\333\24o\323\23r\322\22t\312\22s\313<j\317"
  "Qk\315\\t\306lz\305\212p\316\240h\331\273o\351\261s\353\254h\352\254"
  "n\352\263|\353\246\204\342\232\226\333NLa)I;Nzx\\wp[snSimB^wGrqU|yTy"
  "\210M\202\214\\q\230bn\237]k\251fs\301g~\314a\201\316;o\361\77q\354\77"
  "p\3546y\353)w\361\30k\362\23f\350+i\354\77m\343:j\326@g\3258V\304\77"
  "S\266KW\265IR\252EL\262AW\2600o\2613u\313A\177\334\77\215\3548\212\376"
  "\77\202\3778\213\377\36\210\361\22v\340\22o\337\22r\333\22q\327\30j\315"
  "6g\310Dm\306\\m\314qp\311\213n\323\240k\347\276v\361\257o\347\256l\342"
  "\265w\347\257u\346\256\203\342\256\227\331URd-P7\\y\177gvyi}\202[u{D"
  "kvM\177wW\212\206_\205\214b\177\222_~\233e|\251Y\202\275\\\206\303e\213"
  "\315b\223\316=r\351<i\354=f\3520s\3461z\351!n\341\22b\336)d\340Aj\330"
  "Bd\3158a\3057K\264<B\2378B\250>G\242@G\2660[\270,i\263/l\301;|\316<\177"
  "\3439\217\3670\236\3718\237\372(\224\370\26\213\360\34u\354\35o\341\40"
  "b\321\37e\315@t\316Lv\320gl\327uw\331\217|\342\244}\341\270\212\355\264"
  "\204\337\274x\337\264\201\342\265{\340\260\214\324\264\230\317ZTc4O:"
  "d\214ui~\177||\200g\177xS{vO\202\206Z\216\227a\212\230l\223\245l\217"
  "\256u\220\263p\214\277b\224\303^\224\314_\232\321\77\177\337=}\3418m"
  "\335;t\3460p\346\"g\335\27_\327*d\303B^\275<`\261:Z\264=U\256;=\2417"
  "7\227:<\231=N\264/^\266/f\255;l\274=}\320C}\340B\212\370E\222\372=\230"
  "\370-\212\375\34\210\365\35\204\347\"w\344\34l\327\32l\327Au\325^r\326"
  "tj\340\201|\345\225}\351\257\205\360\305\214\370\277\206\360\302\210"
  "\335\270\203\342\266t\341\262{\321\274\206\314^Ib5M>|\224u~\211t\200"
  "\214}t\203\204g\213\203c\211\221j\205\250l\226\260i\232\272u\251\267"
  "~\252\261z\245\302e\234\307Y\244\327S\237\330K~\336Lr\336Bo\326@b\334"
  "8W\332.\\\320\32X\3210O\3028O\252AM\242BG\2311D\241.=\23670\2350(\222"
  "\77U\246<a\2607n\267;m\277Jx\325@}\346=\213\360:\224\362<\234\354=\220"
  "\3640\215\356%\210\355&\203\352\26w\354\22t\344>i\340ap\332yt\340\217"
  "x\354\241w\371\272\200\367\311\222\372\276\211\352\277\213\347\270\204"
  "\327\272~\323\300\204\322\311\205\320dHe@MB\210\231}\212\230\206\204"
  "\226\210y\211{o\202|o\215\232r\215\255v\232\253o\250\251z\263\240w\260"
  "\250c\255\251Z\252\265X\247\276J\232\320\77\211\320<{\321A~\322Jo\315"
  "FV\314;]\311+_\323+a\3053U\2674E\253B@\236=H\2271B\21795\20126\2029L"
  "\2239]\2321d\244=f\270Bv\315F\206\337V\214\363U\230\355F\226\356=\221"
  "\357\77\224\3415\217\3442\215\346\40}\354\23r\354Ox\351dx\342\177n\340"
  "\225m\357\246t\377\275\201\377\310\205\377\301\211\371\274\204\346\277"
  "|\340\276\204\332\303\210\315\313\214\314dJiLKI\204\220\210\204\226\204"
  "\215\222\220\201\210\212\177\206~\202\234\224\203\240\263z\262\263z\264"
  "\244u\263\252y\275\255h\264\257[\261\244\\\255\266O\242\307=\220\307"
  "\77\226\316G\217\325R~\312La\274An\301>k\3151Z\3064U\272:J\261G8\242"
  "EG\217<J\210FF~A:o:<\2130=\211/M\2209b\237>i\272Qw\324c\206\352\\\217"
  "\352H\213\345\77\221\341B\216\3376\234\3328\236\324\"\224\335\31\200"
  "\361^\203\357dx\354yy\342\210{\364\243t\377\266|\372\316\200\373\305"
  "\211\364\307\220\351\322\220\351\315\206\354\315\203\340\323\212\327"
  "eIoOOH\236\225\214\237\230\227\235\237\223\226\220\206\230\213\210\232"
  "\236\222\226\246\243\216\250\250\201\260\241t\271\237s\300\234j\276\252"
  "^\266\250R\255\254D\263\2716\242\2768\231\311D\221\315@x\300Gp\300Ck"
  "\272Ed\300HZ\275HV\262>Q\263A<\2435<\2316B\202ACkH5c<9y=D\203=K\214E"
  "]\231Ab\253Wj\314hx\341]{\344S\210\333P\217\325R\226\327B\237\316;\241"
  "\326:\223\347/\217\354[\201\351hv\347|x\352\205w\366\232p\375\256w\377"
  "\276x\376\311\201\371\304\211\362\310\210\364\314\224\361\314\210\361"
  "\315\212\353dDzUTI\253\242\225\241\237\224\242\251\217\254\243\213\247"
  "\233\212\246\234\232\234\245\242\227\254\236\223\271\222~\274\225x\267"
  "\227f\306\230X\313\237@\304\2436\302\244:\266\3027\255\306C\237\311>"
  "\222\301D\203\272Qw\301Yd\273WW\267OJ\266@7\264>0\24329\2022Eq\77If@"
  "=^ABc;Hu>=|LH\230Tc\251\\m\275tp\316o\200\315i\212\315_\223\315V\244"
  "\310R\232\320N\227\315\77\233\3334\236\346_\202\346l\201\344\205z\355"
  "\212v\364\233k\374\256m\372\266|\377\276~\376\314\177\371\317\210\375"
  "\322\224\377\326\230\364\324\217\363kC|PTB\253\242z\240\230}\234\233"
  "~\237\246\206\242\241\222\234\242\237\234\237\237\221\233\227\214\242"
  "\226\201\235\222q\242\220^\243\246Y\257\257K\272\255@\263\2341\241\300"
  "3\237\276;\217\275K\210\252L|\237Iq\251Vi\270P_\271PF\272F\77\264CC\237"
  "NE\217QD~QEvZJje^udQ\206bR\224^U\231kW\253vd\272\210j\315|r\327g}\325"
  "\\\214\321W\230\320\\\230\314V\231\313Q\246\333U\263\337b\247\340u\225"
  "\337y\216\350\211\204\342\232\203\346\240}\363\251|\372\265\201\367\304"
  "\200\370\305{\371\310\204\357\330{\347\341z\354sBuOT=\237\242s\237\234"
  "w\227\240\205\226\246\215\222\234\206\230\236\234\242\231\243\215\215"
  "\253\205\215\244z\216\223c\205\221R\231\242J\253\245@\255\2479\262\232"
  "0\230\2668\214\267:\206\257A\200\251Mq\233Rj\243Xc\256ZY\252UN\266KQ"
  "\250@H\246BC\235RJ\205\\PuXLsd^~u\\\217uZ\221z_\232\201[\247\211c\276"
  "\217b\306\177l\315hx\324h\203\313a\220\310]\237\315_\251\315b\261\313"
  "c\267\323b\260\332h\250\347~\227\346\213\216\344\236\217\331\240\206"
  "\351\243z\365\263x\360\267{\366\275v\356\316o\345\325k\360\345u\362p"
  "A{KU4\234\244h\216\237m\213\237\201\205\235\210\212\223\214\216\215\236"
  "\237\204\254\220\200\255o\202\253gz\240St\215K\200\242E\234\2509\236"
  "\2524\246\2454\205\2543\210\237Ay\234Mb\230U\\\226Y\\\224jZ\241fO\247"
  "`Q\255OO\257MP\254XN\237`W\216bU\200fRwqe\213z_\231\203g\234\222\\\252"
  "\223[\257\230a\276\226\\\314\205m\320p\204\317p\202\317i\215\302s\235"
  "\275u\263\301s\264\302s\300\312j\267\315q\240\322v\230\344\206\222\345"
  "\225\225\333\243\217\333\256y\346\252}\354\260s\356\266p\346\313c\343"
  "\323a\360\335n\361k:xFP5\230\240d\202\225u|\221w\204\203y\201x\201\210"
  "{\225\223\177\252\200\177\251l\214\244\\p\237Yb\236Ft\235=\224\2371\231"
  "\245!\242\2440~\2479z\247@k\250C^\236KR\220YS\211_U\220TN\216RU\216E"
  "T\226AN\243TK\217YT\206cR}`^tke\220sb\237x^\243zb\263\212\\\264\202S"
  "\306\211V\321\212u\315\205\206\310p\177\307fx\311l\217\274~\245\270\210"
  "\254\300\206\276\277u\273\274~\247\300\205\230\320\213\224\311\231\224"
  "\313\242\212\311\262{\325\260y\332\263~\341\276y\322\276f\322\312m\333"
  "\325s\354i9rBI0\204\226j~\222i}\212q~w\200\177g\177\204q\225yx\241t\210"
  "\242n\206\250dx\253WZ\240Dl\2351|\227#\202\225\33\230\2316\206\252.q"
  "\2524[\2506_\217BV\203BP\213PE\214FH\202LMyDI~BE\215GC\206JO{VQk]\\k"
  "rl\210w]\237zO\252}_\266\217d\276\213Y\315\213V\322\206n\315\217\215"
  "\304\201}\317ot\320|\223\270\205\243\245\212\246\260\220\264\275s\273"
  "\235\204\252\265\222\223\276\234\223\276\243\237\266\262\221\272\264"
  "|\311\275\203\317\275{\312\264q\305\273o\304\276w\327\306r\341d9rAC)"
  "\200~Zyx\\pvjqfph]~e\\\226ok\236cw\232gu\241`Z\250TP\250B^\244+m\227"
  "\40w\236\36\201\233.|\233/i\243)W\2366X\227CK\201OF\204R<\201OAxD>lB"
  "@}4<~=J\200RQyUZrXrnmdy|X\217}W\252\211T\257\203]\300\213f\300\222a\312"
  "\221z\310\235\203\276\222u\276}l\310\202\212\272\211\236\241\225\265"
  "\247\237\275\257\206\265\216\214\237\233\226\213\231\224\221\245\231"
  "\231\243\235\203\246\254}\256\271\204\264\266\201\276\270}\277\267v\276"
  "\267z\314\303n\332e6jDD%rnRftU`mb]]qaZ\215ee\220qc\237df\246gi\246TV"
  "\260FQ\273>T\254)Y\245\34h\250\30v\240$h\232\40Z\240\37K\233*<\217@3"
  "\211C2\177M0tJ3dI9bC5l59lFFiQ`mNurU{n_]yi_\221rV\233t_\262~]\276~Z\304"
  "\214h\304\226v\306\236y\276\230w\274\217u\306\227\203\272\227\235\251"
  "\241\246\243\245\271\225\230\255\212\231\233\213\231\223\202\234\216"
  "\214\236\214\204\253\203\232\252v\241\263t\261\276~\265\276t\260\274"
  "x\251\307m\277\307]\322d+kAE%khDhpPap^]onZb\210[`\213]T\210c_\215ca\240"
  "RW\2529Q\251+P\234\37^\232\36p\242\35t\254'p\234,h\226)W\2366Q\214HC"
  "\204V8\177W.tR>nTLpTGlD>jPQrPWzUe{]p~YWzoP\212tW\247lX\253qY\273}^\304"
  "\226p\277\237u\276\237{\254\244}\261\234\204\263\237\206\263\226\214"
  "\252\236\234\240\251\264\220\266\254\221\263\247\214\260\243\222\243"
  "\234\215\246\230\206\253\205\221\242|\230\252}\244\270}\256\276r\252"
  "\315q\245\311g\267\317c\320h,h>C!_kA_yNX\177OZrbZnvP\\tQTzTU\220gZ\226"
  "P[\2371]\236$h\226\36i\210\34x\227\30y\254!q\242\"_\235(V\2276R\223E"
  "H\212`\77\212m;\200cOrXUmXQmHHvXL\200ZW{[ly[t\200Z`pfZ\223ua\246x]\264"
  "sd\273\213c\270\223l\262\225q\250\237q\234\246t\252\243\202\256\240\204"
  "\262\240\210\245\260\237\230\276\263\224\315\255\211\301\247\210\303"
  "\261\222\274\242\222\263\225\223\247\212\223\251\202\234\254\201\240"
  "\276\216\263\311\213\262\323w\252\313f\273\317`\300m,`2=\40[uCUmAUvI"
  "ZtPUx_O`gUTmTNzUT~JYy=Q\1771`\213\33f\215\30r\223\31x\247&y\237/k\242"
  "6W\227B_\217MXzWQ\177oA\203mMqmWm`UbZRfcTydT\177XVx\\gr^X\177e_\226p"
  "[\242f^\247h\\\263|`\271\203i\261\213h\236\234l\220\241}\237\240\214"
  "\256\260\217\237\264\216\237\313\253\237\331\272\224\336\273\224\327"
  "\270\237\313\267\235\272\252\237\267\240\237\260\235\244\271\223\235"
  "\301\221\245\315\213\257\314\204\247\327\205\256\320t\271\326^\304i)"
  "c,5\"Np=IvBKtJIlIEsUN`bK]oL]jHRq>Pt:Nn.U\177!e\210\27d\226\22h\2307s"
  "\233Dl\237Kf\224Ua\217aXzjQ\206o\77\201jNrrXolSnr\\ctUruX{sY|iSteM\202"
  "jT\217jW\223bX\240h^\250o[\244\203f\245\177l\232\211m\224\220z\231\236"
  "\216\240\252\230\236\302\223\244\324\244\240\350\260\226\345\301\237"
  "\340\304\242\321\276\242\311\261\235\300\256\244\300\241\237\304\233"
  "\252\322\224\252\321\223\257\340\210\274\343\212\273\342s\307\343h\303"
  "q.`&0(HUKRiKPsUQm_Dn\\JedM\\hGZoIOk:Na8Qc/Sq+U\212$b\213\23a\217+l\224"
  ".m\210;k\177K^re`s_UxaEteNae[_nYTsUUpQfwJlvIsfQlqS\202mQ\177v\\\211h"
  "^\233dd\243pn\253\206\177\246\227{\232\242x~\237}\205\253\214\216\276"
  "\213\227\314\225\245\336\252\237\360\273\221\343\305\231\354\302\236"
  "\346\267\234\323\263\245\310\257\247\330\255\237\332\255\243\337\247"
  "\251\326\223\251\343\224\251\343\227\262\340{\260\330k\276i0b(**NKOH"
  "`NKl\\Do]HlVQrWOhSUU`TLrJQm4Sg/Wk*M~\40Q\203\30Z\224*s\2120j\203*ovJ"
  "suZlhU]pZGkcMdf\\]dWUkYIlKXxKipO\\]GYoR|\177T\211\211Z\210vd\210nr\222"
  "{\201\234\225\221\233\244\223\221\255\215z\255\224\204\265\231\205\302"
  "\224\213\310\230\232\336\250\224\354\275\212\354\276\216\360\260\225"
  "\365\251\225\354\260\237\330\267\251\346\271\257\345\261\253\335\234"
  "\261\340\220\262\340\227\261\343\235\253\342\202\271\330n\307q4b(#,I"
  "AW@WU5^X2c[<aTIbPMdHNYWJEn>Cd:Pd)Eu%C}\33N\214\32`\224\"ay)ou,tgBjeT"
  "hdQdcVT^VZUb^W]]LdXI]RSdHX_MV_Hcr[ltZp\210]{\200q\202{~~\204\222\203"
  "\234\233\220\242\231\204\260\217|\271\216u\271\230y\276\224\210\321\237"
  "\205\342\252\203\364\276\201\366\271\227\364\254\230\375\252\227\364"
  "\253\246\346\251\246\337\264\244\344\263\241\342\240\256\343\225\260"
  "\347\226\257\345\232\242\337\212\265\337v\312r6g(\32""3F>V9JT0LU3LE-"
  "N@;Y=@W<KHIKFbC@_B\77k0G~&I\211\30W\214\22c\212\36]e\30l]\37lU0bZMbS"
  "V`T]dYbV]YS^TKKQHGYGM[UQaQYbI]zcW\210a^\205m_\210pp\217\204x\227\224"
  "\200\242\236y\261\241v\266\235\200\261\235{\272\235v\315\240{\327\252"
  "t\346\251t\361\264x\364\247\230\372\253\241\377\262\240\373\264\242\366"
  "\246\235\364\260\220\354\255\221\344\237\234\346\236\242\343\227\244"
  "\340\226\233\350\214\245\354s\276s8b+\33""98:e6:ZA9WB>G5:>@6@M8=U7>U"
  "4\77N6ON=UG:]4>d%Pu\32X\203.OW5\\RBmNLcIZcFZh:dm6me=qmEjlDja=qTD~NBv"
  "KMzSR\214f\\~\\\\zRf\201Ygyjp\211ze\211\204h\220\215n\231\220u\252\233"
  "m\271\224j\304\244q\337\247j\360\243t\372\247n\365\250|\375\245\214\375"
  "\241\237\370\245\223\370\242\213\367\232\205\351\222\222\345\225\220"
  "\345\220\227\343\200\237\342\202\233\354{\244\354}\276q@_&\35=42h6)\\"
  "D.ZE5O>8\77F:5[;/X1)X22O<@SCBI>G55U*F`\"Zs=YYIkUZmOYuRcpJel;pi0}b=~i"
  "I{oIye;t^<\177R9}]7\212^A\212eV\213Zb\205Wa\200Xa{`nsmnymk\210\205m\223"
  "\216t\252\216n\264\225m\315\232e\337\235]\353\236e\377\252n\372\247}"
  "\366\250\225\365\236\236\360\224\224\362\222\205\361\205\225\346\205"
  "\224\340\206\230\343\225\237\335\216\242\342|\237\337\205\253\346\201"
  "\267sDf$\37@64d\77""3fB+\\=)UG+OZ/A_0,a'(T)1Q9(S=+BAB1;O->_(LfBUOSWF"
  "haGo`Grn@vm;s^+wf8\177q=\220j8\222s6\227m;\221^<\221`7\224f4\205[L\202"
  "\\S\206Oa{T\\~Ugy_dtgb{z`\213\200c\230\201a\242\223n\270\226m\330\235"
  "]\346\226j\374\236h\377\235x\376\235\210\370\250\222\365\226\214\362"
  "\214~\353\204\201\343\204\210\346\206\226\344\212\232\337\217\227\343"
  "\206\240\345|\246\350\204\264qGe*&@RIW`:Ua4[Y8L[5I_4=p2)r)&h)1^6+Y<-"
  "D98>=N:\77P:KQAXK\\UFk`=tq>ru9ne&oH\32uV+\203c6\206c;\216m:\213Y3\222"
  "Q.\221_2\234g(\211i0\212]F\216PQ\205V[}Q[p]OpcGzeH\202tN\221{R\241~X"
  "\273\202_\325\221[\353\230\\\372\235b\375\251t\372\250z\370\227~\363"
  "\212v\367\212v\354\202~\337\206\213\337\200\223\341\210\214\342\223\230"
  "\347\226\233\342\216\256\350\212\300sKb0+<_[WgIUs=[sAY|@K\2138A\2120"
  "5\200'5\201'(r(1m.<c5FO4BEA6GL9Gh=\\Z;yV>wf3il)xP\34x\77\22|N\31\203"
  "V\"\213g+\220n0\220V$\205M$\211])\226a%\226p%\234d3\224\\;\211\\R\202"
  "QZnZRe]<u]:\206l=\237qA\253rL\306\202S\333\206P\354\222W\360\225W\372"
  "\255l\360\234s\360\223u\366\204r\370\206k\346\206y\331~\206\337\211}"
  "\343\212\206\343\225\216\347\233\237\350\240\260\335\237\277vT`0/9c_"
  "^\177\\Z\216P`\226Ha\230;U\2336@\224+7\210/2\202**r):g/@b:FS6@NB:S['"
  "Hj0^_.k[0rU,p`&iK\40pD\23\203O\25\211R\37\206W$\212`%\204W\32\220U\33"
  "\233Z\40\240d\32\230p\36\236x\"\236u*\225o4~cDla5cX0w[.\215d:\241k9\254"
  "tF\305wO\327\211R\353\234[\370\237X\365\254a\364\225_\354\215a\356\202"
  "c\361\210e\345\212m\337\221|\341\212{\327\211\213\337\227\215\337\227"
  "\225\331\237\242\333\244\262rWW03:ndg}cn\235^n\236So\245\77`\2467R\251"
  ",@\233)C\222&8\205(\77k5Kg8D^@:VQ0W^\35U]\32bf\36kg-ia(v]*sI\37n=\23"
  "sI\30\206J\22\213Q\22\202L\27\216K\35\234T\30\262\\\25\271o\22\237t\27"
  "\235u\35\235\206!\216x(\206l0wo4nc-\203g.\214i(\241i0\253r;\277\200H"
  "\314\212O\337\224V\372\251b\371\241X\356\237R\343\227L\337\220O\346\226"
  "S\346\232^\331\222n\337\222v\330\214\212\327\232\222\317\235\230\327"
  "\257\253\323\270\265l^X3*A|Xy\214Y~\232Oy\240Iu\250\77s\243<c\255+H\253"
  "5I\2411C\2241E|@K}CMx;@tJ2cS)oP!rP)t]*\210c2\214[.\203T#uC\"{N\25\213"
  "U\22\220S\36\211O!\236]&\256]\37\256b\31\274d\27\237q\32\226|,\221\177"
  "0\205}3\210v2\177i5mk*pv*\202x*\242}7\265t5\274\2014\311\221\77\332\240"
  "J\360\242^\352\227U\346\223U\337\221U\337\215U\347\224b\337\210V\341"
  "\214W\336\226p\337\227z\326\224\213\331\236\216\335\251\241\335\261\272"
  "oX_0%D\200Q\230\216D\206\2369\177\2336\200\2372\205\2370_\2523K\253-"
  "E\2663B\260;\77\237LJ\230JU\215=Q}A=qP0wC/\204J0\216L0\230J7\222V2\222"
  "P)\210B#\211S\37\214Y\23\231X\36\230b/\242`$\270h$\273_\32\301d\22\227"
  "k\37\224o4\211q:\204h-\212o0|d/fg(yw2\211y5\242\200<\276\2047\310\216"
  ">\320\240=\330\241L\343\247U\346\230^\352\216d\351\220Y\360\216f\357"
  "\215n\351\220Z\343\214Q\356\217X\357\225h\357\241x\354\246\217\352\252"
  "\231\334\264\262pc_9&PxB\241\2059\237\215<\225\222:\220\237<\213\240"
  "=n\261FX\276LL\300BL\270IM\247KY\251ER\243IL\2309C\22120\2169+\222=$"
  "\232F#\237<)\236@+\230L\"\241M(\242U\40\224g\35\243q!\253u2\253t+\266"
  "j'\272h%\274f\33\244j+\232c1\205gE\210o<\224x/\212|.up1y\2030\206\211"
  ";\240\216=\267\232=\310\243F\326\245C\330\235N\340\241a\346\236^\335"
  "\241c\337\232g\337\223q\341\201n\337\203_\346\214U\366\234_\371\241]"
  "\357\261o\360\261\220\360\263\227\356\276\243zbX=$Vs0\246{7\257\2102"
  "\251\212>\244\225>\220\253Nw\274Xk\300Wa\304VV\302Oa\272Q\\\260LZ\261"
  "HT\2505L\250$=\23135\227@0\235\77\"\2414\37\2415#\243=&\262J\"\247Y\40"
  "\246i*\261n-\263\201-\277x&\276x)\264|\40\263w\37\245e(\237a1\215kF\216"
  "\2017\220\2105\206}9|\2036\204\216E\217\243L\242\253S\276\261M\321\251"
  "I\327\234L\332\225X\336\235c\350\232i\353\223i\343\230g\345\223i\342"
  "\200k\361\200Z\364\204S\377\225`\377\251b\372\267s\377\304\221\377\303"
  "\224\377\315\227\177hQI#V\223;\270\2204\265\2306\254\233=\230\240E\211"
  "\263P~\312Sn\321Xb\325PN\315I_\301Pc\264HZ\260BI\2654G\303!;\276&\77"
  "\276+5\2648&\2636#\2462(\260=(\276H'\254W$\250a\36\256n%\257u2\254z\""
  "\263}\36\262\202'\270w&\244q\36\235i-\244i.\250\203,\236\224.\223\204"
  "(\223\202*\237\2227\233\241G\254\252U\272\262a\321\256X\335\247P\327"
  "\252W\333\237b\346\237l\337\232j\344\232k\341\215^\346\217a\352\214`"
  "\360\221^\364\235`\377\247d\377\271p\371\304\216\366\301\210\373\306"
  "\203{dFP#^\241\77\273\252@\262\2527\264\260;\225\256Lz\277Qn\324Le\325"
  "N^\343NK\325K[\312>_\303>U\2759=\307(\77\330\22;\325%>\310'-\310.\36"
  "\276,*\2703(\274:+\302C#\273L\32\246^\27\237m\35\243y+\253w+\256\205"
  "\36\254\206$\262z%\245|\33\254x\33\255d!\270\200&\266\220,\244\210&\242"
  "t)\242\2033\242\223>\243\235O\262\242g\315\247`\331\250O\327\245U\327"
  "\237[\336\255e\327\231p\327\222k\330\222j\333\233g\346\232b\347\235j"
  "\367\237e\377\251g\365\300s\364\317{\370\325x\377\315~~mJV&h\262J\275"
  "\265\77\263\2579\255\270E\231\301P\206\315Hz\331Nb\331Xa\336UU\332MT"
  "\325>M\326;A\323<3\344!7\360\22""5\355&=\354*6\3376\"\3277\34\3143%\305"
  ":\31\300:\25\274J\33\254K\23\251W\32\244e\37\252h\30\251z\30\245v\22"
  "\243s\26\256u\35\264q\36\271f\37\276y$\277\210\34\256w$\250v#\261}6\264"
  "\213<\275\233T\272\236d\315\247_\331\246W\342\245X\344\242Y\332\264p"
  "\332\241k\322\227r\325\232p\331\234p\346\234q\354\231i\360\234i\377\241"
  "i\370\272m\372\306\202\375\307\201\376\315{zl=X&m\270K\300\267M\272\273"
  "L\262\304J\240\306K\202\306Nt\324Nm\340KZ\346RP\337FQ\334IC\342J8\342"
  ";1\363\",\377\22\"\376\31""2\375\36.\3730/\3607%\3444'\3236\31\304+\25"
  "\271;\31\262<\36\242S\35\240Z\22\237j\22\240q\22\245q\22\240v\22\256"
  "x\26\301s\22\306w\30\302\201\31\313\201\22\274\177\40\264z\"\300\204"
  "6\303\201G\304\221Q\300\237`\316\255e\331\260Y\345\256f\344\253e\322"
  "\254v\325\247}\323\246w\321\236t\327\235v\343\241o\346\233c\360\236a"
  "\370\240`\375\270o\377\277x\377\303s\377\321h}l;`%l\315I\273\275B\267"
  "\2716\270\272=\243\3047\225\2769|\251=f\2570\\\2771S\3143J\345C=\340"
  "C6\33438\362/%\373.\30\377+\"\3677\37\360:\35\3412\35\3220(\3031*\276"
  ")\36\275/%\2738.\267C$\253\\\22\253f\22\237v\22\241v\22\234w\22\234\200"
  "\30\237y#\245z$\246\210\35\243\204!\243\221\40\245\236\27\250\243'\250"
  "\2422\263\264;\271\266@\310\301D\335\301P\353\313O\354\305Q\327\303g"
  "\313\300t\310\274\201\306\261y\313\250\177\320\257v\337\261g\337\264"
  "Y\346\253T\347\251W\360\265[\354\277h\355\315hvg6`(k\312;\274\2763\276"
  "\267:\272\2655\255\276'\227\2560\206\2423h\2510b\2740V\3166O\3368C\343"
  "97\34266\346=0\370C\32\375\77%\360>\36\3464\32\341)\"\326)&\276'\36\256"
  "$\30\2637\36\266D*\257P\"\260X\24\255l\26\250z\31\234\177\22\236\201"
  "\22\227\215\34\233\203&\237\205(\233\227%\225\232'\230\230\35\234\235"
  "\32\243\245)\234\265+\247\273&\250\306/\276\303<\330\301@\351\277F\352"
  "\310N\331\307Z\306\314h\300\301~\277\277\202\314\257}\313\262r\330\270"
  "e\335\264\\\341\256V\352\267Y\362\271[\363\275_\354\317jzj7f'i\3151\276"
  "\274/\264\2644\262\256&\260\261#\242\241)\217\227*s\236#d\256&]\314/"
  "L\3430<\351;8\353B,\365L\37\377M\40\362J\34\3538\36\3435\22\337&\37\325"
  "\33%\302\33!\255\32\40\2510*\260J.\262O&\266b$\271m&\256p!\251q\37\251"
  "\203\22\231\212\35\225\204\36\230\217\"\220\221\40\211\237&\211\233\40"
  "\224\247\36\237\265\27\232\317\32\237\332\27\247\334\33\273\326-\323"
  "\313<\335\326C\341\324R\315\314Y\302\320g\303\315u\301\304y\315\301t"
  "\316\277l\306\265i\320\266j\327\263\\\341\255V\354\264U\362\300U\356"
  "\317arl3d)c\303)\267\301.\263\266.\250\267'\237\263\36\237\240&\215\220"
  "$r\242#[\261-R\323+D\3532=\364C0\365N!\364I\"\377R\26\356I\23\342@\22"
  "\3228\26\327,\35\327!'\300#%\257&#\2711(\267G+\272I\35\301X\26\302^&"
  "\264g'\262}\32\250\213\22\230\204&\230\215!\215\215\"~\237&\200\256!"
  "\211\263%\205\255\37\237\276\37\252\316!\254\336)\261\3400\310\3371\341"
  "\3210\341\325\77\341\343A\321\327U\315\314^\317\306j\323\275^\325\303"
  "_\330\303`\316\266c\327\271]\333\275U\334\263W\332\254^\337\272c\336"
  "\322Xom5`%b\303)\261\300,\252\274#\244\263'\237\251%\223\236.\204\215"
  ",r\225;[\250AE\314<>\353=7\364J-\363S\35\366Y\40\377g\31\350H\22\317"
  "C\30\303I\26\3148\40\315-2\276!!\246%\32\261<\32\270E'\303T\30\305V\27"
  "\272f#\264g&\255\203\40\247\232\22\211\1770\220\2034\210\221-x\232)o"
  "\256(y\266+\211\271\"\230\307#\257\315(\263\3230\256\3438\323\3360\351"
  "\3173\340\3327\341\357=\342\327U\326\317b\322\301f\327\274`\337\301U"
  "\317\273P\314\277T\330\265R\330\271M\332\266Y\317\261a\326\302Y\333\327"
  "]in0b+^\272:\256\2700\252\276*\243\253%\235\244,\220\2205\203\2042h\227"
  "AX\255ID\312IA\343R9\343d'\360g\35\363l\31\377f\32\340P\22\333U\22\311"
  "X\30\301M\40\300J0\250C$\2375\36\245\77&\263O&\264]!\272]\32\264f\33"
  "\265f\30\255v\22\244\217\22\212\2004\202\204.|\226'v\241+l\256:{\264"
  ",\206\276-\237\314,\262\3315\274\326;\310\330B\340\316C\357\321C\353"
  "\346=\345\365<\346\333J\335\327L\323\320Q\323\310H\322\310H\323\307C"
  "\315\306\77\314\277G\322\277O\321\270\\\311\274e\311\300j\320\322dkg"
  "7e+U\276=\261\2748\250\264*\235\2444\214\2259\203\213=m{7Q\226\77S\274"
  "MJ\310RB\335e5\347e*\363s#\371z\25\377u\22\327c\22\313d\26\276\\#\272"
  "\\-\270X6\245G1\2247-\242E$\262I\30\255U\32\267`\22\260f\22\256t\30\254"
  "|\33\242\223\22\225\214*\204\231'}\231.y\2510u\270=p\3152z\322.\226\320"
  "6\266\327:\301\337E\323\331N\340\343I\354\337E\362\344I\355\365G\342"
  "\340D\325\340=\315\325@\314\334H\326\331K\330\340G\327\331:\315\315="
  "\321\305B\304\305S\266\306k\301\321p\316\325igk;e-U\305C\250\264@\235"
  "\254<\217\2567\216\237=\206\2209j|D[\237HQ\271QI\302XB\320l=\344o:\357"
  "{5\356\202$\363\204\27\331w\22\315o#\314l&\277e1\271g;\245Y)\241A$\243"
  "M\37\261b\34\261g\30\243\\\22\251k\25\257p%\255\205#\247\212\30\211\225"
  "#|\227)z\237+u\2579l\3029n\3053r\3204\230\3177\255\313=\267\333K\300"
  "\337J\324\353M\343\351B\342\360A\340\370C\327\342A\322\324=\315\324<"
  "\320\324J\324\331I\327\325G\335\3257\337\3227\326\310=\302\314F\265\321"
  "U\305\315R\307\321Vcc1c-T\303S\245\271E\221\253@\177\237:}\236\77\204"
  "\215IowLb\241Z_\276dR\304gJ\310g7\327s2\363w9\366}#\360\221\26\334\211"
  "\27\320\214\40\323\2054\304y3\264s/\271g-\265R&\262`\37\245n\24\237j"
  "\24\233_\30\243e\31\257i(\262t!\246y\22}\232\33z\243\35v\257(u\2760l"
  "\302)u\313)u\3141\212\3138\237\3154\240\333K\241\334U\267\347L\321\356"
  "<\330\360;\337\366>\305\3529\304\3517\317\333)\333\3234\335\325F\341"
  "\322D\333\312@\322\3150\315\322(\314\327-\274\316=\304\3079\304\310\77"
  "b`.i1S\307X\225\300P\220\256D{\250M\177\245Pu\214Nn\200ZZ\243aZ\271o"
  "V\274f@\312i/\327}5\345\2037\344\223)\352\245\32\331\227\40\331\220&"
  "\317\2228\315x4\305o/\274e*\277\\0\272a\36\246p\34\235p\24\227p\30\251"
  "s\34\256v%\232x#\225y\36y\223\30n\244#k\261\36f\301\31i\305\37i\302%"
  "k\310*t\3064\203\3135\221\331A\223\337P\252\345C\304\350\77\323\351A"
  "\327\364\77\306\352/\310\353)\317\355)\326\354&\336\345/\342\332.\341"
  "\315:\341\311,\325\317'\301\332/\274\3341\275\3151\300\310.cb\"o<O\301"
  "`\215\270_\205\240O}\236Wn\236Uj\222al\217]`\245gV\272lQ\302wA\320s/"
  "\336|1\341\226<\344\2521\342\301-\333\261.\327\2440\322\235@\313\215"
  "=\315{7\312q2\307v.\276|\"\247{\37\237t\27\223v\32\237}\27\236|\36\236"
  "s$\221s\36\200\222\36y\246\30l\265\36i\306\24s\310\30k\277\35[\276\40"
  "^\311%i\3173{\3165\211\332E\237\343=\260\347=\267\360;\306\3776\302\364"
  "+\302\373+\306\3670\333\356&\347\351&\356\331,\355\3279\346\3346\320"
  "\331+\300\347+\276\351\36\265\337\36\265\327%_c\36g;U\305b\222\263U\224"
  "\241V\212\236O}\241Qh\232Yj\215^_\250bR\272rT\304wI\312x7\315\2229\321"
  "\2535\320\273.\325\317+\321\265!\316\2604\326\253:\320\2344\325\2177"
  "\325\2200\313\227.\260\230&\241\213\32\242\212\36\226\177'\231z,\232"
  "\1770\233\201\"\222t\40~\222\33o\262\24k\302\31o\274\36x\300\33e\301"
  "\32b\313%j\324&s\330%y\334(\201\350;\234\3504\250\3576\247\371*\257\377"
  "%\266\364&\274\366$\316\3660\320\3543\337\3330\353\3416\360\335\77\342"
  "\3278\313\3210\310\332+\311\341-\276\3340\277\327-ad\34b4Z\310o\227\265"
  "a\217\254^\214\257c\177\244Ym\230Ye}_l\242tf\270\210]\267\213Q\267\203"
  ">\300\2347\300\2650\312\3150\324\330*\303\304\34\314\277-\324\2669\326"
  "\2712\330\253(\316\247!\307\250%\266\240\36\252\247\36\246\235#\222\216"
  "*\235\2116\244\2053\241|%\233n\31\202\221&u\252\"s\300\"~\306\31\202"
  "\310\26v\311\36o\315\34w\327'\204\331$~\346\37~\351%\223\364)\236\365"
  "+\252\376'\247\377\31\256\354.\267\356-\311\3670\333\3603\345\334.\352"
  "\351D\364\353K\353\335K\323\325>\313\315<\316\323G\316\323E\310\3163"
  "fY\36]8^`:O_;KX5DT3=R5<M8=D89J>7VF/VM%YL\40US\37S[\"Za\36^f\33]d\25c"
  "b\30h\\\34f`\34g`\33gY\35cW\31[X\27UW\30ON\35II\36MI\36NH\"PE\37O=\31"
  "HL\30FU\27EY\30A`\31Ce\33=h\30<e\33Cf\34Fm\33Gu\30Fv\33J|\34Q{\30Qz\24"
  "R{\23Wr\36`v\"cv#jr&nl&un&xv(qt(im*ji-gh/ib'c[\36dX\30"};
/* }}} */

static void
about_close(GtkWidget **dialog)
{
  gtk_widget_destroy(*dialog);
  *dialog = NULL;
}

static void
about_dialog(GtkWidget *parent)
{
  static GtkWidget *about = NULL;
  GtkWidget *vbox, *hbox, *widget;
  GdkPixbuf *pixbuf;

  if (about) {
    gtk_window_present(GTK_WINDOW(about));
    return;
  }

  about = gtk_dialog_new_with_buttons(_("About"), GTK_WINDOW(parent),
                                      GTK_DIALOG_NO_SEPARATOR
                                      | GTK_DIALOG_DESTROY_WITH_PARENT,
                                      GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
                                      NULL);
  gtk_container_set_border_width(GTK_CONTAINER(about), 6);
  gtk_window_set_resizable(GTK_WINDOW(about), FALSE);
  gtk_window_set_position(GTK_WINDOW(about), GTK_WIN_POS_CENTER_ON_PARENT);

  vbox = GTK_DIALOG(about)->vbox;
  gtk_box_set_spacing(GTK_BOX(vbox), 12);

  hbox = gtk_hbox_new(FALSE, 6);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

  pixbuf = gdk_pixbuf_new_from_inline(-1, plasma2_about_image, FALSE, NULL);
  widget = gtk_image_new_from_pixbuf(pixbuf);
  g_object_unref(pixbuf);

  gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, FALSE, 0);
  gtk_misc_set_alignment(GTK_MISC(widget), 0.5, 0.0);

  widget = gtk_label_new(NULL);
  gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
  gtk_label_set_markup
    (GTK_LABEL(widget),
     "<big><b>Plasma2</b></big>\n"
       "GIMP plasma cloud generator.\n"
       "\n"
       "By David Ne\304\215as (Yeti).\n"
       "E-mail: <i>yeti@physics.muni.cz</i>\n"
       "Web: <i>http://trific.ath.cx/software/gimp-plugins/plasma2/ </i>\n");
  gtk_label_set_line_wrap(GTK_LABEL(widget), TRUE);
  gtk_label_set_selectable(GTK_LABEL(widget), TRUE);

  g_signal_connect_swapped(about, "delete_event",
                           G_CALLBACK(about_close), &about);
  g_signal_connect_swapped(about, "response",
                           G_CALLBACK(about_close), &about);
  gtk_widget_show_all(about);
}


