/* Split
 *
 * 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.
 */

/* This plug-in splits an image into layers.
 * It's like the reverse of the poster plug-in.
 */

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


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


#define PLUG_IN_NAME    "split"
#define PLUG_IN_VERSION "Split 0.1"
#define PLUG_IN_DATE    "Aug 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);

/* split image */
static gint32 split(gint32 image_id,
		    gint32 drawable_id);

/* user interface */
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   */
}; 

/* split parameters*/
typedef struct 
{
  gdouble columns;
  gdouble rows;
  gint    row_by_row;
  gint    left_to_right;
  gint    top_to_bottom;
} tparameter;

static tparameter parameter =
{
  1,
  1,
  1,
  1,
  1,
};

/* 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,
                         "Splitting an image",
                         "This plug-in splits an image into layers",
                         "Reinhard Geisler",
                         "Reinhard Geisler",
                         PLUG_IN_DATE,
                         "<Image>/Filters/Animation/Split into layers",
                         "RGB*,GRAY*",
                         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;

  gimp_get_data("plug_in_split",&parameter);
  if(!get_parameters(gimp_drawable_get (param[2].data.d_drawable)))
    return;

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

  gimp_set_data("plug_in_split",&parameter,sizeof(tparameter));
}


/********************************************************************/
/* split procedure                                                 */
/********************************************************************/
static gint32 split(gint32 image_id,
		     gint32 drawable_id)
{
  char       text[255];
  gint32     new_image_id=0;
  gint32     new_layer_id=0;

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

  gint layer;
  gint row, column, width, height;
  gdouble d_width, d_height;

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


  drawable = gimp_drawable_get (drawable_id);

  /* determine type of new imange */
  switch(gimp_drawable_type(drawable_id))
    {
    case RGB_IMAGE:
    case RGBA_IMAGE:
      image_type=RGB;
      break;
    case GRAY_IMAGE:
    case GRAYA_IMAGE:
      image_type=GRAY;
      break;
    case INDEXED_IMAGE:
    case INDEXEDA_IMAGE:
      image_type=INDEXED;
      break;
    }
  
  /* calculate split parameters */
  d_width =drawable->width /parameter.columns;
  d_height=drawable->height/parameter.rows;
  width=d_width;
  height=d_height;
  
  /* create new image */
  new_image_id=gimp_image_new(width,height,image_type);

  /*  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);
    }

  /* get copy buffer */
  if((buffer=malloc(height*width*4*sizeof(*buffer)))==NULL)
    exit(EXIT_FAILURE);

  /* loop through new layers */
  gimp_progress_init("splitting image...");
  row   =parameter.top_to_bottom?0:parameter.rows   -1;
  column=parameter.left_to_right?0:parameter.columns-1;

  for(layer=0; layer<parameter.rows*parameter.columns; layer++)
    {
      /* show progress */
      gimp_progress_update((gdouble)layer/
			   (parameter.rows*parameter.columns));
      
      /* initialize destination layer */
      sprintf(text,"frame %d",layer);
      new_layer_id=gimp_layer_new(new_image_id,
				  text,
				  width, height,
				  gimp_drawable_type(drawable_id),
				  100,
				  NORMAL_MODE);
      gimp_image_add_layer(new_image_id, new_layer_id, 0);
      new_drawable=gimp_drawable_get(new_layer_id);
      gimp_pixel_rgn_init(&new_region,new_drawable,
			  0,0,width,height,
			  TRUE,TRUE);
      
      /* initialize source region & get frame */
      gimp_pixel_rgn_init(&region,drawable,
			  column*d_width,row*d_height,width,height,
			  FALSE,FALSE);
      gimp_pixel_rgn_get_rect(&region,buffer,
			      column*d_width,row*d_height,width,height);
      
      /* copy a layer without alpha in an image with alpha */
      bpp_diff=new_drawable->bpp-drawable->bpp;
      if(bpp_diff>0)
	{
	  destination=buffer+new_drawable->bpp*width*height-1;
	  
	  for(source=buffer+drawable->bpp*width*height-1;
	      source>=buffer;)
	    {
	      for(bpp_num=1; bpp_num<=bpp_diff; bpp_num++)
		*destination--=GUCHAR_MAX;
	      for(bpp_num=1; bpp_num<=drawable->bpp; bpp_num++)
		*destination--=*source--;
	    }
	}
      /* is there a possibility for bpp_diff<0??? */
      
      /* copy frame into new layer */
      gimp_pixel_rgn_set_rect(&new_region,buffer,
			      0,0,width,height);	  
      
      /* 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);	

      /* select next source */      
      if(parameter.row_by_row)
	{
	  column+=parameter.left_to_right?1:-1;
	  if(column<0 || column>=parameter.columns)
	    {
	      column =parameter.left_to_right?0:parameter.columns-1; 
	      row   +=parameter.top_to_bottom?1:-1;
	    }
	}
      else
	{
	  row+=parameter.top_to_bottom?1:-1;
	  if(row<0 || row>=parameter.rows)
	    {
	      row    =parameter.top_to_bottom?0:parameter.rows   -1;
	      column+=parameter.left_to_right?1:-1;
	    }
	}
    }
  
  
  gimp_displays_flush();
  
  free(buffer);
  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);
}


void left_to_right_update(GtkWidget *widget, gint32 value)
{
   if (GTK_TOGGLE_BUTTON (widget)->active)
     parameter.left_to_right = value;
}

void top_to_bottom_update(GtkWidget *widget, gint32 value)
{
   if (GTK_TOGGLE_BUTTON (widget)->active)
     parameter.top_to_bottom = value;
}

void row_by_row_update(GtkWidget *widget, gint32 value)
{
   if (GTK_TOGGLE_BUTTON (widget)->active)
     parameter.row_by_row = value;
}

void dialog_create_selection(GtkWidget *ovbox,
			     char *title,
			     char *name[],
			     int *result,
			     GtkSignalFunc dialog_toggle_function)
{
  GtkWidget *iframe, *ivbox;
  GtkWidget *button;

  iframe= gtk_frame_new(title);
  gtk_frame_set_shadow_type(GTK_FRAME(iframe), GTK_SHADOW_ETCHED_IN);
  gtk_box_pack_start(GTK_BOX(ovbox),
                     iframe, FALSE, FALSE, 0);

  ivbox= gtk_vbox_new(FALSE, 5);
  gtk_container_border_width (GTK_CONTAINER (ivbox), 5);
  gtk_container_add(GTK_CONTAINER(iframe), ivbox);
  
  {
    int   i;

    button= NULL;
    for (i=0; name[i]!=NULL; i++)
      {
        button= gtk_radio_button_new_with_label(
           (button==NULL)? NULL :
              gtk_radio_button_group(GTK_RADIO_BUTTON(button)), 
           name[i]);
        gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(button), 
                                    (*result==i));
        gtk_signal_connect (GTK_OBJECT (button), "toggled",
                            (GtkSignalFunc) dialog_toggle_function,
                            (gpointer) i);

        gtk_box_pack_start(GTK_BOX(ivbox), button, FALSE, FALSE,0);
        gtk_widget_show(button);
      }
  }

  gtk_widget_show(ivbox);
  gtk_widget_show(iframe);
}


static gint get_parameters(GDrawable *drawable)
{
  char * name_order[]     = {"Column by Column","Row by Row",   NULL};    
  char * name_horizontal[]= {"Right to Left",   "Left to Right",NULL};   
  char * name_vertical[]  = {"Bottom to Top",   "Top to Bottom",NULL};    

  GtkWidget *dlg;
  GtkWidget *button;
  GtkWidget *table;
  GtkWidget *frame;
  GtkWidget *ovbox;

  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);

  ovbox= gtk_vbox_new(FALSE, 5);
  gtk_container_border_width (GTK_CONTAINER (ovbox), 10);
  gtk_container_add (GTK_CONTAINER (frame), ovbox);

  table = gtk_table_new (6, 3, FALSE);
  gtk_container_border_width (GTK_CONTAINER (table), 10);
  gtk_box_pack_start (GTK_BOX (ovbox), table, TRUE, TRUE, 0);

  /* parameter input sliders */
  dialog_create_value("Columns", GTK_TABLE(table), 3,
		      &parameter.columns,1, drawable->width,  TRUE);
  dialog_create_value("Rows", GTK_TABLE(table), 1,
		      &parameter.rows,   1, drawable->height, TRUE);

  /* check boxes */
  dialog_create_selection(ovbox,
			  "Order",
			  name_order,
			  &parameter.row_by_row,
			  (GtkSignalFunc) row_by_row_update);
  dialog_create_selection(ovbox,
			  "Horizontal Propagation",
			  name_horizontal,
			  &parameter.left_to_right,
			  (GtkSignalFunc) left_to_right_update);
  dialog_create_selection(ovbox,
			  "Vertical Propagation",
			  name_vertical,
			  &parameter.top_to_bottom,
			  (GtkSignalFunc) top_to_bottom_update);

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

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

