// Periodic Painter / Seamless Tiles Creator
// gimp plugin
// version 0.2.0 - development version
//
// to compile & install (on linux):
// for/as current user:
//  $gimptool-2.0 --install periodicpaint.c
// and for system-wide installation (run as root):
//  $gimptool-2.0 --install-admin periodicpaint.c
// following might be needed (?) beforehand (on Arch Linux f.e.): 
//  $export LDFLAGS="$LDFLAGS -lm"
//
//contact: tiborb95 at gmail dot com, any feedback welcomed


#include <libgimp/gimp.h>
#include <stdio.h>
#include <libgimp/gimpui.h>
#include <string.h> //- not needed probably
#include <stdlib.h> // for exit
#include <math.h>


#define VERBOSE FALSE
#define DEBUG FALSE
#define RESPONSE_RESET   99
#define RESPONSE_OK      1
#define ITERATE(x,start,end) for(x=start;x<end;x++)
#define DEITERATE(x,start) for(x=start;x-- > 0 ;)
#define EMPTY FALSE
#define FULL TRUE
#define FROMNEW 0
#define FROMOLD 1
#define NEWTOOLD 0
#define OLDTONEW 3
#define TILELESS 0
#define RECTANGLE 1
#define RESPONSE_QUIT 100
#define RESPONSE_REFRESH 101

//structures
typedef struct {
	guint xperiod;
	guint yperiod;
	gint repeatedlimit;
	guint mode;
	gboolean wta;
	guint width;
	guint height;
	guint channels;

} MyGenVals;
static MyGenVals maindata = {32,32,30,TILELESS,FALSE};

GimpDrawable     *drawable;

//variables
static guchar *rect_in,*rect_out;
static gboolean *rect_processed;
static gboolean locktable[30]={0};
static guchar *r_old,*g_old,*b_old,*a_old, *r_current,*g_current,*b_current,*a_current, *r_new,*g_new,*b_new,*a_new,*tileid;
static GtkWidget *xperiod_spin,*yperiod_spin;
static gboolean rect_initialized=FALSE;
static gboolean oldstatus=EMPTY;
static gint drawable_ID;

//static gboolean preview;
static gboolean process_image = FALSE;
gint32 image_ID,current_layer_ID;

//GUI variables
gchar *title = "Periodic Painter (v. 0.2.0)";
static GtkWidget *combo,*lockcombo;
static gchar *mode1  = " -      ";
static gchar *mode2  = "Basic   ";
static gchar *locktext1  = "Locked image: None  ";
static gchar *locktext2  = "Locked image 1      ";
static gchar *locktext3  = "Locked image 1&2    ";
static gchar *locktext4  = "Locked image 1-3    ";
//functions
static void query (void);
static void run   (const gchar      *name,
                   gint              nparams,
                   const GimpParam  *param,
                   gint             *nreturn_vals,
                   GimpParam       **return_vals);
static void process  (GimpDrawable *drawable,GimpPreview *preview);
GimpPlugInInfo PLUG_IN_INFO = { NULL, NULL, query, run };
static gboolean plugin_gui (GimpDrawable *drawable);
static void response_callback (GtkWidget *widget,  gint response_id);
inline gint basepos(gint x, gint y,gint ch, gint width);
static void populate_tileid();
static void export_mask();
static void export_mask2();
static void spread_pixel(guint x, guint y, guchar r, guchar g, guchar b, guchar a,guint *counter);
static void old_to_new(guint basepos_1);

MAIN()


static void query (void) {
	static GimpParamDef args[] =   {
  		{ GIMP_PDB_INT32, "xperiod", "xperiod" },
    	{ GIMP_PDB_IMAGE, "yperiod", "yperiod"  },
    	{ GIMP_PDB_DRAWABLE,"drawable", "Input drawable" }
   	   };

	gimp_install_procedure (
    	"plug-in-perpainter",
    	title,
    	"Periodic painer - Seamless Tiles Creator - copy your painting in periodic locations. With tileset creation support",
    	"Tibor Bamhor",
    	"GPL v.3",
    	"2013",
    	"_Periodic Painter",
    	"RGB*,GRAY*",
    	GIMP_PLUGIN,
    	G_N_ELEMENTS (args), 0,
    	args, NULL);

	gimp_plugin_menu_register ("plug-in-perpainter",                        
    	"<Image>/Filters/Artistic");
}

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

	/* Setting mandatory output values */
	*nreturn_vals = 1;
	*return_vals  = values;

	values[0].type = GIMP_PDB_STATUS;
	values[0].data.d_status = status;
	
	image_ID = param[1].data.d_image; //<- zistujem imageID

	// Getting run_mode - we won't display a dialog if
	// we are in NONINTERACTIVE mode
  	run_mode = param[0].data.d_int32;

  	/*  Get the specified drawable  */
	drawable = gimp_drawable_get (param[2].data.d_drawable);

    switch (run_mode) {
    	case GIMP_RUN_INTERACTIVE:
            /* Get options last values if needed */
            //gimp_get_data ("plug-in-colorblur", &satvals);

            /* Display the dialog */
            if (DEBUG) printf("Starting periodic painter\n");
            if (! plugin_gui (drawable)) return;
            break;

		case GIMP_RUN_NONINTERACTIVE:
			printf ("\n NON-INTERACTIVE MODE not supported. Quitting...\n\n");
			exit(1);

            break;

		//case GIMP_RUN_WITH_LAST_VALS:
            ///*  Get options last values if needed  */
            //gimp_get_data ("plug-in-colorblur", &satvals);
            //break;

		default:
            break;
        }

	process (drawable,NULL); 


	gimp_displays_flush ();
	gimp_drawable_detach (drawable);
	
	fflush (stdout );
	
	return;
}

void wta_changed(GtkToggleButton *wta_button){
	maindata.wta=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (wta_button));
	if (VERBOSE) printf(" WTA button changed %d\n",gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (wta_button)) );
	}


void commitwrapper (GimpDrawable *drawable){ 
	if (VERBOSE) printf (" User input: Spread changes\n");
	process (drawable,NULL);
	}
	

void reinitwrapper (GimpDrawable *drawable){
	if (VERBOSE) printf (" User input: Re-init\n");
	oldstatus=EMPTY;
	process (drawable,NULL);}

void tile_save(){
	gint new_image_ID,layers_count,c,plugin_layer,drawable_new;
	gint *layers;
	GtkWidget *savedialog;
	char *filename;
	static gboolean debug=FALSE;
	
	if (maindata.mode==TILELESS) {
		if (VERBOSE) printf (" Nothing to save in tile-less mode\n");
		return;}
	
	savedialog = gtk_file_chooser_dialog_new ("Tileset name (*png strongly recommended)",
                                      NULL,
                                      GTK_FILE_CHOOSER_ACTION_SAVE,
                                      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                      GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
                                      NULL);
	gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (savedialog), TRUE);
	
	
	if (gtk_dialog_run (GTK_DIALOG (savedialog)) == GTK_RESPONSE_ACCEPT) {		
		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (savedialog));
		gtk_widget_destroy (savedialog);
		if (VERBOSE) printf ("Saving as: %s\n",filename); }
	else {
		gtk_widget_destroy (savedialog);
		return;};
	
	layers= gimp_image_get_layers (image_ID,&layers_count);
	if (debug) printf ("Number of layers (old image): %1d\n",layers_count);
	ITERATE(c,0,layers_count) {
		if (layers[c]==drawable_ID){
			plugin_layer=c;
			printf (" Drawable is on position %1d/%1d\n", c,layers_count);}}
	
	//if (VERBOSE) printf (" User input: tile save to file\n");
	new_image_ID=gimp_image_duplicate(image_ID);
	if (debug) printf ("ID of new image: %1d\n",new_image_ID);

	layers= gimp_image_get_layers (new_image_ID,&layers_count);
	ITERATE(c,0,layers_count) {
		if (c==plugin_layer){
			if (debug) printf (" Picking layer %1d: ID: %1d\n",c,layers[c]);
			drawable_new=layers[c];			
			continue;}
		gimp_image_remove_layer (new_image_ID,layers[c]);
		if (debug) printf (" Removing layer %1d: ID: %1d\n",c,layers[c]);}

	if (gimp_image_crop(new_image_ID,maindata.xperiod*3,maindata.yperiod*8,0,0))
		if (debug) printf ("Image cropped\n");
	if (gimp_file_save(GIMP_RUN_INTERACTIVE,new_image_ID,drawable_new,filename,filename))
		if (debug) printf ("Image saved\n");
	if (debug) printf ("tile save done\n");
}
	

void export_mask_wrap(){
	if (maindata.mode==TILELESS) return;
	export_mask();
	export_mask2();}
	
void export_mask()
{
	GimpDrawable *new_layer;
	gint layer_ID,basepos_1,basepos_3;
	gint x,y;
	//gfloat br; //brightness
	GimpPixelRgn rgn_out_nl;
	guchar *rect_out_nl;
	
	if (VERBOSE) printf("== Exporting mask to a layer..\n");
	
	current_layer_ID=gimp_image_get_active_layer(image_ID);
	
	//creating and attaching new layer
	layer_ID = gimp_layer_new (image_ID, "TilesIdentif", maindata.width, maindata.height, GIMP_RGB_IMAGE, 50.0,  GIMP_NORMAL_MODE);
	new_layer = gimp_drawable_get (layer_ID);
	gimp_image_add_layer (image_ID,layer_ID,1);
	

	rect_out_nl      = g_new (guchar, maindata.width * maindata.height * 4);	
	gimp_pixel_rgn_init (&rgn_out_nl, new_layer, 0, 0, maindata.width, maindata.height, FALSE, TRUE);

	//populating rgn_out with data
	ITERATE(x,0,maindata.width) {ITERATE (y,0,maindata.height) {
		basepos_1=basepos(x,y,1,maindata.width);
		basepos_3=basepos(x,y,3,maindata.width);
		if (tileid[basepos_1]==0) {
			rect_out_nl[basepos_3    ] = 255;
			rect_out_nl[basepos_3 + 1] = 255;
			rect_out_nl[basepos_3 + 2] = 255;}
		else {
			rect_out_nl[basepos_3    ] = (tileid[basepos_1]*31)%245;
			rect_out_nl[basepos_3 + 1] = (tileid[basepos_1]*103)%245;
			rect_out_nl[basepos_3 + 2] = (tileid[basepos_1]*199)%245;}
		}}
   
	//nahratie udajov do rgn_out
    gimp_pixel_rgn_set_rect (&rgn_out_nl, rect_out_nl,0, 0,maindata.width,maindata.height);
	//printf ("   h\n");
	g_free (rect_out_nl);
	
	gimp_drawable_flush (new_layer);
	gimp_drawable_merge_shadow (new_layer->drawable_id, TRUE);
	gimp_drawable_update (new_layer->drawable_id,0, 0, maindata.width,maindata.height);
	gimp_image_set_active_layer (image_ID,current_layer_ID);
	gimp_displays_flush ();
	gimp_progress_end();
}	


void export_mask2()
{
	GimpDrawable *new_layer;
	gint layer_ID,basepos_1,basepos_3;
	gint x,y;
	//gfloat br; //brightness
	GimpPixelRgn rgn_out_nl;
	guchar *rect_out_nl;
	
	if (VERBOSE) printf("== Exporting mask to a layer..\n");
	
	current_layer_ID=gimp_image_get_active_layer(image_ID);
	
	//creating and attaching new layer
	layer_ID = gimp_layer_new (image_ID, "InvertedMask", maindata.width, maindata.height, GIMP_RGBA_IMAGE, 100.0,  GIMP_NORMAL_MODE);
	new_layer = gimp_drawable_get (layer_ID);
	gimp_image_add_layer (image_ID,layer_ID,1);
	

	rect_out_nl      = g_new (guchar, maindata.width * maindata.height * 4);	
	gimp_pixel_rgn_init (&rgn_out_nl, new_layer, 0, 0, maindata.width, maindata.height, FALSE, TRUE);

	//populating rgn_out with data
	ITERATE(x,0,maindata.width) {ITERATE (y,0,maindata.height) {
		basepos_1=basepos(x,y,1,maindata.width);
		basepos_3=basepos(x,y,4,maindata.width);
		if (tileid[basepos_1]==0) {
			rect_out_nl[basepos_3    ] = 255;
			rect_out_nl[basepos_3 + 1] = 255;
			rect_out_nl[basepos_3 + 2] = 255;
			rect_out_nl[basepos_3 + 3] = 255;}
		else {
			rect_out_nl[basepos_3    ] = 255;
			rect_out_nl[basepos_3 + 1] = 255;
			rect_out_nl[basepos_3 + 2] = 255;
			rect_out_nl[basepos_3 + 3] = 0;}
		}}
   
	//nahratie udajov do rgn_out
    gimp_pixel_rgn_set_rect (&rgn_out_nl, rect_out_nl,0, 0,maindata.width,maindata.height);
	//printf ("   h\n");
	g_free (rect_out_nl);
	
	gimp_drawable_flush (new_layer);
	gimp_drawable_merge_shadow (new_layer->drawable_id, TRUE);
	gimp_drawable_update (new_layer->drawable_id,0, 0, maindata.width,maindata.height);
	gimp_image_set_active_layer (image_ID,current_layer_ID);
	gimp_displays_flush ();

}	



inline gint basepos(gint x, gint y,gint ch, gint width){
	return ch*y*width + ch*x;}



static void populate_old() {
	gint x,y;
	gint basepos_in,basepos_1;

	ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
		basepos_1=basepos(x,y,1,maindata.width); 
		basepos_in=basepos(x,y,maindata.channels,maindata.width); 
		r_old[basepos_1]  	= rect_in[basepos_in  ];
		if (maindata.channels>1) g_old[basepos_1] = rect_in[basepos_in+1]; else g_old[basepos_1]=0;
		if (maindata.channels>2) b_old[basepos_1] = rect_in[basepos_in+2]; else b_old[basepos_1]=0;
		if (maindata.channels>3) a_old[basepos_1] = rect_in[basepos_in+3]; else a_old[basepos_1]=0;
		if (DEBUG && x%200==0 && y%200==0) printf("input: %1d, %1d, %1d, %1d\n",
		r_old[basepos_1],g_old[basepos_1],b_old[basepos_1],a_old[basepos_1]  );
		}}
}	

static void populate_current() {
	gint x,y;
	gint basepos_in,basepos_1;

	ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
		basepos_1=basepos(x,y,1,maindata.width); 
		basepos_in=basepos(x,y,maindata.channels,maindata.width); 
		r_current[basepos_1]  	= rect_in[basepos_in  ];
		if (maindata.channels>1) g_current[basepos_1] = rect_in[basepos_in+1]; else g_current[basepos_1]=0;
		if (maindata.channels>2) b_current[basepos_1] = rect_in[basepos_in+2]; else b_current[basepos_1]=0;
		if (maindata.channels>3) a_current[basepos_1] = rect_in[basepos_in+3]; else a_current[basepos_1]=0;
		}}
}		

static void rect_in_out_check() {
	guint x,y,basepos_rect;
	guint sum=0;
	guint checkcount=0;

	//ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
		//basepos_rect=basepos(x,y,maindata.channels,maindata.width); 
		//if (rect_in[basepos_rect]!=rect_out[basepos_rect] && checkcount<5){
			//printf(" Found r difference on position %1d %1d: in: %1d, out: %1d\n",
			//x,y,rect_in[basepos_rect],rect_out[basepos_rect]);
			//checkcount+=1;}
		//}}
		
	
	ITERATE(x,0,maindata.width*maindata.height*maindata.channels) {
		if (rect_out[x] != rect_in[x]) sum+=1;}
	printf(" differences found: %1d\n",sum);	
	}




void  mode_changed( GtkComboBox *combo, gpointer data ) {
    
    if ( strncmp( gtk_combo_box_get_active_text( combo ), mode1,11) == 0) {
    	maindata.mode=TILELESS;}
    if ( strncmp((gtk_combo_box_get_active_text( combo )),mode2,11) == 0) {
    	maindata.mode=RECTANGLE;}

	populate_tileid();   	
    if (VERBOSE) printf( "  Tile mode changed: %1d\n",maindata.mode );
}

void lock_changed( GtkComboBox *combo, gpointer data ) {
	guint c;
	ITERATE(c,0,30) locktable[c]=FALSE;
	if ( strncmp( gtk_combo_box_get_active_text( combo ), locktext2,20) == 0) {
		locktable[9]=TRUE;
		locktable[16]=TRUE;
		locktable[17]=TRUE;	}
	if ( strncmp( gtk_combo_box_get_active_text( combo ), locktext3,20) == 0) {
		ITERATE(c,0,14) locktable[c]=TRUE;
		locktable[16]=TRUE;
		locktable[17]=TRUE;}
	if ( strncmp( gtk_combo_box_get_active_text( combo ), locktext4,20) == 0) {		
		ITERATE(c, 0,10) locktable[c]=TRUE;
		ITERATE(c,21,25) locktable[c]=TRUE;	}
    if (VERBOSE) printf( "  Locktable changed\n" );
}
	

static void populate_tileid(){
	guint x,y,X,Y,basepos_1;
	const gboolean debug=FALSE;
	//0=passive
	if (maindata.mode==TILELESS) {
		ITERATE(x,0,maindata.width*maindata.height){
			tileid[x]=1;}}
	else if (maindata.mode==RECTANGLE){
		if(debug) printf (" Mode set to RECTANGLE\n");
		ITERATE(x,0,maindata.width*maindata.height){
			tileid[x]=1;}
		guchar matrix[15][18]={
			{ 9,16,17,0,9, 9, 9, 9,9,0,0, 1, 2, 2, 3, 0, 0,0},
			{ 1, 2, 3,0,9, 9, 9, 9,9,0,1,10, 9, 9,11, 3, 0,0},
			{ 8, 0, 4,0,9, 9, 9, 9,9,0,8, 9, 9, 9, 9, 4, 0,0},
			{ 7, 6, 5,0,9, 9, 9, 9,9,0,8, 9, 9, 9, 9, 4, 0,0},
			{21,24, 0,0,9,16, 9,17,9,0,7,13, 9, 9,12, 5, 0,0},
			{22,23, 0,0,9, 9, 9, 9,9,0,0, 7, 6, 6, 5, 0, 0,0},
			{10,11, 0,0,0, 0, 0, 0,0,0,0, 0, 0, 0, 0, 0, 0,0},
			{13,12, 0,0,9, 9, 9, 9,9,0,0, 0, 0, 1, 3, 0, 0,0},
			{ 0, 0, 0,0,9,21, 6,24,9,0,0, 0, 0, 8, 4, 0, 0,0},
			{ 0, 0, 0,0,9, 4, 0, 8,9,0,0, 0, 0, 8, 4, 0, 0,0},
			{ 0, 0, 0,0,9,22, 2,23,9,0,1, 2, 2,23,22, 2, 2,3},
			{ 0, 0, 0,0,9, 9, 9, 9,9,0,7, 6, 6,24,21, 6, 6,5},			
			{ 0, 0, 0,0,0, 0, 0, 0,0,0,0, 0, 0, 8, 4, 0, 0,0},
			{ 0, 0, 0,0,0, 0, 0, 0,0,0,0, 0, 0, 8, 4, 0, 0,0},
			{ 0, 0, 0,0,0, 0, 0, 0,0,0,0, 0, 0, 7, 5, 0, 0,0}
			};
		//putting above into tileid
		ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
			basepos_1=basepos(x,y,1,maindata.width);
			if (y>=15*maindata.yperiod || x>=18*maindata.xperiod) tileid[basepos_1]=0;
			else {
			X=x/maindata.xperiod;Y=y/maindata.yperiod;
			tileid[basepos_1]=matrix[Y][X];			
			if (debug && x%5==0 && y%5==0) printf (" Pixel %1d x %1d  belongs to tile %1d x %1d, value: %1d\n",x,y,X,Y,tileid[basepos_1]);}
			}}
		}
}


//static void copy_data(guchar direction){
	//gint x;
	//guchar *source_r, *source_g, *source_b, *source_a, *target_r, *target_b, *target_g, *target_a;
	
	//if (direction==NEWTOOLD) {source_r=r_new; source_g=g_new; source_b=b_new; source_a=a_new;
		//target_r=r_old; target_g=g_old; target_b=b_old; target_a=a_old;}
	//else if (direction==OLDTONEW) {source_r=r_old; source_g=g_old; source_b=b_old; source_a=a_old;
		//target_r=r_new; target_g=g_new; target_b=b_new; target_a=a_new;}

	//ITERATE(x,0,maindata.height*maindata.width) {target_r[x]=source_r[x];	}
	//if(maindata.channels>1)
		//ITERATE(x,0,maindata.height*maindata.width) {target_g[x]=source_g[x];	}
	//if(maindata.channels>2) 
		//ITERATE(x,0,maindata.height*maindata.width) {target_b[x]=source_b[x];	}	
	//if(maindata.channels>3) 
		//ITERATE(x,0,maindata.height*maindata.width) {target_a[x]=source_a[x];	}
	//}		

static void shift_new(){
	guint x,y,basepos_1,basepos_out;
	const gboolean debug=FALSE;

	ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
		basepos_1=basepos(x,y,1,maindata.width); 
		basepos_out=basepos(x,y,maindata.channels,maindata.width); 
		rect_out[basepos_out  ]  	= r_new[basepos_1];
		r_old[basepos_1]=r_new[basepos_1];
		if (maindata.channels>1) 	rect_out[basepos_out+1]  	= g_new[basepos_1];
		g_old[basepos_1]=g_new[basepos_1];
		if (maindata.channels>2)	rect_out[basepos_out+2]  	= b_new[basepos_1];
		b_old[basepos_1]=b_new[basepos_1];		
		if (maindata.channels>3) 	rect_out[basepos_out+3]  	= a_new[basepos_1];
		a_old[basepos_1]=a_new[basepos_1];	
		if (debug && x%200==0 && y%200==0) printf("shifting old values: %1d , %1d: %1d, %1d, (%1d x %1d)\n",
		rect_out[basepos_out  ],rect_out[basepos_out+1],rect_out[basepos_out+2],rect_out[basepos_out+3],x,y  );
		if (debug ) if (MAX(rect_out[basepos_out  ],MAX(rect_out[basepos_out+1],MAX(rect_out[basepos_out+2],rect_out[basepos_out+3])))>255) {
			printf (" Out of range error: %1d, position %1d x %1d\n",
			MAX(rect_out[basepos_out  ],MAX(rect_out[basepos_out+1],MAX(rect_out[basepos_out+2],rect_out[basepos_out+3]))),x,y);
			exit(1);}
		}}
	}

static void apply_changes2() {
	guint stat[6]={0,0,0,0,0,0};//(0)out of tile,(1)locked,(2)erasing,(3)spreading,(4)left
	guint x,y,basepos_1,sum;
	guint checkcount=0;
	
	ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
		basepos_1=basepos(x,y,1,maindata.width);	

		//changed in previous iterations		
		if (rect_processed[basepos_1]==TRUE){
			 continue;}

		//tileset mode but out of tiles			 
		if (tileid[basepos_1]==0) {
			stat[0]+=1;
			rect_processed[basepos_1]=TRUE;
			old_to_new(basepos_1);
			//continue;
			}
			
		//locked area / reverting to original values
		else if (locktable[tileid[basepos_1]]==TRUE) {
			stat[1]+=1;
			old_to_new(basepos_1);
			//r_new[basepos_local]=r_old[basepos_1];
			//if (maindata.channels>1) g_new[basepos_local]=g_old[basepos_1];
			//if (maindata.channels>2) b_new[basepos_local]=b_old[basepos_1];
			//if (maindata.channels>3) a_new[basepos_local]=a_old[basepos_1];
				}			

		//erasing (white to transparency)
		else if (maindata.channels==4 && maindata.wta==TRUE && r_current[basepos_1]==255 &&
			g_current[basepos_1]==255 && b_current[basepos_1]==255 && a_current[basepos_1]>0  ){
			spread_pixel(x,y,255,255,255,0,&stat[2]);
			//stat[2]+=1;
			}
			
		
		//skipping pixel, nothing to be done
		else if (r_old[basepos_1]==r_current[basepos_1] &&
			g_old[basepos_1]==g_current[basepos_1] &&
			b_old[basepos_1]==b_current[basepos_1] &&
			a_old[basepos_1]==a_current[basepos_1]  ){
			//stat[2]+=1;
				}
		
		//to be spread
		else {
			if (DEBUG && x%10==0 && y%10==0 && checkcount<6) {
				printf (" spreading values: %1d %1d %1d %1d (old values: %1d %1d %1d %1d), px: %1d x %1d\n",
				r_current[basepos_1],g_current[basepos_1],b_current[basepos_1],a_current[basepos_1],
				r_old[basepos_1],g_old[basepos_1],b_old[basepos_1],a_old[basepos_1],x,y);
				checkcount+=1;}
			//stat[3]+=1;
			spread_pixel(x,y,r_current[basepos_1],g_current[basepos_1],b_current[basepos_1],a_current[basepos_1],&stat[3]);
			if (DEBUG && x%10==0 && y%10==0 && checkcount<6) {
				printf ("  new values check: %1d %1d %1d %1d \n",
				r_new[basepos_1],g_new[basepos_1],b_new[basepos_1],a_new[basepos_1]);
				checkcount+=1;}
		}
		}}

	//counting not changed pixels
	ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
		basepos_1=basepos(x,y,1,maindata.width); 
		if  (rect_processed[basepos_1]==FALSE){
			stat[4]+=1;
			old_to_new(basepos_1);}
		}}
	
	
	if (VERBOSE) printf(" Statistic: out of tile %1d, locked %1d, erasing %1d, spreading %1d, left over %1d\n",
		stat[0],stat[1],stat[2],stat[3],stat[4]);
		
	if ( (stat[0]+stat[1]+stat[2]+stat[3]+stat[4]) != (maindata.width*maindata.height) ) 
		printf(" Internal pixel processing error: %1d vs. %1d \n",
		stat[0]+stat[1]+stat[2]+stat[3]+stat[4],maindata.width*maindata.height);
	
	//if (VERBOSE) printf ("  sum=%1d, img size: %1d\n",stat[0]+stat[1]+stat[2]+stat[3]+stat[4],maindata.width*maindata.height);

	//basepos_1=basepos(10,10,1,maindata.width);
	//printf("changing r old: %1d, current: %1d, new: %1d\n", r_old[basepos_1],r_current[basepos_1],r_new[basepos_1]);

}

static void old_to_new(guint basepos) {
	r_new[basepos]=r_old[basepos];
	g_new[basepos]=g_old[basepos];
	b_new[basepos]=b_old[basepos];
	a_new[basepos]=a_old[basepos];	}
	
static void spread_pixel(guint x, guint y, guchar r, guchar g, guchar b, guchar a,guint *counter){
	guint tile_id, basepos_1,local_x,local_y,basepos_local;
	gint multi_x,multi_y;
	
	basepos_1=basepos(x,y,1,maindata.width);	
	tile_id=tileid[basepos_1];
	ITERATE(multi_x,-maindata.repeatedlimit,maindata.repeatedlimit) {ITERATE(multi_y,-maindata.repeatedlimit,maindata.repeatedlimit){
		local_x=multi_x*maindata.xperiod+x;
		local_y=multi_y*maindata.yperiod+y;
		if (local_x<0 || local_x>=maindata.width ||
		local_y<0 || local_y>=maindata.height) continue;
		basepos_local=basepos(local_x,local_y,1,maindata.width);
		//if (rect_processed[basepos_local]==TRUE) continue;
		if (maindata.mode==RECTANGLE && tile_id != tileid[basepos_local]) continue; //this is different tile
		//what if the pixel had been changed yet?
		r_new[basepos_local]=r;
		g_new[basepos_local]=g;
		b_new[basepos_local]=b;				
		a_new[basepos_local]=a;
		rect_processed[basepos_local]=TRUE;
		*counter+=1;}}
	}	
	
		
	
static void reset_changed(){
	gint x;
	ITERATE(x,0,maindata.height*maindata.width) {
		rect_processed[x]=FALSE;}
	}

void process (GimpDrawable *source,GimpPreview *preview) {

	gint         x1, y1, x2, y2; 		//x,y from image not area
	GimpPixelRgn rgn_in, rgn_out;

	if ( !gimp_drawable_is_valid(source->drawable_id)) {
		printf ("Drawable not valid...\n");
		exit(1);}
	
	
	if (VERBOSE ) {
		printf ("== Running Periodic Painter plugin... ==\n");
		} // check this

	// getting coordinates of what will be saturated
	gimp_drawable_mask_bounds (source->drawable_id,&x1, &y1,&x2, &y2);
	maindata.width = x2 - x1;
	maindata.height = y2 - y1;
	maindata.channels = gimp_drawable_bpp (source->drawable_id);//getting number of channels
	drawable_ID=source->drawable_id;
	if (DEBUG) printf (" Drawable ID: %1d\n",drawable_ID);
	
	//changing repeatedlimit
	maindata.repeatedlimit=(gfloat)maindata.width/maindata.xperiod+1;
	if ((gfloat)maindata.height/maindata.yperiod>maindata.repeatedlimit-2) maindata.repeatedlimit=(gfloat)maindata.height/maindata.yperiod+1;
	
	
  //initializing pixelrgn for input and output row
	gimp_pixel_rgn_init (&rgn_in,
                       source,
                       x1, y1,
                       maindata.width, maindata.height,
                       FALSE, FALSE);
                       
	gimp_pixel_rgn_init (&rgn_out,
                       source,
                       x1, y1,
                       maindata.width, maindata.height,
                       TRUE,FALSE);
                       
    if (maindata.width ==1) return;

	if (VERBOSE) {
		printf (" Processed area: %1d x %1d, channels: %1d, repeatedlimit: %1d, mode: %1d\n",maindata.width,maindata.height,maindata.channels,maindata.repeatedlimit,maindata.mode);
		if (oldstatus==EMPTY) printf( " (Taking snapshot....)\n");
		else  printf( " (Spreading changes...)\n");
		}

 	/* Initialise memory for rects*/
 	if (rect_initialized==FALSE) {
	 	rect_in  = g_new (guchar, maindata.channels * maindata.width * maindata.height); 
	 	rect_out = g_new (guchar, maindata.channels * maindata.width * maindata.height); 
		r_old = g_new (guchar, maindata.height  * maindata.width);
		g_old = g_new (guchar, maindata.height  * maindata.width);	
		b_old = g_new (guchar, maindata.height  * maindata.width);	
		a_old = g_new (guchar, maindata.height  * maindata.width);
		r_current = g_new (guchar,  maindata.height  * maindata.width);
		g_current = g_new (guchar,  maindata.height  * maindata.width);	
		b_current = g_new (guchar,  maindata.height  * maindata.width);	
		a_current = g_new (guchar,  maindata.height  * maindata.width);
		r_new = g_new (guchar, maindata.height  * maindata.width);
		g_new = g_new (guchar, maindata.height  * maindata.width);	
		b_new = g_new (guchar, maindata.height  * maindata.width);	
		a_new = g_new (guchar, maindata.height  * maindata.width);
		tileid= g_new (guchar,maindata.height  * maindata.width);
		rect_processed= g_new (gboolean, maindata.height  * maindata.width);
		rect_initialized=TRUE;
		if (DEBUG) printf (" CB: memory allocated\n");
		}
	

	//loading all data to rect_in
	gimp_pixel_rgn_get_rect (&rgn_in,rect_in,x1, y1,maindata.width,maindata.height);
	if (DEBUG) printf (" PP: data loaded in rect_in\n");
	

	
	//if this is first run
	if (oldstatus==EMPTY) {
		//printf ("First run.... populating old\n");
		populate_tileid();
		populate_old();
		if (DEBUG) printf ("First run.... populating old... done\n");
		oldstatus=FULL;
		return;}
	else {
		if (DEBUG) rect_in_out_check();
				
		if (DEBUG) printf (" PP: Applying changes...\n");
		gimp_drawable_flush (source);
		//if not, putting data into current
		populate_current();
		//zeroing rect_processed
		reset_changed();
		apply_changes2();
		shift_new();
		}
	
	//putting data back to drawable
	gimp_pixel_rgn_set_rect (&rgn_out, rect_out,x1, y1,maindata.width,maindata.height);
	if (DEBUG) printf (" PP: data back in rgn\n");   
	
	//update (graphical) of preview/image
	
	gimp_drawable_flush (source);
	//gimp_drawable_merge_shadow (source->drawable_id, TRUE); //second is for undo
	gimp_drawable_update (source->drawable_id,x1,y1,maindata.width,maindata.height);
	gimp_displays_flush();
	//g_free(rect_in);
	//g_free(rect_out);
	
	
	
}
 

gboolean plugin_gui (GimpDrawable *source)
{
	GtkWidget *dialog;
	GtkWidget *label_xperiod, *label_yperiod,*label_mode;
	GtkWidget *t_align,*cb_align,*tile_align;
	GtkWidget *main_vbox,*table,*tile_exp,*tile_vbox;
	GtkObject *xperiod_spin_adj, *yperiod_spin_adj;
	GtkWidget *commitbutton,*maskexportbutton,*wta_button,*tileexportbutton;
	
	gimp_ui_init ("Color Blur", FALSE);
	
	gimp_dialogs_show_help_button (FALSE);
	dialog = gimp_dialog_new (title, "colorblur",
                            NULL, 0,
                            gimp_standard_help_func, "plug-in-perpainter",
                            //NULL,RESPONSE_OK,
                            GTK_STOCK_REFRESH,RESPONSE_REFRESH,
                            GTK_STOCK_QUIT, RESPONSE_QUIT,
                            NULL);

  
	gtk_container_set_border_width (GTK_CONTAINER (dialog), 12); // ???

	main_vbox = gtk_vbox_new (FALSE, 0);
	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), main_vbox);
	gtk_widget_show (main_vbox);
	
	
	//creating table 
	t_align=gtk_alignment_new(0.5,0.5,1,0);
	gtk_box_pack_start (GTK_BOX (main_vbox), t_align, FALSE, FALSE, 0);
	gtk_alignment_set_padding(GTK_ALIGNMENT(t_align),10,15,0,0);
	gtk_widget_show (t_align);	
	
	table=gtk_table_new(1,2,TRUE);
	gtk_table_set_col_spacings( GTK_TABLE(table), 15 );
	gtk_table_set_row_spacings( GTK_TABLE(table), 2 );
	gtk_container_add(GTK_CONTAINER(t_align), table);

		//inserting labels
		label_xperiod = gtk_label_new ("<b>X period:</b>");
		gtk_label_set_use_markup (GTK_LABEL(label_xperiod), TRUE);
		gtk_misc_set_alignment (GTK_MISC (label_xperiod), 0, 0.5);
		gtk_table_attach_defaults(GTK_TABLE(table),label_xperiod,0,1,0,1);
		gtk_widget_show (label_xperiod);
		label_yperiod = gtk_label_new ("<b>Y period:</b>");
		gtk_label_set_use_markup (GTK_LABEL(label_yperiod), TRUE);
		gtk_misc_set_alignment (GTK_MISC (label_yperiod), 0, 0.5);
		gtk_table_attach_defaults(GTK_TABLE(table),label_yperiod,0,1,1,2);
		gtk_widget_show (label_yperiod);
		label_mode = gtk_label_new ("<b>Tiles mode:</b>");
		gtk_label_set_use_markup (GTK_LABEL(label_mode), TRUE);
		gtk_misc_set_alignment (GTK_MISC (label_mode), 0, 0.5);
		gtk_table_attach_defaults(GTK_TABLE(table),label_mode,0,1,2,3);
		gtk_widget_show (label_mode);

  		//inserting spinboxes
		xperiod_spin_adj = gtk_adjustment_new (32, 0, 1000, 1, 10,0);
		xperiod_spin = gtk_spin_button_new (GTK_ADJUSTMENT (xperiod_spin_adj), 2, 0);
		gtk_entry_set_alignment (GTK_ENTRY(xperiod_spin),1);
		gtk_table_attach_defaults(GTK_TABLE(table),xperiod_spin,1,2,0,1);
		gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (xperiod_spin), TRUE);
		gtk_widget_set_tooltip_text( xperiod_spin, "Horizontal frequency (px)");
		gtk_widget_show (xperiod_spin);	
		yperiod_spin_adj = gtk_adjustment_new (32, 0, 1000, 1, 10,0);
		yperiod_spin = gtk_spin_button_new (GTK_ADJUSTMENT (yperiod_spin_adj), 2, 0);
		gtk_entry_set_alignment (GTK_ENTRY(yperiod_spin),1);
		gtk_table_attach_defaults(GTK_TABLE(table),yperiod_spin,1,2,1,2);
		gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (yperiod_spin), TRUE);
		gtk_widget_set_tooltip_text( yperiod_spin, "Vertical frequency (px)");
		gtk_widget_show (yperiod_spin);	
		combo = gtk_combo_box_new_text();
		//gtk_misc_set_alignment(GTK_MISC(combo),1,0.5);
		gtk_combo_box_append_text( GTK_COMBO_BOX( combo),mode1);
		gtk_combo_box_append_text( GTK_COMBO_BOX( combo),mode2);
		gtk_table_attach_defaults(GTK_TABLE(table),combo,1,2,2,3);
		//gtk_widget_set_size_request(combo,120,-1);
		gtk_combo_box_set_active(GTK_COMBO_BOX( combo), 0);
		gtk_widget_show (combo);

	
	gtk_widget_show (table);	


	tile_align=gtk_alignment_new(0,0.5,0,0);
	gtk_box_pack_start (GTK_BOX (main_vbox), tile_align, FALSE, FALSE, 0);
	gtk_alignment_set_padding(GTK_ALIGNMENT(tile_align),20,5,0,0);
	gtk_widget_show (tile_align);

	tile_exp = gtk_expander_new  ("<b>Tileset mode options:</b>" );
	gtk_widget_set_tooltip_text( tile_exp, "Some options in expander are not working in free (not tileset) mode");
	gtk_expander_set_use_markup (GTK_EXPANDER(tile_exp), TRUE);
	gtk_container_add(GTK_CONTAINER(tile_align),tile_exp ); 
	gtk_widget_show (tile_exp);

	tile_vbox= gtk_vbox_new (FALSE,5);
	gtk_container_add (GTK_CONTAINER (tile_exp), tile_vbox);
	gtk_container_set_border_width (GTK_CONTAINER (tile_vbox), 15);
	gtk_widget_show(tile_vbox);


		maskexportbutton = gtk_button_new_with_label("Guiding layers");
		gtk_widget_set_tooltip_text(maskexportbutton, "Create 2 guiding layers and put them below. (You are to edit original layer)...");
		gtk_box_pack_start (GTK_BOX (tile_vbox), maskexportbutton, FALSE, FALSE, 0);
		//gtk_container_add(GTK_CONTAINER(leav_align), leavebutton);
		//gtk_widget_set_size_request(maskexportbutton,80,30);
		gtk_widget_show (maskexportbutton);	
	
	
		wta_button= gtk_check_button_new_with_label("White to tranparency");
		gtk_box_pack_start (GTK_BOX (tile_vbox), wta_button, FALSE, FALSE, 0);
		gtk_widget_set_tooltip_text(wta_button, "Use white color [255,255,255] as eraser. For RGBA layers only!.");
		//gtk_widget_set_size_request(wta_button,0,40);
		gtk_widget_show (wta_button);
	
		lockcombo = gtk_combo_box_new_text();
		gtk_combo_box_append_text( GTK_COMBO_BOX( lockcombo),locktext1);
		gtk_combo_box_append_text( GTK_COMBO_BOX( lockcombo),locktext2);
		gtk_combo_box_append_text( GTK_COMBO_BOX( lockcombo),locktext3);
		//gtk_combo_box_append_text( GTK_COMBO_BOX( lockcombo),locktext4);
		gtk_box_pack_start (GTK_BOX (tile_vbox), lockcombo, FALSE, FALSE, 0);
		//gtk_widget_set_size_request(lockcombo,120,-1);
		gtk_combo_box_set_active(GTK_COMBO_BOX( lockcombo), 0);
		gtk_widget_show (lockcombo);
	
		tileexportbutton = gtk_button_new_with_label("Save tileset");
		gtk_widget_set_tooltip_text(tileexportbutton, "Saves tileset as new file (saves active layer only)");
		gtk_box_pack_end (GTK_BOX (tile_vbox), tileexportbutton, FALSE, FALSE, 0);
		gtk_widget_show (tileexportbutton);	
	

	cb_align=gtk_alignment_new(0.5,0.5,0,0);
	gtk_box_pack_end (GTK_BOX (main_vbox), cb_align, FALSE, FALSE, 0);
	gtk_alignment_set_padding(GTK_ALIGNMENT(cb_align),25,30,0,0);
	gtk_widget_show (cb_align);	

	commitbutton = gtk_button_new_with_label("Spread changes");
	gtk_widget_set_tooltip_text(commitbutton, "The plugin will identify changes made since its last run and spread them over image.");
	gtk_container_add(GTK_CONTAINER(cb_align), commitbutton);
	gtk_widget_set_size_request(commitbutton,120,50);
	gtk_widget_show (commitbutton);	


	process (source,NULL);

	//update of values in maindata
	g_signal_connect (xperiod_spin_adj, "value_changed", G_CALLBACK (gimp_uint_adjustment_update  ), &maindata.xperiod);  
	g_signal_connect (yperiod_spin_adj  , "value_changed", G_CALLBACK (gimp_uint_adjustment_update  ), &maindata.yperiod);  
	g_signal_connect_swapped(G_OBJECT(commitbutton),"clicked",G_CALLBACK( commitwrapper ), source );
	g_signal_connect(G_OBJECT(maskexportbutton),"clicked",G_CALLBACK( export_mask_wrap ), NULL );
	g_signal_connect(G_OBJECT(tileexportbutton),"clicked",G_CALLBACK( tile_save ), NULL );	
	g_signal_connect( G_OBJECT( combo )	, "changed"  		, G_CALLBACK( mode_changed ), NULL );
	g_signal_connect( G_OBJECT( lockcombo )	, "changed"  		, G_CALLBACK( lock_changed ), NULL );
	g_signal_connect (G_OBJECT(wta_button), "toggled"	, G_CALLBACK( wta_changed ), NULL );
	gtk_widget_show (dialog);
  
	//receiving signals from gimp buttons + "x" (close window button)
	g_signal_connect (dialog, "response",
                    G_CALLBACK (response_callback),
                    NULL);

	gtk_main ();
	
	return process_image; //TRUE if image should be processed

}

void response_callback (GtkWidget *widget,  gint response_id) {
	
  switch (response_id) 	 {

    case RESPONSE_QUIT:  
		if (VERBOSE) printf (" User input: Leave\n");
		gtk_widget_destroy (widget);
		gtk_main_quit (); 
	 	g_free(rect_in);
	 	g_free(rect_out);
		g_free(r_old);
		g_free(g_old);	
		g_free(b_old);	
		g_free(a_old);
		g_free(r_current);
		g_free(g_current);	
		g_free(b_current);
		g_free(a_current);
		g_free(r_new);
		g_free(g_new);
		g_free(b_new);
		g_free(a_new);
		g_free(rect_processed);    
		break;

    case RESPONSE_REFRESH:  
	    reinitwrapper(drawable);
    	break;

    default: // if other response - terminate plugin window
	if (VERBOSE) printf (" User input: %1d\n",response_id);

	 	g_free(rect_in);
	 	g_free(rect_out);
		g_free(r_old);
		g_free(g_old);	
		g_free(b_old);	
		g_free(a_old);
		g_free(r_current);
		g_free(g_current);	
		g_free(b_current);
		g_free(a_current);
		g_free(r_new);
		g_free(g_new);
		g_free(b_new);
		g_free(a_new);
		g_free(rect_processed);

      gtk_widget_destroy (widget);
      gtk_main_quit ();
      break;
    };
}

