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