Como escribir un plug-in para gimp (parte II)

Escrito por: David Neary

Traducido de aquí por AntI para Gimp-es

En la primera parte, presentaba los elementos esenciales para construir un plug-in con El Gimp. Ahora produciremos un sencillo pero útil algoritmo que se podría usar en nuestro plug-in.

Introducción

El algoritmo que vamos a implementar es un simple desenfoque (blur). Está incluido en El Gimp "Filtros->Desenfoque[Blur]->Desenfoque(Blur)" con los parámetros predefinidos.

Ese algoritmo es muy simple. Cada píxel en nuestra imagen es reemplazado por un valor promedio de el de sus vecinos. Por ejemplo, si miramos el caso más simple donde los vecinos son 3x3, en ese caso el valor central será reemplazado por 5, el promedio de los 9 números de su alrededor.

Con este método, las diferencias de los bordes son splatted, dando un resultado desenfocado. Uno puede escoger otro radio, usando una matriz (2r + 1) x (2r + 1).

Estructura de la imagen

El último mes, escribí una función run() que no hacía nada útil.Mira otra vez el prototipo run():

static void run (const gchar *name,

gint nparams,

const GimpParam *param,

gint *nreturn_vals,

GimpParam **return_vals);



Vimos que para un filtro (p.e., un plug-in que modifica la imagen), lo primero tres parámetros de entrada fueron el run_mode, un identificador para la imagen, y otro de los dibujables activos (capa o máscara).

Una imagen GIMP es una estructura que contiene, además de otras, guias, capas, máscaras de capa, unos datos asociados a la imagen. La palabra "dibujable" es, a menudo, usada en estructuras internas de GIMP. Un "dibujable" es un objeto del que puedes obtener, y a veces modificar, datos brutos. Así: capas, máscaras de capa, selecciones son todas "dibujables”.

Dibujables (drawable)



Accediendo a los datos

Para obtener su identificador desde un dibujable Gimp, necesitamos la función gimp_drawable_get():



GimpDrawable *gimp_drawable_get(gint32 drawable_id);



Desde esta estructura, uno puede acceder a datos del dibujable a través de un estructura GimpPixelRgn, y puede chequear el tipo de dibujable (RGB, escala de grises). La lista entera de funciones disponibles de un GimpDrawable se puede encontrar en los API.

Dos funciones muy importantes para los plug-ins son gimp_drawable_mask_bounds() y gimp_pixel_rgn_init(). La primera da los límites de la selección activa sobre el dibujable, y la segunda inicia el GimpPixelRgn, que usaremos para acceder a los datos.

Tan pronto como tengamos bien inicializado GimpPixelRgn, podremos acceder a los datos de la imagen de diferentes maneras, por píxel, por rectangulo, por fila o por columna. El mejor método dependerá del algoritmo que tengamos planeado usar. Además, El Gimp usa una arquitectura basada en mosaicos, y abrir y cerrar es caro, así que no deberías usarla más de lo necesario.

Mosaicos (Tiles)

Mosaicos

Las funciones principales para obtener y dar datos de la imagen son:

void gimp_pixel_rgn_get_pixel (GimpPixelRgn *pr,

guchar *buf,

gint x,

gint y);

void gimp_pixel_rgn_get_row (GimpPixelRgn *pr,

guchar *buf,

gint x,

gint y,

gint width);

void gimp_pixel_rgn_get_col (GimpPixelRgn *pr,

guchar *buf,

gint x,

gint y,

gint height);

void gimp_pixel_rgn_get_rect (GimpPixelRgn *pr,

guchar *buf,

gint x,

gint y,

gint width,

gint height);

void gimp_pixel_rgn_set_pixel (GimpPixelRgn *pr,

const guchar *buf,

gint x,

gint y);

void gimp_pixel_rgn_set_row (GimpPixelRgn *pr,

const guchar *buf,

gint x,

gint y,

gint width);

void gimp_pixel_rgn_set_col (GimpPixelRgn *pr,

const guchar *buf,

gint x,

gint y,

gint height);

void gimp_pixel_rgn_set_rect (GimpPixelRgn *pr,

const guchar *buf,

gint x,

gint y,

gint width,

gint height);



Tambien, hay otra manera de acceder a los datos de la imagen (es la usada más a menudo), que permite manejar datos en el nivel del mosaico. Lo veremos en detalle más tarde.



Actualizando la imagen

Al final, un plug-in que ha modificado unos datos de un dibujable debe nivelarlo para enviar los datos al corazón, y para decirle a la aplicación que la representación debe actualizarse. Esto se hace con la siguiente función:



gimp_displays_flush ();

gimp_drawable_detach (drawable);



Implementando blur()

Para ser capaz de probar varios diferentes métodos de proceso, delegaremos el trabajo a la función blur(). Nuestro run() está debajo.



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;



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

gimp_progress_init ("My Blur...");



/* Let's time blur - don't forget to include time.h */

/* {

* time_t before = time(NULL);

*/

blur (drawable);

/* printf("Blur() took %ld seconds.\n", time(NULL) - before);

* } */



gimp_displays_flush ();

gimp_drawable_detach (drawable);



return;

}



Hay unas pocas lineas aquí que necesitan ser explicadas un poco más. La llamada a gimp_progress_init() inicia una medida progresiva para nuestro plug-in. Más tarde, si llamamos a gimp_progress_update(double percent), el porcentaje dado como un parámetro de entrada se mostrará graficamente. El run_mode nos dice, en todo caso, si el plug-in fué lanzado de una manera que los podamos representar en un interfaz gráfico o no.Los valores posibles son GIMP_RUN_INTERACTIVE, GIMP_RUN_NONINTERACTIVE o GIMP_RUN_WITH_LAST_VALS, lo que significa que el plug-in fué ejecutado desde El Gimp, por un script, o desde la entrada del menú "Repetir último filtro"

En cuanto al algoritmo blur, la primera versión usando gimp_pixel_rgn_(get|set)_pixel() se encuentra abajo. Algunas de sus funciones no han sido explicadas todavia.

gimp_drawable_mask_bounds() permite calcular los límites del efecto del filtro, excluyendo cualquier región que no esté en la selección activa. Limitando el proceso de esta manera permite una importante mejora del funcionamiento.

gimp_pixel_rgn_init() toma como parámetros de entrada el drawable (dibujable), sus límites para el procesado, y dos boleanos que modifican, significativamente, el comportamiento de el resultado GimpPixelRgn. Lo primero, dice que operación "set" deben ser hechas en los shadow tiles, en orden de dejar los datos originales como son hasta que se llama a gimp_drawable_merge_shadow(), cuando todos los datos modificados serán mezclados. La segunda dice que los mosaicos modificados serían marcados "sucios" y enviados al corazón para ser mezclados. La mayoria de las veces, para leer datos, uno usa FALSE y FALSE para estos dos parámetros, y para escribir datos, uno usa TRUE y TRUE. Otras combinaciones son posibles pero rara vez usadas.



static void blur(GimpDrawable *drawable)

{

gint i, j, k, channels;

gint x1, y1, x2, y2;

GimpPixelRgn rgn_in, rgn_out;

guchar output[4];



/* Gets upper left and lower right coordinates,

* and layers number in the image */

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

channels = gimp_drawable_bpp(drawable->drawable_id);



/* Initialises two PixelRgns, one to read original data,

* and the other to write output data. That second one will

* be merged at the end by the call to

* gimp_drawable_merge_shadow() */

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



for (i = x1; i < x2; i++)

{

for (j = y1; j < y2; j++)

{

guchar pixel[9][4];



/* Get nine pixels */

gimp_pixel_rgn_get_pixel(&rgn_in, pixel[0], MAX(i-1,x1),

MAX(j-1, y1));

gimp_pixel_rgn_get_pixel(&rgn_in, pixel[1], MAX(i-1,x1), j);

gimp_pixel_rgn_get_pixel(&rgn_in, pixel[2], MAX(i-1,x1),

MIN(j+1,y2-1));



gimp_pixel_rgn_get_pixel(&rgn_in, pixel[3], i, MAX(j-1,y1));

gimp_pixel_rgn_get_pixel(&rgn_in, pixel[4], i, j);

gimp_pixel_rgn_get_pixel(&rgn_in, pixel[5], i, MIN(j+1,y2-1));



gimp_pixel_rgn_get_pixel(&rgn_in, pixel[6], MIN(i+1,x2-1),

MAX(j-1,y1));

gimp_pixel_rgn_get_pixel(&rgn_in, pixel[7], MIN(i+1,x2-1), j);

gimp_pixel_rgn_get_pixel(&rgn_in, pixel[8], MIN(i+1,x2-1),

MIN(j+1,y2-1));



/* For each layer, compute the average of the

* nine */

for (k = 0; k < channels; k++)

{

int tmp, sum = 0;

for (tmp = 0; tmp < 9; tmp ++)

{

sum += pixel[tmp][k];

}

output[k] = sum / 9;

}



gimp_pixel_rgn_set_pixel(&rgn_out, output, i, j);

}



if (i%10 == 0)

gimp_progress_update ((gdouble) (i - x1) / (gdouble) (x2 - x1));

}



/* Update the modified region */

gimp_drawable_flush (drawable);

gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);

gimp_drawable_update (drawable->drawable_id, x1, y1, (x2 - x1),

(y2 - y1));

}



Procesando filas

Nuestra función tiene un bug de retroceso: el funcionamiento. En una selección de 300x300, con el código de tiempo no-comentado, blur() necesita 12 minutos en mi K6-2 350MHz, bien cargado de otra parte. Para comparar, la misma selección necesita 3 segundos con el Desenfoque Gausiano.

Si modificamos nuestra función, mejor usar gimp_pixel_rgn_(get|set)_row() el resultado es mejor de lejos. Reducimos el tiempo para la selección de 300x300 de 760 segundos a 6 segundos, blur() V2 está aquí abajo:



static void blur(GimpDrawable *drawable)

{

gint i, j, k, channels;

gint x1, y1, x2, y2;

GimpPixelRgn rgn_in, rgn_out;

guchar *row1, *row2, *row3;

guchar *outrow;



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

channels = gimp_drawable_bpp(drawable->drawable_id);



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



/* Initialise enough memory for row1, row2, row3, outrow */

row1 = malloc (channels * (x2 - x1) * sizeof *row1);

row2 = malloc (channels * (x2 - x1) * sizeof *row2);

row3 = malloc (channels * (x2 - x1) * sizeof *row3);

outrow = malloc (channels * (x2 - x1) * sizeof *outrow);



for (i = y1; i < y2; i++)

{

/* Get row i-1, i, i+1 */

gimp_pixel_rgn_get_row (&rgn_in, row1, x1, MAX(y1, i-1), (x2 - x1));

gimp_pixel_rgn_get_row (&rgn_in, row2, x1, i, (x2 - x1));

gimp_pixel_rgn_get_row (&rgn_in, row3, x1, MIN(y2-1, i+1), (x2 - x1));



for (j = x1; j < x2; j++)

{

/* For each layer, compute the average of the nine

* pixels */

for (k = 0; k < channels; k++)

{

int sum = 0;

sum = row1[channels * MAX((j - 1 - x1), 0) + k]

+ row1[channels * (j - x1) + k]

+ row1[channels * MIN((j + 1 - x1), x2 - x1 - 1) + k]

+ row2[channels * MAX((j - 1 - x1), 0) + k]

+ row2[channels * (j - x1) + k]

+ row2[channels * MIN((j + 1 - x1), x2 - x1 - 1) + k]

+ row3[channels * MAX((j - 1 - x1), 0) + k]

+ row3[channels * (j - x1) + k]

+ row3[channels * MIN((j + 1 - x1), x2 - x1 - 1) + k];

outrow[channels * (j - x1) + k] = sum / 9;

}



}



gimp_pixel_rgn_set_row(&rgn_out, outrow, x1, i, (x2 - x1));



if (i%10 == 0)

gimp_progress_update ((gdouble) (i - y1) / (gdouble) (y2 - y1));

}



free (row1);

free (row2);

free (row3);

free (outrow);



gimp_drawable_flush (drawable);

gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);

gimp_drawable_update (drawable->drawable_id, x1, y1, (x2 - x1),

(y2 - y1));

}



Próxima parte

En la próxima parte, veremos como procesar la imagen, cuadro a cuadro. Tambien echaremos un vistazo a preferencias, para modificar nuestro algoritmo así como que pueda tomar un parámetro de entrada.

Autor: Dave Neary (Bolsh at NOSPAM.gimp.org) Traducción:AntI (Ant.Ign at gmail)

Este trabajo está licenciado bajo una licencia Creative Commons Attribution-NonCommercial-ShareAlike 2.5.