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)
On-model shots:
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
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.
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.
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:
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?
C:\Users\myname\.gimp-2.8\plug-ins.
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.
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.
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
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