/**************************************************
 * file: waves/waves.c
 *
 * Copyright (c) 1997 Eric L. Hernes (erich@rrnet.com)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software withough specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id$
 */
 

#include <math.h>
#include <string.h>

#include <libgimp/gimp.h>
#include <gtk/gtk.h>
#include <plug-ins/megawidget/megawidget.h>

#ifndef M_PI
#define M_PI    3.14159265358979323846
#endif /* M_PI */

struct Grgb {
  guint8 red;
  guint8 green;
  guint8 blue;
};

struct GRegion {
  gint32 x;
  gint32 y;
  gint32 width;
  gint32 height;
};

struct piArgs {
  gdouble amplitude;
  gdouble phase;
  gdouble wavelength;
  gint32 type;
  gint32 reflective;
};

struct mwPreview *mwp;

static void query(void);
static void run(char *name, int nparam, GParam *param,
                int *nretvals, GParam **retvals);

int pluginCore(struct piArgs *argp, gint32 drawable);
int pluginCoreIA(struct piArgs *argp, gint32 drawable);

static mw_preview_t waves_do_preview;

static void wave(guchar *src, guchar *dest, gint width, gint height, gint bypp,
                 gdouble amplitude, gdouble wavelength, gdouble phase,
                 gint smear, gint reflective, gint verbose);

static guchar bilinear(gdouble x, gdouble y, guchar *v);
#define WITHIN(a, b, c) ((((a) <= (b)) && ((b) <= (c))) ? 1 : 0)

GPlugInInfo PLUG_IN_INFO = {
  NULL, /* init */
  NULL, /* quit */
  query, /* query */
  run, /* run */
};

MAIN()

static void
query(void){
  static GParamDef args[] = {
    { PARAM_INT32, "run_mode", "Interactive, non-interactive" },
    { PARAM_IMAGE, "image", "The Image" },
    { PARAM_DRAWABLE, "drawable", "The Drawable" },
    { PARAM_FLOAT, "amplitude", "The Amplitude of the Waves" },
    { PARAM_FLOAT, "phase", "The Phase of the Waves" },
    { PARAM_FLOAT, "wavelength", "The Wavelength of the Waves" },
    { PARAM_INT32, "type", "Type of waves, black/smeared" },
    { PARAM_INT32, "reflective", "Use Reflection" },
  };
  static int nargs = 8;

  static GParamDef *rets = NULL;
  static int nrets = 0;

  gimp_install_procedure("plug_in_waves",
                         "Distort the image with waves",
                         "none yet",
                         "Eric L. Hernes, Stephen Norris",
                         "Stephen Norris",
                         "1997",
                         "<Image>/Filters/Distorts/Waves",
                         "RGB*, GRAY*",
                         PROC_PLUG_IN,
                         nargs, nrets,
                         args, rets);
}

static void
run(char *name, int nparam, GParam *param,
    int *nretvals, GParam **retvals){
  static GParam rvals[1];

  struct piArgs args;

  *nretvals = 1;
  *retvals = rvals;

  /* bzero(&args, sizeof(struct piArgs)); */
  memset(&args,(int)0,sizeof(struct piArgs));
  args.type=-1;
  gimp_get_data("plug_in_waves", &args);

  rvals[0].type = PARAM_STATUS;
  rvals[0].data.d_status = STATUS_SUCCESS;
  switch (param[0].data.d_int32) {
     GDrawable *drw;
    case RUN_INTERACTIVE:
      /* XXX: add code here for interactive running */
      if(args.type == -1) {
        args.amplitude = 10.0;
        args.wavelength = 10;
        args.phase = 0.0;
        args.type = 0;
        args.reflective = 0;
      }

      drw = gimp_drawable_get(param[2].data.d_drawable);
      mwp=mw_preview_build(drw);

      if (pluginCoreIA(&args, param[2].data.d_drawable)==-1) {
        rvals[0].data.d_status = STATUS_EXECUTION_ERROR;
      } else {
        gimp_set_data("plug_in_waves", &args, sizeof(struct piArgs));
      }

    break;

    case RUN_NONINTERACTIVE:
      /* XXX: add code here for non-interactive running */
      if (nparam != 8) {
        rvals[0].data.d_status = STATUS_CALLING_ERROR;
        break;
      }
      args.amplitude = param[3].data.d_float;
      args.phase = param[4].data.d_float;
      args.wavelength = param[5].data.d_float;
      args.type = param[6].data.d_int32;
      args.reflective = param[7].data.d_int32;

      if (pluginCore(&args, param[2].data.d_drawable)==-1) {
        rvals[0].data.d_status = STATUS_EXECUTION_ERROR;
        break;
      }
    break;

    case RUN_WITH_LAST_VALS:
      /* XXX: add code here for last-values running */
      if (pluginCore(&args, param[2].data.d_drawable)==-1) {
        rvals[0].data.d_status = STATUS_EXECUTION_ERROR;
      }
    break;

  }

}

gint
pluginCore(struct piArgs *argp, gint32 drawable) {
  gint retval=0;
  GDrawable *drw;
  GPixelRgn srcPr, dstPr;
  guchar *src, *dst;
  guint width, height, Bpp;

  drw = gimp_drawable_get(drawable);

  width = drw->width;
  height = drw->height;
  Bpp = drw->bpp;

  src = (guchar*)malloc(width*height*Bpp);
  dst = (guchar*)malloc(width*height*Bpp);
  gimp_pixel_rgn_init (&srcPr, drw, 0, 0, width, height, FALSE, FALSE);
  gimp_pixel_rgn_init (&dstPr, drw, 0, 0, width, height, TRUE, TRUE);
  gimp_pixel_rgn_get_rect(&srcPr, src, 0, 0, width, height);

  wave(src, dst, width, height, Bpp, argp->amplitude, argp->wavelength,
        argp->phase, argp->type==0, argp->reflective, 1);
  gimp_pixel_rgn_set_rect(&dstPr, dst, 0, 0, width, height);

  free(src);
  free(dst);

  gimp_drawable_flush(drw);
  gimp_drawable_merge_shadow (drw->id, TRUE);
  gimp_drawable_update(drw->id, 0, 0, width, height);

  gimp_displays_flush();

  return retval;
}

gint
pluginCoreIA(struct piArgs *argp, gint32 drawable) {
  gint r=-1; /* default to error return */
  GtkWidget *dlg;
  GtkWidget *frame;
  GtkWidget *hbox;
  GtkWidget *vbox;
  GtkWidget *table;
  GtkWidget *preview;
  gint runp;
  static struct mwRadioGroup mode[] = {
    { "Smear", 1 },
    { "Blacken", 0 },
    { NULL, 0 },
  };
  gchar **argv;
  gint argc;

  /* Set args */
  argc = 1;
  argv = g_new(gchar *, 1);
  argv[0] = g_strdup("waves");
  gtk_init(&argc, &argv);
  gtk_rc_parse(gimp_gtkrc());
  
  dlg = mw_app_new("plug_in_waves", "Waves", &runp);

  hbox = gtk_hbox_new(FALSE, 5);
  gtk_container_border_width(GTK_CONTAINER(hbox), 5);
  gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), hbox, TRUE, TRUE, 0);
  gtk_widget_show(hbox);

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
  gtk_widget_show(vbox);

  preview = mw_preview_new(vbox, mwp, &waves_do_preview);
  gtk_object_set_data(GTK_OBJECT(preview), "piArgs", argp);
  gtk_object_set_data(GTK_OBJECT(preview), "mwRadioGroup", mode);
  waves_do_preview(preview);

  mw_toggle_button_new(vbox, NULL, "Reflective", &argp->reflective);

  vbox = gtk_vbox_new(FALSE, 5);
  gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
  gtk_widget_show(vbox);

  frame = gtk_frame_new("Parameters");
  gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show(frame);

  table = gtk_table_new(4, 2, FALSE);
  gtk_container_border_width(GTK_CONTAINER (table), 5);
  gtk_container_add(GTK_CONTAINER(frame), table);

  mw_fscale_entry_new(table, "Amplitude", 0.0, 101.0, 1.0, 5.0, 0.0,
                      0, 1, 1, 2, &argp->amplitude);
  mw_fscale_entry_new(table, "Phase", 0.0, 360.0, 2.0, 5.0, 0.0,
                      0, 1, 2, 3, &argp->phase);
  mw_fscale_entry_new(table, "Wavelength", 0.1, 50.0, 1.0, 5.0, 0.0,
                      0, 1, 3, 4, &argp->wavelength);
  gtk_widget_show(table);

  mw_radio_group_new(vbox, "Mode", mode);

  gtk_widget_show(table);
  gtk_widget_show(dlg);
  gtk_main();
  gdk_flush();

  argp->type = mw_radio_result(mode);

  if(runp){
#if 0
    fprintf(stderr, "running:\n");
    /*fprintf(stderr, "\t(image %d)\n", argp->image);*/
    fprintf(stderr, "\t(drawable %d)\n", argp->drawable);
    fprintf(stderr, "\t(amplitude %f)\n", argp->amplitude);
    fprintf(stderr, "\t(phase %f)\n", argp->phase);
    fprintf(stderr, "\t(wavelength %f)\n", argp->wavelength);
    fprintf(stderr, "\t(type %d)\n", argp->type);
    fprintf(stderr, "\t(reflective %d)\n", argp->reflective);
#endif
    return pluginCore(argp, drawable);
  } else {
    return r;
  }
}

static void
waves_do_preview(GtkWidget *w) {
  static GtkWidget *theWidget = NULL;
  struct piArgs *argp;
  struct mwRadioGroup *rgp;
  guchar *dst;
  gint y;

  if(theWidget==NULL){
    theWidget=w;
  }

  argp = gtk_object_get_data(GTK_OBJECT(theWidget), "piArgs");
  rgp = gtk_object_get_data(GTK_OBJECT(theWidget), "mwRadioGroup");
  argp->type = mw_radio_result(rgp);
  dst = (guchar*)malloc(mwp->width*mwp->height*mwp->bpp);

  wave(mwp->bits, dst, mwp->width, mwp->height,
       mwp->bpp, (argp->amplitude/mwp->scale), (argp->wavelength/mwp->scale),
       argp->phase, argp->type==0, argp->reflective, 0);

  for(y=0;y<mwp->height; y++){
    gtk_preview_draw_row(GTK_PREVIEW(theWidget),
                         dst+(y*mwp->width*mwp->bpp), 0, y,
                         mwp->width);
  }

  gtk_widget_draw(theWidget, NULL);
  gdk_flush();
  free(dst);
}


static void
wave(guchar *src, guchar *dst, gint width, gint height, gint bypp,
     gdouble amplitude, gdouble wavelength, gdouble phase, gint smear,
     gint reflective, gint verbose) {
  long   rowsiz;
  guchar *p;
  guchar *dest;
  gint    x1, y1, x2, y2;
  gint    x, y;
  gint    prog_interval=0;
  gint    x1_in, y1_in, x2_in, y2_in;

  gdouble cen_x, cen_y;       /* Center of wave */
  gdouble xhsiz, yhsiz;       /* Half size of selection */
  gdouble radius, radius2;    /* Radius and radius^2 */
  gdouble amnt, d;
  gdouble needx, needy;
  gdouble dx, dy;
  gdouble xscale, yscale;

  gint xi, yi;

  guchar  values[4];
  guchar  val;

  gint k;

  phase = phase*M_PI/180;
  rowsiz   = width * bypp;

  if (verbose) {
    gimp_progress_init("Waves");
    prog_interval=height/10;
  }


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

  /* Wave the image.  For each pixel in the destination image
   * which is inside the selection, we compute the corresponding
   * waved location in the source image.  We use bilinear
   * interpolation to avoid ugly jaggies.
   *
   * Let's assume that we are operating on a circular area.
   * Every point within <radius> distance of the wave center is
   * waveed to its destination position.
   *
   * Basically, introduce a wave into the image. I made up the
   * forumla initially, but it looks good. Quartic added the
   * wavelength etc. to it while I was asleep :) Just goes to show
   * we should work with people in different time zones more.
   *
   */

  /* Center of selection */

  cen_x = (double) (x2 - 1 + x1) / 2.0;
  cen_y = (double) (y2 - 1 + y1) / 2.0;

  /* Compute wave radii (semiaxes) */

  xhsiz = (double) (x2 - x1) / 2.0;
  yhsiz = (double) (y2 - y1) / 2.0;

  /* These are the necessary scaling factors to turn the wave
     ellipse into a large circle */

  if (xhsiz < yhsiz) {
    xscale = yhsiz / xhsiz;
    yscale = 1.0;
  } else if (xhsiz > yhsiz) {
    xscale = 1.0;
    yscale = xhsiz / yhsiz;
  } else {
    xscale = 1.0;
    yscale = 1.0;
  } /* else */

  radius  = MAX(xhsiz, yhsiz);
  radius2 = radius * radius;

  /* Wave the image! */

  dst += y1 * rowsiz + x1 * bypp;

  wavelength = (wavelength * 2);

  for (y = y1; y < y2; y++) {
    dest = dst;

    if (verbose && (y % prog_interval == 0))
      gimp_progress_update((double)y/(double)height);

    for (x = x1; x < x2; x++) {
      /* Distance from current point to wave center, scaled */

      dx = (x - cen_x) * xscale;
      dy = (y - cen_y) * yscale;

      /* Distance^2 to center of *circle* (our scaled ellipse) */

      d = sqrt(dx * dx + dy * dy);

      /* Use the formula described above. */

      /* Calculate waved point and scale again to ellipsify */

      /*
       * Reflective waves are strange - the effect is much
       * more like a mirror which is in the shape of
       * the wave than anything else.
       */

      if (reflective){
        amnt = amplitude * fabs(sin(((d / wavelength)
                                     * (2.0 * M_PI) + phase)));

        needx = (amnt * dx) / xscale + cen_x;
        needy = (amnt * dy) / yscale + cen_y;
      } else {
        amnt = amplitude * sin(((d / wavelength)
                                * (2.0 * M_PI) + phase));

        needx = (amnt + dx) / xscale + cen_x;
        needy = (amnt + dy) / yscale + cen_y;
      }

      /* Calculations complete; now copy the proper pixel */

      xi = needx;
      yi = needy;

      if (smear){
        if (xi > width - 2){
          xi = width - 2;
        } else if (xi < 0){
          xi = 0;
        }
        if (yi > height - 2){
          yi = height - 2;
        } else if (yi < 0){
          yi = 0;
        }
      }

      p = src + rowsiz * yi + xi * bypp;

      x1_in = WITHIN(0, xi, width - 1);
      y1_in = WITHIN(0, yi, height - 1);
      x2_in = WITHIN(0, xi + 1, width - 1);
      y2_in = WITHIN(0, yi + 1, height - 1);

      for (k = 0; k < bypp; k++) {
        if (x1_in && y1_in)
          values[0] = *(p + k);
        else
          values[0] = 0;

        if (x2_in && y1_in)
          values[1] = *(p + bypp + k);
        else
          values[1] = 0;

        if (x1_in && y2_in)
          values[2] = *(p + rowsiz + k);
        else
          values[2] = 0;

        if (x2_in) {
          if (y2_in)
            values[3] = *(p + bypp + k + rowsiz);
          else
            values[3] = 0;
        } else
          values[3] = 0;

        val = bilinear(needx, needy, values);

        *dest++ = val;
      } /* for */
    } /* for */

    dst += rowsiz;
  } /* for */
  if (verbose) gimp_progress_update(1.0);

} /* wave */

static guchar
bilinear(gdouble x, gdouble y, guchar *v) {
  double m0, m1;
  x = fmod(x, 1.0);
  y = fmod(y, 1.0);

  if (x < 0)
    x += 1.0;
  if (y < 0)
    y += 1.0;

  m0 = (1.0 - x) * v[0] + x * v[1];
  m1 = (1.0 - x) * v[2] + x * v[3];

  return (guchar) ((1.0 - y) * m0 + y * m1);
} /* bilinear */

/*
 * Local Variables:
 * c-file-style: GNU
 * mode: C
 * c-auto-newline: t
 *
 * End:
 */

/* end of file waves/waves.c */
