/*============================================================================*

  Paper Tile 1.0  -- A GIMP PLUG-IN

  Copyright (C) 1997-1999 Hirotsuna Mizuno <s1041150@u-aizu.ac.jp>

  This program  is  free software:  you can redistribute it  and/or  modify it
  under the terms of the GNU 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 <http://www.gnu.org/licenses/>.

 *===========================================================================*/

#include "config.h"

#include <stdlib.h>

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

#include "libgimp/stdplugins-intl.h"


/*===========================================================================*/
/* DEFINES                                                                   */
/*===========================================================================*/

#define PLUG_IN_PROC   "plug-in-papertile"
#define PLUG_IN_BINARY "tile-paper"
#define PLUG_IN_ROLE   "gimp-tile-paper"

/*===========================================================================*/
/* TYPES                                                                     */
/*===========================================================================*/

typedef enum
{
  BACKGROUND_TYPE_TRANSPARENT,
  BACKGROUND_TYPE_INVERTED,
  BACKGROUND_TYPE_IMAGE,
  BACKGROUND_TYPE_FOREGROUND,
  BACKGROUND_TYPE_BACKGROUND,
  BACKGROUND_TYPE_COLOR
} BackgroundType;

typedef enum
{
  FRACTIONAL_TYPE_BACKGROUND,           /* AS BACKGROUND */
  FRACTIONAL_TYPE_IGNORE,               /* NOT OPERATED  */
  FRACTIONAL_TYPE_FORCE                 /* FORCE DIVISION */
} FractionalType;

typedef struct _PluginParams PluginParams;
struct _PluginParams
{
  gint32          tile_width;
  gint32          tile_height;
  gint32          division_x;
  gint32          division_y;
  gdouble         move_max_rate;
  FractionalType  fractional_type;
  gboolean        centering;
  gboolean        wrap_around;
  BackgroundType  background_type;
  GimpRGB         background_color;
};

/*===========================================================================*/
/* VARIABLES                                                                 */
/*===========================================================================*/

static struct
{
  PluginParams  params;

  gint32        image;
  GimpDrawable *drawable;
  gboolean      drawable_has_alpha;

  struct
  {
    gint        x0;
    gint        y0;
    gint        x1;
    gint        y1;
    gint        width;
    gint        height;
  } selection;

  GimpRunMode  run_mode;
  gboolean         run;
} p =
{
  {
    1,                          /* tile_width            */
    1,                          /* tile_height           */
    16,                         /* division_x            */
    16,                         /* division_y            */
    25.0,                       /* move_max_rate         */
    FRACTIONAL_TYPE_BACKGROUND, /* fractional_type       */
    TRUE,                       /* centering             */
    FALSE,                      /* wrap_around           */
    BACKGROUND_TYPE_INVERTED,   /* background_type       */
    { 0.0, 0.0, 1.0, 1.0 }      /* background_color      */
  },

  0,                            /* image                 */
  NULL,                         /* drawable              */
  FALSE,                        /* drawable_has_alpha    */

  { 0, 0, 0, 0, 0, 0 },         /* selection             */

  GIMP_RUN_INTERACTIVE,         /* run_mode              */
  FALSE                         /* run                   */
};

/*---------------------------------------------------------------------------*/

static void
params_save_to_gimp (void)
{
  gimp_set_data (PLUG_IN_PROC, &p.params, sizeof p.params);
}

static void
params_load_from_gimp (void)
{
  gimp_get_data (PLUG_IN_PROC, &p.params);

  if (0 < p.params.division_x)
    {
      p.params.tile_width  = p.drawable->width / p.params.division_x;
      if (0 < p.params.tile_width)
        {
          p.params.division_y = p.drawable->height / p.params.tile_height;
        }
    }

  if (p.params.tile_width <= 0 ||
      p.params.tile_height <= 0 ||
      p.params.division_x <= 0 ||
      p.params.division_y <= 0)
    {
      p.params.tile_width = p.drawable->width;
      p.params.tile_height = p.drawable->height;
      p.params.division_x = p.drawable->width / p.params.tile_width;
      p.params.division_y = p.drawable->height / p.params.tile_height;
    }

  if (!p.drawable_has_alpha)
    {
      if (p.params.background_type == BACKGROUND_TYPE_TRANSPARENT)
        {
          p.params.background_type = BACKGROUND_TYPE_INVERTED;
        }

      gimp_rgb_set_alpha (&p.params.background_color, 1.0);
    }
}

/*===========================================================================*/
/* GUI                                                                       */
/*===========================================================================*/

static struct
{
  GtkObject *tile_width_adj;
  GtkObject *tile_height_adj;
  GtkObject *division_x_adj;
  GtkObject *division_y_adj;
} w;

static void
tile_width_adj_changed (GtkAdjustment *adj)
{
  if (p.params.tile_width != (gint)gtk_adjustment_get_value (adj))
    {
      p.params.tile_width  = gtk_adjustment_get_value (adj);
      p.params.division_x = p.drawable->width  / p.params.tile_width;
      gtk_adjustment_set_value (GTK_ADJUSTMENT (w.division_x_adj),
                                p.params.division_x);
    }
}

static void
tile_height_adj_changed (GtkAdjustment *adj)
{
  if (p.params.tile_height != (gint)gtk_adjustment_get_value (adj))
    {
      p.params.tile_height  = gtk_adjustment_get_value (adj);
      p.params.division_y = p.drawable->height / p.params.tile_height;
      gtk_adjustment_set_value (GTK_ADJUSTMENT (w.division_y_adj),
                                p.params.division_y);
    }
}

static void
division_x_adj_changed (GtkAdjustment *adj)
{
  if (p.params.division_x != (gint) gtk_adjustment_get_value (adj))
    {
      p.params.division_x = gtk_adjustment_get_value (adj);
      p.params.tile_width  = p.drawable->width  / p.params.division_x;
      gtk_adjustment_set_value (GTK_ADJUSTMENT (w.tile_width_adj),
                                p.params.tile_width);
    }
}

static void
division_y_adj_changed (GtkAdjustment *adj)
{
  if (p.params.division_y != (gint)gtk_adjustment_get_value (adj))
    {
      p.params.division_y = gtk_adjustment_get_value (adj);
      p.params.tile_height  = p.drawable->height / p.params.division_y;
      gtk_adjustment_set_value (GTK_ADJUSTMENT (w.tile_height_adj),
                                p.params.tile_height);
    }
}

static void
open_dialog (void)
{
  GtkWidget *dialog;
  GtkWidget *main_hbox;
  GtkWidget *button;
  GtkObject *adjustment;
  GtkWidget *vbox;
  GtkWidget *table;
  GtkWidget *frame;
  GtkWidget *color_button;

  gimp_ui_init (PLUG_IN_BINARY, TRUE);

  dialog = gimp_dialog_new (_("Paper Tile"), PLUG_IN_ROLE,
                            NULL, 0,
                            gimp_standard_help_func, PLUG_IN_PROC,

                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                            GTK_STOCK_OK,     GTK_RESPONSE_OK,

                            NULL);

  gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
                                           GTK_RESPONSE_OK,
                                           GTK_RESPONSE_CANCEL,
                                           -1);

  gimp_window_set_transient (GTK_WINDOW (dialog));

  main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
  gtk_container_set_border_width (GTK_CONTAINER (main_hbox), 12);
  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
                      main_hbox, TRUE, TRUE, 0);
  gtk_widget_show (main_hbox);

  /* Left */
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_box_pack_start (GTK_BOX (main_hbox), vbox, FALSE, FALSE, 0);
  gtk_widget_show (vbox);

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

  table = gtk_table_new (4, 2, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_table_set_row_spacings (GTK_TABLE (table), 6);
  gtk_container_add (GTK_CONTAINER (frame), table);
  gtk_widget_show (table);

  button = gimp_spin_button_new (&w.division_x_adj, p.params.division_x,
                                 1.0, p.drawable->width, 1.0, 5.0, 0, 1, 0);
  gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
                             _("_X:"), 0.0, 0.5,
                             button, 1, TRUE);
  g_signal_connect (w.division_x_adj, "value-changed",
                    G_CALLBACK (division_x_adj_changed),
                    NULL);

  button = gimp_spin_button_new (&w.division_y_adj, p.params.division_y,
                                 1.0, p.drawable->width, 1.0, 5.0, 0, 1, 0);
  gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
                             _("_Y:"), 0.0, 0.5,
                             button, 1, TRUE);
  g_signal_connect (w.division_y_adj, "value-changed",
                    G_CALLBACK (division_y_adj_changed),
                    NULL);

  button = gimp_spin_button_new (&w.tile_width_adj, p.params.tile_width,
                                 1.0, MAX (p.drawable->width,
                                           p.drawable->height),
                                 1.0, 5.0, 0, 1, 0);
  gimp_table_attach_aligned (GTK_TABLE (table), 0, 2,
                             _("_Width:"), 0.0, 0.5,
                             button, 1, TRUE);
  g_signal_connect (w.tile_width_adj, "value-changed",
                    G_CALLBACK (tile_width_adj_changed),
                    NULL);

  button = gimp_spin_button_new (&w.tile_height_adj, p.params.tile_height,
                                 1.0, MAX (p.drawable->width,
                                 p.drawable->height),
                                 1.0, 5.0, 0, 1, 0);
  gimp_table_attach_aligned (GTK_TABLE (table), 0, 3,
                             _("_Height:"), 0.0, 0.5,
                             button, 1, TRUE);
  g_signal_connect (w.tile_height_adj, "value-changed",
                    G_CALLBACK (tile_height_adj_changed),
                    NULL);

  frame = gimp_int_radio_group_new (TRUE, _("Fractional Pixels"),
                                    G_CALLBACK (gimp_radio_button_update),
                                    &p.params.fractional_type,
                                    p.params.fractional_type,

                                    _("_Background"),
                                    FRACTIONAL_TYPE_BACKGROUND, NULL,
                                    _("_Ignore"),
                                    FRACTIONAL_TYPE_IGNORE, NULL,
                                    _("_Force"),
                                    FRACTIONAL_TYPE_FORCE, NULL,

                                    NULL);
  gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

  button = gtk_check_button_new_with_mnemonic(_("C_entering"));
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
                                p.params.centering);
  gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

  g_signal_connect (button, "toggled",
                    G_CALLBACK (gimp_toggle_button_update),
                    &p.params.centering);

  /* Right */
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
  gtk_box_pack_start (GTK_BOX (main_hbox), vbox, TRUE, TRUE, 0);
  gtk_widget_show (vbox);

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

  table = gtk_table_new (2, 2, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_table_set_row_spacings (GTK_TABLE (table), 6);
  gtk_container_add (GTK_CONTAINER (frame), table);
  gtk_widget_show (table);

  button = gimp_spin_button_new (&adjustment, p.params.move_max_rate,
                                 0.0, 100.0, 1.0, 10.0, 0, 1, 0);
  gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
                             _("_Max (%):"), 0.0, 0.5,
                             button, 1, TRUE);
  g_signal_connect (adjustment, "value-changed",
                    G_CALLBACK (gimp_double_adjustment_update),
                    &p.params.move_max_rate);

  button = gtk_check_button_new_with_mnemonic (_("_Wrap around"));
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
                                p.params.wrap_around);
  gtk_table_attach_defaults (GTK_TABLE (table), button, 0, 2, 1, 2);
  gtk_widget_show (button);

  g_signal_connect (button, "toggled",
                    G_CALLBACK (gimp_toggle_button_update),
                    &p.params.wrap_around);

  frame = gimp_int_radio_group_new (TRUE, _("Background Type"),
                                    G_CALLBACK (gimp_radio_button_update),
                                    &p.params.background_type,
                                    p.params.background_type,

                                    _("_Transparent"),
                                    BACKGROUND_TYPE_TRANSPARENT, NULL,
                                    _("I_nverted image"),
                                    BACKGROUND_TYPE_INVERTED, NULL,
                                    _("Im_age"),
                                    BACKGROUND_TYPE_IMAGE, NULL,
                                    _("Fo_reground color"),
                                    BACKGROUND_TYPE_FOREGROUND, NULL,
                                    _("Bac_kground color"),
                                    BACKGROUND_TYPE_BACKGROUND, NULL,
                                    _("S_elect here:"),
                                    BACKGROUND_TYPE_COLOR, &button,

                                    NULL);
  gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
  gtk_widget_show (frame);

  color_button = gimp_color_button_new (_("Background Color"), 100, 16,
                                        &p.params.background_color,
                                        p.drawable_has_alpha ?
                                        GIMP_COLOR_AREA_SMALL_CHECKS :
                                        GIMP_COLOR_AREA_FLAT);
  gtk_box_pack_start (GTK_BOX (gtk_bin_get_child (GTK_BIN (frame))),
                      color_button, TRUE, TRUE, 0);
  gtk_widget_show (color_button);

  g_signal_connect (color_button, "color-changed",
                    G_CALLBACK (gimp_color_button_get_color),
                    &p.params.background_color);

  g_object_bind_property (button,       "active",
                          color_button, "sensitive",
                          G_BINDING_SYNC_CREATE);

  gtk_widget_show (dialog);

  p.run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);

  gtk_widget_destroy (dialog);
}

/*===========================================================================*/
/* PLUGIN CORE                                                               */
/*===========================================================================*/
typedef struct _Tile
{
  guint x;
  guint y;
  gint  z;
  guint width;
  guint height;
  gint  move_x;
  gint  move_y;
} Tile;

static gint
tile_compare (const void *x,
              const void *y)
{
  return ((Tile *) x)->z - ((Tile *) y)->z;
}

static inline gdouble
drand (void)
{
  return g_random_double_range (-1, 1);
}

static inline void
random_move (gint *x,
             gint *y,
             gint  max)
{
  gdouble angle  = drand () * G_PI;
  gdouble radius = drand () * (gdouble) max;
  *x = (gint) (radius * cos (angle));
  *y = (gint) (radius * sin (angle));
}

static void
overlap_RGB (guchar       *base,
             const guchar *top)
{
  base[0] = top[0];
  base[1] = top[1];
  base[2] = top[2];
}

static void
overlap_RGBA (guchar       *base,
              const guchar *top)
{
  gdouble R1 = (gdouble) base[0] / 255.0;
  gdouble G1 = (gdouble) base[1] / 255.0;
  gdouble B1 = (gdouble) base[2] / 255.0;
  gdouble A1 = (gdouble) base[3] / 255.0;
  gdouble R2 = (gdouble)  top[0] / 255.0;
  gdouble G2 = (gdouble)  top[1] / 255.0;
  gdouble B2 = (gdouble)  top[2] / 255.0;
  gdouble A2 = (gdouble)  top[3] / 255.0;
  gdouble A3 = A2 + A1 * (1.0 - A2);

  if (0.0 < A3)
    {
      gdouble R3 = (R1 * A1 * (1.0 - A2) + R2 * A2) / A3;
      gdouble G3 = (G1 * A1 * (1.0 - A2) + G2 * A2) / A3;
      gdouble B3 = (B1 * A1 * (1.0 - A2) + B2 * A2) / A3;

      R3 = CLAMP (R3, 0.0, 1.0);
      G3 = CLAMP (G3, 0.0, 1.0);
      B3 = CLAMP (B3, 0.0, 1.0);

      base[0] = (guchar) (R3 * 255.0);
      base[1] = (guchar) (G3 * 255.0);
      base[2] = (guchar) (B3 * 255.0);
      base[3] = (guchar) (A3 * 255.0);
    }
  else
    {
      base[0] = 0;
      base[1] = 0;
      base[2] = 0;
      base[3] = 0;
    }
}

static inline void
filter (void)
{
  static void (* overlap)(guchar *, const guchar *);
  GimpPixelRgn  src;
  GimpPixelRgn  dst;
  GimpRGB       color;
  guchar     pixel[4];
  gint       division_x;
  gint       division_y;
  gint       offset_x;
  gint       offset_y;
  Tile      *tiles;
  gint       numof_tiles;
  Tile      *t;
  gint       i;
  gint       x;
  gint       y;
  gint       move_max_pixels;
  gint       clear_x0;
  gint       clear_y0;
  gint       clear_x1;
  gint       clear_y1;
  gint       clear_width;
  gint       clear_height;
  guchar    *pixels;
  guchar    *buffer;
  gint       dindex;
  gint       sindex;
  gint       px, py;
  GRand     *gr;

  gr = g_rand_new ();

  /* INITIALIZE */
  gimp_pixel_rgn_init (&src, p.drawable, 0, 0,
                       p.drawable->width, p.drawable->height, FALSE, FALSE);
  gimp_pixel_rgn_init (&dst, p.drawable, 0, 0,
                       p.drawable->width, p.drawable->height, TRUE, TRUE);
  pixels = g_new (guchar,
                  p.drawable->bpp * p.drawable->width * p.drawable->height);
  buffer = g_new (guchar,
                  p.drawable->bpp * p.params.tile_width * p.params.tile_height);

  overlap = p.drawable_has_alpha ? overlap_RGBA : overlap_RGB;

  gimp_progress_init (_("Paper Tile"));

  gimp_drawable_mask_bounds (p.drawable->drawable_id,
                             &p.selection.x0, &p.selection.y0,
                             &p.selection.x1, &p.selection.y1);
  p.selection.width  = p.selection.x1 - p.selection.x0;
  p.selection.height = p.selection.y1 - p.selection.y0;

  gimp_tile_cache_ntiles (2 * (p.selection.width / gimp_tile_width () + 1));

  /* TILES */
  division_x = p.params.division_x;
  division_y = p.params.division_y;
  if (p.params.fractional_type == FRACTIONAL_TYPE_FORCE)
    {
      if (0 < p.drawable->width  % p.params.tile_width) division_x++;
      if (0 < p.drawable->height % p.params.tile_height) division_y++;
      if (p.params.centering)
        {
          if (1 < p.drawable->width % p.params.tile_width)
            {
              division_x++;
              offset_x =
                (p.drawable->width % p.params.tile_width) / 2 -
                p.params.tile_width;
            }
          else
            {
              offset_x = 0;
            }

          if (1 < p.drawable->height % p.params.tile_height)
            {
              division_y++;
              offset_y =
                (p.drawable->height % p.params.tile_height) / 2 -
                p.params.tile_height;
            }
          else
            {
              offset_y = 0;
            }
        }
      else
        {
          offset_x = 0;
          offset_y = 0;
        }
    }
  else
    {
      if (p.params.centering)
        {
          offset_x = (p.drawable->width  % p.params.tile_width) / 2;
          offset_y = (p.drawable->height % p.params.tile_height) / 2;
        }
      else
        {
          offset_x = 0;
          offset_y = 0;
        }
    }

  move_max_pixels = p.params.move_max_rate * p.params.tile_width / 100.0;
  numof_tiles = division_x * division_y;
  t = tiles = g_new(Tile, numof_tiles);

  for (y = 0; y < division_y; y++)
    {
      gint srcy = offset_y + p.params.tile_height * y;

      for (x = 0; x < division_x; x++, t++)
        {
          gint srcx = offset_x + p.params.tile_width * x;

          if (srcx < 0)
            {
              t->x     = 0;
              t->width = srcx + p.params.tile_width;
            }
          else if (srcx + p.params.tile_width < p.drawable->width)
            {
              t->x     = srcx;
              t->width = p.params.tile_width;
            }
          else
            {
              t->x     = srcx;
              t->width = p.drawable->width - srcx;
            }

          if (srcy < 0)
            {
              t->y      = 0;
              t->height = srcy + p.params.tile_height;
            }
          else if (srcy + p.params.tile_height < p.drawable->height)
            {
              t->y      = srcy;
              t->height = p.params.tile_height;
            }
          else
            {
              t->y      = srcy;
              t->height = p.drawable->height - srcy;
            }

          t->z = g_rand_int (gr);
          random_move (&t->move_x, &t->move_y, move_max_pixels);
        }
    }

  qsort (tiles, numof_tiles, sizeof *tiles, tile_compare);

  gimp_pixel_rgn_get_rect (&src, pixels, 0, 0, p.drawable->width,
                           p.drawable->height);

  if (p.params.fractional_type == FRACTIONAL_TYPE_IGNORE)
    {
      clear_x0     = offset_x;
      clear_y0     = offset_y;
      clear_width  = p.params.tile_width * division_x;
      clear_height = p.params.tile_height * division_y;
    }
  else
    {
      clear_x0     = 0;
      clear_y0     = 0;
      clear_width  = p.drawable->width;
      clear_height = p.drawable->height;
    }

  clear_x1 = clear_x0 + clear_width;
  clear_y1 = clear_y0 + clear_height;

  switch (p.params.background_type)
    {
    case BACKGROUND_TYPE_TRANSPARENT:
      for (y = clear_y0; y < clear_y1; y++)
        {
          for (x = clear_x0; x < clear_x1; x++)
            {
              dindex = p.drawable->bpp * (p.drawable->width * y + x);
              for (i = 0; i < p.drawable->bpp; i++)
                {
                  pixels[dindex+i] = 0;
                }
            }
        }
      break;

    case BACKGROUND_TYPE_INVERTED:
      for (y = clear_y0; y < clear_y1; y++)
        {
          for (x = clear_x0; x < clear_x1; x++)
            {
              dindex = p.drawable->bpp * (p.drawable->width * y + x);
              pixels[dindex+0] = 255 - pixels[dindex+0];
              pixels[dindex+1] = 255 - pixels[dindex+1];
              pixels[dindex+2] = 255 - pixels[dindex+2];
            }
        }
      break;

    case BACKGROUND_TYPE_IMAGE:
      break;

    case BACKGROUND_TYPE_FOREGROUND:
      gimp_context_get_foreground (&color);
      gimp_rgb_get_uchar (&color, &pixel[0], &pixel[1], &pixel[2]);
      pixel[3] = 255;
      for (y = clear_y0; y < clear_y1; y++)
        {
          for (x = clear_x0; x < clear_x1; x++)
            {
              dindex = p.drawable->bpp * (p.drawable->width * y + x);
              for (i = 0; i < p.drawable->bpp; i++)
                {
                  pixels[dindex+i] = pixel[i];
                }
            }
        }
      break;

    case BACKGROUND_TYPE_BACKGROUND:
      gimp_context_get_background (&color);
      gimp_rgb_get_uchar (&color, &pixel[0], &pixel[1], &pixel[2]);
      pixel[3] = 255;
      for (y = clear_y0; y < clear_y1; y++)
        {
          for (x = clear_x0; x < clear_x1; x++)
            {
              dindex = p.drawable->bpp * (p.drawable->width * y + x);
              for(i = 0; i < p.drawable->bpp; i++)
                {
                  pixels[dindex+i] = pixel[i];
                }
            }
        }
      break;

    case BACKGROUND_TYPE_COLOR:
      gimp_rgba_get_uchar (&p.params.background_color,
                           pixel, pixel + 1, pixel + 2, pixel + 3);
      for (y = clear_y0; y < clear_y1; y++)
        {
          for (x = clear_x0; x < clear_x1; x++)
            {
              dindex = p.drawable->bpp * (p.drawable->width * y + x);
              for(i = 0; i < p.drawable->bpp; i++)
                {
                  pixels[dindex+i] = pixel[i];
                }
            }
        }
      break;
    }

  /* DRAW */
  for (t = tiles, i = 0; i < numof_tiles; i++, t++)
    {
      gint x0 = t->x + t->move_x;
      gint y0 = t->y + t->move_y;

      gimp_pixel_rgn_get_rect (&src, buffer, t->x, t->y, t->width, t->height);

      for (y = 0; y < t->height; y++)
        {
          py = y0 + y;
          for (x = 0; x < t->width; x++)
            {
              px = x0 + x;
              sindex = p.drawable->bpp * (t->width * y + x);
              if (0 <= px && px < p.drawable->width &&
                  0 <= py && py < p.drawable->height)
                {
                  dindex = p.drawable->bpp * (p.drawable->width * py + px);
                  overlap(&pixels[dindex], &buffer[sindex]);
                }
              else if (p.params.wrap_around)
                {
                  px = (px + p.drawable->width)  % p.drawable->width;
                  py = (py + p.drawable->height) % p.drawable->height;
                  dindex = p.drawable->bpp * (p.drawable->width * py + px);
                  overlap(&pixels[dindex], &buffer[sindex]);
                }
            }
        }

      gimp_progress_update ((gdouble) i / (gdouble) numof_tiles);
    }

  gimp_pixel_rgn_set_rect (&dst, pixels, 0, 0, p.drawable->width,
                           p.drawable->height);

  gimp_progress_update (1.0);
  gimp_drawable_flush (p.drawable);
  gimp_drawable_merge_shadow (p.drawable->drawable_id, TRUE);
  gimp_drawable_update (p.drawable->drawable_id,
                        p.selection.x0, p.selection.y0,
                        p.selection.width, p.selection.height);

  g_rand_free (gr);
  g_free (buffer);
  g_free (pixels);
  g_free (tiles);
}

/*============================================================================*/
/* PLUGIN INTERFACES                                                          */
/*============================================================================*/

static void
plugin_query (void)
{
  static const GimpParamDef args[] =
  {
    { GIMP_PDB_INT32,    "run-mode",         "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
    { GIMP_PDB_IMAGE,    "image",            "Input image"                    },
    { GIMP_PDB_DRAWABLE, "drawable",         "Input drawable"                 },
    { GIMP_PDB_INT32,    "tile-size",        "Tile size (pixels)"             },
    { GIMP_PDB_FLOAT,    "move-max",         "Max move rate (%)"              },
    { GIMP_PDB_INT32,    "fractional-type",  "0:Background 1:Ignore 2:Force"  },
    { GIMP_PDB_INT32,    "wrap-around",      "Wrap around (bool)"             },
    { GIMP_PDB_INT32,    "centering",        "Centering (bool)"               },
    { GIMP_PDB_INT32,    "background-type",  "0:Transparent 1:Inverted 2:Image? 3:FG 4:BG 5:Color"                  },
    { GIMP_PDB_INT32,    "background-color", "Background color (for bg-type 5)" },
    { GIMP_PDB_INT32,    "background-alpha", "Opacity (for bg-type 5)"        }
  };

  gimp_install_procedure (PLUG_IN_PROC,
                          N_("Cut image into paper tiles, and slide them"),
                          "This plug-in cuts an image into paper tiles and "
                          "slides each paper tile.",
                          "Hirotsuna Mizuno <s1041150@u-aizu.ac.jp>",
                          "Copyright (c)1997-1999 Hirotsuna Mizuno",
                          _("September 31, 1999"),
                          N_("_Paper Tile..."),
                          "RGB*",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (args), 0,
                          args, NULL);

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

static void
plugin_run (const gchar      *name,
            gint              numof_params,
            const GimpParam  *params,
            gint             *numof_return_vals,
            GimpParam       **return_vals)
{
  GimpPDBStatusType status = GIMP_PDB_SUCCESS;

  INIT_I18N ();

  p.run                = FALSE;
  p.run_mode           = params[0].data.d_int32;
  p.image              = params[1].data.d_image;
  p.drawable           = gimp_drawable_get(params[2].data.d_drawable);
  p.drawable_has_alpha = gimp_drawable_has_alpha(p.drawable->drawable_id);

  if (gimp_drawable_is_rgb (p.drawable->drawable_id))
    {
      switch (p.run_mode)
        {
        case GIMP_RUN_INTERACTIVE:
          params_load_from_gimp ();
          open_dialog ();
          break;

        case GIMP_RUN_NONINTERACTIVE:
          if (numof_params == 11)
            {
              p.params.tile_width       = params[3].data.d_int32;
              p.params.tile_height      = params[3].data.d_int32;
              p.params.division_x       = p.drawable->width  / p.params.tile_width;
              p.params.division_y       = p.drawable->height / p.params.tile_height;
              p.params.move_max_rate    = params[4].data.d_float;
              p.params.fractional_type  = (FractionalType)params[5].data.d_int32;
              p.params.wrap_around      = params[6].data.d_int32;
              p.params.centering        = params[7].data.d_int32;
              p.params.background_type  = (BackgroundType)params[8].data.d_int32;
              p.params.background_color = params[9].data.d_color;

              /*  FIXME:  this used to be the alpha value
                                          params[10].data.d_int32
               */
              p.run = TRUE;
            }
          else
            {
              status = GIMP_PDB_CALLING_ERROR;
            }
          break;

        case GIMP_RUN_WITH_LAST_VALS:
          params_load_from_gimp ();
          p.run = TRUE;
          break;
        }
    }
  else
    {
      status = GIMP_PDB_EXECUTION_ERROR;
    }

  if (status == GIMP_PDB_SUCCESS && p.run)
    {
      params_save_to_gimp ();

      filter ();

      if (p.run_mode != GIMP_RUN_NONINTERACTIVE)
        gimp_displays_flush ();
    }

  gimp_drawable_detach (p.drawable);

  {
    static GimpParam return_value[1];
    return_value[0].type          = GIMP_PDB_STATUS;
    return_value[0].data.d_status = status;
    *numof_return_vals            = 1;
    *return_vals                  = return_value;
  }
}

const GimpPlugInInfo PLUG_IN_INFO =
{
  NULL,
  NULL,
  plugin_query,
  plugin_run
};

MAIN ()
