// Selective color-based blur
// gimp plugin
// version 0.6.2 - development version
//
// to compile & install (on linux):
// for/as current user:
//  $gimptool-2.0 --install colorblur.c
// and for system-wide installation (run as root):
//  $gimptool-2.0 --install-admin colorblur.c
// following might be needed beforehand (on Arch Linux f.e.): 
//  $export LDFLAGS="$LDFLAGS -lm"
//
//contact: tiborb95 at gmail dot com, any feedback welcomed
// homepage: http://code.google.com/p/selective-color-blur/
// or visit this link http://registry.gimp.org/node/25801



#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 TO_FLOAT_W_GAMMA(x) pow((gfloat)x/255,1/2.2)
#define TO_FLOAT(x) (gfloat)x/255
#define TO_INT_W_GAMMA(x) MAX(MIN(ROUND(pow(x,2.2)*255),255),0)
#define TO_INT(x) MAX(MIN(x*255,255),0)
#define SQ(x) x*x
#define ITERATE(x,start,end) for(x=start;x<end;x++)
#define DEITERATE(x,start) for(x=start;x-- > 0 ;)
#define RADIUSDEFAULT 5
#define TRESHDEFAULT 0.02
#define BRTRESHDEF 0.06
#define ITERDEFAULT 5//6
#define THREADS 3     
#define COLORSDEF 6
#define LINESENSDEF 1.0
//processing modes
#define NOCHANGE 0
#define CARTREDBR 1
#define CARTOONIZE 2
#define DOLINES 3
#define MODEDEF 2     //default
#define MINDIRCOUNT 3
#define PRECLEANDEF FALSE


//structures
typedef struct {
	gfloat tresh;
	guint8 radius;
	guint8 iterations;
	guint8 mode;
	gfloat brtresh; //5
	gfloat linesens;
	gfloat slidersat;
	gboolean exportlayer;
	gboolean doclean;
	gfloat darkpoint;   //10
	gfloat lightpoint;
	guint8 mindircount;
	gfloat satgamma;
	gboolean fastmode;
	gboolean preclean;
	//guint8 cleantresh;  //0=no cleaning
	gboolean preview;
	guint8 channels;
	gint directions[8];   //15
	gfloat treshsq;
	gfloat localweight;
	gint width;
	gint height;
	guint scanpx; //number of scanned pixels, might be smaller then radius
	guint samecountlimit; // for lines cleaning
	guint8 activelayer;
} MyGenVals;
static MyGenVals maindata = {TRESHDEFAULT,RADIUSDEFAULT, ITERDEFAULT,MODEDEF,BRTRESHDEF,
							LINESENSDEF,1,FALSE,FALSE,0,
							1,MINDIRCOUNT,1,FALSE,FALSE};

typedef struct {
	guint8 id;
	guint8 iteration;
	gfloat* rsource;
	gfloat* gsource;
	gfloat* bsource;
	gfloat* rtarget;
	gfloat* gtarget;
	gfloat* btarget;
	gfloat* brsource;
	gfloat* brtarget;
	guint32 stat[4]; //4 good blurable for forced
	guint changedcount;  // statistics for lines
} threaddatastr;

typedef struct {
	gboolean good;
	guint8 len_good;
	guint8 len;
	gfloat sumbrdist;
	gfloat sumhuedist;
	gfloat sumr;
	gfloat sumb;
	gfloat sumg;
	gfloat sumbr;
	gfloat avgr;
	gfloat avgb;
	gfloat avgg;
	gfloat avgbr;
	gfloat minr;
	gfloat ming;
	gfloat minb;
	gfloat minbr;
	gfloat maxr;
	gfloat maxg;
	gfloat maxb;
	gfloat maxbr;					
	gfloat diffratio;
} dirstruct;

//variables
static gboolean previewbool;
static guchar *rect_in,*rect_out;
static gboolean *rect_good; // TRUE for pixels that were blurred in good way
static gfloat *r1,*g1,*b1,*r2,*g2,*b2,*br1,*br2; // working layers to be switched
static gfloat *r_orig,*g_orig,*b_orig,*brorig;   //original layer (can be changed!)
static gfloat *br_unch;     // original br data - should not be changed
static gint basepos_diff[8]; //to speed up calculating local basepos
const static gint8 near_pixels[16]={0,1,1,1,1,0,1,-1,0,-1,-1,-1,-1,0,-1,1};
static guint16 dirstepping[100];

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

//GUI variables
gchar *title = "Selective Color Blur (0.6.2)";
static GtkWidget *radius_spin,*tresh_spin, *iter_spin, *brtresh_spin,*linesens_spin,*slider_sat,*slider_mc;
static GtkWidget *combo;
gchar *mode0 = "Blur the color (only)";
gchar *mode1 = "Cartonize (shrink brightness)";
gchar *mode2 = "Cartoonize   ";
gchar *mode3 = "Create lines ";
static GtkWidget *forcedbutton,*fastbutton,*cleanbutton;

//multithreading related
static GThread *gth[THREADS];
static threaddatastr thread_data[THREADS];

//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);
static void blur_data();
void *blur_step_clean (void *arg);
void *blur_step_noclean (void *arg);
void *create_lines (void *arg);
void *clean_lines (void *arg);
static void fill_rect_out();
static void fill_tmp();
static gfloat	gammed_values[256];
inline gfloat get_bright (gfloat R,gfloat G,gfloat B);
static void  mode_changed( GtkComboBox *combo);
void fs_changed(GtkToggleButton *forcedbutton);
void fs_changed(GtkToggleButton *cleanbutton);
void fb_changed(GtkToggleButton *fastbutton);
void ds_changed(GtkToggleButton *dsbutton);
void do_lines();
inline gint basepos(gint x, gint y,gint ch, gint width);
void sort(gfloat *array);
gfloat reduce(gfloat x,gfloat min,gfloat max);

MAIN()


static void query (void) {
	static GimpParamDef args[] =   {
  		{ GIMP_PDB_INT32, "run-mode", "Run mode" },
    	{ GIMP_PDB_IMAGE, "image", "Input image"  },
    	{ GIMP_PDB_DRAWABLE,"drawable", "Input drawable" },
     	{ GIMP_PDB_INT32,"mode", "Processing mode (0,1,2,3)" },
    	{ GIMP_PDB_INT32,"radius", "radius" },
    	{ GIMP_PDB_INT32,"iterations", "Iterations" },
    	{ GIMP_PDB_FLOAT,"coltresh", "Color treshold" },
    	{ GIMP_PDB_FLOAT,"brtresh", "Brightness treshold" },
    	{ GIMP_PDB_FLOAT,"linesens", "Sensitivity for lines creation" },
    	{ GIMP_PDB_INT32,"docleanup", "Ignore small details (1=True)" },
    	{ GIMP_PDB_FLOAT,"darkpoint", "Modification of 0 brightness point" },
    	{ GIMP_PDB_FLOAT,"lightpoint", "Modification of 0 brightness point" },    	
    	{ GIMP_PDB_FLOAT,"slidersat", "Saturation" },
    	{ GIMP_PDB_INT32,"dopreclean", "Do soft-preclean of image (1=True)" },
     	{ GIMP_PDB_INT32,"mindircount", "number of directions (2-4)" }   ,
      	{ GIMP_PDB_INT32,"precleaning", "clean extremes during processing" }   
    	   };

	gimp_install_procedure (
    	"plug-in-colorblur",
    	title,
    	"Selective Color-based Blurring - blurs color information or whole image, intended for improvement of colors or cartoonization.",
    	"Tibor Bamhor",
    	"GPL v.3",
    	"2011",
    	"_Color Selective Blur",
    	"RGB*",
    	GIMP_PLUGIN,
    	G_N_ELEMENTS (args), 0,
    	args, NULL);

	gimp_plugin_menu_register ("plug-in-colorblur",                        
    	"<Image>/Filters/Blur");
}


void   gimp_uint8_adjustment_update(GtkAdjustment *adjustment, guint8* data){
	*data=MAX(MIN(gtk_adjustment_get_value(adjustment),255),0);
	;}

void static parse_bool(gint value, gint* cur_value,gint position) {
	if (value==-1) return;			
 	if (value>=0 && value <=1) *cur_value=value;
 	else {printf ("  Incorrect argument %1d: %1d (expecting bool - 0/1)\n\n",position, value);exit(1);}
	} 

void static parse_range_int_uint8(gint value, guint8* cur_value,gint position,gint min, gint max){
	if (value==-1) return;
 	if (value>=min && value <=max) *cur_value=value;
 	else {printf ("  Incorrect argument %1d: %1d (expecting int in range %1d - %1d)\n\n",position, value, min,max);exit(1);}
	}	 

void static parse_range_float(gfloat value, gfloat* cur_value,gint position,gfloat min, gfloat max){
	if (value==-1) return;
 	if (value>=min && value <=max) *cur_value=value;
 	else {printf ("  Incorrect argument %1d: %.3f (expecting float in range %.3f - %.3f)\n\n",position, value, min,max);exit(1);}
	}	

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 (! plugin_gui (drawable)) return;
            break;

		case GIMP_RUN_NONINTERACTIVE:
			if (nparams != 16) {
				printf ("\n NON-INTERACTIVE MODE ERROR: wrong number of parameters (%1d, expected: 16). Quitting...\n\n",nparams);
				exit(1);}
				
			if (VERBOSE) printf (" Running non-interactive mode; parameters: %1d\n",nparams);
			parse_range_int_uint8(param[3].data.d_int32,&maindata.mode,1,0,3);
			parse_range_int_uint8(param[4].data.d_int32,&maindata.radius,2,0,255);			
			parse_range_int_uint8(param[5].data.d_int32,&maindata.iterations,3,0,255);				
			parse_range_float(param[6].data.d_float,&maindata.tresh,4,0.001,2);
			parse_range_float(param[7].data.d_float,&maindata.brtresh,5,0.001,1);			
			parse_range_float(param[8].data.d_float,&maindata.linesens,6,0.1,20);				
			parse_bool(param[9].data.d_int32,&maindata.doclean,7);
			parse_range_float(param[10].data.d_float,&maindata.darkpoint,8,-0.5,1.0);	
			parse_range_float(param[11].data.d_float,&maindata.lightpoint,9,0,1.5);	
			parse_range_float(param[12].data.d_float,&maindata.slidersat,10,1,2.5);	
			parse_bool(param[13].data.d_int32,&maindata.fastmode,11);
			parse_range_int_uint8(param[14].data.d_int32,&maindata.mindircount,12,1,4);
			parse_bool(param[15].data.d_int32,&maindata.preclean,13);
			
			 
            //if (nparams != 4)  status = GIMP_PDB_CALLING_ERROR;
            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;
}

static void populate_dirstepping(){
	guint16 i;
	const gboolean debug=FALSE;
	const gint primes[23]= {1, 2, 3, 7, 11, 13, 17, 23, 29, 41, 47, 53, 61, 71, 79, 97, 131, 163, 199, 233, 257, 277, 311  };

	if (maindata.fastmode ==FALSE){
		maindata.scanpx=maindata.radius;
		for (i=0;i<maindata.radius;i+=1) dirstepping[i]=i+1; }
	else{
		ITERATE(i,0,23){
			dirstepping[i]=primes[i];
			if (dirstepping[i]>=maindata.radius){
				dirstepping[i]=maindata.radius;
				break;}
			}
			maindata.scanpx=i+1;			
		}
	
	if (debug){
		for (i=0;i<maindata.scanpx;i+=1){
			printf ("  pixel #%1d to scan: %3d\n", i+1,dirstepping[i]);	}
		printf("  Summary: pixels to scan: %1d, requested radius: %1d\n",maindata.scanpx,maindata.radius);
		}
	
}
	
	
void export_to_layer()
{
	GimpDrawable *new_layer;
	gint layer_ID,basepos_4,basepos_ch;
	gint x,y;
	GimpPixelRgn rgn_out_nl;
	guchar *rect_out_nl;
	
	if (VERBOSE) printf("== Exporting to a layer..\n");
	
	//creating and attaching new layer
	layer_ID = gimp_layer_new (image_ID, "Cartoon", 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_ch=basepos(x,y,maindata.channels,maindata.width);
		basepos_4=basepos(x,y,4,maindata.width);
		rect_out_nl[basepos_4    ] = rect_out[basepos_ch ];
		rect_out_nl[basepos_4 + 1] = rect_out[basepos_ch+1];
		rect_out_nl[basepos_4 + 2] = rect_out[basepos_ch+2];
		if ( maindata.channels ==4)
			rect_out_nl[basepos_4 + 3] = rect_out[basepos_ch + 3 ];	
		else
			rect_out_nl[basepos_4 + 3] =255;
		}}
   
	//nahratie udajov do rgn_out
    gimp_pixel_rgn_set_rect (&rgn_out_nl, rect_out_nl,0, 0,maindata.width,maindata.height);
	
	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_displays_flush ();
	
	gimp_progress_end();
	
	g_free (rect_in) ;
	g_free (rect_out);
	g_free (rect_good);
	g_free (r1);g_free (g1);g_free (b1);g_free (r2);g_free (g2);g_free (b2);
	g_free (br1);g_free (br2);
	g_free (brorig);
	g_free (br_unch);
}	

void fs_changed(GtkToggleButton *forcedbutton){
	maindata.doclean=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (forcedbutton));
	if (DEBUG) printf("Forced smoothening button changed %d\n",gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (forcedbutton)) );
	}

void cb_changed(GtkToggleButton *cleanbutton){
	maindata.preclean=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (cleanbutton));
	if (DEBUG) printf("Clean button changed %d\n",gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (cleanbutton)) );
	}

void fb_changed(GtkToggleButton *fastbutton){
	maindata.fastmode=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (fastbutton));
	if (DEBUG) printf("fast button changed %d\n",gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (fastbutton)) );
	}
	
void  mode_changed( GtkComboBox *combo) {
    
    if ( strncmp( gtk_combo_box_get_active_text( combo ), mode0,20) == 0) {
    	maindata.mode=NOCHANGE;
   		gtk_widget_set_sensitive(linesens_spin,FALSE);
   		gtk_widget_set_sensitive(slider_sat,TRUE);}
    if ( strncmp((gtk_combo_box_get_active_text( combo )),mode1,20) == 0) {
    	maindata.mode=CARTREDBR;
    	gtk_widget_set_sensitive(linesens_spin,FALSE);
    	gtk_widget_set_sensitive(slider_sat,TRUE);}
    if ( strncmp((gtk_combo_box_get_active_text( combo )),mode2,20) == 0) {
    	maindata.mode=CARTOONIZE;
    	gtk_widget_set_sensitive(linesens_spin,FALSE);
    	gtk_widget_set_sensitive(slider_sat,TRUE);}
    if ( strncmp((gtk_combo_box_get_active_text( combo )),mode3,10) == 0) {
    	maindata.mode=DOLINES;
    	gtk_widget_set_sensitive(linesens_spin,TRUE);
    	gtk_widget_set_sensitive(slider_sat,FALSE);}
    	
    if (VERBOSE) printf ("Selected color mode: %.d\n",maindata.mode);
    	}


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

	
inline gfloat get_bright (gfloat R,gfloat G,gfloat B){
	gfloat avg;
	
	avg= 0.299*R + 0.587*G + 0.114*B;
	//avg=(R+G+B)/3.0;
    return avg;}

void exportwrapper (GimpDrawable *drawable){ 
	maindata.exportlayer=TRUE;
	process (drawable,NULL);
	maindata.exportlayer=FALSE;	}

void toggle_active_layer(){
	if (maindata.activelayer==1) maindata.activelayer=2;
	else if (maindata.activelayer==2) maindata.activelayer=1;
	else 
		{printf ("Illegal active layer: %1d\n",maindata.activelayer);
		exit(2);
	}
	}

void *create_lines (void *arg) {
	threaddatastr *data;
	gint c, locx,locy, x, y;
	gint basepos1;
	gint basepos_local;
	gfloat R,G,B,R_local,B_local,G_local,color_diff,br_diff;
	gboolean isline;

	data=(threaddatastr *) arg;
	
	//iteration over central pixels
	for (y=data->id;y<maindata.height;y+=THREADS) { ITERATE(x,0,maindata.width) {
		basepos1=basepos(x,y,1,maindata.width);
		R=r1[basepos1];
		G=g1[basepos1];		
		B=b1[basepos1];		

		//iterate over near pixels
		isline=FALSE;
		for (c=0;c<8;c=c+1) {
			locx=x+near_pixels[c*2];
			locy=y+near_pixels[c*2+1];
			if (locx <0 || locy <0 || locx>=maindata.width || locy >= maindata.height ) continue;
			basepos_local=basepos1 - basepos_diff[c];
			R_local=r1[basepos_local];
			G_local=g1[basepos_local];			
			B_local=b1[basepos_local];
			color_diff=(R_local - R)*(R_local - R) + (G_local - G)*(G_local - G) +
				(B_local - B)*(B_local - B);
			br_diff=ABS(br1[basepos_local] - br1[basepos1]);
			if (color_diff>(maindata.treshsq/maindata.linesens) || br_diff > (maindata.brtresh/maindata.linesens)) {
				isline=TRUE;break;}
			} // end of iteration over near pixels

		if (isline) br2[basepos1]=0;
		else br2[basepos1]=1;

		}}
	return 0;	
}


void test_pixel(guint16 x,guint16 y,dirstruct *dir,gboolean safe,gfloat* rsource,gfloat* gsource,gfloat* bsource,gfloat* brsource){
	guchar c,d;
	guint32 basepos1,localpos1;
	guint16 tmp_x,tmp_y;

	const gboolean debug=FALSE;
	gfloat r_central,g_central,b_central,br_central;
	
	
	basepos1	=basepos(x,y,1,	maindata.width);		
	//printf ("  printing sources: %.3f, %.3f %.3f %.3f\n",rsource[basepos1],gsource[basepos1],bsource[basepos1],brsource[basepos1]);
	r_central	=r_orig[basepos1];
	g_central	=g_orig[basepos1];
	b_central	=b_orig[basepos1];
	br_central	=brorig[basepos1];

	//    * * * * * *  TESTING DIRECTIONS  * * * * * 
	DEITERATE(c,8){   
		//inserting data from central point
		dir[c].minr=r_central;dir[c].ming=g_central;dir[c].minb=b_central;dir[c].minbr=br_central;
		dir[c].maxr=r_central;dir[c].maxg=g_central;dir[c].maxb=b_central;dir[c].maxbr=br_central;
		dir[c].len=1;
		dir[c].sumr=r_central; dir[c].sumg=g_central; dir[c].sumb=b_central;
		dir[c].sumbr=br_central;
		
		//testing pixels withing a direction
		for(d=0;d<maindata.scanpx;d+=1) {
			
			//new method
			tmp_x=x+near_pixels[2*c  ]*dirstepping[d];
			tmp_y=y+near_pixels[2*c+1]*dirstepping[d];				
			if (safe==FALSE){
				if (tmp_x<0 || tmp_y<0 || tmp_x>=maindata.width || tmp_y>=maindata.height){
					//if (debug3&& data->id==1) printf( "  will be skipped!\n");
					break;}}
			localpos1=basepos1+dirstepping[d]*maindata.directions[c];
			
			//modifying min/max values
			if (rsource[localpos1]  < dir[c].minr)  dir[c].minr =rsource[localpos1];
			if (gsource[localpos1]  < dir[c].ming)  dir[c].ming =gsource[localpos1];
			if (bsource[localpos1]  < dir[c].minb)  dir[c].minb =bsource[localpos1];
			if (brsource[localpos1] < dir[c].minbr) dir[c].minbr=brsource[localpos1];
			if (rsource[localpos1]  > dir[c].maxr)  dir[c].maxr =rsource[localpos1];
			if (gsource[localpos1]  > dir[c].maxg)  dir[c].maxg =gsource[localpos1];
			if (bsource[localpos1]  > dir[c].maxb)  dir[c].maxb =bsource[localpos1];
			if (brsource[localpos1] > dir[c].maxbr) dir[c].maxbr=brsource[localpos1];

			//adding values
			dir[c].sumr +=rsource[localpos1];
			dir[c].sumg +=gsource[localpos1];
			dir[c].sumb +=bsource[localpos1];
			dir[c].sumbr+=brsource[localpos1];	
			//increasing len counter
			dir[c].len+=1;}	

		//we are almost done with the direction, just calculating averages is the last task here		
		dir[c].avgr = dir[c].sumr/dir[c].len;
		dir[c].avgg = dir[c].sumg/dir[c].len;
		dir[c].avgb = dir[c].sumb/dir[c].len;
		dir[c].avgbr= dir[c].sumbr/dir[c].len;
		} //end of test of direction 

	if (debug){
		ITERATE(c,0,8) {
			if (isnan(dir[c].minr) || isnan(dir[c].ming) ||isnan(dir[c].minb) ||isnan(dir[c].minbr) ||
				isnan(dir[c].maxr) ||isnan(dir[c].maxg) ||isnan(dir[c].maxb) ||isnan(dir[c].maxbr) ||
				isnan(dir[c].avgr) ||isnan(dir[c].avgg) ||isnan(dir[c].avgb) ||isnan(dir[c].avgbr) )
				printf (" nan found in min max avgvalues\n");}
		}
	}


void *blur_step_v3 (void *arg) {
	threaddatastr *data;
	guint16 x,y;
	guint8 count;
	guint32 basepos1;
	guchar d;
	dirstruct dir[8];
	gboolean pixelsafe;
	gfloat sumr,sumg,sumb,sumbr; //source for new values
	guint8 goodcount;
	gfloat br_range,color_range;
	gfloat gfloatstack[8];
	gfloat localtresh;
	data=(threaddatastr *) arg;
	
	//if (DEBUG) printf ("    Running blur_step_v3 (), thread id: %1d\n",data->id);
	
	for (y=data->id;y<maindata.height;y+=THREADS) { ITERATE(x,0,maindata.width) {

		basepos1	=basepos(x,y,1,	maindata.width);

		if (x<maindata.radius || y<maindata.radius || x>=maindata.width-maindata.radius || y>=maindata.height-maindata.radius)
			pixelsafe=FALSE;
		else pixelsafe=TRUE;		
		test_pixel(x,y,&dir[0],pixelsafe,&data->rsource[0],&data->gsource[0],&data->bsource[0],&data->brsource[0]);


		if (maindata.doclean == FALSE){
			
			//resetting sums
			sumr=0;sumb=0;sumg=0;sumbr=0;count=0;
			
			//testing result from directions, if directions is compliant, sums are increased
			DEITERATE(d,8){
				if (dir[d].len<=1) continue;
				if ((dir[d].maxr-dir[d].minr) >maindata.tresh)  continue;
				if ((dir[d].maxg-dir[d].ming) >maindata.tresh)  continue;
				if ((dir[d].maxb-dir[d].minb) >maindata.tresh)  continue;
				if ((dir[d].maxbr-dir[d].minbr)>maindata.brtresh) continue;	
				sumr+=dir[d].avgr; sumg+=dir[d].avgg;sumb+=dir[d].avgb;sumbr+=dir[d].avgbr;
				count+=1;
				}
				
			//insterting statistics
			if (count<maindata.mindircount) data->stat[0]+=1;
			else {
				data->stat[1]+=1;
				data->stat[2]+=count;}
			
			//PROCESSING
			if (count>=maindata.mindircount && maindata.fastmode==FALSE) {
				data->rtarget[basepos1] = (2*sumr/count +data->rsource[basepos1])/3.0 ;
				data->gtarget[basepos1] = (2*sumg/count +data->gsource[basepos1])/3.0  ;
				data->btarget[basepos1] = (2*sumb/count +data->bsource[basepos1])/3.0  ;
				data->brtarget[basepos1]=(2*sumbr/count +data->brsource[basepos1])/3.0  ;
				}
			else if (count>=maindata.mindircount) {
				data->rtarget[basepos1] = sumr/count;
				data->gtarget[basepos1] = sumg/count;
				data->btarget[basepos1] = sumb/count;
				data->brtarget[basepos1]= sumbr/count;	}			
			else  {
				data->rtarget[basepos1] =r_orig[basepos1];
				data->gtarget[basepos1] =g_orig[basepos1] ;
				data->btarget[basepos1] =b_orig[basepos1] ;
				data->brtarget[basepos1]=brorig[basepos1] ;}
		}


		else {   //forced blut to be here
		
			goodcount=0; // count of goog directions to skip later sorting


			DEITERATE(d,8){  // over directions
				br_range=0, color_range=0;
				
				if (dir[d].len<=1) {
					dir[d].diffratio=10;}
				else {				
					if ((dir[d].maxr-dir[d].minr) >color_range) color_range= dir[d].maxr-dir[d].minr;
					if ((dir[d].maxg-dir[d].ming) >color_range) color_range= dir[d].maxg-dir[d].ming;
					if ((dir[d].maxb-dir[d].minb) >color_range) color_range= dir[d].maxb-dir[d].minb;
					if ((dir[d].maxbr-dir[d].minbr)>br_range) br_range= dir[d].maxbr-dir[d].minbr;
					//now we calculate diff ratio
					//first if both values are below treshold:
					if (br_range/maindata.brtresh <=1 && color_range/maindata.tresh <=1) {
						dir[d].diffratio=-1;goodcount+=1;}
					else { 
						dir[d].diffratio=br_range/maindata.brtresh+color_range/maindata.tresh;}
					}
				gfloatstack[d]=dir[d].diffratio;
			}		

			if (goodcount< maindata.mindircount){
				sort(&gfloatstack[0]);		
				localtresh=gfloatstack[maindata.mindircount-1];	}
			else localtresh=-1;	

			//now we will iterate over directions once again and find and sum averages
			sumr=0;sumb=0;sumg=0;sumbr=0;count=0;
			ITERATE(d,0,8){
				if (dir[d].diffratio>localtresh) continue;
				sumr+=dir[d].avgr; sumg+=dir[d].avgg;sumb+=dir[d].avgb;sumbr+=dir[d].avgbr;count+=1;
				}

			//putting in statistics
			data->stat[1]+=1;  //all pixels are blurrable
			data->stat[2]+=count;
			if (localtresh==-1)data->stat[3]+= 1;


			//putting values into image	
			if (maindata.fastmode==FALSE){ 
				data->rtarget[basepos1] = 0.7*sumr/count +0.3*data->rsource[basepos1] ;
				data->gtarget[basepos1] = 0.7*sumg/count +0.3*data->gsource[basepos1] ;
				data->btarget[basepos1] = 0.7*sumb/count +0.3*data->bsource[basepos1] ;
				data->brtarget[basepos1]= 0.7*sumbr/count+0.3*data->brsource[basepos1] ;				
	
				r_orig[basepos1]  = 0.2*data->rtarget[basepos1]  + 0.8* r_orig[basepos1] ;
				g_orig[basepos1]  = 0.2*data->gtarget[basepos1]  + 0.8* g_orig[basepos1] ;
				b_orig[basepos1]  = 0.2*data->btarget[basepos1]  + 0.8* b_orig[basepos1] ;
				brorig[basepos1]  = 0.2*data->brtarget[basepos1] + 0.8* brorig[basepos1] ;}
			else{
				data->rtarget[basepos1] = sumr/count;
				data->gtarget[basepos1] = sumg/count;
				data->btarget[basepos1] = sumb/count;
				data->brtarget[basepos1]= sumbr/count;				
	
				r_orig[basepos1]  = 0.4*data->rtarget[basepos1]  + 0.6* r_orig[basepos1] ;
				g_orig[basepos1]  = 0.4*data->gtarget[basepos1]  + 0.6* g_orig[basepos1] ;
				b_orig[basepos1]  = 0.4*data->btarget[basepos1]  + 0.6* b_orig[basepos1] ;
				brorig[basepos1]  = 0.4*data->brtarget[basepos1] + 0.6* brorig[basepos1] ;}

		}  //end of forced blur

	}}

	return 0;
}


void *pre_clean2(void* arg) {
	guint16 x,y, tmp_x,tmp_y;
	guint basepos_1,local_basepos;
	guchar c;
	gfloat maxr,minr,maxg,ming,maxb,minb,maxbr,minbr;
	gboolean changed;	
	const gboolean debug=FALSE;
	const gboolean sanitydebug=FALSE;
	threaddatastr *data;
	data=(threaddatastr *) arg;

	if(debug) printf("  pre_clean2 - starting thread %1d\n",data->id);
	
						
	//ITERATE(x,1,maindata.width-1) {ITERATE(y,1,maindata.height-1) {
	for (y=data->id;y<maindata.height;y+=THREADS) { ITERATE(x,0,maindata.width) {
		basepos_1=basepos(x,y,1,maindata.width);
		
		maxr=-1;maxg=-1;maxb=-1;maxbr=-1;minr=2;minb=2;ming=2;minbr=2;
		ITERATE (c,0,8) {
			tmp_x=x+1+near_pixels[2*c  ];
			tmp_y=y+1+near_pixels[2*c+1];
			if (tmp_x<=0 || tmp_y<=0 || tmp_x>=maindata.width+1 || tmp_y>=maindata.height+1) continue;
			local_basepos= basepos_1+maindata.directions[c];		
	
			if (minr >data->rsource[local_basepos] )   minr = data->rsource[local_basepos];
			if (ming >data->gsource[local_basepos] )   ming = data->gsource[local_basepos];
			if (minb >data->bsource[local_basepos] )   minb = data->bsource[local_basepos]; 
			if (minbr>data->brsource[local_basepos])   minbr=data->brsource[local_basepos];
			if (maxr <data->rsource[local_basepos] )   maxr = data->rsource[local_basepos];
			if (maxg <data->gsource[local_basepos] )   maxg = data->gsource[local_basepos]; 
			if (maxb <data->bsource[local_basepos] )   maxb = data->bsource[local_basepos]; 
			if (maxbr<data->brsource[local_basepos])   maxbr=data->brsource[local_basepos];
			if (debug && x%100==50 && y%100==50) 
				printf (" px: %3dx%3d, baseposes: %4d + %4d = %4d\n",x,y,basepos_1,maindata.directions[c],local_basepos);
		}
		
		if (debug && x%100==50 && y%100==50) printf ("   comparing br: %.3f, min: %.3f, max: %.3f\n",data->brsource [basepos_1],minbr,maxbr);
		
		changed=FALSE;
		if (data->rsource [basepos_1]<minr) {data->rtarget[basepos_1]=minr;changed=TRUE;}
			else if (data->rsource [basepos_1]>maxr) {data->rtarget[basepos_1]=maxr;changed=TRUE;}
			else data->rtarget[basepos_1]=data->rsource[basepos_1];
		if (data->gsource [basepos_1]<ming) {data->gtarget[basepos_1]=ming;changed=TRUE;}
			else if (data->gsource [basepos_1]>maxg) {data->gtarget[basepos_1]=maxg;changed=TRUE;}
			else data->gtarget[basepos_1]=data->gsource[basepos_1];
		if (data->bsource [basepos_1]<minb) {data->btarget[basepos_1]=minb;changed=TRUE;}
			else if (data->bsource [basepos_1]>maxb) {data->btarget[basepos_1]=maxb;changed=TRUE;}
			else data->btarget[basepos_1]=data->bsource[basepos_1];
		if (data->brsource [basepos_1]<minbr) {data->brtarget[basepos_1]=minbr;changed=TRUE;}
			else if (data->brsource [basepos_1]>maxbr) {data->brtarget[basepos_1]=maxbr;changed=TRUE;}
			else data->brtarget[basepos_1]=data->brsource[basepos_1];
		
		if (sanitydebug && (fabs(data->brtarget[basepos_1]-data->brsource[basepos_1])>0.2 || isnan(data->brtarget[basepos_1] || data->brtarget[basepos_1]<0.01))) {
			printf (" suspicious: px : %3dx%3d(%5d), thr: %1d, br: %.3f ->[%.3f/%.3f]-> %.3f,r: %.3f ->[%.3f/%.3f]-> %.3f\n",
				x,y,basepos_1,data->id,data->brsource[basepos_1],minbr,maxbr,data->brtarget[basepos_1],
				data->rsource[basepos_1],minr,maxr,data->rtarget[basepos_1]);
			ITERATE (c,0,8) {
				tmp_x=x+1+near_pixels[2*c  ];
				tmp_y=y+1+near_pixels[2*c+1];
				if (tmp_x<=0 || tmp_y<=0 || tmp_x>=maindata.width+1 || tmp_y>=maindata.height+1) continue;
				local_basepos= basepos_1+maindata.directions[c];		
				printf ("   data from near px: %3dx%3d (%5d - %4d), brvalue: %.3f, rvalue: %.3f\n",tmp_x-1,tmp_y-1,local_basepos,maindata.directions[c],data->brsource[local_basepos],data->rsource[local_basepos]);
				}
		}	
		
		if (changed) data->stat[0]+=1;
	}}
	
	//printf ("  Changed pixels in soft_clean: %1d\n",changedcount);
	return 0;
		
}




gfloat reduce(gfloat x,gfloat min,gfloat max){
	if (x>max) return max;
	if (x<min) return min;
	return x;}

void sort(gfloat *array){
	guchar e,c;
	gfloat tmp;
	gboolean changed=FALSE;
	DEITERATE(e,7){
		changed=FALSE;
		DEITERATE(c,7){
			if (array[c]>array[c+1]){  //consider nan s here
				tmp=array[c+1];
				array[c+1]=array[c];
				array[c]=tmp;
				changed=TRUE;}
			}
		if (changed==FALSE) {
			//printf ("quitting after %1d iterations\n",7-e);
			break;}
		}
	}
			

void blur_data() {
	//this is controlling function to initiate single blurring steps
	gint c,d,e;
	//gint resultlayer; // where final data ends
	gint stats[4];
	gfloat statsfloat[4];
	const gboolean debug=FALSE;

	if (debug) printf("  Blurring iterations to do: %.d\n",maindata.iterations);
	//resultlayer=2;

	ITERATE(c,0,maindata.iterations) {
		
		
		//cleaning

		if (debug) printf ("  Soft preclean: %1d\n",maindata.preclean);
		if (maindata.preclean) {
			ITERATE(d,0,THREADS) {
				thread_data[d].id=d;
				//resetting statistics
				thread_data[d].stat[0]=0;
				// definyng source and target arrays
				if (maindata.activelayer == 1) {
					thread_data[d].rsource=r1;
					thread_data[d].gsource=g1;
					thread_data[d].bsource=b1;
					thread_data[d].brsource=br1;				
					thread_data[d].rtarget=r2;
					thread_data[d].gtarget=g2;
					thread_data[d].btarget=b2;
					thread_data[d].brtarget=br2;
					}
				else {
					thread_data[d].rsource=r2;
					thread_data[d].gsource=g2;
					thread_data[d].bsource=b2;
					thread_data[d].brsource=br2;				
					thread_data[d].rtarget=r1;
					thread_data[d].gtarget=g1;
					thread_data[d].btarget=b1;
					thread_data[d].brtarget=br1;
					}	
			gth[d]=g_thread_create( (GThreadFunc) pre_clean2,&thread_data[d],TRUE,NULL);

			 }
			
			ITERATE(d,0,THREADS) { //waiting till all of them ends
				g_thread_join(gth[d]);    }
			
			stats[0]=0;
			ITERATE(d,0,THREADS) stats[0]+=thread_data[d].stat[0];
			if (debug) printf ("   Pre cleaning: %1d pixels modfied\n",stats[0]);
			toggle_active_layer();
		}
		
		
		
		
		if (DEBUG ) printf ("  Initiating iteration: %1d (current active layer: %1d)\n", c+1,maindata.activelayer);
		//calculating some values for this blur iteration
		ITERATE(d,0,THREADS) {
			thread_data[d].id=d;
			//resetting statistics
			thread_data[d].stat[0]=0;
			thread_data[d].stat[1]=0;
			thread_data[d].stat[2]=0;	
			thread_data[d].stat[3]=0;			
			// definyng source and target arrays
			if (maindata.activelayer == 1) {
				thread_data[d].rsource=r1;
				thread_data[d].gsource=g1;
				thread_data[d].bsource=b1;
				thread_data[d].brsource=br1;				
				thread_data[d].rtarget=r2;
				thread_data[d].gtarget=g2;
				thread_data[d].btarget=b2;
				thread_data[d].brtarget=br2;
				}
			else {
				thread_data[d].rsource=r2;
				thread_data[d].gsource=g2;
				thread_data[d].bsource=b2;
				thread_data[d].brsource=br2;				
				thread_data[d].rtarget=r1;
				thread_data[d].gtarget=g1;
				thread_data[d].btarget=b1;
				thread_data[d].brtarget=br1;
				}				
			
			thread_data[d].iteration=c;
				
			
			//unified functions:
			gth[d]=g_thread_create( (GThreadFunc) blur_step_v3,&thread_data[d],TRUE,NULL);
				
			//if (DEBUG) sleep(1);
			 }
	
		ITERATE(d,0,THREADS) { //waiting till all of them ends
			g_thread_join(gth[d]);    }
	
		//collecting statistics
		stats[0]=0;stats[1]=0;stats[2]=0;stats[3]=0;
		ITERATE(e,0,THREADS){
			stats[0]+=thread_data[e].stat[0];
			stats[1]+=thread_data[e].stat[1];
			stats[2]+=thread_data[e].stat[2];
			stats[3]+=thread_data[e].stat[3];}	
			
		statsfloat[0]=(gfloat)stats[0]*100/(stats[0]+stats[1]);
		statsfloat[1]=(gfloat)stats[1]*100/(stats[0]+stats[1]);
		statsfloat[2]=(gfloat)stats[2]*100/(maindata.height*maindata.width*8); // overall
		statsfloat[3]=(gfloat)stats[3]*100/(maindata.height*maindata.width);  //good blurable for forced
						
		//blur_step(width,height,maindata.endlayer, threshred);
		
		if (VERBOSE){
			printf ("  Update progress: %.3f\n",0.1 + (double)(((c+1)/(double)maindata.iterations)*0.8) );
			if (maindata.doclean==FALSE)
				printf ("   Statistics: Not blurrable: %2.2f%%, blurrable:  %2.2f%%. overall  %2.2f%%\n",statsfloat[0],statsfloat[1],statsfloat[2]);
			else
				printf ("   Statistics: px with sufficient good dirs %2.2f, overall %2.2f%%\n",statsfloat[3],statsfloat[2]);
			}
		if (maindata.preview==FALSE){ gimp_progress_update( 0.1 + (double)(((c+1)/(double)maindata.iterations)*0.8));}

		//togling activelayer after end of iteration
		toggle_active_layer();

		} // end of single iteration steps
		
		//the final data should end up in tmp1 and br1
	if (maindata.activelayer ==2) { //this is OK
		if (debug) printf ("Copying data back to from resultlayer #2 to rgbbr1\n");
		ITERATE(c,0,maindata.width * maindata.height){
			br1[c]=br2[c]; 
			r1[c]=r2[c];
			g1[c]=g2[c];
			b1[c]=b2[c];}}
}

void do_lines() {
	//doing cartoon lines
	guchar d;
	//guint basepos_1,changedcount,samecount;
	guint changedcount;
	//samecount=0;
	
	if (DEBUG ) printf (" Initiating cartoon lines creation.\n");			
	
	ITERATE(d,0,THREADS) {
		gth[d]=g_thread_create( (GThreadFunc) create_lines,&thread_data[d],TRUE,NULL); }

	ITERATE(d,0,THREADS) { //waiting till all of them ends
		g_thread_join(gth[d]);    }


	//cleaning iteration 1
	// definyng source and target arrays
	ITERATE(d,0,THREADS) {	thread_data[d].brsource=br2;				
							thread_data[d].brtarget=br1;}
	maindata.samecountlimit=3;
	ITERATE(d,0,THREADS) {
		gth[d]=g_thread_create( (GThreadFunc) clean_lines,&thread_data[d],TRUE,NULL); }

	ITERATE(d,0,THREADS) { //waiting till all of them ends
		g_thread_join(gth[d]);    }	
	
	changedcount=0;
	ITERATE(d,0,THREADS) changedcount+=thread_data[d].changedcount;
	if (VERBOSE) printf (" Cleaning lines - changed pixels: %1d (samecount treshold: %1d)\n", changedcount,maindata.samecountlimit);	


	//cleaning iteration 2
	// definyng source and target arrays
	ITERATE(d,0,THREADS) {	thread_data[d].brsource=br1;				
							thread_data[d].brtarget=br2;}
	maindata.samecountlimit=2;
	ITERATE(d,0,THREADS) {
		gth[d]=g_thread_create( (GThreadFunc) clean_lines,&thread_data[d],TRUE,NULL); }

	ITERATE(d,0,THREADS) { //waiting till all of them ends
		g_thread_join(gth[d]);    }	
	
	changedcount=0;
	ITERATE(d,0,THREADS) changedcount+=thread_data[d].changedcount;
	if (VERBOSE) printf (" Cleaning lines - changed pixels: %1d (samecount treshold: %1d)\n", changedcount,maindata.samecountlimit);	
	
	
	//cleaning iteration 3
	// definyng source and target arrays
	ITERATE(d,0,THREADS) {	thread_data[d].brsource=br2;				
							thread_data[d].brtarget=br1;}
	maindata.samecountlimit=1;
	ITERATE(d,0,THREADS) {
		gth[d]=g_thread_create( (GThreadFunc) clean_lines,&thread_data[d],TRUE,NULL); }

	ITERATE(d,0,THREADS) { //waiting till all of them ends
		g_thread_join(gth[d]);    }	
	
	changedcount=0;
	ITERATE(d,0,THREADS) changedcount+=thread_data[d].changedcount;
	if (VERBOSE) printf (" Cleaning lines - changed pixels: %1d (samecount treshold: %1d)\n", changedcount,maindata.samecountlimit);	


	}


void *clean_lines (void *arg) {
	threaddatastr *data;
	guchar d,samecount;
	guint x, y;
	gint basepos_1;

	data=(threaddatastr *) arg;
	data->changedcount=0;

	for (y=data->id;y<maindata.height;y+=THREADS) { ITERATE(x,0,maindata.width) {
		basepos_1=basepos(x,y,1,maindata.width);
			if (  x==0 || y==0 || x==maindata.width-1 || y==maindata.height-1 ){
				data->brtarget[basepos_1]=data->brsource[basepos_1];
				continue;}
			samecount=0;
			ITERATE(d,0,8) {
				if (data->brsource[basepos_1+maindata.directions[d]]==data->brsource[basepos_1]) {
					samecount+=1;
					if (samecount>maindata.samecountlimit) break;}}
			if (samecount >maindata.samecountlimit){ data->brtarget[basepos_1]=data->brsource[basepos_1];}
			else {data->brtarget[basepos_1] = ((data->brsource[basepos_1]==1)? 0 : 1);
				data->changedcount+=1;}
		}}					
	return 0;	
	}


void restore_bright(){
	gint y,x,basepos_1;
	
	ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
		basepos_1=basepos(x,y,1,maindata.width);
		br1[basepos_1] = br_unch[basepos_1];	
		}}
	}	

void shift_bright(gfloat start, gfloat end){
	gint y,x,basepos_1;
	ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
	basepos_1=basepos(x,y,1,maindata.width);
	br1[basepos_1] = start + br1[basepos_1] * (end-start);}}
	}

void findminmax(gfloat v1, gfloat v2,gfloat v3,gfloat* min, gfloat* max){
	*min=v1;
	if (v2<*min) *min=v2;	
	if (v3<*min) *min=v3;	
	*max=v1;
	if (v2>*max) *max=v2;	
	if (v3>*max) *max=v3;	}

		
void change_saturation(){
	gint y,x,basepos_1;
	gfloat sat,newsat,boost;
	const gboolean debug=FALSE;
	const gfloat breakpoint=0.25;
	
	if (debug) printf( "  maindata.satgamma: %.3f (%.3f)\n",maindata.satgamma,1.0/maindata.satgamma);
	ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
		////basepos_out=basepos(x,y,maindata.channels,maindata.width);
		basepos_1=basepos(x,y,1,maindata.width);	
		
		if (r1[basepos_1]==g1[basepos_1] && r1[basepos_1]==b1[basepos_1]) continue;
		
		//avg=br1[basepos_1];
		sat=(ABS(r1[basepos_1]) + ABS(g1[basepos_1]) + ABS(b1[basepos_1]))/3.0;

		//aplying gamma
		if (sat<breakpoint){
			newsat=pow(sat/breakpoint,1.0/maindata.satgamma)* breakpoint;
			if (newsat/sat > 4.0) newsat=sat*4;}
		else newsat=sat;
		
		if (sat>0) boost =newsat/sat;
		else boost=1;

		//changing values
		r1[basepos_1]=r1[basepos_1]*boost;
		g1[basepos_1]=g1[basepos_1]*boost;
		b1[basepos_1]=b1[basepos_1]*boost;	
		//if (debug && x==100 && y%10==0) printf(" changing saturation: %.3f->%.3f, new rgb: %.3f %.3f %.3f,Boost: %.3f\n",
		//	sat,newsat,r1[basepos_1],g1[basepos_1],b1[basepos_1],boost);
		if (debug  && ( isnan(r1[basepos_1]) || isnan(g1[basepos_1]) || isnan(b1[basepos_1]) )){
		printf ("  Saturation: nan found. new RGB: %.3f %.3f %.3f, boost: %.3f (%.3f/%.3f)!!!\n",r1[basepos_1],g1[basepos_1],b1[basepos_1],boost,newsat,sat);
		}
		}}	
	}
				
					
void fill_rect_out() {
	gint x,y;
	gfloat r,g,b;
	gint basepos_1, basepos_out;
	gfloat bright;

	ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
		basepos_out=basepos(x,y,maindata.channels,maindata.width);
		basepos_1=basepos(x,y,1,maindata.width);

		if (maindata.mode == DOLINES) {
			rect_out[basepos_out  ]=TO_INT_W_GAMMA(br1[basepos_1]);
			rect_out[basepos_out+1]=rect_out[basepos_out  ];
			rect_out[basepos_out+2]=rect_out[basepos_out  ];
			if (maindata.channels==4) rect_out[basepos_out+3]=255;//rect_in[basepos_out+3];
			continue;}
		else 
			bright = br1[basepos_1];

		r=r1[basepos_1]+bright;
		g=g1[basepos_1]+bright;
		b=b1[basepos_1]+bright;
		if (r<0) r=0;if (g<0) g=0;if (b<0) b=0;
		if (r>1) r=1;if (g>1) g=1;if (b>1) b=1;
		rect_out[basepos_out  ]=TO_INT_W_GAMMA(r);
		rect_out[basepos_out+1]=TO_INT_W_GAMMA(g);
		rect_out[basepos_out+2]=TO_INT_W_GAMMA(b);			
		if (maindata.channels==4) rect_out[basepos_out+3]=rect_in[basepos_out+3];
		}}
}


void fill_tmp() {
	guint x,y,stats[1];
	guchar d;
	gint basepos_in,basepos_1;
	gfloat r,g,b;
	const gboolean debug=FALSE;
	

	ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
		basepos_in  = basepos(x,y,maindata.channels,maindata.width);
		basepos_1   = basepos(x,y,1,maindata.width);

		r=TO_FLOAT_W_GAMMA(rect_in[basepos_in  ]);
		g=TO_FLOAT_W_GAMMA(rect_in[basepos_in+1]);
		b=TO_FLOAT_W_GAMMA(rect_in[basepos_in+2]);		

		br1[basepos_1   ]   = get_bright(r,g,b);
		r1[basepos_1]		= r-br1[basepos_1];
		g1[basepos_1]		= g-br1[basepos_1];
		b1[basepos_1]		= b-br1[basepos_1];	
		
		if(debug && ( (x==0 || x==100 || x==maindata.width-1) && (y==0 || y==100 || y==maindata.height-1) ) ) 
		printf ("Pixel %3d x %3d: br: %.3f\n", x,y,br1[basepos_1   ] );
		
		}}	
	
	if (maindata.preclean) {
		
		//cleaning original values
	ITERATE(d,0,THREADS) {
		thread_data[d].id=d;
		//resetting statistics
		thread_data[d].stat[0]=0;
		// definyng source and target arrays
		thread_data[d].rsource=r1;
		thread_data[d].gsource=g1;
		thread_data[d].bsource=b1;
		thread_data[d].brsource=br1;				
		thread_data[d].rtarget=r2;
		thread_data[d].gtarget=g2;
		thread_data[d].btarget=b2;
		thread_data[d].brtarget=br2;

		gth[d]=g_thread_create( (GThreadFunc) pre_clean2,&thread_data[d],TRUE,NULL);
		 }
			
		ITERATE(d,0,THREADS) { //waiting till all of them ends
			g_thread_join(gth[d]);    }
			
		stats[0]=0;
		ITERATE(d,0,THREADS) stats[0]+=thread_data[d].stat[0];
		if (DEBUG) printf ("   Pre cleaning: %1d pixels modfied\n",stats[0]);
	
		//now updating rest of values	
		ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
			basepos_1   = basepos(x,y,1,maindata.width);
			brorig[basepos_1]   = br2[basepos_1];
			r_orig[basepos_1]  	= r2[basepos_1];
			g_orig[basepos_1]  	= g2[basepos_1];
			b_orig[basepos_1]  	= b2[basepos_1];	
			if (maindata.mode==NOCHANGE) br_unch[basepos_1] = br2[basepos_1];	
			if(debug && ( (x==0 || x==100 || x==maindata.width-1) && (y==0 || y==100 || y==maindata.height-1) ) ) 
				printf (" Soft cleaning - pixel %3d x %3d: br: %.3f\n", x,y,br1[basepos_1   ] );
			}}
		maindata.activelayer=2;
		}
	//else we just populate the rest arrays with layer 1 data
	else {
		ITERATE(y,0,maindata.height) { ITERATE(x,0,maindata.width) {
			basepos_1   = basepos(x,y,1,maindata.width);
			brorig[basepos_1]   = br1[basepos_1];
			r_orig[basepos_1]  	= r1[basepos_1];
			g_orig[basepos_1]  	= g1[basepos_1];
			b_orig[basepos_1]  	= b1[basepos_1];
			if (maindata.mode==NOCHANGE) br_unch[basepos_1] = br1[basepos_1];
			}}
		maindata.activelayer=1;
	}	
	if (DEBUG) printf("   tmp_fill(): current active layer: %1d\n",maindata.activelayer);
}


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

	gint         x1, y1, x2, y2; 		//x,y from image not area
	GimpPixelRgn rgn_in, rgn_out;
	gint i,c;
	gint basepos_c,basepos_l,locx,locy;

	if (VERBOSE ) {
		printf ("== Running Selective Color Blur plugin... ==\n");
		} // check this

 	//make sure g_thread is running
 	if( !g_thread_supported() )  g_thread_init(NULL);
 	if( !g_thread_supported() ) {
     	printf("g_thread NOT supported\n");
     	exit (1);}

	// getting coordinates of what will be saturated
	if (preview){
		gimp_preview_get_position (preview, &x1, &y1);
		gimp_preview_get_size (preview, &maindata.width, &maindata.height);
		x2 = x1 + maindata.width;
		y2 = y1 + maindata.height;
		maindata.preview=TRUE;
		}
    else {
		gimp_drawable_mask_bounds (source->drawable_id,&x1, &y1,&x2, &y2);
		maindata.width = x2 - x1;
		maindata.height = y2 - y1;
		maindata.preview=FALSE;}
     
     if (!maindata.preview) gimp_progress_init ("Selective Color Blur...");
     
	 //converting tresh from raw to square
	 maindata.treshsq=pow(maindata.tresh,2);
	 //maindata.shortrad=(gint)maindata.radius*0.7; // ? needed
	    
	 maindata.channels = gimp_drawable_bpp (source->drawable_id);//getting number of channels

	for (i=0;i<256;i++) { //to speed up conversion from raw RGB to gammed values
		gammed_values[i]=pow((gfloat)i/255,1/2.2);}

  //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,
                       preview == NULL, TRUE);
                       
    if (maindata.width ==1) return;
	

	maindata.satgamma=maindata.slidersat;
		
	if (maindata.height>=65535 || maindata.width>=65535 ) {
		printf ("Sizes of image too big, contact maintainer/developer...\n");
		exit(1);}
		

	if (VERBOSE) {
		printf (" Processed area          : %4d x %4d    Channels    : %1d\n",maindata.width,maindata.height,maindata.channels);
		printf (" Processing mode         : %1d\n",maindata.mode);
		printf (" Radius                  : %1d px           Iterations  : %1d\n",	maindata.radius,maindata.iterations);
		printf (" Hue tresh               : %.3f/%.4f   Brtresh     : %.3f\n",maindata.tresh, maindata.treshsq,  maindata.brtresh);
		printf (" Lines creation sens.    : %.3f    %s\n", maindata.linesens,(maindata.mode!=3)?"(not used now)":"");
		printf (" Brightness modification : %.2f - %.2f\n", maindata.darkpoint,maindata.lightpoint);
		printf (" Slider saturation       : %.2f\n",maindata.slidersat);
		printf (" Saturation boost: Gamma : %.2f\n", maindata.satgamma);
		printf (" Minimum good directions : %1d\n", maindata.mindircount);	
		printf (" Fast mode               : %s\n",(maindata.fastmode)?"yes":"no");	
		printf (" Forced blur             : %s\n",(maindata.doclean)?"yes":"no");
		printf (" Cleaning                : %sd\n",(maindata.preclean)?"yes":"no");
		printf (" Exporting as new layer  : %s\n", (maindata.exportlayer)?"yes":"no");
		}
	
	//populating dirstepping
	populate_dirstepping();
	
 	/* Initialise memory for rects*/
	rect_in  =   g_new (guchar, maindata.channels  * maindata.width * maindata.height); 
	rect_out= g_new (guchar, maindata.channels * maindata.width * maindata.height);
	rect_good=g_new (gboolean,maindata.width * maindata.height);
	r1	=g_new (gfloat, maindata.width * maindata.height ); 
	g1	=g_new (gfloat, maindata.width * maindata.height ); 
	b1	=g_new (gfloat, maindata.width * maindata.height ); 
	r2	=g_new (gfloat, maindata.width * maindata.height ); 
	g2	=g_new (gfloat, maindata.width * maindata.height ); 
	b2	=g_new (gfloat, maindata.width * maindata.height ); 
	br1	=g_new (gfloat, maindata.width * maindata.height);
	br2	=g_new (gfloat, maindata.width * maindata.height);
	r_orig=g_new (gfloat, maindata.width * maindata.height);
	g_orig=g_new (gfloat, maindata.width * maindata.height);
	b_orig=g_new (gfloat, maindata.width * maindata.height);	
	brorig= g_new (gfloat, maindata.width * maindata.height);
	if (maindata.mode==NOCHANGE) br_unch=g_new (gfloat, maindata.width * maindata.height);

	//marking all pixels unsafe
	if (maindata.doclean) ITERATE(c,0,maindata.width*maindata.height) {rect_good[c]=FALSE;};

	if (DEBUG) printf (" CB: memory allocated\n");
	
	if (DEBUG && maindata.preview) printf (" Doing preview\n");
	if (!maindata.preview) gimp_progress_init ("Selective color blur...");
	
	//calculating direction differentials 
	ITERATE(c,0,8) {
		maindata.directions[c]=basepos(1+near_pixels[c*2],1+near_pixels[c*2+1],1,maindata.width) - basepos(1,1,1,maindata.width);
		if (DEBUG) printf ("  Directions stepping for dir %1d (basepos diff) is: %4d for x+%2d and y+%2d\n",c, maindata.directions[c],near_pixels[c*2], near_pixels[c*2+1]);}


	//calculating basepos_diff
	if (maindata.mode==DOLINES || maindata.doclean) {
		basepos_c=basepos(1,1,1,maindata.width);
		for (c=0;c<8;c=c+1) {
			locx=1+near_pixels[c*2];
			locy=1+near_pixels[c*2+1];
			basepos_l     =basepos(locx,locy,1,maindata.width);
			basepos_diff[c]   =basepos_c - basepos_l;
			if (DEBUG) printf ("  DEBUG: Basepos diff: %6d, (%2d x %2d)\n",basepos_diff[c],locx,locy);
			}
		}
	
	//loading all data to rect_in
	gimp_pixel_rgn_get_rect (&rgn_in,rect_in,x1, y1,maindata.width,maindata.height);
	if (DEBUG) printf (" CB: data loaded in rect_in\n");

	//fill input data to rect_tmp
	fill_tmp();
	if (DEBUG) printf (" CB: primary data loaded to rect_tmp\n");
	
	if ( maindata.preview == FALSE ) gimp_progress_update (0.1);
	
	//blurring data
	blur_data ();
	if (DEBUG) printf (" CB: blurring done\n");
	
	if (maindata.mode == DOLINES) 
		do_lines();
	else {
		if (maindata.mode==NOCHANGE)	restore_bright();
		if (maindata.satgamma > 1)	change_saturation();
		if (!(maindata.darkpoint==0 && maindata.lightpoint==1)) shift_bright(maindata.darkpoint,maindata.lightpoint);
		else if (maindata.mode==CARTREDBR) shift_bright(0.3,0.9);
		}
		
		
	//calculating data
	fill_rect_out();
	if (DEBUG) printf (" CB: rect_out filled\n");
	if (DEBUG) printf( "DEBUG: rect_out [0,0]: %1d, %1d, %1d\n", rect_out[0],rect_out[1],rect_out[1]);

	if (maindata.exportlayer) {
		export_to_layer();
		gimp_progress_end();
		return;}

	//putting data back to drawable
    gimp_pixel_rgn_set_rect (&rgn_out, rect_out,x1, y1,maindata.width,maindata.height);
 	if (DEBUG) printf ("CB: data back in rgn\n");   

	g_free (rect_in) ;
	g_free (rect_out);
	g_free (rect_good);
	g_free (r1);g_free (g1);g_free (b1);g_free (r2);g_free (g2);g_free (b2);
	g_free (r_orig),g_free(g_orig), g_free(b_orig);
	g_free (br1);g_free (br2);
	g_free (brorig);
	if (maindata.mode==NOCHANGE) g_free (br_unch);
	if (DEBUG) printf ("rect freeied\n");  

  //update (graphical) of preview/image
	if (preview) {
    	gimp_drawable_preview_draw_region (GIMP_DRAWABLE_PREVIEW (preview),
        &rgn_out);}

    else {
		gimp_drawable_flush (source);
		gimp_drawable_merge_shadow (source->drawable_id, TRUE);
		gimp_drawable_update (source->drawable_id,x1,y1,maindata.width,maindata.height);
		gimp_displays_flush ();}
	if (DEBUG) printf ("drawable updated\n");
	gimp_progress_end();
}
 

gboolean plugin_gui (GimpDrawable *source)
{
  GtkWidget *dialog;
  GtkWidget *main_vbox,*table,*mc_table;
  GtkWidget *label_radius, *label_tresh, *label_iter, *br_tresh,*lines_sens,*label_sat,*label_md;
  GtkWidget *preview;
  GtkObject *radius_spin_adj, *iter_spin_adj, *tresh_spin_adj, *brtresh_spin_adj,*linesens_spin_adj,*slider_sat_adj,*slider_mc_adj;
  GtkWidget *exportbutton;
  GtkWidget *eb_align,*t_align,*m_align,*mc_align;

  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-colorblur",
                            GIMP_STOCK_RESET, RESPONSE_RESET,
                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                            GTK_STOCK_OK,     RESPONSE_OK,
                            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);
	
	preview = gimp_drawable_preview_new (source, &previewbool);
	gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
	gtk_widget_set_tooltip_text(preview, "Preview is AUTOEXPANDING. Technical comment: \
 When calculating this preview, only visible area is considered. \
 That might lead to slightly different resuls when computing final image afterwards.");
	gtk_widget_show (preview);

	m_align=gtk_alignment_new(0.5,0.5,0.5,0);
	gtk_box_pack_start (GTK_BOX (main_vbox), m_align, FALSE, FALSE, 0);
	gtk_alignment_set_padding(GTK_ALIGNMENT(m_align),15,5,0,0);
	gtk_widget_show (m_align);	
	
	combo = gtk_combo_box_new_text();
	gtk_combo_box_append_text( GTK_COMBO_BOX( combo),mode0);
	gtk_combo_box_append_text( GTK_COMBO_BOX( combo),mode1);
	gtk_combo_box_append_text( GTK_COMBO_BOX( combo),mode2);
	gtk_combo_box_append_text( GTK_COMBO_BOX( combo),mode3);
	gtk_container_add(GTK_CONTAINER(m_align), combo);
	gtk_widget_set_tooltip_text(combo,"Select what to do");	
	gtk_combo_box_set_active(GTK_COMBO_BOX( combo), MODEDEF);  //default
	gtk_widget_show (combo);
	
	//creating table 
	t_align=gtk_alignment_new(0,0.5,0,0);
	gtk_box_pack_start (GTK_BOX (main_vbox), t_align, FALSE, FALSE, 0);
	gtk_alignment_set_padding(GTK_ALIGNMENT(t_align),25,15,10,0);
	gtk_widget_show (t_align);	
	
	table=gtk_table_new(2,5,FALSE);
	gtk_table_set_col_spacings( GTK_TABLE(table), 25 );
	gtk_table_set_row_spacings( GTK_TABLE(table), 3 );
	gtk_container_add(GTK_CONTAINER(t_align), table);

		//inserting labels
		label_radius = gtk_label_new ("<b>Blur Radius:</b>");
		gtk_label_set_use_markup (GTK_LABEL(label_radius), TRUE);
		gtk_misc_set_alignment (GTK_MISC (label_radius), 0, 0.5);
		gtk_table_attach_defaults(GTK_TABLE(table),label_radius,0,1,0,1);
		gtk_widget_show (label_radius);
		label_iter = gtk_label_new ("<b>Blur Iterations:</b>");
		gtk_label_set_use_markup (GTK_LABEL(label_iter), TRUE);
		gtk_misc_set_alignment (GTK_MISC (label_iter), 0, 0.5);
		gtk_table_attach_defaults(GTK_TABLE(table),label_iter,0,1,1,2);
		gtk_widget_show (label_iter);
		label_tresh = gtk_label_new ("<b>Color Treshold:</b>");
		gtk_label_set_use_markup (GTK_LABEL(label_tresh), TRUE);
		gtk_misc_set_alignment (GTK_MISC (label_tresh), 0, 0.5);
		gtk_table_attach_defaults(GTK_TABLE(table),label_tresh,0,1,2,3);
		gtk_widget_show (label_tresh);
		br_tresh = gtk_label_new ("<b>Value Treshold:</b>");
		gtk_label_set_use_markup (GTK_LABEL(br_tresh), TRUE);
		gtk_misc_set_alignment (GTK_MISC (br_tresh), 0, 0.5);
		gtk_table_attach_defaults(GTK_TABLE(table),br_tresh,0,1,3,4);
		gtk_widget_show (br_tresh);
		lines_sens = gtk_label_new ("<b>Lines sensitivity:</b>");
		gtk_label_set_use_markup (GTK_LABEL(lines_sens), TRUE);
		gtk_misc_set_alignment (GTK_MISC (lines_sens), 0, 0.5);
		gtk_table_attach_defaults(GTK_TABLE(table),lines_sens,0,1,4,5);
		gtk_widget_show (lines_sens);


  		//inserting spinboxes
		radius_spin_adj = gtk_adjustment_new (RADIUSDEFAULT, 0, 100, 1, 10,0);
		radius_spin = gtk_spin_button_new (GTK_ADJUSTMENT (radius_spin_adj), 2, 0);
		gtk_table_attach_defaults(GTK_TABLE(table),radius_spin,1,2,0,1);
		gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (radius_spin), TRUE);
		gtk_widget_set_tooltip_text( radius_spin, "Radius per iteration, use smaller radius for more fragmented images or simply for smaller images. With higher radius (above 10) consider using the Fast mode");
		gtk_widget_show (radius_spin);	
		iter_spin_adj = gtk_adjustment_new (ITERDEFAULT, 0, 100, 1, 10,0);
		iter_spin = gtk_spin_button_new (GTK_ADJUSTMENT (iter_spin_adj), 2, 0);
		gtk_table_attach_defaults(GTK_TABLE(table),iter_spin,1,2,1,2);
		gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (iter_spin), TRUE);
		gtk_widget_set_tooltip_text( iter_spin, "Use higher value to smooth the image.");
		gtk_widget_show (iter_spin);
		tresh_spin_adj = gtk_adjustment_new (TRESHDEFAULT,  0.001, 2, 0.005, 0.1,0);
		tresh_spin = gtk_spin_button_new (GTK_ADJUSTMENT (tresh_spin_adj),0.001, 3);
		gtk_table_attach_defaults(GTK_TABLE(table),tresh_spin,1,2,2,3);
		gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (tresh_spin), TRUE);
		gtk_widget_set_tooltip_text( tresh_spin, "3D distance in normalized RGB colorspace, brightness is not considered here.");
		gtk_widget_show (tresh_spin);	
		brtresh_spin_adj = gtk_adjustment_new (BRTRESHDEF,  0.001, 1, 0.01, 0.1,0);
		brtresh_spin = gtk_spin_button_new (GTK_ADJUSTMENT (brtresh_spin_adj),0.01, 3);
		gtk_table_attach_defaults(GTK_TABLE(table),brtresh_spin,1,2,3,4);
		gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (brtresh_spin), TRUE);
		gtk_widget_set_tooltip_text( brtresh_spin, "Treshold for brightness, set 1 to ignore the brightness during blurring");
		//gtk_widget_set_size_request(tresh_spin,40,25);
		gtk_widget_show (brtresh_spin);	
		linesens_spin_adj = gtk_adjustment_new (LINESENSDEF,  0.1, 20, 0.1, 0.1,0);
		linesens_spin = gtk_spin_button_new (GTK_ADJUSTMENT (linesens_spin_adj),0.1, 1);
		gtk_table_attach_defaults(GTK_TABLE(table),linesens_spin,1,2,4,5);
		gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (linesens_spin), TRUE);
		gtk_widget_set_tooltip_text( linesens_spin, "Controls how many black pixels (~lines) will be generated. \
 A pixel is made black if the RGB / brightness distance from it to any of adjacent pixels is bigger than treshold.\
 (Used only in \"Create lines\" mode)");
		//gtk_widget_set_size_request(tresh_spin,40,25);
		gtk_widget_set_sensitive(linesens_spin,FALSE);
		gtk_widget_show (linesens_spin);	
	
	gtk_widget_show (table);	



	//creating table 
	mc_align=gtk_alignment_new(0,0.5,0,0);
	gtk_box_pack_start (GTK_BOX (main_vbox), mc_align, FALSE, FALSE, 0);
	gtk_alignment_set_padding(GTK_ALIGNMENT(mc_align),25,15,10,0);
	gtk_widget_show (mc_align);	
	
	mc_table=gtk_table_new(2,2,FALSE);
	gtk_table_set_col_spacings( GTK_TABLE(mc_table), 25 );
	gtk_table_set_row_spacings( GTK_TABLE(mc_table), 3 );
	gtk_container_add(GTK_CONTAINER(mc_align), mc_table);

		//inserting labels
		label_sat = gtk_label_new ("<b>Saturation:</b>");
		gtk_label_set_use_markup (GTK_LABEL(label_sat), TRUE);
		gtk_misc_set_alignment (GTK_MISC (label_sat), 0, 0.5);
		gtk_table_attach_defaults(GTK_TABLE(mc_table),label_sat,0,1,0,1);
		gtk_widget_show (label_sat);
		
		label_md = gtk_label_new ("<b>Strictness:</b>");
		gtk_label_set_use_markup (GTK_LABEL(label_md), TRUE);
		gtk_misc_set_alignment (GTK_MISC (label_md), 0, 0.5);
		gtk_table_attach_defaults(GTK_TABLE(mc_table),label_md,0,1,1,2);
		gtk_widget_show (label_md);		


  		//inserting spinboxes
		slider_sat_adj = gtk_adjustment_new (1.0, 1.0, 2.5, 0.01, 0.1, 0);
		slider_sat = gtk_hscale_new (GTK_ADJUSTMENT (slider_sat_adj));
		gtk_table_attach_defaults(GTK_TABLE(mc_table),slider_sat,1,2,0,1);
		gtk_widget_set_tooltip_text(slider_sat, "Simple (non-linear) saturation tool.");
		gtk_scale_set_digits(GTK_SCALE (slider_sat), 2) ;
		gtk_widget_set_size_request(slider_sat,150,30);
		//gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (slider_sat), TRUE);
		gtk_widget_show (slider_sat);

		slider_mc_adj = gtk_adjustment_new (MINDIRCOUNT, 1.0, 4, 1, 1, 0);
		slider_mc = gtk_hscale_new (GTK_ADJUSTMENT (slider_mc_adj));
		gtk_widget_set_tooltip_text(slider_mc, "Control sharpness, the higher number, less sharper angles.");
		gtk_scale_set_digits(GTK_SCALE (slider_mc), 0) ;
		gtk_table_attach_defaults(GTK_TABLE(mc_table),slider_mc,1,2,1,2);
		gtk_widget_set_size_request(slider_mc,150,30);
		gtk_widget_show (slider_mc);

	gtk_widget_show (mc_table);	

 
	forcedbutton= gtk_check_button_new_with_label("Forced smoothing");
	gtk_widget_set_tooltip_text( forcedbutton, 
		"Removes small discrete areas. Forces blurring over most convenient directions.");
	gtk_box_pack_start (GTK_BOX (main_vbox), forcedbutton, FALSE, FALSE, 0);
	gtk_widget_set_size_request(forcedbutton,0,25);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(forcedbutton), FALSE);
	gtk_widget_show (forcedbutton);

	fastbutton= gtk_check_button_new_with_label("Fast mode");
	gtk_widget_set_tooltip_text( fastbutton, 
		"Less accurate but faster, strongly recommended for bigger radiuses.");
	gtk_box_pack_start (GTK_BOX (main_vbox), fastbutton, FALSE, FALSE, 0);
	gtk_widget_set_size_request(fastbutton,0,25);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(fastbutton), FALSE);
	gtk_widget_show (fastbutton);

	cleanbutton= gtk_check_button_new_with_label("Soft cleaning");
	gtk_widget_set_tooltip_text( cleanbutton, 
		"Eliminates pixel-sized extremes (pixels) that differ from their surrounding.");
	gtk_box_pack_start (GTK_BOX (main_vbox), cleanbutton, FALSE, FALSE, 0);
	gtk_widget_set_size_request(cleanbutton,0,25);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cleanbutton), FALSE);
	gtk_widget_show (cleanbutton);
	
	eb_align=gtk_alignment_new(0.5,0.5,0.5,0);
	gtk_box_pack_start (GTK_BOX (main_vbox), eb_align, FALSE, FALSE, 0);
	gtk_alignment_set_padding(GTK_ALIGNMENT(eb_align),20,15,0,0);
	gtk_widget_show (eb_align);	
	
	exportbutton = gtk_button_new_with_label("Export as new layer");
	gtk_widget_set_tooltip_text(exportbutton, "Plugin window will stay alive with all settings preserved. During export/calculation entire plugin's window is frozen.");
	gtk_container_add(GTK_CONTAINER(eb_align), exportbutton);
	gtk_widget_show (exportbutton);	
		
	g_signal_connect_swapped (preview, "invalidated",  G_CALLBACK (process),source);
	g_signal_connect_swapped (radius_spin_adj   , "value_changed", G_CALLBACK (gimp_preview_invalidate),preview);
	g_signal_connect_swapped (iter_spin_adj     , "value_changed", G_CALLBACK (gimp_preview_invalidate),preview);
	g_signal_connect_swapped (tresh_spin_adj    , "value_changed", G_CALLBACK (gimp_preview_invalidate),preview);
	g_signal_connect_swapped (brtresh_spin_adj  , "value_changed", G_CALLBACK (gimp_preview_invalidate),preview);
	g_signal_connect_swapped (linesens_spin_adj , "value_changed", G_CALLBACK (gimp_preview_invalidate),preview);
 	g_signal_connect_swapped (slider_sat_adj,   "value_changed", G_CALLBACK (gimp_preview_invalidate), preview);
 	g_signal_connect_swapped (slider_mc_adj,   "value_changed", G_CALLBACK (gimp_preview_invalidate), preview);
	g_signal_connect_swapped (G_OBJECT(combo   ), "changed"      , G_CALLBACK (gimp_preview_invalidate),preview);
	g_signal_connect_swapped (G_OBJECT(forcedbutton),"toggled"    , G_CALLBACK (gimp_preview_invalidate),preview);
	g_signal_connect_swapped (G_OBJECT(fastbutton),"toggled"    , G_CALLBACK (gimp_preview_invalidate),preview);
	g_signal_connect_swapped (G_OBJECT(cleanbutton),"toggled"    , G_CALLBACK (gimp_preview_invalidate),preview);
	process (source, GIMP_PREVIEW (preview));

	//update of values in maindata
	g_signal_connect (radius_spin_adj, "value_changed", G_CALLBACK (gimp_uint8_adjustment_update  ), &maindata.radius);  
	g_signal_connect (iter_spin_adj  , "value_changed", G_CALLBACK (gimp_uint8_adjustment_update  ), &maindata.iterations);  
	g_signal_connect (tresh_spin_adj , "value_changed", G_CALLBACK (gimp_float_adjustment_update), &maindata.tresh);
	g_signal_connect (brtresh_spin_adj,"value_changed", G_CALLBACK (gimp_float_adjustment_update), &maindata.brtresh); 
	g_signal_connect (linesens_spin_adj,"value_changed", G_CALLBACK (gimp_float_adjustment_update), &maindata.linesens); 
	g_signal_connect (slider_sat_adj, "value_changed", G_CALLBACK (gimp_float_adjustment_update), &maindata.slidersat);  
	g_signal_connect (slider_mc_adj, "value_changed", G_CALLBACK (gimp_uint8_adjustment_update), &maindata.mindircount); 
	//g_signal_connect (slider_cl_adj, "value_changed", G_CALLBACK (gimp_uint8_adjustment_update), &maindata.cleantresh); 
	g_signal_connect (G_OBJECT( combo    ),  "changed", G_CALLBACK ( mode_changed               ), NULL );
	g_signal_connect (G_OBJECT( forcedbutton ),  "toggled", G_CALLBACK ( fs_changed                 ), NULL );
	g_signal_connect (G_OBJECT( fastbutton ),  "toggled", G_CALLBACK ( fb_changed                 ), NULL );
	g_signal_connect (G_OBJECT( cleanbutton ),  "toggled", G_CALLBACK ( cb_changed                 ), NULL );
	g_signal_connect_swapped(G_OBJECT(exportbutton),"clicked",G_CALLBACK( exportwrapper ), source );
	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_RESET:
		if (VERBOSE)  printf ("Resetting... \n");
		
		gtk_spin_button_set_value(GTK_SPIN_BUTTON(radius_spin),RADIUSDEFAULT);
		gtk_spin_button_set_value(GTK_SPIN_BUTTON(iter_spin),ITERDEFAULT);
		gtk_spin_button_set_value(GTK_SPIN_BUTTON(tresh_spin),TRESHDEFAULT);
		gtk_spin_button_set_value(GTK_SPIN_BUTTON(brtresh_spin),BRTRESHDEF);
		gtk_spin_button_set_value(GTK_SPIN_BUTTON(linesens_spin),LINESENSDEF);
		gtk_range_set_value(GTK_RANGE(slider_sat),1.0);
		gtk_range_set_value(GTK_RANGE(slider_mc),MINDIRCOUNT);
		gtk_combo_box_set_active(GTK_COMBO_BOX( combo), MODEDEF);
		gtk_widget_set_sensitive(slider_sat,TRUE);
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (cleanbutton   ),FALSE);
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fastbutton   ),FALSE);
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (forcedbutton   ),FALSE);		
		gtk_widget_set_sensitive(linesens_spin,FALSE);
		break;

    case RESPONSE_OK:  //quitting plugin window and applying change
	  //printf ("OK... \n");
	  process_image=TRUE; 
	  gtk_widget_destroy (widget);
	  gtk_main_quit ();     
	  break;

    default: // if other response - terminate plugin window
      gtk_widget_destroy (widget);
      gtk_main_quit ();
      break;
    };
}

