PTdecode/CImg-1.3.0/examples/gmic4gimp.cpp

Mon, 03 Aug 2009 23:41:04 +0100

author
Philip Pemberton <philpem@philpem.me.uk>
date
Mon, 03 Aug 2009 23:41:04 +0100
changeset 11
69416826d18c
parent 5
1204ebf9340d
permissions
-rwxr-xr-x

added dep/*.d and obj/*.o to hgignore

     1 /*
     2  #
     3  #  File        : gmic4gimp.cpp
     4  #                ( C++ source file )
     5  #
     6  #  Description : G'MIC for GIMP - A plug-in to allow the use
     7  #                of G'MIC commands in GIMP.
     8  #                This file is a part of the CImg Library project.
     9  #                ( http://cimg.sourceforge.net )
    10  #
    11  #  Copyright   : David Tschumperle (GREYCstoration API)
    12  #
    13  #  License     : CeCILL v2.0
    14  #                ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
    15  #
    16  #  This software is governed by the CeCILL  license under French law and
    17  #  abiding by the rules of distribution of free software.  You can  use,
    18  #  modify and/ or redistribute the software under the terms of the CeCILL
    19  #  license as circulated by CEA, CNRS and INRIA at the following URL
    20  #  "http://www.cecill.info".
    21  #
    22  #  As a counterpart to the access to the source code and  rights to copy,
    23  #  modify and redistribute granted by the license, users are provided only
    24  #  with a limited warranty  and the software's author,  the holder of the
    25  #  economic rights,  and the successive licensors  have only  limited
    26  #  liability.
    27  #
    28  #  In this respect, the user's attention is drawn to the risks associated
    29  #  with loading,  using,  modifying and/or developing or reproducing the
    30  #  software by the user in light of its specific status of free software,
    31  #  that may mean  that it is complicated to manipulate,  and  that  also
    32  #  therefore means  that it is reserved for developers  and  experienced
    33  #  professionals having in-depth computer knowledge. Users are therefore
    34  #  encouraged to load and test the software's suitability as regards their
    35  #  requirements in conditions enabling the security of their systems and/or
    36  #  data to be ensured and, more generally, to use and operate it in the
    37  #  same conditions as regards security.
    38  #
    39  #  The fact that you are presently reading this means that you have had
    40  #  knowledge of the CeCILL license and that you accept its terms.
    41  #
    42 */
    44 // Include necessary header files.
    45 //--------------------------------
    46 #define cimg_display_type 0
    47 #include "gmic.h"
    48 #include "gmic4gimp_def.h"
    49 #include <pthread.h>
    50 #include <locale>
    51 #include <gtk/gtk.h>
    52 #include <libgimp/gimp.h>
    53 #include <libgimp/gimpui.h>
    54 using namespace cimg_library;
    56 // Define plug-in global variables.
    57 //---------------------------------
    58 CImgList<char> gmic_entries;           // The list of recognized G'MIC menu entries (stored as 'char*' strings).
    59 CImgList<char> gmic_commands;          // The list of corresponding G'MIC commands to process the image.
    60 CImgList<char> gmic_preview_commands;  // The list of corresponding G'MIC commands to preview the image.
    61 CImgList<char> gmic_arguments;         // The list of corresponding needed filter arguments.
    62 char *gmic_macros;                     // The array of customized G'MIC macros.
    63 GtkTreeStore *filter_store;            // A list of available filter entries (used by the GtkTreeView).
    64 bool return_create_dialog;             // Return value of the 'create_gui_dialog()' function (set by events handlers).
    65 void **event_infos;                    // Infos that are passed to the GUI callback functions.
    66 char *path_home;                       // The path where configuration files are looked for.
    68 // Replace '[]' by '()' in a C-string.
    69 //------------------------------------
    70 void strparenthesis(char *const s) {
    71   for (char *ns = s; *ns; ++ns) if (*ns=='[') *ns = '('; else if (*ns==']') *ns = ')';
    72 }
    74 // Set/get plug-in global variables in GIMP.
    75 //------------------------------------------
    76 void set_current_filter(const unsigned int current_filter) {
    77   const unsigned int ncurrent_filter = current_filter>gmic_entries.size?0:current_filter;
    78   gimp_set_data("gmic_current_filter",&ncurrent_filter,sizeof(unsigned int));
    79 }
    81 unsigned int get_current_filter() {
    82   unsigned int current_filter = 0;
    83   gimp_get_data("gmic_current_filter",&current_filter);
    84   if (current_filter>gmic_entries.size) current_filter = 0;
    85   return current_filter;
    86 }
    88 void set_filter_nbparams(const unsigned int filter, const unsigned int nbparams) {
    89   char s_tmp[256] = { 0 };
    90   std::sprintf(s_tmp,"gmic_filter%u_nbparams",filter);
    91   gimp_set_data(s_tmp,&nbparams,sizeof(unsigned int));
    92 }
    94 unsigned int get_filter_nbparams(const unsigned int filter) {
    95   char s_tmp[256] = { 0 };
    96   std::sprintf(s_tmp,"gmic_filter%u_nbparams",filter);
    97   unsigned int nbparams = 0;
    98   gimp_get_data(s_tmp,&nbparams);
    99   return nbparams;
   100 }
   102 void set_filter_parameter(const unsigned int filter, const unsigned int n, const char *const param) {
   103   char s_tmp[256] = { 0 };
   104   std::sprintf(s_tmp,"gmic_filter%u_parameter%u",filter,n);
   105   gimp_set_data(s_tmp,param,cimg::strlen(param)+1);
   106 }
   108 const char *get_filter_parameter(const unsigned int filter, const unsigned int n) {
   109   char s_tmp[256] = { 0 };
   110   std::sprintf(s_tmp,"gmic_filter%u_parameter%u",filter,n);
   111   static char res[4096] = { 0 };
   112   res[0] = 0;
   113   gimp_get_data(s_tmp,res);
   114   return res;
   115 }
   117 unsigned int get_verbosity_level() {
   118   unsigned int verbosity = 0;
   119   gimp_get_data("gmic_verbosity",&verbosity);
   120   return verbosity;
   121 }
   123 void set_verbosity_level(const unsigned int verbosity) {
   124   gimp_set_data("gmic_verbosity",&verbosity,sizeof(unsigned int));
   125 }
   127 // Return G'MIC command line needed to run the selected filter.
   128 //--------------------------------------------------------------
   129 const char* get_commandline(const bool preview) {
   130   const unsigned int
   131     verbosity_level = get_verbosity_level(),
   132     filter = get_current_filter(),
   133     nbparams = get_filter_nbparams(filter);
   134   if (!filter) return 0;
   136   static CImg<char> res;
   138   CImgList<char> lres;
   139   switch (verbosity_level) {
   140   case 0: lres.insert(CImg<char>("-v- -",5)); break;
   141   case 1: lres.insert(CImg<char>("-",1)); break;
   142   default: lres.insert(CImg<char>("-v+ -debug -",12));
   143   }
   145   const unsigned int N = filter - 1;
   146   const CImg<char> &command_item = (preview?gmic_preview_commands[N]:gmic_commands[N]);
   147   if (command_item) {
   148     lres.insert(command_item);
   149     if (nbparams) {
   150       lres[1].last() = ' ';
   151       for (unsigned int p = 0; p<nbparams; ++p) {
   152         const char *const param = get_filter_parameter(filter,p);
   153         if (param) lres.insert(CImg<char>(param,cimg::strlen(param)+1)).last().last() = ',';
   154       }
   155     }
   156     (res = lres.get_append('x')).last() = 0;
   157   }
   158   return res.ptr();
   159 }
   161 // Process image region with G'MIC.
   162 //---------------------------------
   164 // Define structure to store the arguments needed by the processing thread.
   165 struct st_process_thread {
   166   pthread_t thread;
   167   CImgList<float> images;
   168   const char *commandline;
   169   unsigned int verbosity_level;
   170   pthread_mutex_t is_running;
   171 };
   173 // Thread that does the image processing part (call the G'MIC library).
   174 void *process_thread(void *arg) {
   175   st_process_thread &spt = *(st_process_thread*)arg;
   176   try {
   177     if (spt.verbosity_level>0)
   178       std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Running G'MIC to process the image, with command : %s\n",spt.commandline);
   179     std::setlocale(LC_NUMERIC,"C");
   180     gmic(spt.commandline,spt.images,gmic_macros,false);
   181     if (spt.verbosity_level>0)
   182       std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : G'MIC successfully returned !\n");
   183   } catch (gmic_exception &e) {
   184     if (spt.verbosity_level>0)
   185       std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Error encountered when running G'MIC :\n*** %s\n",e.message);
   186     spt.images.assign();
   187   }
   188   pthread_mutex_unlock(&spt.is_running);
   189   pthread_exit(0);
   190   return 0;
   191 }
   193 // Routine called to process the current GIMP image.
   194 void process_image(GimpDrawable *drawable, const char *last_commandline) {
   195   const unsigned int filter = get_current_filter();
   196   if (!last_commandline && !filter) return;
   197   const char *commandline = last_commandline?last_commandline:get_commandline(false);
   198   if (!commandline || !cimg::strcmp(commandline,"-v- -nop")) return;
   199   gimp_progress_init_printf(" G'MIC Toolbox : %s...",gmic_entries[filter-1].ptr());
   201   // Read GIMP image region data and make a CImg<float> instance from it.
   202   GimpPixelRgn src_region;
   203   gint x1, y1, x2, y2;
   204   gimp_drawable_mask_bounds(drawable->drawable_id,&x1,&y1,&x2,&y2);  // Get coordinates of the current layer selection.
   205   const gint width  = x2 - x1, height = y2 - y1, channels = drawable->bpp;
   206   gimp_pixel_rgn_init(&src_region,drawable,x1,y1,width,height,false,false);
   207   guchar *const src_row = g_new(guchar,width*channels);
   208   CImg<float> img(width,height,1,channels);
   209   cimg_forY(img,y) {
   210     gimp_pixel_rgn_get_row(&src_region,src_row,x1,y1+y,width);
   211     const guchar *ptrs = src_row;
   212     cimg_forX(img,x) cimg_forV(img,k) img(x,y,k) = (float)*(ptrs++);
   213   }
   214   g_free(src_row);
   216   // Call G'MIC interpreter on the CImg<float> image in a new thread.
   217   st_process_thread spt;
   218   spt.images.assign(1);
   219   img.transfer_to(spt.images[0]);
   220   spt.commandline = commandline;
   221   spt.verbosity_level = get_verbosity_level();
   222   pthread_mutex_init(&spt.is_running,0);
   223   pthread_mutex_lock(&spt.is_running);
   224   pthread_create(&(spt.thread),0,process_thread,(void*)&spt);
   226   // Do a small animation with the progress bar, while waiting for
   227   // the termination of the processing thread.
   228   while (pthread_mutex_trylock(&spt.is_running)) { gimp_progress_pulse(); cimg::wait(500); }
   229   gimp_progress_update(1.0);
   230   pthread_join(spt.thread,0);
   231   pthread_mutex_unlock(&spt.is_running);
   232   pthread_mutex_destroy(&spt.is_running);
   234   // Force the resulting images to have all the same 2D GRAY, GRAYA, RGB or RGBA format.
   235   if (!spt.images) { gimp_progress_end(); return; }
   236   unsigned int max_width = 0, max_height = 0, max_channels = 0;
   237   cimglist_for(spt.images,p) {
   238     const CImg<float>& img = spt.images[p];
   239     if (img.width>max_width) max_width = img.width;
   240     if (img.height>max_height) max_height = img.height;
   241     if (img.dim>max_channels) max_channels = img.dim;
   242   }
   243   if (max_channels>4) max_channels = 4;
   244   cimglist_apply(spt.images,resize)(-100,-100,1,max_channels);
   246   // Transfer the result image back into GIMP.
   247   if (spt.images.size==1 && (int)max_width==width && (int)max_height==height && (int)max_channels==channels) {
   249     // When the result image has same dimensions than the source :
   250     // Replace the selected region of the original GIMP image.
   251     CImg<float> &res = spt.images[0];
   252     GimpPixelRgn dest_region;
   253     guchar *const dest_row = g_new(guchar,res.dimx()*res.dimv());
   254     gimp_pixel_rgn_init(&dest_region,drawable,0,0,drawable->width,drawable->height,true,true);
   255     cimg_forY(res,y) {
   256       guchar *ptrd = dest_row;
   257       cimg_forX(res,x) cimg_forV(res,k) *(ptrd++) = (guchar)res(x,y,k);
   258       gimp_pixel_rgn_set_row(&dest_region,dest_row,x1,y1+y,width);
   259     }
   260     g_free(dest_row);
   261     spt.images.assign();
   262     gimp_drawable_flush(drawable);
   263     gimp_drawable_merge_shadow(drawable->drawable_id,true);
   264     gimp_drawable_update(drawable->drawable_id,x1,y1,x2-x1,y2-y1);
   265     gimp_displays_flush();
   266   } else {
   268     // When the result image has different dimensions than the source :
   269     // Returns a new GIMP image.
   270     gint id_img = gimp_image_new(max_width,max_height,max_channels<=2?GIMP_GRAY:GIMP_RGB);
   271     gimp_image_undo_group_start(id_img);
   273     cimglist_for(spt.images,p) {
   274       CImg<float> &res = spt.images[p];
   275       gint id_layer = gimp_layer_new(id_img,"image",res.dimx(),res.dimy(),
   276                                      res.dimv()==1?GIMP_GRAY_IMAGE:
   277                                      res.dimv()==2?GIMP_GRAYA_IMAGE:
   278                                      res.dimv()==3?GIMP_RGB_IMAGE:
   279                                      GIMP_RGBA_IMAGE,
   280                                      100.0,GIMP_NORMAL_MODE);
   281       gimp_image_add_layer(id_img,id_layer,0);
   282       GimpDrawable *ndrawable = gimp_drawable_get(id_layer);
   284       GimpPixelRgn dest_region;
   285       guchar *const dest_row = g_new(guchar,res.dimx()*res.dimv());
   286       gimp_pixel_rgn_init(&dest_region,ndrawable,0,0,ndrawable->width,ndrawable->height,true,true);
   287       cimg_forY(res,y) {
   288         guchar *ptrd = dest_row;
   289         cimg_forX(res,x) cimg_forV(res,k) *(ptrd++) = (guchar)res(x,y,k);
   290         gimp_pixel_rgn_set_row(&dest_region,dest_row,0,y,res.dimx());
   291       }
   292       g_free(dest_row);
   293       res.assign();
   294       gimp_drawable_flush(ndrawable);
   295       gimp_drawable_merge_shadow(ndrawable->drawable_id,true);
   296       gimp_drawable_update(ndrawable->drawable_id,0,0,ndrawable->width,ndrawable->height);
   297       gimp_drawable_detach(ndrawable);
   298     }
   299     gimp_display_new(id_img);
   300     gimp_image_undo_group_end(id_img);
   301     gimp_displays_flush();
   302   }
   303   gimp_progress_end();
   304 }
   306 // Process preview with G'MIC.
   307 //-----------------------------
   308 void process_preview(GimpPreview *preview) {
   309   const unsigned int filter = get_current_filter();
   310   if (!filter) return;
   311   const char *const commandline = get_commandline(true);
   312   if (!commandline || !cimg::strcmp(commandline,"-v- -nop")) return;
   314   // Read GIMP image preview and make a CImg<float> instance from it.
   315   gint width, height, channels;
   316   guchar *const ptr0 = gimp_zoom_preview_get_source(GIMP_ZOOM_PREVIEW(preview),&width,&height,&channels), *ptrs = ptr0;
   317   CImg<float> img(width,height,1,channels);
   318   cimg_forXY(img,x,y) cimg_forV(img,k) img(x,y,k) = (float)*(ptrs++);
   320   // Call G'MIC interpreter on the preview image.
   321   CImgList<float> gmic_images(1);
   322   img.transfer_to(gmic_images[0]);
   323   try {
   324     if (get_verbosity_level()>0)
   325       std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Running G'MIC to process the preview, with command : %s\n",commandline);
   326     std::setlocale(LC_NUMERIC,"C");
   327     gmic(commandline,gmic_images,gmic_macros,false);
   328     if (get_verbosity_level()>0)
   329       std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : G'MIC successfully returned !\n");
   330   } catch (gmic_exception &e) {
   331     if (get_verbosity_level()>0)
   332       std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Error encountered when running G'MIC :\n*** %s\n",e.message);
   333     gmic_images.assign();
   334   }
   336   // Get current image preview from the processed data.
   337   if (gmic_images.size && gmic_images[0]) {
   338     CImg<float>& res = gmic_images[0];
   339     if (res.width>res.height) {
   340       const unsigned int _nheight = res.height*width/res.width, nheight = _nheight?_nheight:1;
   341       res.resize(width,nheight,1,-100,2);
   342     } else {
   343       const unsigned int _nwidth = res.width*height/res.height, nwidth = _nwidth?_nwidth:1;
   344       res.resize(nwidth,height,1,-100,2);
   345     }
   346     if (res.dimx()!=width || res.dimy()!=height) res.resize(width,height,1,-100,0,0,1);
   347     switch (channels) {
   348     case 1:
   349       switch (res.dim) {
   350       case 1: break;
   351       case 2: res.channel(0); break;
   352       case 3: res.channel(0); break;
   353       case 4: res.channel(0); break;
   354       default: res.channel(0);
   355       } break;
   356     case 2:
   357       switch (res.dim) {
   358       case 1: res.resize(-100,-100,1,2,0).get_shared_channel(1).fill(255); break;
   359       case 2: break;
   360       case 3: res.channels(0,1).get_shared_channel(1).fill(255); break;
   361       case 4: res.get_shared_channel(1) = res.get_shared_channel(3); res.channels(0,1); break;
   362       default: res.channels(0,1).get_shared_channel(1).fill(255);
   363       } break;
   364     case 3:
   365       switch (res.dim) {
   366       case 1: res.resize(-100,-100,1,3); break;
   367       case 2: res.channel(0).resize(-100,-100,1,3); break;
   368       case 3: break;
   369       case 4: res.channels(0,2); break;
   370       default: res.channels(0,2);
   371       } break;
   372     case 4:
   373       switch (res.dim) {
   374       case 1: res.resize(-100,-100,1,4).get_shared_channel(3).fill(255); break;
   375       case 2:
   376         res.resize(-100,-100,1,4,0);
   377         res.get_shared_channel(3) = res.get_shared_channel(1);
   378         res.get_shared_channel(1) = res.get_shared_channel(0);
   379         res.get_shared_channel(2) = res.get_shared_channel(0);
   380         break;
   381       case 3: res.resize(-100,-100,1,4,0).get_shared_channel(3).fill(255); break;
   382       case 4: break;
   383       default: res.resize(-100,-100,1,4,0);
   384       } break;
   385     }
   386     guchar *ptrd = ptr0;
   387     cimg_forXY(res,x,y) cimg_forV(res,k) *(ptrd++) = (guchar)res(x,y,k);
   388     gimp_preview_draw_buffer(preview,ptr0,width*channels);
   389     g_free(ptr0);
   390   }
   391 }
   393 // Define event functions for GUI.
   394 //--------------------------------
   396 // Handle responses to the parameter widgets.
   397 void on_float_parameter_changed(GtkAdjustment *scale, gpointer user_data) {
   398   const unsigned int arg = *(unsigned int*)user_data;
   399   double value = 0;
   400   gimp_double_adjustment_update(scale,&value);
   401   char s_value[1024] = { 0 };
   402   std::sprintf(s_value,"%g",value);
   403   set_filter_parameter(get_current_filter(),arg,s_value);
   404   return_create_dialog = true;
   405 }
   407 void on_int_parameter_changed(GtkAdjustment *scale, gpointer user_data) {
   408   const unsigned int arg = *(unsigned int*)user_data;
   409   int value = 0;
   410   gimp_int_adjustment_update(scale,&value);
   411   char s_value[1024] = { 0 };
   412   std::sprintf(s_value,"%d",value);
   413   set_filter_parameter(get_current_filter(),arg,s_value);
   414   return_create_dialog = true;
   415 }
   417 void on_bool_parameter_changed(GtkCheckButton *checkbutton, gpointer user_data) {
   418   const unsigned int arg = *(unsigned int*)user_data;
   419   int value = 0;
   420   g_object_get(checkbutton,"active",&value,NULL);
   421   char s_value[1024] = { 0 };
   422   std::sprintf(s_value,"%d",value?1:0);
   423   set_filter_parameter(get_current_filter(),arg,s_value);
   424   return_create_dialog = true;
   425 }
   427 void on_list_parameter_changed(GtkComboBox *combobox, gpointer user_data) {
   428   const unsigned int arg = *(unsigned int*)user_data;
   429   int value = 0;
   430   g_object_get(combobox,"active",&value,NULL);
   431   char s_value[1024] = { 0 };
   432   std::sprintf(s_value,"%d",value);
   433   set_filter_parameter(get_current_filter(),arg,s_value);
   434   return_create_dialog = true;
   435 }
   437 void on_text_parameter_changed(GtkButton *button, gpointer user_data) {
   438   button = 0;
   439   const unsigned int arg = *(unsigned int*)user_data;
   440   GtkWidget *entry = *((GtkWidget**)user_data+1);
   441   const char *s_value = gtk_entry_get_text(GTK_ENTRY(entry));
   442   set_filter_parameter(get_current_filter(),arg,s_value);
   443   return_create_dialog = true;
   444 }
   446 void on_file_parameter_changed(GtkFileChooserButton *widget, gpointer user_data){
   447   const unsigned int arg = *(unsigned int*)user_data;
   448   const char
   449     *const filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget)),
   450     *s_value = filename?filename:"";
   451   set_filter_parameter(get_current_filter(),arg,s_value);
   452   return_create_dialog = true;
   453 }
   455 void on_color_parameter_changed(GtkColorButton *widget, gpointer user_data){
   456   const unsigned int arg = *(unsigned int*)user_data;
   457   GdkColor color;
   458   gtk_color_button_get_color(GTK_COLOR_BUTTON(widget),&color);
   459   char s_value[1024] = { 0 };
   460   if (gtk_color_button_get_use_alpha(GTK_COLOR_BUTTON(widget)))
   461     std::sprintf(s_value,"%d,%d,%d,%d",
   462                  color.red>>8,color.green>>8,color.blue>>8,gtk_color_button_get_alpha(GTK_COLOR_BUTTON(widget))>>8);
   463   else std::sprintf(s_value,"%d,%d,%d",
   464                     color.red>>8,color.green>>8,color.blue>>8);
   465   set_filter_parameter(get_current_filter(),arg,s_value);
   466   return_create_dialog = true;
   467 }
   469 // Create parameter GUI for specific chosen filter.
   470 //--------------------------------------------------
   471 void create_parameters_gui(const bool reset) {
   472   const unsigned int filter = get_current_filter();
   474   // Remove widget in the current frame if necessary.
   475   GtkWidget *frame = 0;
   476   gimp_get_data("gmic_gui_frame",&frame);
   477   if (frame) {
   478     GtkWidget *child = GTK_WIDGET(gtk_bin_get_child(GTK_BIN(frame)));
   479     if (child) gtk_container_remove(GTK_CONTAINER(frame),child);
   480   }
   482   GtkWidget *table = 0;
   483   if (!filter) {  // No filter selected -> Default message.
   484     table = gtk_table_new(1,1,false);
   485     gtk_widget_show(table);
   486     GtkWidget *label = gtk_label_new(NULL);
   487     gtk_label_set_markup(GTK_LABEL(label),"<i>Select a filter...</i>");
   488     gtk_widget_show(label);
   489     gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
   490                      (GtkAttachOptions)(GTK_EXPAND),(GtkAttachOptions)(GTK_EXPAND),0,0);
   491     gtk_misc_set_alignment (GTK_MISC(label),0,0.5);
   492     gtk_frame_set_label(GTK_FRAME(frame),NULL);
   493   } else { // Filter selected -> Build parameter table.
   494     GtkWidget *preview = 0;
   495     gimp_get_data("gmic_gui_preview",&preview);
   496     const unsigned int N = filter - 1;
   497     char nlabel[4096] = { 0 };
   498     std::sprintf(nlabel,"<b>  %s : </b>",gmic_entries[N].ptr());
   499     GtkWidget *frame_title = gtk_label_new(NULL);
   500     gtk_widget_show(frame_title);
   501     gtk_label_set_markup(GTK_LABEL(frame_title),nlabel);
   502     gtk_frame_set_label_widget(GTK_FRAME(frame),frame_title);
   504     char argname[4096] = { 0 }, argtype[4096] = { 0 }, argarg[4096] = { 0 };
   505     unsigned int nb_arguments = 0;
   506     for (const char *argument = gmic_arguments[N].ptr(); *argument; ) {
   507       if (std::sscanf(argument,"%4095[^=]=%4095[^(](%4095[^)]",argname,argtype,&(argarg[0]=0))>=2) {
   508         argument += cimg::strlen(argname) + cimg::strlen(argtype) + cimg::strlen(argarg) + 3;
   509         if (*argument) ++argument;
   510         ++nb_arguments;
   511       } else break;
   512     }
   514     if (!nb_arguments) { // Selected filter has no parameters -> Default message.
   515       table = gtk_table_new(1,1,false);
   516       gtk_widget_show(table);
   517       GtkWidget *label = gtk_label_new(NULL);
   518       gtk_label_set_markup(GTK_LABEL(label),"<i>No parameters to set...</i>");
   519       gtk_widget_show(label);
   520       gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
   521                        (GtkAttachOptions)(GTK_EXPAND),(GtkAttachOptions)(GTK_EXPAND),0,0);
   522       gtk_misc_set_alignment (GTK_MISC(label),0,0.5);
   523     } else { // Selected filter has parameters -> Create parameter table.
   525       // Create new table for putting parameters inside.
   526       table = gtk_table_new(3,nb_arguments,false);
   527       gtk_widget_show(table);
   528       gtk_table_set_row_spacings(GTK_TABLE(table),6);
   529       gtk_table_set_col_spacings(GTK_TABLE(table),6);
   530       gtk_container_set_border_width(GTK_CONTAINER(table),8);
   532       // Parse arguments list and add recognized one to the table.
   533       event_infos = new void*[2*nb_arguments];
   534       int current_parameter = 0, current_line = 0;
   535       for (const char *argument = gmic_arguments[N].ptr(); *argument; ) {
   536         if (std::sscanf(argument,"%4095[^=]=%4095[^(](%4095[^)]",argname,argtype,&(argarg[0]=0))>=2) {
   537           argument += cimg::strlen(argname) + cimg::strlen(argtype) + cimg::strlen(argarg) + 3;
   538           if (*argument) ++argument;
   539           cimg::strclean(argname);
   540           cimg::strclean(argtype);
   541           const char *const s_value = get_filter_parameter(filter,current_parameter);
   543           // Check for a float-valued parameter -> Create GtkAdjustment.
   544           bool found_valid_item = false;
   545           if (!found_valid_item && !cimg::strcasecmp(argtype,"float")) {
   546             float initial_value = 0, min_value = 0, max_value = 100;
   547             std::setlocale(LC_NUMERIC,"C");
   548             std::sscanf(argarg,"%f%*c%f%*c%f",&initial_value,&min_value,&max_value);
   549             if (!reset && std::sscanf(s_value,"%f",&initial_value)) {}
   550             GtkObject *scale = gimp_scale_entry_new(GTK_TABLE(table),0,current_line,argname,100,6,
   551                                                     (gdouble)initial_value,(gdouble)min_value,(gdouble)max_value,
   552                                                     0.1,0.1,2,true,0,0,0,0);
   553             event_infos[2*current_parameter] = (void*)current_parameter;
   554             event_infos[2*current_parameter+1] = (void*)0;
   555             on_float_parameter_changed(GTK_ADJUSTMENT(scale),(void*)(event_infos+2*current_parameter));
   556             g_signal_connect(scale,"value_changed",G_CALLBACK(on_float_parameter_changed),
   557                              (void*)(event_infos+2*current_parameter));
   558             g_signal_connect_swapped(scale,"value_changed",G_CALLBACK(gimp_preview_invalidate),preview);
   559             found_valid_item = true;
   560             ++current_parameter;
   561           }
   563           // Check for an int-valued parameter -> Create GtkAdjustment.
   564           if (!found_valid_item && !cimg::strcasecmp(argtype,"int")) {
   565             float initial_value = 0, min_value = 0, max_value = 100;
   566             std::setlocale(LC_NUMERIC,"C");
   567             std::sscanf(argarg,"%f%*c%f%*c%f",&initial_value,&min_value,&max_value);
   568             if (!reset && std::sscanf(s_value,"%f",&initial_value)) {}
   569             GtkObject *scale = gimp_scale_entry_new(GTK_TABLE(table),0,current_line,argname,100,6,
   570                                                     (gdouble)(int)initial_value,(gdouble)(int)min_value,
   571                                                     (gdouble)(int)max_value,
   572                                                     1,1,0,true,0,0,0,0);
   573             event_infos[2*current_parameter] = (void*)current_parameter;
   574             event_infos[2*current_parameter+1] = (void*)0;
   575             on_int_parameter_changed(GTK_ADJUSTMENT(scale),(void*)(event_infos+2*current_parameter));
   576             g_signal_connect(scale,"value_changed",G_CALLBACK(on_int_parameter_changed),
   577                              (void*)(event_infos+2*current_parameter));
   578             g_signal_connect_swapped(scale,"value_changed",G_CALLBACK(gimp_preview_invalidate),preview);
   579             found_valid_item = true;
   580             ++current_parameter;
   581           }
   583           // Check for a bool-valued parameter -> Create GtkCheckButton.
   584           if (!found_valid_item && !cimg::strcasecmp(argtype,"bool")) {
   585             unsigned int initial_value = 0;
   586             std::sscanf(argarg,"%u",&initial_value);
   587             if (!reset && std::sscanf(s_value,"%u",&initial_value)) {}
   588             GtkWidget *checkbutton = gtk_check_button_new_with_label(argname);
   589             gtk_widget_show(checkbutton);
   590             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton),initial_value?true:false);
   591             gtk_table_attach(GTK_TABLE(table),checkbutton,0,2,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   592             event_infos[2*current_parameter] = (void*)current_parameter;
   593             event_infos[2*current_parameter+1] = (void*)0;
   594             on_bool_parameter_changed(GTK_CHECK_BUTTON(checkbutton),(void*)(event_infos+2*current_parameter));
   595             g_signal_connect(checkbutton,"toggled",G_CALLBACK(on_bool_parameter_changed),
   596                              (void*)(event_infos+2*current_parameter));
   597             g_signal_connect_swapped(checkbutton,"toggled",G_CALLBACK(gimp_preview_invalidate),preview);
   598             found_valid_item = true;
   599             ++current_parameter;
   600           }
   602           // Check for a list-valued parameter -> Create GtkComboBox.
   603           if (!found_valid_item && !cimg::strcasecmp(argtype,"choice")) {
   604             GtkWidget *label = gtk_label_new(argname);
   605             gtk_widget_show(label);
   606             gtk_table_attach(GTK_TABLE(table),label,0,1,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   607             gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
   608             GtkWidget *combobox = gtk_combo_box_new_text();
   609             gtk_widget_show(combobox);
   610             char s_entry[4096] = { 0 }, end = 0; int err2 = 0;
   611             unsigned int initial_value = 0;
   612             const char *entries = argarg;
   613             if (std::sscanf(entries,"%u",&initial_value)==1) entries+=std::sprintf(s_entry,"%u",initial_value) + 1;
   614             while (*entries) {
   615               if ((err2 = std::sscanf(entries,"%4095[^,]%c",s_entry,&end))>0) {
   616                 entries += cimg::strlen(s_entry) + (err2==2?1:0);
   617                 cimg::strclean(s_entry);
   618                 strparenthesis(s_entry);
   619                 gtk_combo_box_append_text(GTK_COMBO_BOX(combobox),s_entry);
   620               } else break;
   621             }
   622             if (!reset && std::sscanf(s_value,"%u",&initial_value)) {}
   623             gtk_combo_box_set_active(GTK_COMBO_BOX(combobox),initial_value);
   624             gtk_table_attach(GTK_TABLE(table),combobox,1,3,current_line,current_line+1,
   625                              (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),(GtkAttachOptions)(GTK_FILL),0,0);
   626             event_infos[2*current_parameter] = (void*)current_parameter;
   627             event_infos[2*current_parameter+1] = (void*)0;
   628             on_list_parameter_changed(GTK_COMBO_BOX(combobox),(void*)(event_infos+2*current_parameter));
   629             g_signal_connect(combobox,"changed",G_CALLBACK(on_list_parameter_changed),
   630                              (void*)(event_infos+2*current_parameter));
   631             g_signal_connect_swapped(combobox,"changed",G_CALLBACK(gimp_preview_invalidate),preview);
   632             found_valid_item = true;
   633             ++current_parameter;
   634           }
   636           // Check for a text-valued parameter -> Create GtkEntry.
   637           if (!found_valid_item && !cimg::strcasecmp(argtype,"text")) {
   638             GtkWidget *label = gtk_label_new(argname);
   639             gtk_widget_show(label);
   640             gtk_table_attach(GTK_TABLE(table),label,0,1,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   641             gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
   642             GtkWidget *entry = gtk_entry_new_with_max_length(4095);
   643             gtk_widget_show(entry);
   644             cimg::strclean(argarg);
   645             if (!reset && *s_value) gtk_entry_set_text(GTK_ENTRY(entry),s_value);
   646             else gtk_entry_set_text(GTK_ENTRY(entry),argarg);
   647             gtk_table_attach(GTK_TABLE(table),entry,1,2,current_line,current_line+1,
   648                              (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),(GtkAttachOptions)0,0,0);
   649             GtkWidget *button = gtk_button_new_with_label("Update");
   650             gtk_widget_show(button);
   651             gtk_table_attach(GTK_TABLE(table),button,2,3,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   652             event_infos[2*current_parameter] = (void*)current_parameter;
   653             event_infos[2*current_parameter+1] = (void*)entry;
   654             on_text_parameter_changed(GTK_BUTTON(button),(void*)(event_infos+2*current_parameter));
   655             g_signal_connect(button,"clicked",G_CALLBACK(on_text_parameter_changed),
   656                              (void*)(event_infos+2*current_parameter));
   657             g_signal_connect_swapped(button,"clicked",G_CALLBACK(gimp_preview_invalidate),preview);
   658             found_valid_item = true;
   659             ++current_parameter;
   660           }
   662           // Check for a filename parameter -> Create GtkFileChooserButton.
   663           if (!found_valid_item && !cimg::strcasecmp(argtype,"file")) {
   664             GtkWidget *label = gtk_label_new(argname);
   665             gtk_widget_show(label);
   666             gtk_table_attach(GTK_TABLE(table),label,0,1,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   667             gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
   668             GtkWidget *filechooser = gtk_file_chooser_button_new(argname,GTK_FILE_CHOOSER_ACTION_OPEN);
   669             gtk_widget_show(filechooser);
   670             cimg::strclean(argarg);
   671             if (!reset && *s_value) gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(filechooser),s_value);
   672             else gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(filechooser),argarg);
   673             gtk_table_attach(GTK_TABLE(table),filechooser,1,3,current_line,current_line+1,
   674                              (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),(GtkAttachOptions)0,0,0);
   675             event_infos[2*current_parameter] = (void*)current_parameter;
   676             event_infos[2*current_parameter+1] = (void*)0;
   677             on_file_parameter_changed(GTK_FILE_CHOOSER_BUTTON(filechooser),(void*)(event_infos+2*current_parameter));
   678             g_signal_connect(filechooser,"file-set",G_CALLBACK(on_file_parameter_changed),
   679                              (void*)(event_infos+2*current_parameter));
   680             g_signal_connect_swapped(filechooser,"file-set",G_CALLBACK(gimp_preview_invalidate),preview);
   681             found_valid_item = true;
   682             ++current_parameter;
   683           }
   685           // Check for a color -> Create GtkColorButton.
   686           if (!found_valid_item && !cimg::strcasecmp(argtype,"color")) {
   687             GtkWidget *hbox = gtk_hbox_new(false,6);
   688             gtk_widget_show(hbox);
   689             gtk_table_attach(GTK_TABLE(table),hbox,0,2,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   690             GtkWidget *label = gtk_label_new(argname);
   691             gtk_widget_show(label);
   692             gtk_box_pack_start(GTK_BOX(hbox),label,false,false,0);
   693             GtkWidget *colorchooser = gtk_color_button_new();
   694             gtk_widget_show(colorchooser);
   695             gtk_color_button_set_title(GTK_COLOR_BUTTON(colorchooser),argname);
   696             gtk_box_pack_start(GTK_BOX(hbox),colorchooser,false,false,0);
   697             event_infos[2*current_parameter] = (void*)current_parameter;
   698             event_infos[2*current_parameter+1] = (void*)0;
   699             cimg::strclean(argarg);
   700             unsigned int red = 0, green = 0, blue = 0, alpha = 255;
   701             const int err = std::sscanf(argarg,"%u%*c%u%*c%u%*c%u",&red,&green,&blue,&alpha);
   702             if (!reset && std::sscanf(s_value,"%u%*c%u%*c%u%*c%u",&red,&green,&blue,&alpha)==err) {}
   703             GdkColor col;
   704             col.pixel = 0; col.red = red<<8; col.green = green<<8; col.blue = blue<<8;
   705             gtk_color_button_set_color(GTK_COLOR_BUTTON(colorchooser),&col);
   706             if (err==4) {
   707               gtk_color_button_set_use_alpha(GTK_COLOR_BUTTON(colorchooser),true);
   708               gtk_color_button_set_alpha(GTK_COLOR_BUTTON(colorchooser),alpha<<8);
   709             } else gtk_color_button_set_use_alpha(GTK_COLOR_BUTTON(colorchooser),false);
   710             on_color_parameter_changed(GTK_COLOR_BUTTON(colorchooser),(void*)(event_infos+2*current_parameter));
   711             g_signal_connect(colorchooser,"color-set",G_CALLBACK(on_color_parameter_changed),
   712                              (void*)(event_infos+2*current_parameter));
   713             g_signal_connect_swapped(colorchooser,"color-set",G_CALLBACK(gimp_preview_invalidate),preview);
   714             found_valid_item = true;
   715             ++current_parameter;
   716           }
   718           // Check for a note -> Create GtkLabel.
   719           if (!found_valid_item && !cimg::strcasecmp(argtype,"note")) {
   720             cimg::strclean(argarg);
   721             GtkWidget *label = gtk_label_new(NULL);
   722             cimg::strescape(argarg);
   723             strparenthesis(argarg);
   724             gtk_label_set_markup(GTK_LABEL(label),argarg);
   725             gtk_label_set_line_wrap(GTK_LABEL(label),true);
   726             gtk_widget_show(label);
   727             gtk_table_attach(GTK_TABLE(table),label,0,3,current_line,current_line+1,GTK_FILL,GTK_SHRINK,0,0);
   728             gtk_misc_set_alignment(GTK_MISC(label),0,0.5);
   729             found_valid_item = true;
   730           }
   732           if (!found_valid_item) {
   733             if (get_verbosity_level()>0)
   734               std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Found invalid parameter type '%s' for argument '%s'.\n",argtype,argname);
   735           } else ++current_line;
   736         } else break;
   737       }
   738       set_filter_nbparams(filter,current_parameter);
   739     }
   740   }
   741   gtk_container_add(GTK_CONTAINER(frame),table);
   742 }
   744 // Called when the selected filter changed (in the combo-box).
   745 void on_filter_changed(GtkTreeSelection *selection, gpointer user_data) {
   746   user_data = 0;
   747   GtkTreeIter iter;
   748   GtkTreeModel *model;
   749   unsigned int choice = 0;
   750   if (gtk_tree_selection_get_selected(selection,&model,&iter))
   751     gtk_tree_model_get(model,&iter,0,&choice,-1);
   752   set_current_filter(choice);
   753   create_parameters_gui(false);
   754   return_create_dialog = true;
   755 }
   757 // Handle responses to the dialog window buttons.
   758 void on_verbosity_level_changed(GtkComboBox *combobox, gpointer user_data) {
   759   user_data = 0;
   760   int value = 0;
   761   g_object_get(combobox,"active",&value,NULL);
   762   set_verbosity_level(value);
   763 }
   765 void on_dialog_reset_clicked(GtkButton *widget, gpointer data) {
   766   widget = 0; data = 0;
   767   create_parameters_gui(true);
   768   return_create_dialog = true;
   769 }
   771 void on_dialog_cancel_clicked(GtkButton *widget, gpointer data) {
   772   widget = 0; data = 0;
   773   return_create_dialog = false;
   774   gtk_main_quit();
   775 }
   777 void on_dialog_apply_clicked(GtkButton *widget, gpointer data) {
   778   widget = 0;
   779   GimpDrawable *drawable = (GimpDrawable*)data;
   780   process_image(drawable,0);
   781   return_create_dialog = false;
   782 }
   784 void on_dialog_ok_clicked(GtkButton *widget, gpointer data) {
   785   widget = 0; data = 0;
   786   gtk_main_quit();
   787 }
   789 void on_update_button_clicked(GtkButton *widget, gpointer data) {
   790   widget = 0;
   791   GtkWidget *dialog = (GtkWidget*)data;
   792   char update_filename[1024] = { 0 }, update_command[1024] = { 0 }, src_filename[1024] = { 0 }, dest_filename[1024] = { 0 };
   793   const char
   794     *const update_url = "http://www.greyc.ensicaen.fr/~dtschump",
   795     *const path_tmp = cimg::temporary_path();
   796   std::sprintf(update_filename,"gmic4gimp_def.%d",gmic_version);
   797   std::sprintf(src_filename,"%s/%s",path_tmp,update_filename);
   798   std::sprintf(dest_filename,"%s/.%s",path_home,update_filename);
   799   if (get_verbosity_level()>0) {
   800     std::sprintf(update_command,"wget %s/%s -O %s",update_url,update_filename,src_filename);
   801     std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Running update procedure, with command : %s\n",update_command);
   802   } else std::sprintf(update_command,"wget --quiet %s/%s -O %s",update_url,update_filename,src_filename);
   803   int status = cimg::system(update_command);
   804   status = 0;
   805   std::FILE *file_s = std::fopen(src_filename,"r");
   806   bool succeed = false;
   807   if (file_s) {
   808     unsigned int size_s = 0;
   809     std::fseek(file_s,0,SEEK_END);
   810     size_s = (unsigned int)std::ftell(file_s);
   811     std::rewind(file_s);
   812     if (size_s) {
   813       std::FILE *file_d = std::fopen(dest_filename,"w");
   814       char *buffer = new char[size_s], sep = 0;
   815       if (file_d &&
   816           std::fread(buffer,sizeof(char),size_s,file_s)==size_s &&
   817           std::sscanf(buffer,"#@gim%c",&sep)==1 && sep=='p' &&
   818           std::fwrite(buffer,sizeof(char),size_s,file_d)==size_s) { succeed = true; std::fclose(file_d); }
   819       delete[] buffer;
   820     }
   821     std::fclose(file_s);
   822   }
   823   if (!succeed) {
   824     GtkWidget *message = gtk_message_dialog_new_with_markup(GTK_WINDOW(dialog),GTK_DIALOG_MODAL,GTK_MESSAGE_ERROR,GTK_BUTTONS_OK,
   825                                                             "<b>Filters update failed !</b>\n\n"
   826                                                             "A valid version of the update file :\n\n"
   827                                                             "<i>%s/%s</i>\n\n"
   828                                                             "  ...could not be retrieved from the G'MIC server.\n\n"
   829                                                             "Please check your Internet connection or\n"
   830                                                             "try a manual update instead.",update_url,update_filename);
   831     gtk_widget_show(message);
   832     gtk_dialog_run(GTK_DIALOG(message));
   833     gtk_widget_destroy(message);
   834   } else {
   835     GtkWidget *message = gtk_message_dialog_new_with_markup(GTK_WINDOW(dialog),GTK_DIALOG_MODAL,GTK_MESSAGE_INFO,GTK_BUTTONS_OK,
   836                                                 "<b>Filters update succeed !</b>\n\n"
   837                                                 "The G'MIC Toolbox must be restarted now.");
   838     gtk_widget_show(message);
   839     gtk_dialog_run(GTK_DIALOG(message));
   840     gtk_widget_destroy(message);
   841     return_create_dialog = false;
   842     set_current_filter(0);
   843     gtk_main_quit();
   844   }
   845 }
   847 // Create main plug-in dialog window and wait for a response.
   848 //-----------------------------------------------------------
   849 bool create_dialog_gui(GimpDrawable *drawable) {
   851   // Init GUI_specific variables
   852   gimp_ui_init("gmic",true);
   853   event_infos = 0;
   855   // Create main plug-in dialog window.
   856   GtkWidget
   857     *dialog = gimp_dialog_new("The G'MIC Toolbox","gmic",0,(GtkDialogFlags)0,gimp_standard_help_func,"gmic",NULL),
   858     *cancel_button = gimp_dialog_add_button(GIMP_DIALOG(dialog),GTK_STOCK_CANCEL,GTK_RESPONSE_CANCEL),
   859     *reset_button = gimp_dialog_add_button(GIMP_DIALOG(dialog),GIMP_STOCK_RESET,1),
   860     *apply_button = gimp_dialog_add_button(GIMP_DIALOG(dialog),GTK_STOCK_APPLY,GTK_RESPONSE_APPLY),
   861     *ok_button = gimp_dialog_add_button(GIMP_DIALOG(dialog),GTK_STOCK_OK,GTK_RESPONSE_OK);
   862   gimp_window_set_transient(GTK_WINDOW(dialog));
   863   g_signal_connect(dialog,"close",G_CALLBACK(on_dialog_cancel_clicked),0);
   864   g_signal_connect(dialog,"delete-event",G_CALLBACK(on_dialog_cancel_clicked),0);
   865   g_signal_connect(cancel_button,"clicked",G_CALLBACK(on_dialog_cancel_clicked),0);
   866   g_signal_connect(apply_button,"clicked",G_CALLBACK(on_dialog_apply_clicked),drawable);
   867   g_signal_connect(ok_button,"clicked",G_CALLBACK(on_dialog_ok_clicked),0);
   869   GtkWidget *dialog_hbox = gtk_hbox_new(false,0);
   870   gtk_widget_show(dialog_hbox);
   871   gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox),dialog_hbox);
   873   // Create the left pane, containing the preview, the show commmand and the update buttons and the author name.
   874   GtkWidget *left_pane = gtk_vbox_new(false,4);
   875   gtk_widget_show(left_pane);
   876   gtk_box_pack_start(GTK_BOX(dialog_hbox),left_pane,true,true,0);
   878   GtkWidget *preview = gimp_zoom_preview_new(drawable);
   879   gtk_widget_show(preview);
   880   gtk_box_pack_start(GTK_BOX(left_pane),preview,true,true,0);
   881   gimp_set_data("gmic_gui_preview",&preview,sizeof(GtkWidget*));
   882   g_signal_connect(preview,"invalidated",G_CALLBACK(process_preview),0);
   883   g_signal_connect_swapped(apply_button,"clicked",G_CALLBACK(gimp_preview_invalidate),preview);
   885   GtkWidget *verbosity_hbuttonbox = gtk_hbutton_box_new();
   886   gtk_widget_show(verbosity_hbuttonbox);
   887   gtk_box_pack_start(GTK_BOX(left_pane),verbosity_hbuttonbox,false,false,0);
   889   GtkWidget *verbosity_combobox = gtk_combo_box_new_text();
   890   gtk_widget_show(verbosity_combobox);
   891   gtk_combo_box_append_text(GTK_COMBO_BOX(verbosity_combobox),"Quiet mode");
   892   gtk_combo_box_append_text(GTK_COMBO_BOX(verbosity_combobox),"Verbose mode");
   893   gtk_combo_box_append_text(GTK_COMBO_BOX(verbosity_combobox),"Debug mode");
   894   gtk_combo_box_set_active(GTK_COMBO_BOX(verbosity_combobox),get_verbosity_level());
   895   gtk_container_add(GTK_CONTAINER(verbosity_hbuttonbox),verbosity_combobox);
   896   g_signal_connect(verbosity_combobox,"changed",G_CALLBACK(on_verbosity_level_changed),0);
   898   GtkWidget *update_hbuttonbox = gtk_hbutton_box_new();
   899   gtk_widget_show(update_hbuttonbox);
   900   gtk_box_pack_start(GTK_BOX(left_pane),update_hbuttonbox,false,false,0);
   901   GtkWidget
   902     *tmp_button = gtk_button_new_from_stock(GTK_STOCK_REFRESH),
   903     *update_image = gtk_button_get_image(GTK_BUTTON(tmp_button)),
   904     *update_button = gtk_button_new_with_mnemonic("_Update filters");
   905   gtk_button_set_image(GTK_BUTTON(update_button),update_image);
   906   gtk_widget_show(update_button);
   907   gtk_container_add(GTK_CONTAINER(update_hbuttonbox),update_button);
   908   g_signal_connect(update_button,"clicked",G_CALLBACK(on_update_button_clicked),(void*)dialog);
   910   GtkWidget *about_label = gtk_label_new(NULL);
   911   gtk_label_set_markup(GTK_LABEL(about_label),
   912                        "\n<span color=\"#666666\"><small>"
   913                        "<b>G'MIC</b> is proposed to you\n"
   914                        "   by <i>David Tschumperle</i>"
   915                        "</small></span>");
   916   gtk_widget_show(about_label);
   917   gtk_box_pack_start(GTK_BOX(left_pane),about_label,false,false,0);
   919   const unsigned int logo_width = 102, logo_height = 22;
   920   GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(data_logo,GDK_COLORSPACE_RGB,false,8,
   921                                                logo_width,logo_height,3*logo_width,0,0);
   922   GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
   923   gtk_widget_show(image);
   924   gtk_box_pack_start(GTK_BOX(left_pane),image,false,false,0);
   926   // Create the middle pane, which contains the filters treeview.
   927   GtkWidget *middle_pane = gtk_frame_new(NULL);
   928   gtk_widget_show(middle_pane);
   929   gtk_container_set_border_width(GTK_CONTAINER(middle_pane),4);
   930   gtk_widget_set_size_request(middle_pane,250,-1);
   931   gtk_box_pack_start(GTK_BOX(dialog_hbox),middle_pane,false,false,0);
   933   GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL,NULL);
   934   gtk_widget_show(scrolledwindow);
   935   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow),GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
   936   gtk_container_add(GTK_CONTAINER(middle_pane),scrolledwindow);
   938   GtkWidget *treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(filter_store));
   939   GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
   940   GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(" Available filters :",renderer,"text",1,NULL);
   941   gtk_tree_view_append_column(GTK_TREE_VIEW(treeview),column);
   943   GtkTreeSelection *select = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
   944   gtk_tree_selection_set_mode(select,GTK_SELECTION_SINGLE);
   945   g_signal_connect(G_OBJECT(select),"changed",G_CALLBACK(on_filter_changed),0);
   946   g_signal_connect_swapped(select,"changed",G_CALLBACK(gimp_preview_invalidate),preview);
   947   gtk_widget_show(treeview);
   948   gtk_container_add(GTK_CONTAINER(scrolledwindow),treeview);
   949   g_signal_connect(reset_button,"clicked",G_CALLBACK(on_dialog_reset_clicked),select);
   950   g_signal_connect_swapped(reset_button,"clicked",G_CALLBACK(gimp_preview_invalidate),preview);
   952   // Create the right pane which contains the parameters frame.
   953   GtkWidget *parameters_frame = gtk_frame_new(NULL);
   954   gtk_widget_show(parameters_frame);
   955   gtk_container_set_border_width(GTK_CONTAINER(parameters_frame),4);
   956   gtk_widget_set_size_request(parameters_frame,450,-1);
   957   gtk_box_pack_start(GTK_BOX(dialog_hbox),parameters_frame,false,false,0);
   958   gimp_set_data("gmic_gui_frame",&parameters_frame,sizeof(GtkWidget*));
   959   create_parameters_gui(false);
   961   // Show dialog window and wait for user response.
   962   gtk_widget_show(dialog);
   963   gtk_main();
   965   // Destroy dialog box widget and free resources.
   966   gtk_widget_destroy(dialog);
   967   gtk_widget_destroy(tmp_button);
   968   if (event_infos) delete[] event_infos;
   969   return return_create_dialog;
   970 }
   972 // 'Run' function needed by GIMP plug-in API.
   973 //-------------------------------------------
   974 void gmic_run(const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) {
   976   // Init plug-in variables.
   977   static GimpParam values[1];
   978   values[0].type = GIMP_PDB_STATUS;
   979   *return_vals  = values;
   980   *nreturn_vals = 1;
   981   name = 0;
   982   nparams = 0;
   983   GimpRunMode run_mode;
   984   run_mode = (GimpRunMode)param[0].data.d_int32;
   985   if (run_mode==GIMP_RUN_NONINTERACTIVE) {
   986     std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : ERROR, this plug-in cannot be run in non-interactive mode.\n");
   987     values[0].data.d_status = GIMP_PDB_CALLING_ERROR;
   988     return;
   989   }
   990   gmic_macros = 0;
   991   filter_store = 0;
   992   return_create_dialog = true;
   993   path_home = getenv(cimg_OS!=2?"HOME":"APPDATA");
   995   // Check that no instance of the plug-in is already running.
   996   bool is_existing_instance = 0;
   997   gimp_get_data("gmic_instance",&is_existing_instance);
   998   if (is_existing_instance) {
   999     std::fprintf(stderr,"\n*** Plug-in 'gmic4gimp' : Existing instance of the plug-in is already running.\n");
  1000     return;
  1002   is_existing_instance = true;
  1003   gimp_set_data("gmic_instance",&is_existing_instance,sizeof(bool));
  1005   // Read user-defined configuration files '.gmic_def' and '.gmic', when possible.
  1006   unsigned size_update = 0, size_custom = 0, size_def = sizeof(data_gmic4gimp_def);
  1007   char filename_update[1024] = { 0 }, filename_custom[1024] = { 0 };
  1008   std::sprintf(filename_update,"%s/.gmic4gimp_def.%d",path_home,gmic_version);
  1009   std::sprintf(filename_custom,"%s/.gmic4gimp",path_home);
  1010   std::FILE
  1011     *file_update = std::fopen(filename_update,"r"),
  1012     *file_custom = std::fopen(filename_custom,"r");
  1013   if (file_update) {
  1014     std::fseek(file_update,0,SEEK_END);
  1015     size_update = (unsigned int)std::ftell(file_update);
  1016     std::rewind(file_update);
  1018   if (file_custom) {
  1019     std::fseek(file_custom,0,SEEK_END);
  1020     size_custom = (unsigned int)std::ftell(file_custom);
  1021     std::rewind(file_custom);
  1023   const unsigned int size_final = size_update + size_custom + size_def + 1;
  1024   char *ptrd = gmic_macros = new char[size_final];
  1025   if (size_custom) { ptrd+=std::fread(ptrd,1,size_custom,file_custom); std::fclose(file_custom); }
  1026   if (size_update) { ptrd+=std::fread(ptrd,1,size_update,file_update); std::fclose(file_update); }
  1027   if (size_def)    { std::memcpy(ptrd,data_gmic4gimp_def,size_def); ptrd+=size_def; }
  1028   *ptrd = 0;
  1030   // Parse available G'MIC filters definitions.
  1031   GtkTreeIter iter, parent[16];
  1032   filter_store = gtk_tree_store_new(2,G_TYPE_UINT,G_TYPE_STRING);
  1033   char line[256*1024] = { 0 }, entry[4096] = { 0 }, command[4096] = { 0 };
  1034   char preview_command[4096] = { 0 }, arguments[4096] = { 0 };
  1035   int level = 0;
  1036   for (const char *data = gmic_macros; *data; ) {
  1037     if (*data=='\n') ++data;
  1038     else {
  1039       if (std::sscanf(data,"%262143[^\n]\n",line)>0) data += cimg::strlen(line) + 1;
  1040       arguments[0] = 0;
  1041       if (line[0]=='#') {
  1042         const int err = std::sscanf(line,"#@gimp %4095[^:]: %4095[^, ]%*c %4095[^, ]%*c %4095[^\n]",
  1043                                     entry,command,preview_command,arguments);
  1044         strparenthesis(entry);
  1045         if (err==1) { // If entry is a menu folder.
  1046           cimg::strclean(entry);
  1047           char *nentry = entry;
  1048           while (*nentry=='_') { ++nentry; --level; }
  1049           if (level<0) level = 0;
  1050           if (level>15) level = 15;
  1051           cimg::strclean(nentry);
  1052           if (*nentry) {
  1053             gtk_tree_store_append(filter_store,&parent[level],level?&parent[level-1]:0);
  1054             gtk_tree_store_set(filter_store,&parent[level],0,0,1,nentry,-1);
  1055             ++level;
  1057         } else if (err>=2) { // If entry is a regular filter.
  1058           cimg::strclean(entry);
  1059           cimg::strclean(command);
  1060           gmic_entries.insert(CImg<char>(entry,cimg::strlen(entry)+1));
  1061           gmic_commands.insert(CImg<char>(command,cimg::strlen(command)+1));
  1062           gmic_arguments.insert(CImg<char>(arguments,cimg::strlen(arguments)+1));
  1063           if (err>=3) {
  1064             cimg::strclean(preview_command);
  1065             gmic_preview_commands.insert(CImg<char>(preview_command,cimg::strlen(preview_command)+1));
  1067           gtk_tree_store_append(filter_store,&iter,level?&parent[level-1]:0);
  1068           gtk_tree_store_set(filter_store,&iter,0,gmic_entries.size,1,entry,-1);
  1074   // Get currenty selected drawable and run image filter on it.
  1075   GimpDrawable *drawable = gimp_drawable_get(param[2].data.d_drawable);
  1076   gimp_tile_cache_ntiles(2*(drawable->width/gimp_tile_width()+1));
  1077   if (run_mode==GIMP_RUN_INTERACTIVE) {
  1078     if (create_dialog_gui(drawable)) {
  1079       process_image(drawable,0);
  1080       const char *commandline = get_commandline(false);
  1081       if (commandline) { // Remember command line for the next use of the filter.
  1082         char s_tmp[256] = { 0 };
  1083         std::sprintf(s_tmp,"gmic_commandline%u",get_current_filter());
  1084         gimp_set_data(s_tmp,commandline,cimg::strlen(commandline));
  1087   } else if (run_mode==GIMP_RUN_WITH_LAST_VALS) {
  1088     const unsigned int filter = get_current_filter();
  1089     if (filter) {
  1090       char s_tmp[256] = { 0 };
  1091       std::sprintf(s_tmp,"gmic_commandline%u",filter);
  1092       char commandline[4096] = { 0 };
  1093       gimp_get_data(s_tmp,&commandline);
  1094       process_image(drawable,commandline);
  1098   // Free plug-in resources.
  1099   delete[] gmic_macros;
  1100   values[0].data.d_status = GIMP_PDB_SUCCESS;
  1101   is_existing_instance = false;
  1102   gimp_set_data("gmic_instance",&is_existing_instance,sizeof(bool));
  1105 // 'Query' function needed by GIMP plug-in API.
  1106 //---------------------------------------------
  1107 void gmic_query() {
  1108   static const GimpParamDef args[] = {
  1109     {GIMP_PDB_INT32,    "run_mode", "Interactive, non-interactive"},
  1110     {GIMP_PDB_IMAGE,    "image", "(unused)"},
  1111     {GIMP_PDB_DRAWABLE, "drawable", "Drawable to draw on"}
  1112   };
  1114   gimp_install_procedure("gmic",                     // name
  1115                          "G'MIC Toolbox",            // blurb
  1116                          "G'MIC Toolbox",            // help
  1117                          "David Tschumperle",        // author
  1118                          "David Tschumperle",        // copyright
  1119                          "2008-12-02",               // date
  1120                          "_G'MIC Toolbox...",        // menu_path
  1121                          "RGB*, GRAY*",              // image_types
  1122                          GIMP_PLUGIN,                // type
  1123                          G_N_ELEMENTS(args),         // nparams
  1124                          0,                          // nreturn_vals
  1125                          args,                       // params
  1126                          0);                         // return_vals
  1128   gimp_plugin_menu_register("gmic", "<Image>/Filters");
  1131 GimpPlugInInfo PLUG_IN_INFO = { 0, 0, gmic_query, gmic_run };
  1132 MAIN();