PTdecode/CImg-1.3.0/examples/gmic4gimp.cpp

changeset 5
1204ebf9340d
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/PTdecode/CImg-1.3.0/examples/gmic4gimp.cpp	Mon Aug 03 14:09:20 2009 +0100
     1.3 @@ -0,0 +1,1132 @@
     1.4 +/*
     1.5 + #
     1.6 + #  File        : gmic4gimp.cpp
     1.7 + #                ( C++ source file )
     1.8 + #
     1.9 + #  Description : G'MIC for GIMP - A plug-in to allow the use
    1.10 + #                of G'MIC commands in GIMP.
    1.11 + #                This file is a part of the CImg Library project.
    1.12 + #                ( http://cimg.sourceforge.net )
    1.13 + #
    1.14 + #  Copyright   : David Tschumperle (GREYCstoration API)
    1.15 + #
    1.16 + #  License     : CeCILL v2.0
    1.17 + #                ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
    1.18 + #
    1.19 + #  This software is governed by the CeCILL  license under French law and
    1.20 + #  abiding by the rules of distribution of free software.  You can  use,
    1.21 + #  modify and/ or redistribute the software under the terms of the CeCILL
    1.22 + #  license as circulated by CEA, CNRS and INRIA at the following URL
    1.23 + #  "http://www.cecill.info".
    1.24 + #
    1.25 + #  As a counterpart to the access to the source code and  rights to copy,
    1.26 + #  modify and redistribute granted by the license, users are provided only
    1.27 + #  with a limited warranty  and the software's author,  the holder of the
    1.28 + #  economic rights,  and the successive licensors  have only  limited
    1.29 + #  liability.
    1.30 + #
    1.31 + #  In this respect, the user's attention is drawn to the risks associated
    1.32 + #  with loading,  using,  modifying and/or developing or reproducing the
    1.33 + #  software by the user in light of its specific status of free software,
    1.34 + #  that may mean  that it is complicated to manipulate,  and  that  also
    1.35 + #  therefore means  that it is reserved for developers  and  experienced
    1.36 + #  professionals having in-depth computer knowledge. Users are therefore
    1.37 + #  encouraged to load and test the software's suitability as regards their
    1.38 + #  requirements in conditions enabling the security of their systems and/or
    1.39 + #  data to be ensured and, more generally, to use and operate it in the
    1.40 + #  same conditions as regards security.
    1.41 + #
    1.42 + #  The fact that you are presently reading this means that you have had
    1.43 + #  knowledge of the CeCILL license and that you accept its terms.
    1.44 + #
    1.45 +*/
    1.46 +
    1.47 +// Include necessary header files.
    1.48 +//--------------------------------
    1.49 +#define cimg_display_type 0
    1.50 +#include "gmic.h"
    1.51 +#include "gmic4gimp_def.h"
    1.52 +#include <pthread.h>
    1.53 +#include <locale>
    1.54 +#include <gtk/gtk.h>
    1.55 +#include <libgimp/gimp.h>
    1.56 +#include <libgimp/gimpui.h>
    1.57 +using namespace cimg_library;
    1.58 +
    1.59 +// Define plug-in global variables.
    1.60 +//---------------------------------
    1.61 +CImgList<char> gmic_entries;           // The list of recognized G'MIC menu entries (stored as 'char*' strings).
    1.62 +CImgList<char> gmic_commands;          // The list of corresponding G'MIC commands to process the image.
    1.63 +CImgList<char> gmic_preview_commands;  // The list of corresponding G'MIC commands to preview the image.
    1.64 +CImgList<char> gmic_arguments;         // The list of corresponding needed filter arguments.
    1.65 +char *gmic_macros;                     // The array of customized G'MIC macros.
    1.66 +GtkTreeStore *filter_store;            // A list of available filter entries (used by the GtkTreeView).
    1.67 +bool return_create_dialog;             // Return value of the 'create_gui_dialog()' function (set by events handlers).
    1.68 +void **event_infos;                    // Infos that are passed to the GUI callback functions.
    1.69 +char *path_home;                       // The path where configuration files are looked for.
    1.70 +
    1.71 +// Replace '[]' by '()' in a C-string.
    1.72 +//------------------------------------
    1.73 +void strparenthesis(char *const s) {
    1.74 +  for (char *ns = s; *ns; ++ns) if (*ns=='[') *ns = '('; else if (*ns==']') *ns = ')';
    1.75 +}
    1.76 +
    1.77 +// Set/get plug-in global variables in GIMP.
    1.78 +//------------------------------------------
    1.79 +void set_current_filter(const unsigned int current_filter) {
    1.80 +  const unsigned int ncurrent_filter = current_filter>gmic_entries.size?0:current_filter;
    1.81 +  gimp_set_data("gmic_current_filter",&ncurrent_filter,sizeof(unsigned int));
    1.82 +}
    1.83 +
    1.84 +unsigned int get_current_filter() {
    1.85 +  unsigned int current_filter = 0;
    1.86 +  gimp_get_data("gmic_current_filter",&current_filter);
    1.87 +  if (current_filter>gmic_entries.size) current_filter = 0;
    1.88 +  return current_filter;
    1.89 +}
    1.90 +
    1.91 +void set_filter_nbparams(const unsigned int filter, const unsigned int nbparams) {
    1.92 +  char s_tmp[256] = { 0 };
    1.93 +  std::sprintf(s_tmp,"gmic_filter%u_nbparams",filter);
    1.94 +  gimp_set_data(s_tmp,&nbparams,sizeof(unsigned int));
    1.95 +}
    1.96 +
    1.97 +unsigned int get_filter_nbparams(const unsigned int filter) {
    1.98 +  char s_tmp[256] = { 0 };
    1.99 +  std::sprintf(s_tmp,"gmic_filter%u_nbparams",filter);
   1.100 +  unsigned int nbparams = 0;
   1.101 +  gimp_get_data(s_tmp,&nbparams);
   1.102 +  return nbparams;
   1.103 +}
   1.104 +
   1.105 +void set_filter_parameter(const unsigned int filter, const unsigned int n, const char *const param) {
   1.106 +  char s_tmp[256] = { 0 };
   1.107 +  std::sprintf(s_tmp,"gmic_filter%u_parameter%u",filter,n);
   1.108 +  gimp_set_data(s_tmp,param,cimg::strlen(param)+1);
   1.109 +}
   1.110 +
   1.111 +const char *get_filter_parameter(const unsigned int filter, const unsigned int n) {
   1.112 +  char s_tmp[256] = { 0 };
   1.113 +  std::sprintf(s_tmp,"gmic_filter%u_parameter%u",filter,n);
   1.114 +  static char res[4096] = { 0 };
   1.115 +  res[0] = 0;
   1.116 +  gimp_get_data(s_tmp,res);
   1.117 +  return res;
   1.118 +}
   1.119 +
   1.120 +unsigned int get_verbosity_level() {
   1.121 +  unsigned int verbosity = 0;
   1.122 +  gimp_get_data("gmic_verbosity",&verbosity);
   1.123 +  return verbosity;
   1.124 +}
   1.125 +
   1.126 +void set_verbosity_level(const unsigned int verbosity) {
   1.127 +  gimp_set_data("gmic_verbosity",&verbosity,sizeof(unsigned int));
   1.128 +}
   1.129 +
   1.130 +// Return G'MIC command line needed to run the selected filter.
   1.131 +//--------------------------------------------------------------
   1.132 +const char* get_commandline(const bool preview) {
   1.133 +  const unsigned int
   1.134 +    verbosity_level = get_verbosity_level(),
   1.135 +    filter = get_current_filter(),
   1.136 +    nbparams = get_filter_nbparams(filter);
   1.137 +  if (!filter) return 0;
   1.138 +
   1.139 +  static CImg<char> res;
   1.140 +
   1.141 +  CImgList<char> lres;
   1.142 +  switch (verbosity_level) {
   1.143 +  case 0: lres.insert(CImg<char>("-v- -",5)); break;
   1.144 +  case 1: lres.insert(CImg<char>("-",1)); break;
   1.145 +  default: lres.insert(CImg<char>("-v+ -debug -",12));
   1.146 +  }
   1.147 +
   1.148 +  const unsigned int N = filter - 1;
   1.149 +  const CImg<char> &command_item = (preview?gmic_preview_commands[N]:gmic_commands[N]);
   1.150 +  if (command_item) {
   1.151 +    lres.insert(command_item);
   1.152 +    if (nbparams) {
   1.153 +      lres[1].last() = ' ';
   1.154 +      for (unsigned int p = 0; p<nbparams; ++p) {
   1.155 +        const char *const param = get_filter_parameter(filter,p);
   1.156 +        if (param) lres.insert(CImg<char>(param,cimg::strlen(param)+1)).last().last() = ',';
   1.157 +      }
   1.158 +    }
   1.159 +    (res = lres.get_append('x')).last() = 0;
   1.160 +  }
   1.161 +  return res.ptr();
   1.162 +}
   1.163 +
   1.164 +// Process image region with G'MIC.
   1.165 +//---------------------------------
   1.166 +
   1.167 +// Define structure to store the arguments needed by the processing thread.
   1.168 +struct st_process_thread {
   1.169 +  pthread_t thread;
   1.170 +  CImgList<float> images;
   1.171 +  const char *commandline;
   1.172 +  unsigned int verbosity_level;
   1.173 +  pthread_mutex_t is_running;
   1.174 +};
   1.175 +
   1.176 +// Thread that does the image processing part (call the G'MIC library).
   1.177 +void *process_thread(void *arg) {
   1.178 +  st_process_thread &spt = *(st_process_thread*)arg;
   1.179 +  try {
   1.180 +    if (spt.verbosity_level>0)
   1.181 +      std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Running G'MIC to process the image, with command : %s\n",spt.commandline);
   1.182 +    std::setlocale(LC_NUMERIC,"C");
   1.183 +    gmic(spt.commandline,spt.images,gmic_macros,false);
   1.184 +    if (spt.verbosity_level>0)
   1.185 +      std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : G'MIC successfully returned !\n");
   1.186 +  } catch (gmic_exception &e) {
   1.187 +    if (spt.verbosity_level>0)
   1.188 +      std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Error encountered when running G'MIC :\n*** %s\n",e.message);
   1.189 +    spt.images.assign();
   1.190 +  }
   1.191 +  pthread_mutex_unlock(&spt.is_running);
   1.192 +  pthread_exit(0);
   1.193 +  return 0;
   1.194 +}
   1.195 +
   1.196 +// Routine called to process the current GIMP image.
   1.197 +void process_image(GimpDrawable *drawable, const char *last_commandline) {
   1.198 +  const unsigned int filter = get_current_filter();
   1.199 +  if (!last_commandline && !filter) return;
   1.200 +  const char *commandline = last_commandline?last_commandline:get_commandline(false);
   1.201 +  if (!commandline || !cimg::strcmp(commandline,"-v- -nop")) return;
   1.202 +  gimp_progress_init_printf(" G'MIC Toolbox : %s...",gmic_entries[filter-1].ptr());
   1.203 +
   1.204 +  // Read GIMP image region data and make a CImg<float> instance from it.
   1.205 +  GimpPixelRgn src_region;
   1.206 +  gint x1, y1, x2, y2;
   1.207 +  gimp_drawable_mask_bounds(drawable->drawable_id,&x1,&y1,&x2,&y2);  // Get coordinates of the current layer selection.
   1.208 +  const gint width  = x2 - x1, height = y2 - y1, channels = drawable->bpp;
   1.209 +  gimp_pixel_rgn_init(&src_region,drawable,x1,y1,width,height,false,false);
   1.210 +  guchar *const src_row = g_new(guchar,width*channels);
   1.211 +  CImg<float> img(width,height,1,channels);
   1.212 +  cimg_forY(img,y) {
   1.213 +    gimp_pixel_rgn_get_row(&src_region,src_row,x1,y1+y,width);
   1.214 +    const guchar *ptrs = src_row;
   1.215 +    cimg_forX(img,x) cimg_forV(img,k) img(x,y,k) = (float)*(ptrs++);
   1.216 +  }
   1.217 +  g_free(src_row);
   1.218 +
   1.219 +  // Call G'MIC interpreter on the CImg<float> image in a new thread.
   1.220 +  st_process_thread spt;
   1.221 +  spt.images.assign(1);
   1.222 +  img.transfer_to(spt.images[0]);
   1.223 +  spt.commandline = commandline;
   1.224 +  spt.verbosity_level = get_verbosity_level();
   1.225 +  pthread_mutex_init(&spt.is_running,0);
   1.226 +  pthread_mutex_lock(&spt.is_running);
   1.227 +  pthread_create(&(spt.thread),0,process_thread,(void*)&spt);
   1.228 +
   1.229 +  // Do a small animation with the progress bar, while waiting for
   1.230 +  // the termination of the processing thread.
   1.231 +  while (pthread_mutex_trylock(&spt.is_running)) { gimp_progress_pulse(); cimg::wait(500); }
   1.232 +  gimp_progress_update(1.0);
   1.233 +  pthread_join(spt.thread,0);
   1.234 +  pthread_mutex_unlock(&spt.is_running);
   1.235 +  pthread_mutex_destroy(&spt.is_running);
   1.236 +
   1.237 +  // Force the resulting images to have all the same 2D GRAY, GRAYA, RGB or RGBA format.
   1.238 +  if (!spt.images) { gimp_progress_end(); return; }
   1.239 +  unsigned int max_width = 0, max_height = 0, max_channels = 0;
   1.240 +  cimglist_for(spt.images,p) {
   1.241 +    const CImg<float>& img = spt.images[p];
   1.242 +    if (img.width>max_width) max_width = img.width;
   1.243 +    if (img.height>max_height) max_height = img.height;
   1.244 +    if (img.dim>max_channels) max_channels = img.dim;
   1.245 +  }
   1.246 +  if (max_channels>4) max_channels = 4;
   1.247 +  cimglist_apply(spt.images,resize)(-100,-100,1,max_channels);
   1.248 +
   1.249 +  // Transfer the result image back into GIMP.
   1.250 +  if (spt.images.size==1 && (int)max_width==width && (int)max_height==height && (int)max_channels==channels) {
   1.251 +
   1.252 +    // When the result image has same dimensions than the source :
   1.253 +    // Replace the selected region of the original GIMP image.
   1.254 +    CImg<float> &res = spt.images[0];
   1.255 +    GimpPixelRgn dest_region;
   1.256 +    guchar *const dest_row = g_new(guchar,res.dimx()*res.dimv());
   1.257 +    gimp_pixel_rgn_init(&dest_region,drawable,0,0,drawable->width,drawable->height,true,true);
   1.258 +    cimg_forY(res,y) {
   1.259 +      guchar *ptrd = dest_row;
   1.260 +      cimg_forX(res,x) cimg_forV(res,k) *(ptrd++) = (guchar)res(x,y,k);
   1.261 +      gimp_pixel_rgn_set_row(&dest_region,dest_row,x1,y1+y,width);
   1.262 +    }
   1.263 +    g_free(dest_row);
   1.264 +    spt.images.assign();
   1.265 +    gimp_drawable_flush(drawable);
   1.266 +    gimp_drawable_merge_shadow(drawable->drawable_id,true);
   1.267 +    gimp_drawable_update(drawable->drawable_id,x1,y1,x2-x1,y2-y1);
   1.268 +    gimp_displays_flush();
   1.269 +  } else {
   1.270 +
   1.271 +    // When the result image has different dimensions than the source :
   1.272 +    // Returns a new GIMP image.
   1.273 +    gint id_img = gimp_image_new(max_width,max_height,max_channels<=2?GIMP_GRAY:GIMP_RGB);
   1.274 +    gimp_image_undo_group_start(id_img);
   1.275 +
   1.276 +    cimglist_for(spt.images,p) {
   1.277 +      CImg<float> &res = spt.images[p];
   1.278 +      gint id_layer = gimp_layer_new(id_img,"image",res.dimx(),res.dimy(),
   1.279 +                                     res.dimv()==1?GIMP_GRAY_IMAGE:
   1.280 +                                     res.dimv()==2?GIMP_GRAYA_IMAGE:
   1.281 +                                     res.dimv()==3?GIMP_RGB_IMAGE:
   1.282 +                                     GIMP_RGBA_IMAGE,
   1.283 +                                     100.0,GIMP_NORMAL_MODE);
   1.284 +      gimp_image_add_layer(id_img,id_layer,0);
   1.285 +      GimpDrawable *ndrawable = gimp_drawable_get(id_layer);
   1.286 +
   1.287 +      GimpPixelRgn dest_region;
   1.288 +      guchar *const dest_row = g_new(guchar,res.dimx()*res.dimv());
   1.289 +      gimp_pixel_rgn_init(&dest_region,ndrawable,0,0,ndrawable->width,ndrawable->height,true,true);
   1.290 +      cimg_forY(res,y) {
   1.291 +        guchar *ptrd = dest_row;
   1.292 +        cimg_forX(res,x) cimg_forV(res,k) *(ptrd++) = (guchar)res(x,y,k);
   1.293 +        gimp_pixel_rgn_set_row(&dest_region,dest_row,0,y,res.dimx());
   1.294 +      }
   1.295 +      g_free(dest_row);
   1.296 +      res.assign();
   1.297 +      gimp_drawable_flush(ndrawable);
   1.298 +      gimp_drawable_merge_shadow(ndrawable->drawable_id,true);
   1.299 +      gimp_drawable_update(ndrawable->drawable_id,0,0,ndrawable->width,ndrawable->height);
   1.300 +      gimp_drawable_detach(ndrawable);
   1.301 +    }
   1.302 +    gimp_display_new(id_img);
   1.303 +    gimp_image_undo_group_end(id_img);
   1.304 +    gimp_displays_flush();
   1.305 +  }
   1.306 +  gimp_progress_end();
   1.307 +}
   1.308 +
   1.309 +// Process preview with G'MIC.
   1.310 +//-----------------------------
   1.311 +void process_preview(GimpPreview *preview) {
   1.312 +  const unsigned int filter = get_current_filter();
   1.313 +  if (!filter) return;
   1.314 +  const char *const commandline = get_commandline(true);
   1.315 +  if (!commandline || !cimg::strcmp(commandline,"-v- -nop")) return;
   1.316 +
   1.317 +  // Read GIMP image preview and make a CImg<float> instance from it.
   1.318 +  gint width, height, channels;
   1.319 +  guchar *const ptr0 = gimp_zoom_preview_get_source(GIMP_ZOOM_PREVIEW(preview),&width,&height,&channels), *ptrs = ptr0;
   1.320 +  CImg<float> img(width,height,1,channels);
   1.321 +  cimg_forXY(img,x,y) cimg_forV(img,k) img(x,y,k) = (float)*(ptrs++);
   1.322 +
   1.323 +  // Call G'MIC interpreter on the preview image.
   1.324 +  CImgList<float> gmic_images(1);
   1.325 +  img.transfer_to(gmic_images[0]);
   1.326 +  try {
   1.327 +    if (get_verbosity_level()>0)
   1.328 +      std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Running G'MIC to process the preview, with command : %s\n",commandline);
   1.329 +    std::setlocale(LC_NUMERIC,"C");
   1.330 +    gmic(commandline,gmic_images,gmic_macros,false);
   1.331 +    if (get_verbosity_level()>0)
   1.332 +      std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : G'MIC successfully returned !\n");
   1.333 +  } catch (gmic_exception &e) {
   1.334 +    if (get_verbosity_level()>0)
   1.335 +      std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Error encountered when running G'MIC :\n*** %s\n",e.message);
   1.336 +    gmic_images.assign();
   1.337 +  }
   1.338 +
   1.339 +  // Get current image preview from the processed data.
   1.340 +  if (gmic_images.size && gmic_images[0]) {
   1.341 +    CImg<float>& res = gmic_images[0];
   1.342 +    if (res.width>res.height) {
   1.343 +      const unsigned int _nheight = res.height*width/res.width, nheight = _nheight?_nheight:1;
   1.344 +      res.resize(width,nheight,1,-100,2);
   1.345 +    } else {
   1.346 +      const unsigned int _nwidth = res.width*height/res.height, nwidth = _nwidth?_nwidth:1;
   1.347 +      res.resize(nwidth,height,1,-100,2);
   1.348 +    }
   1.349 +    if (res.dimx()!=width || res.dimy()!=height) res.resize(width,height,1,-100,0,0,1);
   1.350 +    switch (channels) {
   1.351 +    case 1:
   1.352 +      switch (res.dim) {
   1.353 +      case 1: break;
   1.354 +      case 2: res.channel(0); break;
   1.355 +      case 3: res.channel(0); break;
   1.356 +      case 4: res.channel(0); break;
   1.357 +      default: res.channel(0);
   1.358 +      } break;
   1.359 +    case 2:
   1.360 +      switch (res.dim) {
   1.361 +      case 1: res.resize(-100,-100,1,2,0).get_shared_channel(1).fill(255); break;
   1.362 +      case 2: break;
   1.363 +      case 3: res.channels(0,1).get_shared_channel(1).fill(255); break;
   1.364 +      case 4: res.get_shared_channel(1) = res.get_shared_channel(3); res.channels(0,1); break;
   1.365 +      default: res.channels(0,1).get_shared_channel(1).fill(255);
   1.366 +      } break;
   1.367 +    case 3:
   1.368 +      switch (res.dim) {
   1.369 +      case 1: res.resize(-100,-100,1,3); break;
   1.370 +      case 2: res.channel(0).resize(-100,-100,1,3); break;
   1.371 +      case 3: break;
   1.372 +      case 4: res.channels(0,2); break;
   1.373 +      default: res.channels(0,2);
   1.374 +      } break;
   1.375 +    case 4:
   1.376 +      switch (res.dim) {
   1.377 +      case 1: res.resize(-100,-100,1,4).get_shared_channel(3).fill(255); break;
   1.378 +      case 2:
   1.379 +        res.resize(-100,-100,1,4,0);
   1.380 +        res.get_shared_channel(3) = res.get_shared_channel(1);
   1.381 +        res.get_shared_channel(1) = res.get_shared_channel(0);
   1.382 +        res.get_shared_channel(2) = res.get_shared_channel(0);
   1.383 +        break;
   1.384 +      case 3: res.resize(-100,-100,1,4,0).get_shared_channel(3).fill(255); break;
   1.385 +      case 4: break;
   1.386 +      default: res.resize(-100,-100,1,4,0);
   1.387 +      } break;
   1.388 +    }
   1.389 +    guchar *ptrd = ptr0;
   1.390 +    cimg_forXY(res,x,y) cimg_forV(res,k) *(ptrd++) = (guchar)res(x,y,k);
   1.391 +    gimp_preview_draw_buffer(preview,ptr0,width*channels);
   1.392 +    g_free(ptr0);
   1.393 +  }
   1.394 +}
   1.395 +
   1.396 +// Define event functions for GUI.
   1.397 +//--------------------------------
   1.398 +
   1.399 +// Handle responses to the parameter widgets.
   1.400 +void on_float_parameter_changed(GtkAdjustment *scale, gpointer user_data) {
   1.401 +  const unsigned int arg = *(unsigned int*)user_data;
   1.402 +  double value = 0;
   1.403 +  gimp_double_adjustment_update(scale,&value);
   1.404 +  char s_value[1024] = { 0 };
   1.405 +  std::sprintf(s_value,"%g",value);
   1.406 +  set_filter_parameter(get_current_filter(),arg,s_value);
   1.407 +  return_create_dialog = true;
   1.408 +}
   1.409 +
   1.410 +void on_int_parameter_changed(GtkAdjustment *scale, gpointer user_data) {
   1.411 +  const unsigned int arg = *(unsigned int*)user_data;
   1.412 +  int value = 0;
   1.413 +  gimp_int_adjustment_update(scale,&value);
   1.414 +  char s_value[1024] = { 0 };
   1.415 +  std::sprintf(s_value,"%d",value);
   1.416 +  set_filter_parameter(get_current_filter(),arg,s_value);
   1.417 +  return_create_dialog = true;
   1.418 +}
   1.419 +
   1.420 +void on_bool_parameter_changed(GtkCheckButton *checkbutton, gpointer user_data) {
   1.421 +  const unsigned int arg = *(unsigned int*)user_data;
   1.422 +  int value = 0;
   1.423 +  g_object_get(checkbutton,"active",&value,NULL);
   1.424 +  char s_value[1024] = { 0 };
   1.425 +  std::sprintf(s_value,"%d",value?1:0);
   1.426 +  set_filter_parameter(get_current_filter(),arg,s_value);
   1.427 +  return_create_dialog = true;
   1.428 +}
   1.429 +
   1.430 +void on_list_parameter_changed(GtkComboBox *combobox, gpointer user_data) {
   1.431 +  const unsigned int arg = *(unsigned int*)user_data;
   1.432 +  int value = 0;
   1.433 +  g_object_get(combobox,"active",&value,NULL);
   1.434 +  char s_value[1024] = { 0 };
   1.435 +  std::sprintf(s_value,"%d",value);
   1.436 +  set_filter_parameter(get_current_filter(),arg,s_value);
   1.437 +  return_create_dialog = true;
   1.438 +}
   1.439 +
   1.440 +void on_text_parameter_changed(GtkButton *button, gpointer user_data) {
   1.441 +  button = 0;
   1.442 +  const unsigned int arg = *(unsigned int*)user_data;
   1.443 +  GtkWidget *entry = *((GtkWidget**)user_data+1);
   1.444 +  const char *s_value = gtk_entry_get_text(GTK_ENTRY(entry));
   1.445 +  set_filter_parameter(get_current_filter(),arg,s_value);
   1.446 +  return_create_dialog = true;
   1.447 +}
   1.448 +
   1.449 +void on_file_parameter_changed(GtkFileChooserButton *widget, gpointer user_data){
   1.450 +  const unsigned int arg = *(unsigned int*)user_data;
   1.451 +  const char
   1.452 +    *const filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget)),
   1.453 +    *s_value = filename?filename:"";
   1.454 +  set_filter_parameter(get_current_filter(),arg,s_value);
   1.455 +  return_create_dialog = true;
   1.456 +}
   1.457 +
   1.458 +void on_color_parameter_changed(GtkColorButton *widget, gpointer user_data){
   1.459 +  const unsigned int arg = *(unsigned int*)user_data;
   1.460 +  GdkColor color;
   1.461 +  gtk_color_button_get_color(GTK_COLOR_BUTTON(widget),&color);
   1.462 +  char s_value[1024] = { 0 };
   1.463 +  if (gtk_color_button_get_use_alpha(GTK_COLOR_BUTTON(widget)))
   1.464 +    std::sprintf(s_value,"%d,%d,%d,%d",
   1.465 +                 color.red>>8,color.green>>8,color.blue>>8,gtk_color_button_get_alpha(GTK_COLOR_BUTTON(widget))>>8);
   1.466 +  else std::sprintf(s_value,"%d,%d,%d",
   1.467 +                    color.red>>8,color.green>>8,color.blue>>8);
   1.468 +  set_filter_parameter(get_current_filter(),arg,s_value);
   1.469 +  return_create_dialog = true;
   1.470 +}
   1.471 +
   1.472 +// Create parameter GUI for specific chosen filter.
   1.473 +//--------------------------------------------------
   1.474 +void create_parameters_gui(const bool reset) {
   1.475 +  const unsigned int filter = get_current_filter();
   1.476 +
   1.477 +  // Remove widget in the current frame if necessary.
   1.478 +  GtkWidget *frame = 0;
   1.479 +  gimp_get_data("gmic_gui_frame",&frame);
   1.480 +  if (frame) {
   1.481 +    GtkWidget *child = GTK_WIDGET(gtk_bin_get_child(GTK_BIN(frame)));
   1.482 +    if (child) gtk_container_remove(GTK_CONTAINER(frame),child);
   1.483 +  }
   1.484 +
   1.485 +  GtkWidget *table = 0;
   1.486 +  if (!filter) {  // No filter selected -> Default message.
   1.487 +    table = gtk_table_new(1,1,false);
   1.488 +    gtk_widget_show(table);
   1.489 +    GtkWidget *label = gtk_label_new(NULL);
   1.490 +    gtk_label_set_markup(GTK_LABEL(label),"<i>Select a filter...</i>");
   1.491 +    gtk_widget_show(label);
   1.492 +    gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
   1.493 +                     (GtkAttachOptions)(GTK_EXPAND),(GtkAttachOptions)(GTK_EXPAND),0,0);
   1.494 +    gtk_misc_set_alignment (GTK_MISC(label),0,0.5);
   1.495 +    gtk_frame_set_label(GTK_FRAME(frame),NULL);
   1.496 +  } else { // Filter selected -> Build parameter table.
   1.497 +    GtkWidget *preview = 0;
   1.498 +    gimp_get_data("gmic_gui_preview",&preview);
   1.499 +    const unsigned int N = filter - 1;
   1.500 +    char nlabel[4096] = { 0 };
   1.501 +    std::sprintf(nlabel,"<b>  %s : </b>",gmic_entries[N].ptr());
   1.502 +    GtkWidget *frame_title = gtk_label_new(NULL);
   1.503 +    gtk_widget_show(frame_title);
   1.504 +    gtk_label_set_markup(GTK_LABEL(frame_title),nlabel);
   1.505 +    gtk_frame_set_label_widget(GTK_FRAME(frame),frame_title);
   1.506 +
   1.507 +    char argname[4096] = { 0 }, argtype[4096] = { 0 }, argarg[4096] = { 0 };
   1.508 +    unsigned int nb_arguments = 0;
   1.509 +    for (const char *argument = gmic_arguments[N].ptr(); *argument; ) {
   1.510 +      if (std::sscanf(argument,"%4095[^=]=%4095[^(](%4095[^)]",argname,argtype,&(argarg[0]=0))>=2) {
   1.511 +        argument += cimg::strlen(argname) + cimg::strlen(argtype) + cimg::strlen(argarg) + 3;
   1.512 +        if (*argument) ++argument;
   1.513 +        ++nb_arguments;
   1.514 +      } else break;
   1.515 +    }
   1.516 +
   1.517 +    if (!nb_arguments) { // Selected filter has no parameters -> Default message.
   1.518 +      table = gtk_table_new(1,1,false);
   1.519 +      gtk_widget_show(table);
   1.520 +      GtkWidget *label = gtk_label_new(NULL);
   1.521 +      gtk_label_set_markup(GTK_LABEL(label),"<i>No parameters to set...</i>");
   1.522 +      gtk_widget_show(label);
   1.523 +      gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
   1.524 +                       (GtkAttachOptions)(GTK_EXPAND),(GtkAttachOptions)(GTK_EXPAND),0,0);
   1.525 +      gtk_misc_set_alignment (GTK_MISC(label),0,0.5);
   1.526 +    } else { // Selected filter has parameters -> Create parameter table.
   1.527 +
   1.528 +      // Create new table for putting parameters inside.
   1.529 +      table = gtk_table_new(3,nb_arguments,false);
   1.530 +      gtk_widget_show(table);
   1.531 +      gtk_table_set_row_spacings(GTK_TABLE(table),6);
   1.532 +      gtk_table_set_col_spacings(GTK_TABLE(table),6);
   1.533 +      gtk_container_set_border_width(GTK_CONTAINER(table),8);
   1.534 +
   1.535 +      // Parse arguments list and add recognized one to the table.
   1.536 +      event_infos = new void*[2*nb_arguments];
   1.537 +      int current_parameter = 0, current_line = 0;
   1.538 +      for (const char *argument = gmic_arguments[N].ptr(); *argument; ) {
   1.539 +        if (std::sscanf(argument,"%4095[^=]=%4095[^(](%4095[^)]",argname,argtype,&(argarg[0]=0))>=2) {
   1.540 +          argument += cimg::strlen(argname) + cimg::strlen(argtype) + cimg::strlen(argarg) + 3;
   1.541 +          if (*argument) ++argument;
   1.542 +          cimg::strclean(argname);
   1.543 +          cimg::strclean(argtype);
   1.544 +          const char *const s_value = get_filter_parameter(filter,current_parameter);
   1.545 +
   1.546 +          // Check for a float-valued parameter -> Create GtkAdjustment.
   1.547 +          bool found_valid_item = false;
   1.548 +          if (!found_valid_item && !cimg::strcasecmp(argtype,"float")) {
   1.549 +            float initial_value = 0, min_value = 0, max_value = 100;
   1.550 +            std::setlocale(LC_NUMERIC,"C");
   1.551 +            std::sscanf(argarg,"%f%*c%f%*c%f",&initial_value,&min_value,&max_value);
   1.552 +            if (!reset && std::sscanf(s_value,"%f",&initial_value)) {}
   1.553 +            GtkObject *scale = gimp_scale_entry_new(GTK_TABLE(table),0,current_line,argname,100,6,
   1.554 +                                                    (gdouble)initial_value,(gdouble)min_value,(gdouble)max_value,
   1.555 +                                                    0.1,0.1,2,true,0,0,0,0);
   1.556 +            event_infos[2*current_parameter] = (void*)current_parameter;
   1.557 +            event_infos[2*current_parameter+1] = (void*)0;
   1.558 +            on_float_parameter_changed(GTK_ADJUSTMENT(scale),(void*)(event_infos+2*current_parameter));
   1.559 +            g_signal_connect(scale,"value_changed",G_CALLBACK(on_float_parameter_changed),
   1.560 +                             (void*)(event_infos+2*current_parameter));
   1.561 +            g_signal_connect_swapped(scale,"value_changed",G_CALLBACK(gimp_preview_invalidate),preview);
   1.562 +            found_valid_item = true;
   1.563 +            ++current_parameter;
   1.564 +          }
   1.565 +
   1.566 +          // Check for an int-valued parameter -> Create GtkAdjustment.
   1.567 +          if (!found_valid_item && !cimg::strcasecmp(argtype,"int")) {
   1.568 +            float initial_value = 0, min_value = 0, max_value = 100;
   1.569 +            std::setlocale(LC_NUMERIC,"C");
   1.570 +            std::sscanf(argarg,"%f%*c%f%*c%f",&initial_value,&min_value,&max_value);
   1.571 +            if (!reset && std::sscanf(s_value,"%f",&initial_value)) {}
   1.572 +            GtkObject *scale = gimp_scale_entry_new(GTK_TABLE(table),0,current_line,argname,100,6,
   1.573 +                                                    (gdouble)(int)initial_value,(gdouble)(int)min_value,
   1.574 +                                                    (gdouble)(int)max_value,
   1.575 +                                                    1,1,0,true,0,0,0,0);
   1.576 +            event_infos[2*current_parameter] = (void*)current_parameter;
   1.577 +            event_infos[2*current_parameter+1] = (void*)0;
   1.578 +            on_int_parameter_changed(GTK_ADJUSTMENT(scale),(void*)(event_infos+2*current_parameter));
   1.579 +            g_signal_connect(scale,"value_changed",G_CALLBACK(on_int_parameter_changed),
   1.580 +                             (void*)(event_infos+2*current_parameter));
   1.581 +            g_signal_connect_swapped(scale,"value_changed",G_CALLBACK(gimp_preview_invalidate),preview);
   1.582 +            found_valid_item = true;
   1.583 +            ++current_parameter;
   1.584 +          }
   1.585 +
   1.586 +          // Check for a bool-valued parameter -> Create GtkCheckButton.
   1.587 +          if (!found_valid_item && !cimg::strcasecmp(argtype,"bool")) {
   1.588 +            unsigned int initial_value = 0;
   1.589 +            std::sscanf(argarg,"%u",&initial_value);
   1.590 +            if (!reset && std::sscanf(s_value,"%u",&initial_value)) {}
   1.591 +            GtkWidget *checkbutton = gtk_check_button_new_with_label(argname);
   1.592 +            gtk_widget_show(checkbutton);
   1.593 +            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton),initial_value?true:false);
   1.594 +            gtk_table_attach(GTK_TABLE(table),checkbutton,0,2,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   1.595 +            event_infos[2*current_parameter] = (void*)current_parameter;
   1.596 +            event_infos[2*current_parameter+1] = (void*)0;
   1.597 +            on_bool_parameter_changed(GTK_CHECK_BUTTON(checkbutton),(void*)(event_infos+2*current_parameter));
   1.598 +            g_signal_connect(checkbutton,"toggled",G_CALLBACK(on_bool_parameter_changed),
   1.599 +                             (void*)(event_infos+2*current_parameter));
   1.600 +            g_signal_connect_swapped(checkbutton,"toggled",G_CALLBACK(gimp_preview_invalidate),preview);
   1.601 +            found_valid_item = true;
   1.602 +            ++current_parameter;
   1.603 +          }
   1.604 +
   1.605 +          // Check for a list-valued parameter -> Create GtkComboBox.
   1.606 +          if (!found_valid_item && !cimg::strcasecmp(argtype,"choice")) {
   1.607 +            GtkWidget *label = gtk_label_new(argname);
   1.608 +            gtk_widget_show(label);
   1.609 +            gtk_table_attach(GTK_TABLE(table),label,0,1,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   1.610 +            gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
   1.611 +            GtkWidget *combobox = gtk_combo_box_new_text();
   1.612 +            gtk_widget_show(combobox);
   1.613 +            char s_entry[4096] = { 0 }, end = 0; int err2 = 0;
   1.614 +            unsigned int initial_value = 0;
   1.615 +            const char *entries = argarg;
   1.616 +            if (std::sscanf(entries,"%u",&initial_value)==1) entries+=std::sprintf(s_entry,"%u",initial_value) + 1;
   1.617 +            while (*entries) {
   1.618 +              if ((err2 = std::sscanf(entries,"%4095[^,]%c",s_entry,&end))>0) {
   1.619 +                entries += cimg::strlen(s_entry) + (err2==2?1:0);
   1.620 +                cimg::strclean(s_entry);
   1.621 +                strparenthesis(s_entry);
   1.622 +                gtk_combo_box_append_text(GTK_COMBO_BOX(combobox),s_entry);
   1.623 +              } else break;
   1.624 +            }
   1.625 +            if (!reset && std::sscanf(s_value,"%u",&initial_value)) {}
   1.626 +            gtk_combo_box_set_active(GTK_COMBO_BOX(combobox),initial_value);
   1.627 +            gtk_table_attach(GTK_TABLE(table),combobox,1,3,current_line,current_line+1,
   1.628 +                             (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),(GtkAttachOptions)(GTK_FILL),0,0);
   1.629 +            event_infos[2*current_parameter] = (void*)current_parameter;
   1.630 +            event_infos[2*current_parameter+1] = (void*)0;
   1.631 +            on_list_parameter_changed(GTK_COMBO_BOX(combobox),(void*)(event_infos+2*current_parameter));
   1.632 +            g_signal_connect(combobox,"changed",G_CALLBACK(on_list_parameter_changed),
   1.633 +                             (void*)(event_infos+2*current_parameter));
   1.634 +            g_signal_connect_swapped(combobox,"changed",G_CALLBACK(gimp_preview_invalidate),preview);
   1.635 +            found_valid_item = true;
   1.636 +            ++current_parameter;
   1.637 +          }
   1.638 +
   1.639 +          // Check for a text-valued parameter -> Create GtkEntry.
   1.640 +          if (!found_valid_item && !cimg::strcasecmp(argtype,"text")) {
   1.641 +            GtkWidget *label = gtk_label_new(argname);
   1.642 +            gtk_widget_show(label);
   1.643 +            gtk_table_attach(GTK_TABLE(table),label,0,1,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   1.644 +            gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
   1.645 +            GtkWidget *entry = gtk_entry_new_with_max_length(4095);
   1.646 +            gtk_widget_show(entry);
   1.647 +            cimg::strclean(argarg);
   1.648 +            if (!reset && *s_value) gtk_entry_set_text(GTK_ENTRY(entry),s_value);
   1.649 +            else gtk_entry_set_text(GTK_ENTRY(entry),argarg);
   1.650 +            gtk_table_attach(GTK_TABLE(table),entry,1,2,current_line,current_line+1,
   1.651 +                             (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),(GtkAttachOptions)0,0,0);
   1.652 +            GtkWidget *button = gtk_button_new_with_label("Update");
   1.653 +            gtk_widget_show(button);
   1.654 +            gtk_table_attach(GTK_TABLE(table),button,2,3,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   1.655 +            event_infos[2*current_parameter] = (void*)current_parameter;
   1.656 +            event_infos[2*current_parameter+1] = (void*)entry;
   1.657 +            on_text_parameter_changed(GTK_BUTTON(button),(void*)(event_infos+2*current_parameter));
   1.658 +            g_signal_connect(button,"clicked",G_CALLBACK(on_text_parameter_changed),
   1.659 +                             (void*)(event_infos+2*current_parameter));
   1.660 +            g_signal_connect_swapped(button,"clicked",G_CALLBACK(gimp_preview_invalidate),preview);
   1.661 +            found_valid_item = true;
   1.662 +            ++current_parameter;
   1.663 +          }
   1.664 +
   1.665 +          // Check for a filename parameter -> Create GtkFileChooserButton.
   1.666 +          if (!found_valid_item && !cimg::strcasecmp(argtype,"file")) {
   1.667 +            GtkWidget *label = gtk_label_new(argname);
   1.668 +            gtk_widget_show(label);
   1.669 +            gtk_table_attach(GTK_TABLE(table),label,0,1,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   1.670 +            gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
   1.671 +            GtkWidget *filechooser = gtk_file_chooser_button_new(argname,GTK_FILE_CHOOSER_ACTION_OPEN);
   1.672 +            gtk_widget_show(filechooser);
   1.673 +            cimg::strclean(argarg);
   1.674 +            if (!reset && *s_value) gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(filechooser),s_value);
   1.675 +            else gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(filechooser),argarg);
   1.676 +            gtk_table_attach(GTK_TABLE(table),filechooser,1,3,current_line,current_line+1,
   1.677 +                             (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),(GtkAttachOptions)0,0,0);
   1.678 +            event_infos[2*current_parameter] = (void*)current_parameter;
   1.679 +            event_infos[2*current_parameter+1] = (void*)0;
   1.680 +            on_file_parameter_changed(GTK_FILE_CHOOSER_BUTTON(filechooser),(void*)(event_infos+2*current_parameter));
   1.681 +            g_signal_connect(filechooser,"file-set",G_CALLBACK(on_file_parameter_changed),
   1.682 +                             (void*)(event_infos+2*current_parameter));
   1.683 +            g_signal_connect_swapped(filechooser,"file-set",G_CALLBACK(gimp_preview_invalidate),preview);
   1.684 +            found_valid_item = true;
   1.685 +            ++current_parameter;
   1.686 +          }
   1.687 +
   1.688 +          // Check for a color -> Create GtkColorButton.
   1.689 +          if (!found_valid_item && !cimg::strcasecmp(argtype,"color")) {
   1.690 +            GtkWidget *hbox = gtk_hbox_new(false,6);
   1.691 +            gtk_widget_show(hbox);
   1.692 +            gtk_table_attach(GTK_TABLE(table),hbox,0,2,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   1.693 +            GtkWidget *label = gtk_label_new(argname);
   1.694 +            gtk_widget_show(label);
   1.695 +            gtk_box_pack_start(GTK_BOX(hbox),label,false,false,0);
   1.696 +            GtkWidget *colorchooser = gtk_color_button_new();
   1.697 +            gtk_widget_show(colorchooser);
   1.698 +            gtk_color_button_set_title(GTK_COLOR_BUTTON(colorchooser),argname);
   1.699 +            gtk_box_pack_start(GTK_BOX(hbox),colorchooser,false,false,0);
   1.700 +            event_infos[2*current_parameter] = (void*)current_parameter;
   1.701 +            event_infos[2*current_parameter+1] = (void*)0;
   1.702 +            cimg::strclean(argarg);
   1.703 +            unsigned int red = 0, green = 0, blue = 0, alpha = 255;
   1.704 +            const int err = std::sscanf(argarg,"%u%*c%u%*c%u%*c%u",&red,&green,&blue,&alpha);
   1.705 +            if (!reset && std::sscanf(s_value,"%u%*c%u%*c%u%*c%u",&red,&green,&blue,&alpha)==err) {}
   1.706 +            GdkColor col;
   1.707 +            col.pixel = 0; col.red = red<<8; col.green = green<<8; col.blue = blue<<8;
   1.708 +            gtk_color_button_set_color(GTK_COLOR_BUTTON(colorchooser),&col);
   1.709 +            if (err==4) {
   1.710 +              gtk_color_button_set_use_alpha(GTK_COLOR_BUTTON(colorchooser),true);
   1.711 +              gtk_color_button_set_alpha(GTK_COLOR_BUTTON(colorchooser),alpha<<8);
   1.712 +            } else gtk_color_button_set_use_alpha(GTK_COLOR_BUTTON(colorchooser),false);
   1.713 +            on_color_parameter_changed(GTK_COLOR_BUTTON(colorchooser),(void*)(event_infos+2*current_parameter));
   1.714 +            g_signal_connect(colorchooser,"color-set",G_CALLBACK(on_color_parameter_changed),
   1.715 +                             (void*)(event_infos+2*current_parameter));
   1.716 +            g_signal_connect_swapped(colorchooser,"color-set",G_CALLBACK(gimp_preview_invalidate),preview);
   1.717 +            found_valid_item = true;
   1.718 +            ++current_parameter;
   1.719 +          }
   1.720 +
   1.721 +          // Check for a note -> Create GtkLabel.
   1.722 +          if (!found_valid_item && !cimg::strcasecmp(argtype,"note")) {
   1.723 +            cimg::strclean(argarg);
   1.724 +            GtkWidget *label = gtk_label_new(NULL);
   1.725 +            cimg::strescape(argarg);
   1.726 +            strparenthesis(argarg);
   1.727 +            gtk_label_set_markup(GTK_LABEL(label),argarg);
   1.728 +            gtk_label_set_line_wrap(GTK_LABEL(label),true);
   1.729 +            gtk_widget_show(label);
   1.730 +            gtk_table_attach(GTK_TABLE(table),label,0,3,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   1.731 +            gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
   1.732 +            found_valid_item = true;
   1.733 +          }
   1.734 +
   1.735 +          if (!found_valid_item) {
   1.736 +            if (get_verbosity_level()>0)
   1.737 +              std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Found invalid parameter type '%s' for argument '%s'.\n",argtype,argname);
   1.738 +          } else ++current_line;
   1.739 +        } else break;
   1.740 +      }
   1.741 +      set_filter_nbparams(filter,current_parameter);
   1.742 +    }
   1.743 +  }
   1.744 +  gtk_container_add(GTK_CONTAINER(frame),table);
   1.745 +}
   1.746 +
   1.747 +// Called when the selected filter changed (in the combo-box).
   1.748 +void on_filter_changed(GtkTreeSelection *selection, gpointer user_data) {
   1.749 +  user_data = 0;
   1.750 +  GtkTreeIter iter;
   1.751 +  GtkTreeModel *model;
   1.752 +  unsigned int choice = 0;
   1.753 +  if (gtk_tree_selection_get_selected(selection,&model,&iter))
   1.754 +    gtk_tree_model_get(model,&iter,0,&choice,-1);
   1.755 +  set_current_filter(choice);
   1.756 +  create_parameters_gui(false);
   1.757 +  return_create_dialog = true;
   1.758 +}
   1.759 +
   1.760 +// Handle responses to the dialog window buttons.
   1.761 +void on_verbosity_level_changed(GtkComboBox *combobox, gpointer user_data) {
   1.762 +  user_data = 0;
   1.763 +  int value = 0;
   1.764 +  g_object_get(combobox,"active",&value,NULL);
   1.765 +  set_verbosity_level(value);
   1.766 +}
   1.767 +
   1.768 +void on_dialog_reset_clicked(GtkButton *widget, gpointer data) {
   1.769 +  widget = 0; data = 0;
   1.770 +  create_parameters_gui(true);
   1.771 +  return_create_dialog = true;
   1.772 +}
   1.773 +
   1.774 +void on_dialog_cancel_clicked(GtkButton *widget, gpointer data) {
   1.775 +  widget = 0; data = 0;
   1.776 +  return_create_dialog = false;
   1.777 +  gtk_main_quit();
   1.778 +}
   1.779 +
   1.780 +void on_dialog_apply_clicked(GtkButton *widget, gpointer data) {
   1.781 +  widget = 0;
   1.782 +  GimpDrawable *drawable = (GimpDrawable*)data;
   1.783 +  process_image(drawable,0);
   1.784 +  return_create_dialog = false;
   1.785 +}
   1.786 +
   1.787 +void on_dialog_ok_clicked(GtkButton *widget, gpointer data) {
   1.788 +  widget = 0; data = 0;
   1.789 +  gtk_main_quit();
   1.790 +}
   1.791 +
   1.792 +void on_update_button_clicked(GtkButton *widget, gpointer data) {
   1.793 +  widget = 0;
   1.794 +  GtkWidget *dialog = (GtkWidget*)data;
   1.795 +  char update_filename[1024] = { 0 }, update_command[1024] = { 0 }, src_filename[1024] = { 0 }, dest_filename[1024] = { 0 };
   1.796 +  const char
   1.797 +    *const update_url = "http://www.greyc.ensicaen.fr/~dtschump",
   1.798 +    *const path_tmp = cimg::temporary_path();
   1.799 +  std::sprintf(update_filename,"gmic4gimp_def.%d",gmic_version);
   1.800 +  std::sprintf(src_filename,"%s/%s",path_tmp,update_filename);
   1.801 +  std::sprintf(dest_filename,"%s/.%s",path_home,update_filename);
   1.802 +  if (get_verbosity_level()>0) {
   1.803 +    std::sprintf(update_command,"wget %s/%s -O %s",update_url,update_filename,src_filename);
   1.804 +    std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Running update procedure, with command : %s\n",update_command);
   1.805 +  } else std::sprintf(update_command,"wget --quiet %s/%s -O %s",update_url,update_filename,src_filename);
   1.806 +  int status = cimg::system(update_command);
   1.807 +  status = 0;
   1.808 +  std::FILE *file_s = std::fopen(src_filename,"r");
   1.809 +  bool succeed = false;
   1.810 +  if (file_s) {
   1.811 +    unsigned int size_s = 0;
   1.812 +    std::fseek(file_s,0,SEEK_END);
   1.813 +    size_s = (unsigned int)std::ftell(file_s);
   1.814 +    std::rewind(file_s);
   1.815 +    if (size_s) {
   1.816 +      std::FILE *file_d = std::fopen(dest_filename,"w");
   1.817 +      char *buffer = new char[size_s], sep = 0;
   1.818 +      if (file_d &&
   1.819 +          std::fread(buffer,sizeof(char),size_s,file_s)==size_s &&
   1.820 +          std::sscanf(buffer,"#@gim%c",&sep)==1 && sep=='p' &&
   1.821 +          std::fwrite(buffer,sizeof(char),size_s,file_d)==size_s) { succeed = true; std::fclose(file_d); }
   1.822 +      delete[] buffer;
   1.823 +    }
   1.824 +    std::fclose(file_s);
   1.825 +  }
   1.826 +  if (!succeed) {
   1.827 +    GtkWidget *message = gtk_message_dialog_new_with_markup(GTK_WINDOW(dialog),GTK_DIALOG_MODAL,GTK_MESSAGE_ERROR,GTK_BUTTONS_OK,
   1.828 +                                                            "<b>Filters update failed !</b>\n\n"
   1.829 +                                                            "A valid version of the update file :\n\n"
   1.830 +                                                            "<i>%s/%s</i>\n\n"
   1.831 +                                                            "  ...could not be retrieved from the G'MIC server.\n\n"
   1.832 +                                                            "Please check your Internet connection or\n"
   1.833 +                                                            "try a manual update instead.",update_url,update_filename);
   1.834 +    gtk_widget_show(message);
   1.835 +    gtk_dialog_run(GTK_DIALOG(message));
   1.836 +    gtk_widget_destroy(message);
   1.837 +  } else {
   1.838 +    GtkWidget *message = gtk_message_dialog_new_with_markup(GTK_WINDOW(dialog),GTK_DIALOG_MODAL,GTK_MESSAGE_INFO,GTK_BUTTONS_OK,
   1.839 +                                                "<b>Filters update succeed !</b>\n\n"
   1.840 +                                                "The G'MIC Toolbox must be restarted now.");
   1.841 +    gtk_widget_show(message);
   1.842 +    gtk_dialog_run(GTK_DIALOG(message));
   1.843 +    gtk_widget_destroy(message);
   1.844 +    return_create_dialog = false;
   1.845 +    set_current_filter(0);
   1.846 +    gtk_main_quit();
   1.847 +  }
   1.848 +}
   1.849 +
   1.850 +// Create main plug-in dialog window and wait for a response.
   1.851 +//-----------------------------------------------------------
   1.852 +bool create_dialog_gui(GimpDrawable *drawable) {
   1.853 +
   1.854 +  // Init GUI_specific variables
   1.855 +  gimp_ui_init("gmic",true);
   1.856 +  event_infos = 0;
   1.857 +
   1.858 +  // Create main plug-in dialog window.
   1.859 +  GtkWidget
   1.860 +    *dialog = gimp_dialog_new("The G'MIC Toolbox","gmic",0,(GtkDialogFlags)0,gimp_standard_help_func,"gmic",NULL),
   1.861 +    *cancel_button = gimp_dialog_add_button(GIMP_DIALOG(dialog),GTK_STOCK_CANCEL,GTK_RESPONSE_CANCEL),
   1.862 +    *reset_button = gimp_dialog_add_button(GIMP_DIALOG(dialog),GIMP_STOCK_RESET,1),
   1.863 +    *apply_button = gimp_dialog_add_button(GIMP_DIALOG(dialog),GTK_STOCK_APPLY,GTK_RESPONSE_APPLY),
   1.864 +    *ok_button = gimp_dialog_add_button(GIMP_DIALOG(dialog),GTK_STOCK_OK,GTK_RESPONSE_OK);
   1.865 +  gimp_window_set_transient(GTK_WINDOW(dialog));
   1.866 +  g_signal_connect(dialog,"close",G_CALLBACK(on_dialog_cancel_clicked),0);
   1.867 +  g_signal_connect(dialog,"delete-event",G_CALLBACK(on_dialog_cancel_clicked),0);
   1.868 +  g_signal_connect(cancel_button,"clicked",G_CALLBACK(on_dialog_cancel_clicked),0);
   1.869 +  g_signal_connect(apply_button,"clicked",G_CALLBACK(on_dialog_apply_clicked),drawable);
   1.870 +  g_signal_connect(ok_button,"clicked",G_CALLBACK(on_dialog_ok_clicked),0);
   1.871 +
   1.872 +  GtkWidget *dialog_hbox = gtk_hbox_new(false,0);
   1.873 +  gtk_widget_show(dialog_hbox);
   1.874 +  gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),dialog_hbox);
   1.875 +
   1.876 +  // Create the left pane, containing the preview, the show commmand and the update buttons and the author name.
   1.877 +  GtkWidget *left_pane = gtk_vbox_new(false,4);
   1.878 +  gtk_widget_show(left_pane);
   1.879 +  gtk_box_pack_start(GTK_BOX(dialog_hbox),left_pane,true,true,0);
   1.880 +
   1.881 +  GtkWidget *preview = gimp_zoom_preview_new(drawable);
   1.882 +  gtk_widget_show(preview);
   1.883 +  gtk_box_pack_start(GTK_BOX(left_pane),preview,true,true,0);
   1.884 +  gimp_set_data("gmic_gui_preview",&preview,sizeof(GtkWidget*));
   1.885 +  g_signal_connect(preview,"invalidated",G_CALLBACK(process_preview),0);
   1.886 +  g_signal_connect_swapped(apply_button,"clicked",G_CALLBACK(gimp_preview_invalidate),preview);
   1.887 +
   1.888 +  GtkWidget *verbosity_hbuttonbox = gtk_hbutton_box_new();
   1.889 +  gtk_widget_show(verbosity_hbuttonbox);
   1.890 +  gtk_box_pack_start(GTK_BOX(left_pane),verbosity_hbuttonbox,false,false,0);
   1.891 +
   1.892 +  GtkWidget *verbosity_combobox = gtk_combo_box_new_text();
   1.893 +  gtk_widget_show(verbosity_combobox);
   1.894 +  gtk_combo_box_append_text(GTK_COMBO_BOX(verbosity_combobox),"Quiet mode");
   1.895 +  gtk_combo_box_append_text(GTK_COMBO_BOX(verbosity_combobox),"Verbose mode");
   1.896 +  gtk_combo_box_append_text(GTK_COMBO_BOX(verbosity_combobox),"Debug mode");
   1.897 +  gtk_combo_box_set_active(GTK_COMBO_BOX(verbosity_combobox),get_verbosity_level());
   1.898 +  gtk_container_add(GTK_CONTAINER(verbosity_hbuttonbox),verbosity_combobox);
   1.899 +  g_signal_connect(verbosity_combobox,"changed",G_CALLBACK(on_verbosity_level_changed),0);
   1.900 +
   1.901 +  GtkWidget *update_hbuttonbox = gtk_hbutton_box_new();
   1.902 +  gtk_widget_show(update_hbuttonbox);
   1.903 +  gtk_box_pack_start(GTK_BOX(left_pane),update_hbuttonbox,false,false,0);
   1.904 +  GtkWidget
   1.905 +    *tmp_button = gtk_button_new_from_stock(GTK_STOCK_REFRESH),
   1.906 +    *update_image = gtk_button_get_image(GTK_BUTTON(tmp_button)),
   1.907 +    *update_button = gtk_button_new_with_mnemonic("_Update filters");
   1.908 +  gtk_button_set_image(GTK_BUTTON(update_button),update_image);
   1.909 +  gtk_widget_show(update_button);
   1.910 +  gtk_container_add(GTK_CONTAINER(update_hbuttonbox),update_button);
   1.911 +  g_signal_connect(update_button,"clicked",G_CALLBACK(on_update_button_clicked),(void*)dialog);
   1.912 +
   1.913 +  GtkWidget *about_label = gtk_label_new(NULL);
   1.914 +  gtk_label_set_markup(GTK_LABEL(about_label),
   1.915 +                       "\n<span color=\"#666666\"><small>"
   1.916 +                       "<b>G'MIC</b> is proposed to you\n"
   1.917 +                       "   by <i>David Tschumperle</i>"
   1.918 +                       "</small></span>");
   1.919 +  gtk_widget_show(about_label);
   1.920 +  gtk_box_pack_start(GTK_BOX(left_pane),about_label,false,false,0);
   1.921 +
   1.922 +  const unsigned int logo_width = 102, logo_height = 22;
   1.923 +  GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(data_logo,GDK_COLORSPACE_RGB,false,8,
   1.924 +                                               logo_width,logo_height,3*logo_width,0,0);
   1.925 +  GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
   1.926 +  gtk_widget_show(image);
   1.927 +  gtk_box_pack_start(GTK_BOX(left_pane),image,false,false,0);
   1.928 +
   1.929 +  // Create the middle pane, which contains the filters treeview.
   1.930 +  GtkWidget *middle_pane = gtk_frame_new(NULL);
   1.931 +  gtk_widget_show(middle_pane);
   1.932 +  gtk_container_set_border_width(GTK_CONTAINER(middle_pane),4);
   1.933 +  gtk_widget_set_size_request(middle_pane,250,-1);
   1.934 +  gtk_box_pack_start(GTK_BOX(dialog_hbox),middle_pane,false,false,0);
   1.935 +
   1.936 +  GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL,NULL);
   1.937 +  gtk_widget_show(scrolledwindow);
   1.938 +  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow),GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
   1.939 +  gtk_container_add(GTK_CONTAINER(middle_pane),scrolledwindow);
   1.940 +
   1.941 +  GtkWidget *treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(filter_store));
   1.942 +  GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
   1.943 +  GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(" Available filters :",renderer,"text",1,NULL);
   1.944 +  gtk_tree_view_append_column(GTK_TREE_VIEW(treeview),column);
   1.945 +
   1.946 +  GtkTreeSelection *select = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
   1.947 +  gtk_tree_selection_set_mode(select,GTK_SELECTION_SINGLE);
   1.948 +  g_signal_connect(G_OBJECT(select),"changed",G_CALLBACK(on_filter_changed),0);
   1.949 +  g_signal_connect_swapped(select,"changed",G_CALLBACK(gimp_preview_invalidate),preview);
   1.950 +  gtk_widget_show(treeview);
   1.951 +  gtk_container_add(GTK_CONTAINER(scrolledwindow),treeview);
   1.952 +  g_signal_connect(reset_button,"clicked",G_CALLBACK(on_dialog_reset_clicked),select);
   1.953 +  g_signal_connect_swapped(reset_button,"clicked",G_CALLBACK(gimp_preview_invalidate),preview);
   1.954 +
   1.955 +  // Create the right pane which contains the parameters frame.
   1.956 +  GtkWidget *parameters_frame = gtk_frame_new(NULL);
   1.957 +  gtk_widget_show(parameters_frame);
   1.958 +  gtk_container_set_border_width(GTK_CONTAINER(parameters_frame),4);
   1.959 +  gtk_widget_set_size_request(parameters_frame,450,-1);
   1.960 +  gtk_box_pack_start(GTK_BOX(dialog_hbox),parameters_frame,false,false,0);
   1.961 +  gimp_set_data("gmic_gui_frame",&parameters_frame,sizeof(GtkWidget*));
   1.962 +  create_parameters_gui(false);
   1.963 +
   1.964 +  // Show dialog window and wait for user response.
   1.965 +  gtk_widget_show(dialog);
   1.966 +  gtk_main();
   1.967 +
   1.968 +  // Destroy dialog box widget and free resources.
   1.969 +  gtk_widget_destroy(dialog);
   1.970 +  gtk_widget_destroy(tmp_button);
   1.971 +  if (event_infos) delete[] event_infos;
   1.972 +  return return_create_dialog;
   1.973 +}
   1.974 +
   1.975 +// 'Run' function needed by GIMP plug-in API.
   1.976 +//-------------------------------------------
   1.977 +void gmic_run(const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) {
   1.978 +
   1.979 +  // Init plug-in variables.
   1.980 +  static GimpParam values[1];
   1.981 +  values[0].type = GIMP_PDB_STATUS;
   1.982 +  *return_vals  = values;
   1.983 +  *nreturn_vals = 1;
   1.984 +  name = 0;
   1.985 +  nparams = 0;
   1.986 +  GimpRunMode run_mode;
   1.987 +  run_mode = (GimpRunMode)param[0].data.d_int32;
   1.988 +  if (run_mode==GIMP_RUN_NONINTERACTIVE) {
   1.989 +    std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : ERROR, this plug-in cannot be run in non-interactive mode.\n");
   1.990 +    values[0].data.d_status = GIMP_PDB_CALLING_ERROR;
   1.991 +    return;
   1.992 +  }
   1.993 +  gmic_macros = 0;
   1.994 +  filter_store = 0;
   1.995 +  return_create_dialog = true;
   1.996 +  path_home = getenv(cimg_OS!=2?"HOME":"APPDATA");
   1.997 +
   1.998 +  // Check that no instance of the plug-in is already running.
   1.999 +  bool is_existing_instance = 0;
  1.1000 +  gimp_get_data("gmic_instance",&is_existing_instance);
  1.1001 +  if (is_existing_instance) {
  1.1002 +    std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Existing instance of the plug-in is already running.\n");
  1.1003 +    return;
  1.1004 +  }
  1.1005 +  is_existing_instance = true;
  1.1006 +  gimp_set_data("gmic_instance",&is_existing_instance,sizeof(bool));
  1.1007 +
  1.1008 +  // Read user-defined configuration files '.gmic_def' and '.gmic', when possible.
  1.1009 +  unsigned size_update = 0, size_custom = 0, size_def = sizeof(data_gmic4gimp_def);
  1.1010 +  char filename_update[1024] = { 0 }, filename_custom[1024] = { 0 };
  1.1011 +  std::sprintf(filename_update,"%s/.gmic4gimp_def.%d",path_home,gmic_version);
  1.1012 +  std::sprintf(filename_custom,"%s/.gmic4gimp",path_home);
  1.1013 +  std::FILE
  1.1014 +    *file_update = std::fopen(filename_update,"r"),
  1.1015 +    *file_custom = std::fopen(filename_custom,"r");
  1.1016 +  if (file_update) {
  1.1017 +    std::fseek(file_update,0,SEEK_END);
  1.1018 +    size_update = (unsigned int)std::ftell(file_update);
  1.1019 +    std::rewind(file_update);
  1.1020 +  }
  1.1021 +  if (file_custom) {
  1.1022 +    std::fseek(file_custom,0,SEEK_END);
  1.1023 +    size_custom = (unsigned int)std::ftell(file_custom);
  1.1024 +    std::rewind(file_custom);
  1.1025 +  }
  1.1026 +  const unsigned int size_final = size_update + size_custom + size_def + 1;
  1.1027 +  char *ptrd = gmic_macros = new char[size_final];
  1.1028 +  if (size_custom) { ptrd+=std::fread(ptrd,1,size_custom,file_custom); std::fclose(file_custom); }
  1.1029 +  if (size_update) { ptrd+=std::fread(ptrd,1,size_update,file_update); std::fclose(file_update); }
  1.1030 +  if (size_def)    { std::memcpy(ptrd,data_gmic4gimp_def,size_def); ptrd+=size_def; }
  1.1031 +  *ptrd = 0;
  1.1032 +
  1.1033 +  // Parse available G'MIC filters definitions.
  1.1034 +  GtkTreeIter iter, parent[16];
  1.1035 +  filter_store = gtk_tree_store_new(2,G_TYPE_UINT,G_TYPE_STRING);
  1.1036 +  char line[256*1024] = { 0 }, entry[4096] = { 0 }, command[4096] = { 0 };
  1.1037 +  char preview_command[4096] = { 0 }, arguments[4096] = { 0 };
  1.1038 +  int level = 0;
  1.1039 +  for (const char *data = gmic_macros; *data; ) {
  1.1040 +    if (*data=='\n') ++data;
  1.1041 +    else {
  1.1042 +      if (std::sscanf(data,"%262143[^\n]\n",line)>0) data += cimg::strlen(line) + 1;
  1.1043 +      arguments[0] = 0;
  1.1044 +      if (line[0]=='#') {
  1.1045 +        const int err = std::sscanf(line,"#@gimp %4095[^:]: %4095[^, ]%*c %4095[^, ]%*c %4095[^\n]",
  1.1046 +                                    entry,command,preview_command,arguments);
  1.1047 +        strparenthesis(entry);
  1.1048 +        if (err==1) { // If entry is a menu folder.
  1.1049 +          cimg::strclean(entry);
  1.1050 +          char *nentry = entry;
  1.1051 +          while (*nentry=='_') { ++nentry; --level; }
  1.1052 +          if (level<0) level = 0;
  1.1053 +          if (level>15) level = 15;
  1.1054 +          cimg::strclean(nentry);
  1.1055 +          if (*nentry) {
  1.1056 +            gtk_tree_store_append(filter_store,&parent[level],level?&parent[level-1]:0);
  1.1057 +            gtk_tree_store_set(filter_store,&parent[level],0,0,1,nentry,-1);
  1.1058 +            ++level;
  1.1059 +          }
  1.1060 +        } else if (err>=2) { // If entry is a regular filter.
  1.1061 +          cimg::strclean(entry);
  1.1062 +          cimg::strclean(command);
  1.1063 +          gmic_entries.insert(CImg<char>(entry,cimg::strlen(entry)+1));
  1.1064 +          gmic_commands.insert(CImg<char>(command,cimg::strlen(command)+1));
  1.1065 +          gmic_arguments.insert(CImg<char>(arguments,cimg::strlen(arguments)+1));
  1.1066 +          if (err>=3) {
  1.1067 +            cimg::strclean(preview_command);
  1.1068 +            gmic_preview_commands.insert(CImg<char>(preview_command,cimg::strlen(preview_command)+1));
  1.1069 +          }
  1.1070 +          gtk_tree_store_append(filter_store,&iter,level?&parent[level-1]:0);
  1.1071 +          gtk_tree_store_set(filter_store,&iter,0,gmic_entries.size,1,entry,-1);
  1.1072 +        }
  1.1073 +      }
  1.1074 +    }
  1.1075 +  }
  1.1076 +
  1.1077 +  // Get currenty selected drawable and run image filter on it.
  1.1078 +  GimpDrawable *drawable = gimp_drawable_get(param[2].data.d_drawable);
  1.1079 +  gimp_tile_cache_ntiles(2*(drawable->width/gimp_tile_width()+1));
  1.1080 +  if (run_mode==GIMP_RUN_INTERACTIVE) {
  1.1081 +    if (create_dialog_gui(drawable)) {
  1.1082 +      process_image(drawable,0);
  1.1083 +      const char *commandline = get_commandline(false);
  1.1084 +      if (commandline) { // Remember command line for the next use of the filter.
  1.1085 +        char s_tmp[256] = { 0 };
  1.1086 +        std::sprintf(s_tmp,"gmic_commandline%u",get_current_filter());
  1.1087 +        gimp_set_data(s_tmp,commandline,cimg::strlen(commandline));
  1.1088 +      }
  1.1089 +    }
  1.1090 +  } else if (run_mode==GIMP_RUN_WITH_LAST_VALS) {
  1.1091 +    const unsigned int filter = get_current_filter();
  1.1092 +    if (filter) {
  1.1093 +      char s_tmp[256] = { 0 };
  1.1094 +      std::sprintf(s_tmp,"gmic_commandline%u",filter);
  1.1095 +      char commandline[4096] = { 0 };
  1.1096 +      gimp_get_data(s_tmp,&commandline);
  1.1097 +      process_image(drawable,commandline);
  1.1098 +    }
  1.1099 +  }
  1.1100 +
  1.1101 +  // Free plug-in resources.
  1.1102 +  delete[] gmic_macros;
  1.1103 +  values[0].data.d_status = GIMP_PDB_SUCCESS;
  1.1104 +  is_existing_instance = false;
  1.1105 +  gimp_set_data("gmic_instance",&is_existing_instance,sizeof(bool));
  1.1106 +}
  1.1107 +
  1.1108 +// 'Query' function needed by GIMP plug-in API.
  1.1109 +//---------------------------------------------
  1.1110 +void gmic_query() {
  1.1111 +  static const GimpParamDef args[] = {
  1.1112 +    {GIMP_PDB_INT32,    "run_mode", "Interactive, non-interactive"},
  1.1113 +    {GIMP_PDB_IMAGE,    "image", "(unused)"},
  1.1114 +    {GIMP_PDB_DRAWABLE, "drawable", "Drawable to draw on"}
  1.1115 +  };
  1.1116 +
  1.1117 +  gimp_install_procedure("gmic",                     // name
  1.1118 +                         "G'MIC Toolbox",            // blurb
  1.1119 +                         "G'MIC Toolbox",            // help
  1.1120 +                         "David Tschumperle",        // author
  1.1121 +                         "David Tschumperle",        // copyright
  1.1122 +                         "2008-12-02",               // date
  1.1123 +                         "_G'MIC Toolbox...",        // menu_path
  1.1124 +                         "RGB*, GRAY*",              // image_types
  1.1125 +                         GIMP_PLUGIN,                // type
  1.1126 +                         G_N_ELEMENTS(args),         // nparams
  1.1127 +                         0,                          // nreturn_vals
  1.1128 +                         args,                       // params
  1.1129 +                         0);                         // return_vals
  1.1130 +
  1.1131 +  gimp_plugin_menu_register("gmic", "<Image>/Filters");
  1.1132 +}
  1.1133 +
  1.1134 +GimpPlugInInfo PLUG_IN_INFO = { 0, 0, gmic_query, gmic_run };
  1.1135 +MAIN();