/* Streak
 *
 * This is a plug-in for the GIMP.
 *
 * Copyright (C) Reinhard Geisler
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (version 2) as 
 * published by the Free Software Foundation.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* Since this is my first GIMP plug-in, I derived parts of the code
 * (in particular the UIF) from several other plug-ins
 * (ellipse, noisify blur,..).
 * Special thanks to the programmers.
 */

/* This plug-in simulates a streak camera.
 *
 * A streak camera images an object through a slit - thus getting a 
 * "one dimensional image". This image is propagated along the
 * second dimension of the image plane at a constant speed.
 * The result is a picture of the time dependency of the object.
 *
 * The plug-in takes a film (multilayer image), cuts a slice of
 * selectable width and position out of each layer and puts the
 * slices together to the streak image.
 */

/*
 * Installation:
 * gimptool --install streak.c
 *
 * Enjoy!
 */


#include <stdlib.h>
#include <stdio.h>
#include <libgimp/gimp.h>
#include <gtk/gtk.h>


#define PLUG_IN_NAME    "streak"
#define PLUG_IN_VERSION "Streak 0.3"
#define PLUG_IN_DATE    "May 2000"


#define GUCHAR_MAX 255
#define SCALE_WIDTH 125
#define ENTRY_WIDTH  60


/********************************************************************/
/* Prototypes                                                       */
/********************************************************************/

/* communication to the GIMP */
static void query(void);

static void run(gchar   *name,
                gint     nparams,
                GParam  *param,
                gint    *nreturn_vals,
                GParam **return_vals);

/* streak creation */
static gint32 streak(gint32 image_id,
		   gint32 drawable_id);

/* user interface */
static void dialog_close_callback(GtkWidget *widget,gpointer data);
static void dialog_ok_callback(GtkWidget *widget,gpointer data);
static void dialog_entry_update(GtkWidget *widget, gdouble *value);
static void dialog_scale_update(GtkAdjustment *adjustment, gdouble *value);
static void dialog_create_value(char *title,GtkTable *table,int row, 
                                gdouble *value,double left,double right,
                                gdouble IsInteger);
static gint get_parameters(GDrawable *drawable);


/********************************************************************/
/* Variables                                                        */
/********************************************************************/

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

/* slit parameters, double version */
gdouble d_slit_position, d_slit_width;

/* status of parameter input: if TRUE: use plug-in; if FALSE: cancel */
gint parameter_ok=FALSE;


/********************************************************************/
/* procedures                                                       */
/********************************************************************/
MAIN()

/* communication to the GIMP */
static void query(void)
{
  static GParamDef params[] =
  {
    { PARAM_INT32,    "run_mode",      "Interactive"},
    { PARAM_IMAGE,    "image_id",      "Input image" },
    { PARAM_DRAWABLE, "drawable_id",   "Input drawable" }
  };

  static GParamDef *return_vals  = NULL;
  static int        nparams      = sizeof(params)/sizeof(params[0]);
  static int        nreturn_vals = 0;
  

  gimp_install_procedure("plug_in_"PLUG_IN_NAME,
                         "Streak image creation",
                         "This plug-in creates a streak-image from a film.",
                         "Reinhard Geisler",
                         "Reinhard Geisler",
                         PLUG_IN_DATE,
                         "<Image>/Filters/Animation/Streak",
                         "RGB*,GRAY*,INDEXED*",
                         PROC_PLUG_IN,
                         nparams,
                         nreturn_vals,
                         params,
                         return_vals);
}


/********************************************************************/
static void run(gchar   *name,
                gint     nparams,
                GParam  *param,
                gint    *nreturn_vals,
                GParam **return_vals)
{
  gint32     new_image_id=0;

  if(!get_parameters(gimp_drawable_get (param[2].data.d_drawable)))
    return;

  new_image_id=streak(param[1].data.d_image,param[2].data.d_drawable);

  gimp_display_new (new_image_id);  
}


/********************************************************************/
/* streak procedure                                                 */
/********************************************************************/
static gint32 streak(gint32 image_id,
		     gint32 drawable_id)
{
  gint32     new_image_id=0;
  gint32     new_layer_id=0;

  GImageType image_type=RGB;
  GDrawableType drawable_type;
  GDrawable *drawable, *new_drawable;
  GPixelRgn region, new_region;

  gint32 layers_number, number;
  gint32* layers;

  gint slit_position, slit_width, width, height, new_position;

  guchar *buffer, *source, *destination;
  guint bpp_diff, bpp_num;


  /* get layers & number of layers */
  layers=gimp_image_get_layers (image_id, &layers_number);

  drawable = gimp_drawable_get (drawable_id);

  /* check & calculate slit parameters */
  slit_position=d_slit_position-d_slit_width/2;
  slit_width=d_slit_width+.5;
  if(slit_position<0) slit_position=0;
  if(slit_position+slit_width>drawable->width)
    slit_position=drawable->width-slit_width;
  
  /* determine size of new image */
  width =slit_width*layers_number;
  height=drawable->height;

  /* determine type of new imange */
  switch(gimp_drawable_type(drawable_id))
    {
    case RGB_IMAGE:
    case RGBA_IMAGE:
      image_type=RGB;
      drawable_type=RGB_IMAGE;
      break;
    case GRAY_IMAGE:
    case GRAYA_IMAGE:
      image_type=GRAY;
      drawable_type=GRAY_IMAGE;
      break;
    case INDEXED_IMAGE:
    case INDEXEDA_IMAGE:
      image_type=INDEXED;
      drawable_type=INDEXED_IMAGE;
      break;
    }
  
  /* create new image with one layer */
  new_image_id=gimp_image_new(width,height,image_type);
  new_layer_id=gimp_layer_new(new_image_id,
			      "Background",
			      width, height,
			      drawable_type,
			      100,
			      NORMAL_MODE);
  gimp_image_add_layer(new_image_id, new_layer_id, 0);
  new_drawable=gimp_drawable_get(new_layer_id);


  /*  copy the colormap, if necessary  */
  if (image_type == INDEXED)
    {
      int ncols;
      guchar *cmap;
      
      cmap = gimp_image_get_cmap (image_id, &ncols);
      gimp_image_set_cmap (new_image_id, cmap, ncols);
      g_free (cmap);
    }


  /* calculate streak... */
  
  /* get copy buffer */
  if((buffer=malloc(height*slit_width*
		    drawable->bpp*sizeof(*buffer)))==NULL)
    exit(EXIT_FAILURE);

  /* initialize destination region: complete image */
  gimp_pixel_rgn_init(&new_region,new_drawable,
		      0,0,width,height,
		      TRUE,TRUE);


  /* loop through layers */
  gimp_progress_init("creating streak image...");
  new_position=0;
  for(number=layers_number-1; number>=0; number--)
  {
    /* show progress */
    gimp_progress_update((gdouble)new_position/width);

    /* select layer */
    drawable = gimp_drawable_get (layers[number]);

    /* initialize source region */
    gimp_pixel_rgn_init(&region,drawable,
			slit_position,0,slit_width,height,
			FALSE,FALSE);

    /* get slit */
    gimp_pixel_rgn_get_rect(&region,buffer,
			    slit_position,0,slit_width,height);
   
    /* copy a layer with alpha in an image without alpha */
    bpp_diff=drawable->bpp-new_drawable->bpp;
    if(bpp_diff>0)
      {
	source=buffer;
	for(destination=buffer;
	    destination<buffer+new_drawable->bpp*slit_width*height;)
	  {
	    for(bpp_num=1; bpp_num<=new_drawable->bpp; bpp_num++)
	      *destination++=*source++;
	    source+=bpp_diff;
	  }
      }
    
    /* copy slit into new image */
    gimp_pixel_rgn_set_rect(&new_region,buffer,
			    new_position,0,slit_width,height);

    new_position+=slit_width;
  }

  /* Tity up dirty drawable */
  gimp_drawable_flush(new_drawable);
  gimp_drawable_merge_shadow(new_drawable->id, TRUE);
  gimp_drawable_update(new_drawable->id,0,0,width,height);
  gimp_drawable_detach(new_drawable);
  gimp_displays_flush();
  
  return new_image_id;
}


/********************************************************************/
/* user interface                                                   */
/********************************************************************/

/* exit */
static void dialog_close_callback(GtkWidget *widget, gpointer data)
{
  gtk_main_quit ();
}


/* click "OK" */
static void dialog_ok_callback (GtkWidget *widget, gpointer data)
{
  parameter_ok=TRUE;
  gtk_widget_destroy (GTK_WIDGET (data));
}

static void dialog_entry_update(GtkWidget *widget, gdouble *value)
{
  GtkAdjustment *adjustment;
  gdouble        new_value;
  
  new_value = atof(gtk_entry_get_text(GTK_ENTRY(widget)));
  
  if (*value != new_value) {
    adjustment = gtk_object_get_user_data(GTK_OBJECT(widget));
    
    if ((new_value >= adjustment->lower) &&
        (new_value <= adjustment->upper)) {
      *value            = new_value;
      adjustment->value = new_value;
      
      gtk_signal_emit_by_name(GTK_OBJECT(adjustment), "value_changed");
    }
  }
}


static void dialog_scale_update (GtkAdjustment *adjustment, gdouble *value)
{
  GtkWidget *entry;
  char       buf[256];
  
  if (*value != adjustment->value) 
  {
    *value = adjustment->value;
    
    entry = gtk_object_get_user_data(GTK_OBJECT(adjustment));
    
    if (entry->private_flags)
      sprintf(buf, "%0.0f", *value);
    else
      sprintf(buf, "%0.2f", *value);
      
    gtk_signal_handler_block_by_data(GTK_OBJECT(entry), value);
    gtk_entry_set_text(GTK_ENTRY(entry), buf);
    gtk_signal_handler_unblock_by_data(GTK_OBJECT(entry), value);
  }
}


static void dialog_create_value(char     *title, 
				GtkTable *table, 
				int      row, 
				gdouble  *value, 
				double   left, 
				double   right,
				gdouble  IsInteger)
{
  GtkWidget *label;
  GtkWidget *scale;
  GtkWidget *entry;
  GtkObject *scale_data;
  char       buf[256];
  
  label = gtk_label_new(title);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
  gtk_table_attach(table, label, 0, 1, row, row + 1, GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(label);
  
  scale_data = gtk_adjustment_new(*value, left, right,1.0,1.0,0.0);
  
  gtk_signal_connect(GTK_OBJECT(scale_data), "value_changed",
		     (GtkSignalFunc) dialog_scale_update,
		     value);
  
  scale = gtk_hscale_new(GTK_ADJUSTMENT(scale_data));
  gtk_widget_set_usize(scale, SCALE_WIDTH, 0);
  gtk_table_attach(table, scale, 1, 2, row, row + 1,
		   GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
  gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE);
  gtk_scale_set_digits(GTK_SCALE(scale), 3);
  gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS);
  gtk_widget_show(scale);
  
  entry = gtk_entry_new();
  gtk_object_set_user_data(GTK_OBJECT(entry), scale_data);
  gtk_object_set_user_data(scale_data, entry);
  gtk_widget_set_usize(entry, ENTRY_WIDTH, 0);

  entry->private_flags = IsInteger;
  
  if (entry->private_flags)
    sprintf(buf, "%0.0f", *value);
  else
    sprintf(buf, "%0.2f", *value);

  gtk_entry_set_text(GTK_ENTRY(entry), buf);
  gtk_signal_connect(GTK_OBJECT(entry), "changed",
		     (GtkSignalFunc) dialog_entry_update,
		     value);
  gtk_table_attach(GTK_TABLE(table), entry, 2, 3, row, row + 1,
		   GTK_FILL, GTK_FILL, 4, 0);
  gtk_widget_show(entry);
}


static gint get_parameters(GDrawable *drawable)
{
  GtkWidget *dlg;
  GtkWidget *button;
  GtkWidget *table;
  GtkWidget *frame;

  gchar **argv;
  gint argc;

 
  argc = 1;
  argv = g_new (gchar *, 1);
  argv[0] = g_strdup (PLUG_IN_NAME);

  gtk_init (&argc, &argv);
  gtk_rc_parse (gimp_gtkrc ());

  /* open new dialog */
  dlg = gtk_dialog_new ();

  gtk_window_set_title (GTK_WINDOW (dlg), PLUG_IN_VERSION);
  gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) dialog_close_callback,
		      NULL);

  /* Action area... */
  /* button "OK"*/
  button = gtk_button_new_with_label ("OK");
  GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
                      (GtkSignalFunc) dialog_ok_callback,
                      dlg);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area),
		      button, TRUE, TRUE, 0);
  gtk_widget_grab_default (button);
  gtk_widget_show (button);

  /* button "OK"*/
  button = gtk_button_new_with_label ("Cancel");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
			     (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT (dlg));
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area),
		      button, TRUE, TRUE, 0);
  gtk_widget_show (button);


  /*  parameter settings  */
  frame = gtk_frame_new ("Parameter Settings");
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_border_width (GTK_CONTAINER (frame), 10);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), frame, TRUE, TRUE, 0);

  table = gtk_table_new (6, 3, FALSE);
  gtk_container_border_width (GTK_CONTAINER (table), 10);
  gtk_container_add (GTK_CONTAINER (frame), table);

  /* parameter input sliders */
  d_slit_position=drawable->width/2;
  dialog_create_value("Slit Position", GTK_TABLE(table), 1,
		      &d_slit_position, 1, drawable->width, TRUE);
  dialog_create_value("Slit Width", GTK_TABLE(table), 3,
		      &d_slit_width,    1, drawable->width, TRUE);

  gtk_widget_show (table);
  gtk_widget_show (frame);
  gtk_widget_show (dlg);

  gtk_main ();
  gdk_flush ();
  return parameter_ok;
}
