Home› Technical Talk

UVPadder Filter for GIMP 2.6

polycounter lvl 8
Offline / Send Message
JamesWild polycounter lvl 8
UVPadder GIMP Filter
Automatically generate padding for UV islands!

Win32 Binary Download:
http://www.mediafire.com/?i7d128nobj898k5

You may need to put this DLL alongside it:
http://www.mediafire.com/?3yjj8ocv97wm35h

Latest Linux source code:
#include <stdlib.h>
#include <string.h>
#include <libgimp/gimp.h>

static void query( void );

static void run
(

	const gchar * name,
	gint nparams,
	const GimpParam * param,
	gint * nreturn_vals,
	GimpParam  ** return_vals
);

static GimpPDBStatusType Pad( GimpDrawable * Drawable );

GimpPlugInInfo PLUG_IN_INFO =
{

	NULL,
	NULL,
	query,
	run

};

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_install_procedure (
    "plug-in-uvpadder",
    "UV Padder",
    "Expands the colour in the image",
    "James Wild (built on an example by David Neary)",
    "",
    "2012",
    "_UV Padder",
    "RGB*, GRAY*",
    GIMP_PLUGIN,
    G_N_ELEMENTS (args), 0,
    args, NULL);

  	gimp_plugin_menu_register ("plug-in-uvpadder", "<Image>/Filters/Texturing");

}

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;

	*nreturn_vals = 1;
	*return_vals  = values;

	values[0].type = GIMP_PDB_STATUS;
	values[0].data.d_status = status;

	run_mode = param[0].data.d_int32;

	drawable = gimp_drawable_get (param[2].data.d_drawable);

	gimp_progress_init ("Padding UVs...");

  	status = Pad( drawable );

	gimp_displays_flush ();
	gimp_drawable_detach (drawable);

}

static GimpPDBStatusType Pad( GimpDrawable * Drawable )
{

	gint X, Y, Channels, AlphaChannel;
	gint Y1, Y2, X1, X2, Width, Height;
	GimpPixelRgn InputRegion, OutputRegion;
	guchar * Input, * Output;
	guint * Accumulator;
	guint Remaining = 0;
	guint Total = 0;
	guint Passes = 0;
	guint Pixel = 0;

  	gimp_drawable_mask_bounds( Drawable->drawable_id, &X1, &Y1, &X2, &Y2 );

	Channels = gimp_drawable_bpp( Drawable->drawable_id );

	if( Channels < 2 )
	{

		g_message("This plugin requires at least two channels.  The last will be taken as an alpha channel while all those proceeding, colour.");

		return GIMP_PDB_CALLING_ERROR;

	}

	Width = X2 - X1;
	Height = Y2 - Y1;
	AlphaChannel = Channels - 1;

	gimp_pixel_rgn_init( &InputRegion, Drawable, X1, Y1, Width, Height, FALSE, FALSE );
	gimp_pixel_rgn_init( &OutputRegion, Drawable, X1, Y1, Width, Height, TRUE, TRUE );

	Input = ( guchar * ) malloc( Channels * sizeof( guchar ) * Width * Height );

	if( Input == NULL )
	{

		g_message("Failed to allocate memory for a pixel buffer to pad UVs.");

		return GIMP_PDB_EXECUTION_ERROR;

	}

	Output = ( guchar * ) malloc( Channels * sizeof( guchar ) * Width * Height );

	if( Output == NULL )
	{

		g_message("Failed to allocate memory for a pixel buffer to pad UVs.");

		free( Input );

		return GIMP_PDB_EXECUTION_ERROR;

	}

	Accumulator = ( guint * ) malloc( AlphaChannel * sizeof( guint ) );

	if( Accumulator == NULL )
	{

		g_message("Failed to allocate memory for the accmulator buffer to pad UVs.");

		free( Input );
		free( Output );

		return GIMP_PDB_EXECUTION_ERROR;

	}

	gimp_pixel_rgn_get_rect( &InputRegion, Input, X1, Y1, Width, Height );

	//Count the empty pixels in the buffer.
	Pixel = 0;

	for( X = 0; X < Width; X++ )
		for( Y = 0; Y < Height; Y++ )
		{

			if( Input[ Pixel + AlphaChannel ] != 255 )
				Remaining++;

			Pixel += Channels;

		}

	Remaining *= 2;

	Total = Remaining;

	if( Total == Width * Height * 2 )
	{

		g_message("There are no solid pixels in the image to expand.");

		free( Input );
		free( Output );
		free( Accumulator );
	
		return GIMP_PDB_CALLING_ERROR;

	}

	memcpy( Output, Input, sizeof( guchar ) * Width * Height * Channels );

	//Filter over and over until no empty pixels remain in the image.
	while( Remaining > 0 )
	{

		//Given a buffer and an offset into that buffer, if the alpha channel is 255 (solid) it adds the values to the accumulator buffer.
		#define Accumulate( Index, InputBuffer )\
		\
			TempPixel = Index;\
			\
			if( InputBuffer[ TempPixel + AlphaChannel ] == 255 )\
			{\
			\
				for( Channel = 0; Channel < AlphaChannel; Channel++ )\
					Accumulator[ Channel ] += InputBuffer[ TempPixel++ ]; \
				\
					Accumulated++;\
			\
			}

		//Iterates through an input buffer, running Accumulate() on the four neighbours of each pixel meeting a condition.
		#define IterateAndSample( Condition, InputBuffer, OutputBuffer )\
			\
			Pixel = 0;\
			\
			for( Y = 0; Y < Height; Y++ )\
			{\
			\
				for( X = 0; X < Width; X++ )\
				{\
				\
					if( Condition )\
					{\
					\
						guchar Accumulated = 0;\
						guint TempPixel = 0;\
						guchar Channel = 0;\
						\
						for( Channel = 0; Channel < AlphaChannel; Channel++ )\
							Accumulator[ Channel ] = 0;\
						\
						if( X > 0 )\
							Accumulate( Pixel - Channels, InputBuffer )\
						\
						if( X < ( Width - 1 ) )\
							Accumulate( Pixel + Channels, InputBuffer )\
						\
						if( Y > 0 )\
							Accumulate( Pixel - ( Channels * Width ), InputBuffer )\
						\
						if( Y < ( Height - 1 ) )\
							Accumulate( Pixel + ( Channels * Width ), InputBuffer )\
						\
						if( Accumulated > 0 )\
						{\
						\
							TempPixel = Pixel;\
							\
							for( Channel = 0; Channel < AlphaChannel; Channel++ )\
								OutputBuffer[ TempPixel++ ] = Accumulator[ Channel ] / Accumulated;\
							\
							OutputBuffer[ TempPixel ] = 255;\
							\
							Remaining--;\
						\
						}\
					\
					}\
					\
					Pixel += Channels;\
				\
				}\
				\
				if( ( X % 128 ) == 0 )\
					gimp_progress_update( ( gdouble ) ( Total - Remaining ) / ( gdouble ) ( Total ) );\
			\
			}

		//Attempt to expand all empty pixels.
		gimp_progress_set_text_printf("Padding UVs, pass %d, %d pixels remaining", Passes, Remaining );
		IterateAndSample( Input[ Pixel + AlphaChannel ] != 255, Input, Output )

		//Filter newly solid pixels by averaging their newly solid neighbours.
		gimp_progress_set_text_printf("Filtering UVs, pass %d, %d pixels remaining", Passes++, Remaining );
		IterateAndSample( Input[ Pixel + AlphaChannel ] != Output[ Pixel + AlphaChannel ], Output, Input )

		memcpy( Output, Input, sizeof( guchar ) * Width * Height * Channels );

	}

	gimp_pixel_rgn_set_rect( &OutputRegion, Output, X1, Y1, Width, Height );

	free( Input );
	free( Output );
	free( Accumulator );

  	gimp_drawable_flush( Drawable );
  	gimp_drawable_merge_shadow( Drawable->drawable_id, TRUE );
  	gimp_drawable_update( Drawable->drawable_id, X1, Y1, Width, Height );

	return GIMP_PDB_SUCCESS;

}

Original post:

I'm trying to establish a Linux workflow, and while I previously had a Photoshop Action (from the Polycount Wiki) to generate UV padding from a RGB texture map with a mask in the alpha channel, I couldn't find anything not-convoluted to do this here.

Quickly threw together a GIMP plugin in C by modifying the example blur filter, the filter's a bit iffy (there's probably better ways to fill the spaces - it's slow and makes very blurry padding by doing a one-pixel floodfill every pass, in a single thread, which may or may not be what you want) but if you're after a barely functional UV padding filter, here you go!

I've set it up to always take the last channel as alpha, and to only treat values of 255 as "solid". Empty pixels sample their surroundings each pass, averaging those that are "solid" which is why it blurs rather than streaking. I've only been using GIMP a few days and am not familiar with the codebase/APIs, so if I've done something a bit wrong, I won't be offended if you tell me!

Freely released, distribute all you want, modify all you want, credit'd be appreciated if you do, no liability for faults or damage.

Sample: (4096x4096, downsampled afterwards to 1024x1024, took about 35 seconds - if I downsample it first, it takes under five seconds. It's quite fast on sensibly sized images but performance goes through the floor very suddenly)
N1Xlq.jpg
Eivak.jpg

On-model shots:
igDQs.jpg

Source code:
#include <stdlib.h>
#include <string.h>
#include <libgimp/gimp.h>

static void query (void);
static void run   (const gchar      *name,
                   gint              nparams,
                   const GimpParam  *param,
                   gint             *nreturn_vals,
                   GimpParam       **return_vals);
static void blur  (GimpDrawable     *drawable);

GimpPlugInInfo PLUG_IN_INFO =
{
  NULL,
  NULL,
  query,
  run
};

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_install_procedure (
    "plug-in-uvpadder",
    "UV Padder",
    "Expands the colour in the image",
    "James Wild (built on an example by David Neary)",
    "",
    "2012",
    "_UV Padder",
    "RGB*, GRAY*",
    GIMP_PLUGIN,
    G_N_ELEMENTS (args), 0,
    args, NULL);

  	gimp_plugin_menu_register ("plug-in-uvpadder", "<Image>/Filters/Texturing");
}

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;

	*nreturn_vals = 1;
	*return_vals  = values;

	values[0].type = GIMP_PDB_STATUS;
	values[0].data.d_status = status;

	run_mode = param[0].data.d_int32;

	drawable = gimp_drawable_get (param[2].data.d_drawable);

	gimp_progress_init ("Padding UVs...");

  	blur(drawable);

	gimp_displays_flush ();
	gimp_drawable_detach (drawable);

}

static void blur( GimpDrawable *drawable )
{

	gint         i, j, k, channels, alphachannel;
	gint         x1, y1, x2, y2, width, height;
	GimpPixelRgn rgn_in, rgn_out;
	guchar *     input, *output;
	guint *accumulator;

  	gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2);

	channels = gimp_drawable_bpp (drawable->drawable_id);

	if( channels < 2 )
	{

		g_message("This plugin requires at least two channels.  The last will be taken as an alpha channel while all those proceeding, colour.");
		return;

	}

	width = x2 - x1;
	height = y2 - y1;
	alphachannel = channels - 1;

	gimp_pixel_rgn_init (&rgn_in,
                       drawable,
                       x1, y1,
                       x2 - x1, y2 - y1,
                       FALSE, FALSE);
	gimp_pixel_rgn_init (&rgn_out,
                       drawable,
                       x1, y1,
                       x2 - x1, y2 - y1,
                       TRUE, TRUE);

	input = (guchar*)malloc( 2 * channels * sizeof(guchar) * width * height );

	if( input == NULL )
	{
		g_message("Failed to allocate memory for a temp buffer to pad UVs.");
		return;
	}

	accumulator = (guint*)malloc(alphachannel * sizeof(guint));

	if( accumulator == NULL )
	{

		g_message("Failed to allocate memory for the accmulator buffer to pad UVs.");

		free( input );

		return;

	}

	output = &input[channels * width * height];

	guint remaining = 0;
	guint passes = 0;

	gimp_pixel_rgn_get_rect(&rgn_in,input, x1, y1, x2 - x1, y2 - y1);

	memcpy(output, input, sizeof( guchar ) * width * height * channels );

	#define calculateoffset(x,y) ( ( (x) + ( (y) * width ) ) * channels )

		for( i = 0; i < width; i++ )
			for( j = 0; j < height; j++ )
				if( input[ (calculateoffset(i, j)) + alphachannel ] != 255 )
					remaining++;

	while( remaining > 0 )
	{

		gimp_progress_set_text_printf("Padding UVs, pass %d, %d pixels remaining", passes++, remaining );

		for( i = 0; i < width; i++ )
		{

			for( j = 0; j < height; j++ )
			{

				guint offset = calculateoffset(i, j);

				if( input[ offset + alphachannel ] != 255 )
				{


					guchar accumulated = 0;
					guint tempoffset = 0;

					for( k = 0; k < alphachannel; k++ )
						accumulator[k] = 0;

					#define accumulate( index ) \
						tempoffset = (index);\
						if( input[ tempoffset + alphachannel ] == 255 )\
						{\
							for( k = 0; k < alphachannel; k++ )\
								accumulator[k] += input[tempoffset++]; \
							accumulated++;\
						}
					if( i > 0 )
						accumulate( calculateoffset( i - 1, j ) );

					if( i < ( width - 1 ) )
						accumulate( calculateoffset( i + 1, j ) );

					if( j > 0 )
						accumulate( calculateoffset( i, j - 1 ) );

					if( j < ( height - 1 ) )
						accumulate( calculateoffset( i, j + 1 ) );

					if( accumulated > 0 )
					{

						for( k = 0; k < alphachannel; k++ )
							output[ offset++ ] = accumulator[ k ] / accumulated;

						output[ offset ] = 255;

						remaining--;
		
					}

				}

			}

			if( ( i % 128 ) == 0 )
				gimp_progress_update ((gdouble) ( (width*height) - remaining ) / (gdouble) (width*height));

		}

		memcpy(input, output, sizeof( guchar ) * width * height * channels );

	}

	gimp_pixel_rgn_set_rect(&rgn_out,output, x1, y1, x2 - x1, y2 - y1);

	free( input );
	free(accumulator);

  	gimp_drawable_flush (drawable);
  	gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
  	gimp_drawable_update (drawable->drawable_id, x1, y1, x2 - x1, y2 - y1);

}

I'm on Linux right now so I don't know how to do this on Windows/etc but just save the code as uvpadder.c, open a terminal in the folder you saved it in and hit:

gimptool-2.0 --install uvpadder.c

If all goes well you'll be able to run the filter after restarting GIMP by just going Filters->Texturing->UV Padder. It'll keep iterating passes until no more changes are to be made.

I apologize for the current horrendously sloppy code and might clean it up at a later date (Remove all the i, j, k, etc. variable names)

Replies

  • respawnrt
    Offline / Send Message
    respawnrt polycounter lvl 8
    OOh nice, i use gimp(unfortunately :p) and i'd like this to work, but i get this.Any advice ?
    F:\test>gimptool-2.0 --install uvpadder.c
    'gimptool-2.0' is not recognized as an internal or external command,
    operable program or batch file.
  • JamesWild
    Offline / Send Message
    JamesWild polycounter lvl 8
    Oh sorry just looked it up, and it looks actually quite complex to do on Windows. I'll compile it soonish, going to do some more work on it first at a time that isn't 2AM :D
  • JamesWild
    Offline / Send Message
    JamesWild polycounter lvl 8
    Improvements made to the algorithm! Refactored, renamed variables meaningfully, consistent style, more meaningful progress bar updates, new interpolation algorithm produces drastically better, less streaky filling:
    7UAkr.png
    #include <stdlib.h>
    #include <string.h>
    #include <libgimp/gimp.h>
    
    static void query( void );
    
    static void run
    (
    
    	const gchar * name,
    	gint nparams,
    	const GimpParam * param,
    	gint * nreturn_vals,
    	GimpParam  ** return_vals
    );
    
    static GimpPDBStatusType Pad( GimpDrawable * Drawable );
    
    GimpPlugInInfo PLUG_IN_INFO =
    {
    
    	NULL,
    	NULL,
    	query,
    	run
    
    };
    
    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_install_procedure (
        "plug-in-uvpadder",
        "UV Padder",
        "Expands the colour in the image",
        "James Wild (built on an example by David Neary)",
        "",
        "2012",
        "_UV Padder",
        "RGB*, GRAY*",
        GIMP_PLUGIN,
        G_N_ELEMENTS (args), 0,
        args, NULL);
    
      	gimp_plugin_menu_register ("plug-in-uvpadder", "<Image>/Filters/Texturing");
    
    }
    
    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;
    
    	*nreturn_vals = 1;
    	*return_vals  = values;
    
    	values[0].type = GIMP_PDB_STATUS;
    	values[0].data.d_status = status;
    
    	run_mode = param[0].data.d_int32;
    
    	drawable = gimp_drawable_get (param[2].data.d_drawable);
    
    	gimp_progress_init ("Padding UVs...");
    
      	status = Pad( drawable );
    
    	gimp_displays_flush ();
    	gimp_drawable_detach (drawable);
    
    }
    
    static GimpPDBStatusType Pad( GimpDrawable * Drawable )
    {
    
    	gint X, Y, Channels, AlphaChannel;
    	gint Y1, Y2, X1, X2, Width, Height;
    	GimpPixelRgn InputRegion, OutputRegion;
    	guchar * Input, * Output;
    	guint * Accumulator;
    	guint Remaining = 0;
    	guint Total = 0;
    	guint Passes = 0;
    	guint Pixel = 0;
    
      	gimp_drawable_mask_bounds( Drawable->drawable_id, &X1, &Y1, &X2, &Y2 );
    
    	Channels = gimp_drawable_bpp( Drawable->drawable_id );
    
    	if( Channels < 2 )
    	{
    
    		g_message("This plugin requires at least two channels.  The last will be taken as an alpha channel while all those proceeding, colour.");
    		return;
    
    	}
    
    	Width = X2 - X1;
    	Height = Y2 - Y1;
    	AlphaChannel = Channels - 1;
    
    	gimp_pixel_rgn_init( &InputRegion, Drawable, X1, Y1, Width, Height, FALSE, FALSE );
    	gimp_pixel_rgn_init( &OutputRegion, Drawable, X1, Y1, Width, Height, TRUE, TRUE );
    
    	Input = ( guchar * ) malloc( Channels * sizeof( guchar ) * Width * Height );
    
    	if( Input == NULL )
    	{
    
    		g_message("Failed to allocate memory for a pixel buffer to pad UVs.");
    
    		return GIMP_PDB_EXECUTION_ERROR;
    
    	}
    
    	Output = ( guchar * ) malloc( Channels * sizeof( guchar ) * Width * Height );
    
    	if( Output == NULL )
    	{
    
    		g_message("Failed to allocate memory for a pixel buffer to pad UVs.");
    
    		free( Input );
    
    		return GIMP_PDB_EXECUTION_ERROR;
    
    	}
    
    	Accumulator = ( guint * ) malloc( AlphaChannel * sizeof( guint ) );
    
    	if( Accumulator == NULL )
    	{
    
    		g_message("Failed to allocate memory for the accmulator buffer to pad UVs.");
    
    		free( Input );
    		free( Output );
    
    		return GIMP_PDB_EXECUTION_ERROR;
    
    	}
    
    	gimp_pixel_rgn_get_rect( &InputRegion, Input, X1, Y1, Width, Height );
    
    	//Count the empty pixels in the buffer.
    	Pixel = 0;
    
    	for( X = 0; X < Width; X++ )
    		for( Y = 0; Y < Height; Y++ )
    		{
    
    			if( Input[ Pixel + AlphaChannel ] != 255 )
    				Remaining++;
    
    			Pixel += Channels;
    
    		}
    
    	Total = Remaining;
    
    	if( Total == 0 )
    	{
    
    		g_message("There are no solid pixels in the image to expand.");
    
    		free( Input );
    		free( Output );
    		free( Accumulator );
    	
    		return GIMP_PDB_EXECUTION_ERROR;
    
    	}
    
    	memcpy( Output, Input, sizeof( guchar ) * Width * Height * Channels );
    
    	//Filter over and over until no empty pixels remain in the image.
    	while( Remaining > 0 )
    	{
    
    		//Given a buffer and an offset into that buffer, if the alpha channel is 255 (solid) it adds the values to the accumulator buffer.
    		#define Accumulate( Index, InputBuffer )\
    		\
    			TempPixel = Index;\
    			\
    			if( InputBuffer[ TempPixel + AlphaChannel ] == 255 )\
    			{\
    			\
    				for( Channel = 0; Channel < AlphaChannel; Channel++ )\
    					Accumulator[ Channel ] += InputBuffer[ TempPixel++ ]; \
    				\
    					Accumulated++;\
    			\
    			}
    
    		//Iterates through an input buffer, running Accumulate() on the four neighbours of each pixel meeting a condition.
    		#define IterateAndSample( Condition, InputBuffer, OutputBuffer, AfterAccumuluation )\
    			\
    			Pixel = 0;\
    			\
    			for( Y = 0; Y < Width; Y++ )\
    			{\
    			\
    				for( X = 0; X < Height; X++ )\
    				{\
    				\
    					if( Condition )\
    					{\
    					\
    						guchar Accumulated = 0;\
    						guint TempPixel = 0;\
    						guchar Channel = 0;\
    						\
    						for( Channel = 0; Channel < AlphaChannel; Channel++ )\
    							Accumulator[ Channel ] = 0;\
    						\
    						if( X > 0 )\
    							Accumulate( Pixel - Channels, InputBuffer )\
    						\
    						if( X < ( Width - 1 ) )\
    							Accumulate( Pixel + Channels, InputBuffer )\
    						\
    						if( Y > 0 )\
    							Accumulate( Pixel - ( Channels * Width ), InputBuffer )\
    						\
    						if( Y < ( Height - 1 ) )\
    							Accumulate( Pixel + ( Channels * Width ), InputBuffer )\
    						\
    						if( Accumulated > 0 )\
    						{\
    						\
    							TempPixel = Pixel;\
    							\
    							for( Channel = 0; Channel < AlphaChannel; Channel++ )\
    								OutputBuffer[ TempPixel++ ] = Accumulator[ Channel ] / Accumulated;\
    							\
    							OutputBuffer[ TempPixel ] = 255;\
    							\
    							AfterAccumuluation\
    						\
    						}\
    					\
    					}\
    					\
    					Pixel += Channels;\
    				\
    				}\
    				\
    				if( ( X % 128 ) == 0 )\
    					gimp_progress_update( ( gdouble ) ( Total - Remaining ) / ( gdouble ) ( Total ) );\
    			\
    			}
    
    		//Attempt to expand all empty pixels.
    		gimp_progress_set_text_printf("Padding UVs, pass %d, %d pixels remaining", Passes++, Remaining );
    		IterateAndSample( Input[ Pixel + AlphaChannel ] != 255, Input, Output, Remaining--; )
    
    		//Filter newly solid pixels by averaging their newly solid neighbours.
    		gimp_progress_set_text_printf("Filtering UVs, pass %d, %d pixels remaining", Passes++, Remaining );
    		IterateAndSample( Input[ Pixel + AlphaChannel ] != Output[ Pixel + AlphaChannel ], Output, Input, )
    
    		memcpy( Output, Input, sizeof( guchar ) * Width * Height * Channels );
    
    	}
    
    	gimp_pixel_rgn_set_rect( &OutputRegion, Output, X1, Y1, Width, Height );
    
    	free( Input );
    	free( Output );
    	free( Accumulator );
    
      	gimp_drawable_flush( Drawable );
      	gimp_drawable_merge_shadow( Drawable->drawable_id, TRUE );
      	gimp_drawable_update( Drawable->drawable_id, X1, Y1, Width, Height );
    
    	return GIMP_PDB_SUCCESS;
    
    }
    
    
    Windows binaries coming in the next hour if all goes well.

    EDIT: Ok, wow is compiling this on Windows a pain. Not because of Windows, but because none of the dependencies are easily available on Windows and it attempts to work like Linux when it's Windows. Currently struggling to get GLib's includes complete because it comes without some configuration file.
  • JamesWild
    Offline / Send Message
    JamesWild polycounter lvl 8
    http://www.mediafire.com/?i7d128nobj898k5
    Here's a Win32 build. Just pop it in the .gimp-2.8/plug-ins/ folder in your user folder (the parent of Documents/Pictures/etc.) I had to gut GLib to make it build because it was missing so much stuff. I also fixed some bugs, see below for the source to an updated Linux version:
    #include <stdlib.h>
    #include <string.h>
    #include <libgimp/gimp.h>
    
    static void query( void );
    
    static void run
    (
    
    	const gchar * name,
    	gint nparams,
    	const GimpParam * param,
    	gint * nreturn_vals,
    	GimpParam  ** return_vals
    );
    
    static GimpPDBStatusType Pad( GimpDrawable * Drawable );
    
    GimpPlugInInfo PLUG_IN_INFO =
    {
    
    	NULL,
    	NULL,
    	query,
    	run
    
    };
    
    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_install_procedure (
        "plug-in-uvpadder",
        "UV Padder",
        "Expands the colour in the image",
        "James Wild (built on an example by David Neary)",
        "",
        "2012",
        "_UV Padder",
        "RGB*, GRAY*",
        GIMP_PLUGIN,
        G_N_ELEMENTS (args), 0,
        args, NULL);
    
      	gimp_plugin_menu_register ("plug-in-uvpadder", "<Image>/Filters/Texturing");
    
    }
    
    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;
    
    	*nreturn_vals = 1;
    	*return_vals  = values;
    
    	values[0].type = GIMP_PDB_STATUS;
    	values[0].data.d_status = status;
    
    	run_mode = param[0].data.d_int32;
    
    	drawable = gimp_drawable_get (param[2].data.d_drawable);
    
    	gimp_progress_init ("Padding UVs...");
    
      	status = Pad( drawable );
    
    	gimp_displays_flush ();
    	gimp_drawable_detach (drawable);
    
    }
    
    static GimpPDBStatusType Pad( GimpDrawable * Drawable )
    {
    
    	gint X, Y, Channels, AlphaChannel;
    	gint Y1, Y2, X1, X2, Width, Height;
    	GimpPixelRgn InputRegion, OutputRegion;
    	guchar * Input, * Output;
    	guint * Accumulator;
    	guint Remaining = 0;
    	guint Total = 0;
    	guint Passes = 0;
    	guint Pixel = 0;
    
      	gimp_drawable_mask_bounds( Drawable->drawable_id, &X1, &Y1, &X2, &Y2 );
    
    	Channels = gimp_drawable_bpp( Drawable->drawable_id );
    
    	if( Channels < 2 )
    	{
    
    		g_message("This plugin requires at least two channels.  The last will be taken as an alpha channel while all those proceeding, colour.");
    
    		return GIMP_PDB_CALLING_ERROR;
    
    	}
    
    	Width = X2 - X1;
    	Height = Y2 - Y1;
    	AlphaChannel = Channels - 1;
    
    	gimp_pixel_rgn_init( &InputRegion, Drawable, X1, Y1, Width, Height, FALSE, FALSE );
    	gimp_pixel_rgn_init( &OutputRegion, Drawable, X1, Y1, Width, Height, TRUE, TRUE );
    
    	Input = ( guchar * ) malloc( Channels * sizeof( guchar ) * Width * Height );
    
    	if( Input == NULL )
    	{
    
    		g_message("Failed to allocate memory for a pixel buffer to pad UVs.");
    
    		return GIMP_PDB_EXECUTION_ERROR;
    
    	}
    
    	Output = ( guchar * ) malloc( Channels * sizeof( guchar ) * Width * Height );
    
    	if( Output == NULL )
    	{
    
    		g_message("Failed to allocate memory for a pixel buffer to pad UVs.");
    
    		free( Input );
    
    		return GIMP_PDB_EXECUTION_ERROR;
    
    	}
    
    	Accumulator = ( guint * ) malloc( AlphaChannel * sizeof( guint ) );
    
    	if( Accumulator == NULL )
    	{
    
    		g_message("Failed to allocate memory for the accmulator buffer to pad UVs.");
    
    		free( Input );
    		free( Output );
    
    		return GIMP_PDB_EXECUTION_ERROR;
    
    	}
    
    	gimp_pixel_rgn_get_rect( &InputRegion, Input, X1, Y1, Width, Height );
    
    	//Count the empty pixels in the buffer.
    	Pixel = 0;
    
    	for( X = 0; X < Width; X++ )
    		for( Y = 0; Y < Height; Y++ )
    		{
    
    			if( Input[ Pixel + AlphaChannel ] != 255 )
    				Remaining++;
    
    			Pixel += Channels;
    
    		}
    
    	Remaining *= 2;
    
    	Total = Remaining;
    
    	if( Total == Width * Height * 2 )
    	{
    
    		g_message("There are no solid pixels in the image to expand.");
    
    		free( Input );
    		free( Output );
    		free( Accumulator );
    	
    		return GIMP_PDB_CALLING_ERROR;
    
    	}
    
    	memcpy( Output, Input, sizeof( guchar ) * Width * Height * Channels );
    
    	//Filter over and over until no empty pixels remain in the image.
    	while( Remaining > 0 )
    	{
    
    		//Given a buffer and an offset into that buffer, if the alpha channel is 255 (solid) it adds the values to the accumulator buffer.
    		#define Accumulate( Index, InputBuffer )\
    		\
    			TempPixel = Index;\
    			\
    			if( InputBuffer[ TempPixel + AlphaChannel ] == 255 )\
    			{\
    			\
    				for( Channel = 0; Channel < AlphaChannel; Channel++ )\
    					Accumulator[ Channel ] += InputBuffer[ TempPixel++ ]; \
    				\
    					Accumulated++;\
    			\
    			}
    
    		//Iterates through an input buffer, running Accumulate() on the four neighbours of each pixel meeting a condition.
    		#define IterateAndSample( Condition, InputBuffer, OutputBuffer )\
    			\
    			Pixel = 0;\
    			\
    			for( Y = 0; Y < Height; Y++ )\
    			{\
    			\
    				for( X = 0; X < Width; X++ )\
    				{\
    				\
    					if( Condition )\
    					{\
    					\
    						guchar Accumulated = 0;\
    						guint TempPixel = 0;\
    						guchar Channel = 0;\
    						\
    						for( Channel = 0; Channel < AlphaChannel; Channel++ )\
    							Accumulator[ Channel ] = 0;\
    						\
    						if( X > 0 )\
    							Accumulate( Pixel - Channels, InputBuffer )\
    						\
    						if( X < ( Width - 1 ) )\
    							Accumulate( Pixel + Channels, InputBuffer )\
    						\
    						if( Y > 0 )\
    							Accumulate( Pixel - ( Channels * Width ), InputBuffer )\
    						\
    						if( Y < ( Height - 1 ) )\
    							Accumulate( Pixel + ( Channels * Width ), InputBuffer )\
    						\
    						if( Accumulated > 0 )\
    						{\
    						\
    							TempPixel = Pixel;\
    							\
    							for( Channel = 0; Channel < AlphaChannel; Channel++ )\
    								OutputBuffer[ TempPixel++ ] = Accumulator[ Channel ] / Accumulated;\
    							\
    							OutputBuffer[ TempPixel ] = 255;\
    							\
    							Remaining--;\
    						\
    						}\
    					\
    					}\
    					\
    					Pixel += Channels;\
    				\
    				}\
    				\
    				if( ( X % 128 ) == 0 )\
    					gimp_progress_update( ( gdouble ) ( Total - Remaining ) / ( gdouble ) ( Total ) );\
    			\
    			}
    
    		//Attempt to expand all empty pixels.
    		gimp_progress_set_text_printf("Padding UVs, pass %d, %d pixels remaining", Passes, Remaining );
    		IterateAndSample( Input[ Pixel + AlphaChannel ] != 255, Input, Output )
    
    		//Filter newly solid pixels by averaging their newly solid neighbours.
    		gimp_progress_set_text_printf("Filtering UVs, pass %d, %d pixels remaining", Passes++, Remaining );
    		IterateAndSample( Input[ Pixel + AlphaChannel ] != Output[ Pixel + AlphaChannel ], Output, Input )
    
    		memcpy( Output, Input, sizeof( guchar ) * Width * Height * Channels );
    
    	}
    
    	gimp_pixel_rgn_set_rect( &OutputRegion, Output, X1, Y1, Width, Height );
    
    	free( Input );
    	free( Output );
    	free( Accumulator );
    
      	gimp_drawable_flush( Drawable );
      	gimp_drawable_merge_shadow( Drawable->drawable_id, TRUE );
      	gimp_drawable_update( Drawable->drawable_id, X1, Y1, Width, Height );
    
    	return GIMP_PDB_SUCCESS;
    
    }
    

    Bugfixes:
    -Fixed a missing return value.
    -Further improved the progress bar.
    -Fixed a width/height mixup resulting in segmentation faults.

    If I get feedback on it indicating it's a decent tool I'll stick it on the wiki. Now I have the groundwork down, does anyone have ideas for other simple filters they want for GIMP?
  • respawnrt
    Offline / Send Message
    respawnrt polycounter lvl 8
    James, thanks for win build, however i get this error "libgcc_s_dw2-1.dll missing " when i run the exe. I put the exe in
    C:\Users\myname\.gimp-2.8\plug-ins.
  • JamesWild
    Offline / Send Message
    JamesWild polycounter lvl 8
    Oh sorry for some reason it's not included the standard C library in the binary.

    http://www.mediafire.com/?3yjj8ocv97wm35h
    Sticking it alongside should fix it; if you have MinGW it installs this... somewhere and you don't need it to run MinGW programs.

    Thanks for testing.
  • respawnrt
    Offline / Send Message
    respawnrt polycounter lvl 8
    Damn, another error now that i put that .dll: libgimp 2.0-0.dll missing. Again just to be sure, i'm in gimp 2.8/plug-ins folder where i put the exe and the first error .dll.
  • respawnrt
    Offline / Send Message
    respawnrt polycounter lvl 8
    Oh James, i put you on the wrong track...i thought i gotta run the exe after placing it in plugin folder you see, and got those errors but i just ran gimp and it works fine ! srry forget about those .dlls it works yay :D GJ.
    4vSep.png
  • JamesWild
    Offline / Send Message
    JamesWild polycounter lvl 8
    Thanks!

    Yeah, the GIMP's system may be a bit weird but it follows the UNIXish system of programs running programs to complete tasks. As an added bonus because it runs under a seperate process a crash has no impact on the stability of GIMP unlike Photoshop where I've had the whole thing destabilize by one buggy plugin.
  • dacanizares
    Hi!

    This looks awesome but it doesn't work on Gimp 2.10.x. Do you plan to update it? It also would be great to create a Github repo with this plugin.

    Thanks
  • dacanizares
    It's actually working, just copy the .dll into the Gimp bin folder.

    From other side, I could find instructions on how to compile it on Windows, any idea? All resources seem to be really outdated as for 2019 :(
  • dacanizares
    Here is a backup link, just in case that something stops working: https://drive.google.com/open?id=1RCsXhOGDbCiR0eMkQT4hv1smSN6ODF9A

Sign In or Register to comment.