1 /** -*- mode: c++ ; c-basic-offset: 2 -*-
2  *
3  *  @file host_gimp.cpp
4  *
5  *  Copyright 2017 Sebastien Fourey
6  *
7  *  This file is part of G'MIC-Qt, a generic plug-in for raster graphics
8  *  editors, offering hundreds of filters thanks to the underlying G'MIC
9  *  image processing framework.
10  *
11  *  gmic_qt is free software: you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation, either version 3 of the License, or
14  *  (at your option) any later version.
15  *
16  *  gmic_qt is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with gmic_qt.  If not, see <http://www.gnu.org/licenses/>.
23  *
24  */
25 #include <libgimp/gimp.h>
26 #include <QDebug>
27 #include <QFileInfo>
28 #include <QRegExp>
29 #include <QString>
30 #include <algorithm>
31 #include <limits>
32 #include <stack>
33 #include <vector>
34 #include "Common.h"
35 #include "Host/host.h"
36 #include "ImageTools.h"
37 #include "gmic_qt.h"
38 #include "gmic.h"
39 
40 /*
41  * Part of this code is much inspired by the original source code
42  * of the GTK version of the gmic plug-in for GIMP by David Tschumperl\'e.
43  */
44 
45 #define _gimp_image_get_item_position gimp_image_get_item_position
46 
47 #if (GIMP_MAJOR_VERSION == 2) && (GIMP_MINOR_VERSION <= 7) && (GIMP_MICRO_VERSION <= 14)
48 #define _gimp_item_get_visible gimp_drawable_get_visible
49 #else
50 #define _gimp_item_get_visible gimp_item_get_visible
51 #endif
52 
53 namespace GmicQt
54 {
55 const QString HostApplicationName = QString("GIMP %1.%2").arg(GIMP_MAJOR_VERSION).arg(GIMP_MINOR_VERSION);
56 const char * HostApplicationShortname = GMIC_QT_XSTRINGIFY(GMIC_HOST);
57 #if (GIMP_MAJOR_VERSION < 2) || ((GIMP_MAJOR_VERSION == 2) && (GIMP_MINOR_VERSION <= 8))
58 const bool DarkThemeIsDefault = false;
59 #else
60 const bool DarkThemeIsDefault = true;
61 #endif
62 
63 } // namespace GmicQt
64 
65 namespace
66 {
67 
68 int gmic_qt_gimp_image_id;
69 cimg_library::CImg<int> inputLayerDimensions;
70 std::vector<int> inputLayers;
71 
72 #if (GIMP_MAJOR_VERSION >= 3 || GIMP_MINOR_VERSION > 8) && !defined(GIMP_NORMAL_MODE)
73 typedef GimpLayerMode GimpLayerModeEffects;
74 #define GIMP_NORMAL_MODE GIMP_LAYER_MODE_NORMAL
75 const QMap<QString, GimpLayerModeEffects> BlendingModesMap = {{QString("alpha"), GIMP_LAYER_MODE_NORMAL},
76                                                               {QString("normal"), GIMP_LAYER_MODE_NORMAL},
77                                                               {QString("dissolve"), GIMP_LAYER_MODE_DISSOLVE},
78                                                               {QString("behind"), GIMP_LAYER_MODE_BEHIND},
79                                                               {QString("colorerase"), GIMP_LAYER_MODE_COLOR_ERASE},
80                                                               {QString("erase"), GIMP_LAYER_MODE_ERASE},
81                                                               {QString("merge"), GIMP_LAYER_MODE_MERGE},
82                                                               {QString("split"), GIMP_LAYER_MODE_SPLIT},
83                                                               {QString("lighten"), GIMP_LAYER_MODE_LIGHTEN_ONLY},
84                                                               {QString("lumalighten"), GIMP_LAYER_MODE_LUMA_LIGHTEN_ONLY},
85                                                               {QString("screen"), GIMP_LAYER_MODE_SCREEN},
86                                                               {QString("dodge"), GIMP_LAYER_MODE_DODGE},
87                                                               {QString("addition"), GIMP_LAYER_MODE_ADDITION},
88                                                               {QString("darken"), GIMP_LAYER_MODE_DARKEN_ONLY},
89                                                               {QString("lumadarken"), GIMP_LAYER_MODE_LUMA_DARKEN_ONLY},
90                                                               {QString("multiply"), GIMP_LAYER_MODE_MULTIPLY},
91                                                               {QString("burn"), GIMP_LAYER_MODE_BURN},
92                                                               {QString("overlay"), GIMP_LAYER_MODE_OVERLAY},
93                                                               {QString("softlight"), GIMP_LAYER_MODE_SOFTLIGHT},
94                                                               {QString("hardlight"), GIMP_LAYER_MODE_HARDLIGHT},
95                                                               {QString("vividlight"), GIMP_LAYER_MODE_VIVID_LIGHT},
96                                                               {QString("pinlight"), GIMP_LAYER_MODE_PIN_LIGHT},
97                                                               {QString("linearlight"), GIMP_LAYER_MODE_LINEAR_LIGHT},
98                                                               {QString("hardmix"), GIMP_LAYER_MODE_HARD_MIX},
99                                                               {QString("difference"), GIMP_LAYER_MODE_DIFFERENCE},
100                                                               {QString("subtract"), GIMP_LAYER_MODE_SUBTRACT},
101                                                               {QString("grainextract"), GIMP_LAYER_MODE_GRAIN_EXTRACT},
102                                                               {QString("grainmerge"), GIMP_LAYER_MODE_GRAIN_MERGE},
103                                                               {QString("divide"), GIMP_LAYER_MODE_DIVIDE},
104                                                               {QString("hue"), GIMP_LAYER_MODE_HSV_HUE},
105                                                               {QString("saturation"), GIMP_LAYER_MODE_HSV_SATURATION},
106                                                               {QString("color"), GIMP_LAYER_MODE_HSL_COLOR},
107                                                               {QString("value"), GIMP_LAYER_MODE_HSV_VALUE},
108                                                               {QString("lchhue"), GIMP_LAYER_MODE_LCH_HUE},
109                                                               {QString("lchchroma"), GIMP_LAYER_MODE_LCH_CHROMA},
110                                                               {QString("lchcolor"), GIMP_LAYER_MODE_LCH_COLOR},
111                                                               {QString("lchlightness"), GIMP_LAYER_MODE_LCH_LIGHTNESS},
112                                                               {QString("luminance"), GIMP_LAYER_MODE_LUMINANCE},
113                                                               {QString("exclusion"), GIMP_LAYER_MODE_EXCLUSION}};
114 #else
115 const QMap<QString, GimpLayerModeEffects> BlendingModesMap = {{QString("alpha"), GIMP_NORMAL_MODE},
116                                                               {QString("normal"), GIMP_NORMAL_MODE},
117                                                               {QString("dissolve"), GIMP_DISSOLVE_MODE},
118                                                               {QString("lighten"), GIMP_LIGHTEN_ONLY_MODE},
119                                                               {QString("screen"), GIMP_SCREEN_MODE},
120                                                               {QString("dodge"), GIMP_DODGE_MODE},
121                                                               {QString("addition"), GIMP_ADDITION_MODE},
122                                                               {QString("darken"), GIMP_DARKEN_ONLY_MODE},
123                                                               {QString("multiply"), GIMP_MULTIPLY_MODE},
124                                                               {QString("burn"), GIMP_BURN_MODE},
125                                                               {QString("overlay"), GIMP_OVERLAY_MODE},
126                                                               {QString("softlight"), GIMP_SOFTLIGHT_MODE},
127                                                               {QString("hardlight"), GIMP_HARDLIGHT_MODE},
128                                                               {QString("difference"), GIMP_DIFFERENCE_MODE},
129                                                               {QString("subtract"), GIMP_SUBTRACT_MODE},
130                                                               {QString("grainextract"), GIMP_GRAIN_EXTRACT_MODE},
131                                                               {QString("grainmerge"), GIMP_GRAIN_MERGE_MODE},
132                                                               {QString("divide"), GIMP_DIVIDE_MODE},
133                                                               {QString("hue"), GIMP_HUE_MODE},
134                                                               {QString("saturation"), GIMP_SATURATION_MODE},
135                                                               {QString("color"), GIMP_COLOR_MODE},
136                                                               {QString("value"), GIMP_VALUE_MODE}};
137 #endif
138 
reverseBlendingModeMap(const QMap<QString,GimpLayerModeEffects> & string2mode)139 QMap<GimpLayerModeEffects, QString> reverseBlendingModeMap(const QMap<QString, GimpLayerModeEffects> & string2mode)
140 {
141   QMap<GimpLayerModeEffects, QString> result;
142   QMap<QString, GimpLayerModeEffects>::const_iterator it = string2mode.cbegin();
143   while (it != string2mode.cend()) {
144     result[it.value()] = it.key();
145     ++it;
146   }
147   result[GIMP_NORMAL_MODE] = QString("alpha");
148   return result;
149 }
150 
blendingMode2String(const GimpLayerModeEffects & blendingMode)151 QString blendingMode2String(const GimpLayerModeEffects & blendingMode)
152 {
153   static QMap<GimpLayerModeEffects, QString> mode2string = reverseBlendingModeMap(BlendingModesMap);
154   QMap<GimpLayerModeEffects, QString>::const_iterator it = mode2string.find(blendingMode);
155   if (it != mode2string.cend()) {
156     return it.value();
157   } else {
158     return QString("alpha");
159   }
160 }
161 
162 // Get layer blending mode from string.
163 //-------------------------------------
get_output_layer_props(const char * const s,GimpLayerModeEffects & blendmode,double & opacity,int & posx,int & posy,cimg_library::CImg<char> & name)164 void get_output_layer_props(const char * const s, GimpLayerModeEffects & blendmode, double & opacity, int & posx, int & posy, cimg_library::CImg<char> & name)
165 {
166   if (!s || !*s)
167     return;
168   QString str(s);
169 
170   // Read output blending mode.
171   QRegExp modeRe("mode\\(\\s*([^)]*)\\s*\\)");
172   if (modeRe.indexIn(str) != -1) {
173     QString modeStr = modeRe.cap(1).trimmed();
174     if (BlendingModesMap.find(modeStr) != BlendingModesMap.end()) {
175       blendmode = BlendingModesMap[modeStr];
176     }
177   }
178 
179   // Read output opacity.
180   QRegExp opacityRe("opacity\\(\\s*([^)]*)\\s*\\)");
181   if (opacityRe.indexIn(str) != -1) {
182     QString opacityStr = opacityRe.cap(1).trimmed();
183     bool ok = false;
184     double x = opacityStr.toDouble(&ok);
185     if (ok) {
186       opacity = x;
187       if (opacity < 0) {
188         opacity = 0;
189       } else if (opacity > 100) {
190         opacity = 100;
191       }
192     }
193   }
194 
195   // Read output positions.
196   QRegExp posRe("pos\\(\\s*(-?\\d*)[^)](-?\\d*)\\s*\\)");
197   if (posRe.indexIn(str) != -1) {
198     QString xStr = posRe.cap(1);
199     QString yStr = posRe.cap(2);
200     bool okX = false;
201     bool okY = false;
202     int x = xStr.toInt(&okX);
203     int y = yStr.toInt(&okY);
204     if (okX && okY) {
205       posx = x;
206       posy = y;
207     }
208   }
209 
210   // Read output name.
211   const char * S = std::strstr(s, "name(");
212   if (S) {
213     const char * ps = S + 5;
214     unsigned int level = 1;
215     while (*ps && level) {
216       if (*ps == '(') {
217         ++level;
218       } else if (*ps == ')') {
219         --level;
220       }
221       ++ps;
222     }
223     if (!level || *(ps - 1) == ')') {
224       name.assign(S + 5, (unsigned int)(ps - S - 5)).back() = 0;
225       cimg_for(name, pn, char)
226       {
227         if (*pn == 21) {
228           *pn = '(';
229         } else if (*pn == 22) {
230           *pn = ')';
231         }
232       }
233     }
234   }
235 }
236 
get_gimp_layers_flat_list(int imageId,int * count)237 const int * get_gimp_layers_flat_list(int imageId, int * count)
238 {
239   static std::vector<int> layersId;
240   std::stack<int> idStack;
241 
242   int layersCount = 0;
243   const int * layers = gimp_image_get_layers(imageId, &layersCount);
244   for (int i = layersCount - 1; i >= 0; --i) {
245     idStack.push(layers[i]);
246   }
247 
248   layersId.clear();
249   while (!idStack.empty()) {
250     if (gimp_item_is_group(idStack.top())) {
251       int childCount = 0;
252       const int * children = gimp_item_get_children(idStack.top(), &childCount);
253       idStack.pop();
254       for (int i = childCount - 1; i >= 0; --i) {
255         idStack.push(children[i]);
256       }
257     } else {
258       layersId.push_back(idStack.top());
259       idStack.pop();
260     }
261   }
262   *count = layersId.size();
263   return layersId.data();
264 }
265 
266 } // namespace
267 
gmic_qt_show_message(const char * message)268 void gmic_qt_show_message(const char * message)
269 {
270   static bool first = true;
271 
272   if (first) {
273     gimp_progress_init(message);
274     first = false;
275   } else {
276     gimp_progress_set_text_printf("%s", message);
277   }
278 }
279 
gmic_qt_apply_color_profile(cimg_library::CImg<float> & image)280 void gmic_qt_apply_color_profile(cimg_library::CImg<float> & image)
281 {
282 #if (GIMP_MAJOR_VERSION < 2) || ((GIMP_MAJOR_VERSION == 2) && (GIMP_MINOR_VERSION <= 8))
283   unused(image);
284 // SWAP RED<->GREEN CHANNELS : FOR TESTING PURPOSE ONLY!
285 //  cimg_forXY(image,x,y) {
286 //    std::swap(image(x,y,0,0),image(x,y,0,1));
287 //  }
288 #else
289   unused(image);
290 //  GimpColorProfile * const img_profile = gimp_image_get_effective_color_profile(gmic_qt_gimp_image_id);
291 //  GimpColorConfig * const color_config = gimp_get_color_configuration();
292 //  if (!img_profile || !color_config) {
293 //    return;
294 //  }
295 
296 //    if (!image || image.spectrum() < 3 || image.spectrum() > 4 ) {
297 //      continue;
298 //    }
299 //    const Babl *const fmt = babl_format(image.spectrum()==3?"R'G'B' float":"R'G'B'A float");
300 //    GimpColorTransform *const transform = gimp_widget_get_color_transform(gui_preview,
301 //                                                                          color_config,
302 //                                                                          img_profile,
303 //                                                                          fmt,
304 //                                                                          fmt);
305 //    if (!transform) {
306 //      continue;
307 //    }
308 //    cimg_library::CImg<float> corrected;
309 //    image.get_permute_axes("cxyz").move_to(corrected) /= 255;
310 //    gimp_color_transform_process_pixels(transform,fmt,corrected,fmt,corrected,
311 //                                        corrected.height()*corrected.depth());
312 //    (corrected.permute_axes("yzcx")*=255).cut(0,255).move_to(image);
313 //    g_object_unref(transform);
314 #endif
315 }
316 
gmic_qt_get_layers_extent(int * width,int * height,GmicQt::InputMode mode)317 void gmic_qt_get_layers_extent(int * width, int * height, GmicQt::InputMode mode)
318 {
319   int layersCount = 0;
320   // const int * begLayers = gimp_image_get_layers(gmic_qt_gimp_image_id, &layersCount);
321   const int * begLayers = get_gimp_layers_flat_list(gmic_qt_gimp_image_id, &layersCount);
322   const int * endLayers = begLayers + layersCount;
323   int activeLayerID = gimp_image_get_active_layer(gmic_qt_gimp_image_id);
324 
325   // Build list of input layers IDs
326   std::vector<int> layers;
327   switch (mode) {
328   case GmicQt::NoInput:
329     break;
330   case GmicQt::Active:
331     if ((activeLayerID >= 0) && !gimp_item_is_group(activeLayerID)) {
332       layers.push_back(activeLayerID);
333     }
334     break;
335   case GmicQt::All:
336   case GmicQt::AllDesc:
337     layers.assign(begLayers, endLayers);
338     break;
339   case GmicQt::ActiveAndBelow:
340     if ((activeLayerID >= 0) && !gimp_item_is_group(activeLayerID)) {
341       layers.push_back(activeLayerID);
342       const int * p = std::find(begLayers, endLayers, activeLayerID);
343       if (p < endLayers - 1) {
344         layers.push_back(*(p + 1));
345       }
346     }
347     break;
348   case GmicQt::ActiveAndAbove:
349     if ((activeLayerID >= 0) && !gimp_item_is_group(activeLayerID)) {
350       const int * p = std::find(begLayers, endLayers, activeLayerID);
351       if (p > begLayers) {
352         layers.push_back(*(p - 1));
353       }
354       layers.push_back(activeLayerID);
355     }
356     break;
357   case GmicQt::AllVisibles:
358   case GmicQt::AllVisiblesDesc:
359   case GmicQt::AllInvisibles:
360   case GmicQt::AllInvisiblesDesc: {
361     bool visibility = (mode == GmicQt::AllVisibles || mode == GmicQt::AllVisiblesDesc);
362     for (int i = 0; i < layersCount; ++i) {
363       if (_gimp_item_get_visible(begLayers[i]) == visibility) {
364         layers.push_back(begLayers[i]);
365       }
366     }
367   } break;
368   default:
369     break;
370   }
371 
372   gint rgn_x, rgn_y, rgn_width, rgn_height;
373   *width = 0;
374   *height = 0;
375   for (int layer : layers) {
376     if (!gimp_item_is_valid(layer)) {
377       continue;
378     }
379     if (!gimp_drawable_mask_intersect(layer, &rgn_x, &rgn_y, &rgn_width, &rgn_height)) {
380       continue;
381     }
382     *width = std::max(*width, rgn_width);
383     *height = std::max(*height, rgn_height);
384   }
385 }
386 
gmic_qt_get_image_size(int * width,int * height)387 void gmic_qt_get_image_size(int * width, int * height)
388 {
389   *width = 0;
390   *height = 0;
391   int layersCount = 0;
392   get_gimp_layers_flat_list(gmic_qt_gimp_image_id, &layersCount);
393   if (layersCount > 0) {
394     int active_layer_id = gimp_image_get_active_layer(gmic_qt_gimp_image_id);
395     if (active_layer_id >= 0) {
396       if (gimp_item_is_valid(active_layer_id)) {
397         gint32 id = gimp_item_get_image(active_layer_id);
398         *width = gimp_image_width(id);
399         *height = gimp_image_height(id);
400       }
401     }
402   }
403 }
404 
gmic_qt_get_cropped_images(gmic_list<float> & images,gmic_list<char> & imageNames,double x,double y,double width,double height,GmicQt::InputMode mode)405 void gmic_qt_get_cropped_images(gmic_list<float> & images, gmic_list<char> & imageNames, double x, double y, double width, double height, GmicQt::InputMode mode)
406 {
407   using cimg_library::CImg;
408   using cimg_library::CImgList;
409   int layersCount = 0;
410   const int * layers = get_gimp_layers_flat_list(gmic_qt_gimp_image_id, &layersCount);
411   const int * end_layers = layers + layersCount;
412   int active_layer_id = gimp_image_get_active_layer(gmic_qt_gimp_image_id);
413 
414   const bool entireImage = (x < 0 && y < 0 && width < 0 && height < 0) || (x == 0.0 && y == 0 && width == 1 && height == 0);
415   if (entireImage) {
416     x = 0.0;
417     y = 0.0;
418     width = 1.0;
419     height = 1.0;
420   }
421 
422   // Build list of input layers IDs
423   inputLayers.clear();
424   switch (mode) {
425   case GmicQt::NoInput:
426     break;
427   case GmicQt::Active:
428     if ((active_layer_id >= 0) && !gimp_item_is_group(active_layer_id)) {
429       inputLayers.push_back(active_layer_id);
430     }
431     break;
432   case GmicQt::All:
433   case GmicQt::AllDesc:
434     inputLayers.assign(layers, end_layers);
435     if (mode == GmicQt::AllDesc) {
436       std::reverse(inputLayers.begin(), inputLayers.end());
437     }
438     break;
439   case GmicQt::ActiveAndBelow:
440     if ((active_layer_id >= 0) && !gimp_item_is_group(active_layer_id)) {
441       inputLayers.push_back(active_layer_id);
442       const int * p = std::find(layers, end_layers, active_layer_id);
443       if (p < end_layers - 1) {
444         inputLayers.push_back(*(p + 1));
445       }
446     }
447     break;
448   case GmicQt::ActiveAndAbove:
449     if ((active_layer_id >= 0) && !gimp_item_is_group(active_layer_id)) {
450       const int * p = std::find(layers, end_layers, active_layer_id);
451       if (p > layers) {
452         inputLayers.push_back(*(p - 1));
453       }
454       inputLayers.push_back(active_layer_id);
455     }
456     break;
457   case GmicQt::AllVisibles:
458   case GmicQt::AllVisiblesDesc:
459   case GmicQt::AllInvisibles:
460   case GmicQt::AllInvisiblesDesc: {
461     bool visibility = (mode == GmicQt::AllVisibles || mode == GmicQt::AllVisiblesDesc);
462     for (int i = 0; i < layersCount; ++i) {
463       if (_gimp_item_get_visible(layers[i]) == visibility) {
464         inputLayers.push_back(layers[i]);
465       }
466     }
467     if (mode == GmicQt::AllVisiblesDesc || mode == GmicQt::AllInvisiblesDesc) {
468       std::reverse(inputLayers.begin(), inputLayers.end());
469     }
470   } break;
471   default:
472     break;
473   }
474 
475   // Retrieve image list
476   images.assign(inputLayers.size());
477   imageNames.assign(inputLayers.size());
478   inputLayerDimensions.assign(inputLayers.size(), 4);
479   gint rgn_x, rgn_y, rgn_width, rgn_height;
480 
481   gboolean isSelection = 0;
482   gint selX1 = 0, selY1 = 0, selX2 = 0, selY2 = 0;
483   if (!gimp_selection_bounds(gmic_qt_gimp_image_id, &isSelection, &selX1, &selY1, &selX2, &selY2)) {
484     isSelection = 0;
485     selX1 = 0;
486     selY1 = 0;
487   }
488 
489   cimglist_for(images, l)
490   {
491     if (!gimp_item_is_valid(inputLayers[l])) {
492       continue;
493     }
494     if (!gimp_drawable_mask_intersect(inputLayers[l], &rgn_x, &rgn_y, &rgn_width, &rgn_height)) {
495       inputLayerDimensions(l, 0) = 0;
496       inputLayerDimensions(l, 1) = 0;
497       inputLayerDimensions(l, 2) = 0;
498       inputLayerDimensions(l, 3) = 0;
499       continue;
500     }
501     const int spectrum = (gimp_drawable_is_rgb(inputLayers[l]) ? 3 : 1) + (gimp_drawable_has_alpha(inputLayers[l]) ? 1 : 0);
502 
503     const int dw = static_cast<int>(gimp_drawable_width(inputLayers[l]));
504     const int dh = static_cast<int>(gimp_drawable_height(inputLayers[l]));
505     const int ix = static_cast<int>(entireImage ? rgn_x : (rgn_x + x * rgn_width));
506     const int iy = static_cast<int>(entireImage ? rgn_y : (rgn_y + y * rgn_height));
507     const int iw = entireImage ? rgn_width : std::min(dw - ix, static_cast<int>(1 + std::ceil(rgn_width * width)));
508     const int ih = entireImage ? rgn_height : std::min(dh - iy, static_cast<int>(1 + std::ceil(rgn_height * height)));
509 
510     if (entireImage) {
511       inputLayerDimensions(l, 0) = rgn_width;
512       inputLayerDimensions(l, 1) = rgn_height;
513     } else {
514       inputLayerDimensions(l, 0) = iw;
515       inputLayerDimensions(l, 1) = ih;
516     }
517     inputLayerDimensions(l, 2) = 1;
518     inputLayerDimensions(l, 3) = spectrum;
519 
520     const float opacity = gimp_layer_get_opacity(inputLayers[l]);
521     const GimpLayerModeEffects blendMode = gimp_layer_get_mode(inputLayers[l]);
522     int xPos = 0;
523     int yPos = 0;
524     if (isSelection) {
525       xPos = selX1;
526       yPos = selY1;
527     } else {
528       gimp_drawable_offsets(inputLayers[l], &xPos, &yPos);
529     }
530     QString noParenthesisName(gimp_item_get_name(inputLayers[l]));
531     noParenthesisName.replace(QChar('('), QChar(21)).replace(QChar(')'), QChar(22));
532 
533     QString name = QString("mode(%1),opacity(%2),pos(%3,%4),name(%5)").arg(blendingMode2String(blendMode)).arg(opacity).arg(xPos).arg(yPos).arg(noParenthesisName);
534     QByteArray ba = name.toUtf8();
535     gmic_image<char>::string(ba.constData()).move_to(imageNames[l]);
536 #if (GIMP_MAJOR_VERSION < 2) || ((GIMP_MAJOR_VERSION == 2) && (GIMP_MINOR_VERSION <= 8))
537     GimpDrawable * drawable = gimp_drawable_get(inputLayers[l]);
538     GimpPixelRgn region;
539     gimp_pixel_rgn_init(&region, drawable, ix, iy, iw, ih, false, false);
540     CImg<unsigned char> img(spectrum, iw, ih);
541     gimp_pixel_rgn_get_rect(&region, img, ix, iy, iw, ih);
542     gimp_drawable_detach(drawable);
543     img.permute_axes("yzcx");
544 #else
545     GeglRectangle rect;
546     gegl_rectangle_set(&rect, ix, iy, iw, ih);
547     GeglBuffer * buffer = gimp_drawable_get_buffer(inputLayers[l]);
548     const char * const format = spectrum == 1 ? "Y' " gmic_pixel_type_str : spectrum == 2 ? "Y'A " gmic_pixel_type_str : spectrum == 3 ? "R'G'B' " gmic_pixel_type_str : "R'G'B'A " gmic_pixel_type_str;
549     CImg<float> img(spectrum, iw, ih);
550     gegl_buffer_get(buffer, &rect, 1, babl_format(format), img.data(), 0, GEGL_ABYSS_NONE);
551     (img *= 255).permute_axes("yzcx");
552     g_object_unref(buffer);
553 #endif
554     img.move_to(images[l]);
555   }
556 }
557 
gmic_qt_output_images(gmic_list<gmic_pixel_type> & images,const gmic_list<char> & imageNames,GmicQt::OutputMode outputMode,const char * verboseLayersLabel)558 void gmic_qt_output_images(gmic_list<gmic_pixel_type> & images, const gmic_list<char> & imageNames, GmicQt::OutputMode outputMode, const char * verboseLayersLabel)
559 {
560   // Output modes in original gmic_gimp_gtk : 0/Replace 1/New layer 2/New active layer  3/New image
561 
562   // spectrum to GIMP image types conversion table
563   GimpImageType spectrum2gimpImageTypes[5] = {GIMP_INDEXED_IMAGE, // (unused)
564                                               GIMP_GRAY_IMAGE, GIMP_GRAYA_IMAGE, GIMP_RGB_IMAGE, GIMP_RGBA_IMAGE};
565 
566   // Get output layers dimensions and check if input/output layers have compatible dimensions.
567   unsigned int max_spectrum = 0;
568   struct Position {
569     gint x, y;
570     Position() : x(0), y(0) {}
571   } top_left, bottom_right;
572   cimglist_for(images, l)
573   {
574     if (images[l].is_empty()) {
575       images.remove(l--);
576       continue;
577     } // Discard possible empty images.
578 
579     bottom_right.x = std::max(bottom_right.x, (gint)images[l]._width);
580     bottom_right.y = std::max(bottom_right.y, (gint)images[l]._height);
581     if (images[l]._spectrum > max_spectrum) {
582       max_spectrum = images[l]._spectrum;
583     }
584   }
585 
586   int image_nb_layers = 0;
587   get_gimp_layers_flat_list(gmic_qt_gimp_image_id, &image_nb_layers);
588   unsigned int image_width = 0, image_height = 0;
589   if (inputLayers.size()) {
590     image_width = gimp_image_width(gmic_qt_gimp_image_id);
591     image_height = gimp_image_height(gmic_qt_gimp_image_id);
592   }
593 
594   int is_selection = 0, sel_x0 = 0, sel_y0 = 0, sel_x1 = 0, sel_y1 = 0;
595   if (!gimp_selection_bounds(gmic_qt_gimp_image_id, &is_selection, &sel_x0, &sel_y0, &sel_x1, &sel_y1)) {
596     is_selection = 0;
597   } else if (outputMode == GmicQt::InPlace || outputMode == GmicQt::NewImage) {
598     sel_x0 = sel_y0 = 0;
599   }
600 
601   bool is_compatible_dimensions = (images.size() == inputLayers.size());
602   for (unsigned int p = 0; p < images.size() && is_compatible_dimensions; ++p) {
603     const cimg_library::CImg<gmic_pixel_type> & img = images[p];
604     const bool source_is_alpha = (inputLayerDimensions(p, 3) == 2 || inputLayerDimensions(p, 3) >= 4);
605     const bool dest_is_alpha = (img.spectrum() == 2 || img.spectrum() >= 4);
606     if (dest_is_alpha && !source_is_alpha) {
607       gimp_layer_add_alpha(inputLayers[p]);
608       ++inputLayerDimensions(p, 3);
609     }
610     if (img.width() != inputLayerDimensions(p, 0) || img.height() != inputLayerDimensions(p, 1)) {
611       is_compatible_dimensions = false;
612     }
613   }
614 
615   // Transfer output layers back into GIMP.
616   GimpLayerModeEffects layer_blendmode = GIMP_NORMAL_MODE;
617   gint layer_posx = 0, layer_posy = 0;
618   double layer_opacity = 100;
619   cimg_library::CImg<char> layer_name;
620 
621   if (outputMode == GmicQt::InPlace) {
622     gint rgn_x, rgn_y, rgn_width, rgn_height;
623     gimp_image_undo_group_start(gmic_qt_gimp_image_id);
624     if (is_compatible_dimensions) { // Direct replacement of the layer data.
625       for (unsigned int p = 0; p < images.size(); ++p) {
626         layer_blendmode = gimp_layer_get_mode(inputLayers[p]);
627         layer_opacity = gimp_layer_get_opacity(inputLayers[p]);
628         gimp_drawable_offsets(inputLayers[p], &layer_posx, &layer_posy);
629         cimg_library::CImg<char>::string(gimp_item_get_name(inputLayers[p])).move_to(layer_name);
630         get_output_layer_props(imageNames[p], layer_blendmode, layer_opacity, layer_posx, layer_posy, layer_name);
631         if (is_selection) {
632           layer_posx = 0;
633           layer_posy = 0;
634         }
635         cimg_library::CImg<gmic_pixel_type> & img = images[p];
636         GmicQt::calibrate_image(img, inputLayerDimensions(p, 3), false);
637         if (gimp_drawable_mask_intersect(inputLayers[p], &rgn_x, &rgn_y, &rgn_width, &rgn_height)) {
638 #if (GIMP_MAJOR_VERSION < 2) || ((GIMP_MAJOR_VERSION == 2) && (GIMP_MINOR_VERSION <= 8))
639           GimpDrawable * drawable = gimp_drawable_get(inputLayers[p]);
640           GimpPixelRgn region;
641           gimp_pixel_rgn_init(&region, drawable, rgn_x, rgn_y, rgn_width, rgn_height, true, true);
642           GmicQt::image2uchar(img);
643           gimp_pixel_rgn_set_rect(&region, (guchar *)img.data(), rgn_x, rgn_y, rgn_width, rgn_height);
644           gimp_drawable_flush(drawable);
645           gimp_drawable_merge_shadow(inputLayers[p], true);
646           gimp_drawable_update(inputLayers[p], rgn_x, rgn_y, rgn_width, rgn_height);
647           gimp_drawable_detach(drawable);
648 #else
649           GeglRectangle rect;
650           gegl_rectangle_set(&rect, rgn_x, rgn_y, rgn_width, rgn_height);
651           GeglBuffer * buffer = gimp_drawable_get_shadow_buffer(inputLayers[p]);
652           const char * const format = img.spectrum() == 1 ? "Y' float" : img.spectrum() == 2 ? "Y'A float" : img.spectrum() == 3 ? "R'G'B' float" : "R'G'B'A float";
653           (img /= 255).permute_axes("cxyz");
654           gegl_buffer_set(buffer, &rect, 0, babl_format(format), img.data(), 0);
655           g_object_unref(buffer);
656           gimp_drawable_merge_shadow(inputLayers[p], true);
657           gimp_drawable_update(inputLayers[p], 0, 0, img.width(), img.height());
658 #endif
659           gimp_layer_set_mode(inputLayers[p], layer_blendmode);
660           gimp_layer_set_opacity(inputLayers[p], layer_opacity);
661           gimp_layer_set_offsets(inputLayers[p], layer_posx, layer_posy);
662 
663           if (verboseLayersLabel) { // Verbose (layer name)
664             gimp_item_set_name(inputLayers[p], verboseLayersLabel);
665           } else if (layer_name) {
666             gimp_item_set_name(inputLayers[p], layer_name);
667           }
668         }
669         img.assign();
670       }
671     } else { // Indirect replacement: create new layers.
672       gimp_selection_none(gmic_qt_gimp_image_id);
673       const int layer_pos = _gimp_image_get_item_position(gmic_qt_gimp_image_id, inputLayers[0]);
674       top_left.x = top_left.y = 0;
675       bottom_right.x = bottom_right.y = 0;
676       for (unsigned int p = 0; p < images.size(); ++p) {
677         if (images[p]) {
678           layer_posx = layer_posy = 0;
679           if (p < inputLayers.size()) {
680             layer_blendmode = gimp_layer_get_mode(inputLayers[p]);
681             layer_opacity = gimp_layer_get_opacity(inputLayers[p]);
682             if (!is_selection) {
683               gimp_drawable_offsets(inputLayers[p], &layer_posx, &layer_posy);
684             }
685             cimg_library::CImg<char>::string(gimp_item_get_name(inputLayers[p])).move_to(layer_name);
686             gimp_image_remove_layer(gmic_qt_gimp_image_id, inputLayers[p]);
687           } else {
688             layer_blendmode = GIMP_NORMAL_MODE;
689             layer_opacity = 100;
690             layer_name.assign();
691           }
692           get_output_layer_props(imageNames[p], layer_blendmode, layer_opacity, layer_posx, layer_posy, layer_name);
693           if (is_selection) {
694             layer_posx = 0;
695             layer_posy = 0;
696           }
697           top_left.x = std::min(top_left.x, layer_posx);
698           top_left.y = std::min(top_left.y, layer_posy);
699           bottom_right.x = std::max(bottom_right.x, (gint)(layer_posx + images[p]._width));
700           bottom_right.y = std::max(bottom_right.y, (gint)(layer_posy + images[p]._height));
701           cimg_library::CImg<gmic_pixel_type> & img = images[p];
702           if (gimp_image_base_type(gmic_qt_gimp_image_id) == GIMP_GRAY) {
703             GmicQt::calibrate_image(img, (img.spectrum() == 1 || img.spectrum() == 3) ? 1 : 2, false);
704           } else {
705             GmicQt::calibrate_image(img, (img.spectrum() == 1 || img.spectrum() == 3) ? 3 : 4, false);
706           }
707           gint layer_id = gimp_layer_new(gmic_qt_gimp_image_id, verboseLayersLabel, img.width(), img.height(), spectrum2gimpImageTypes[std::min(img.spectrum(), 4)], layer_opacity, layer_blendmode);
708           gimp_layer_set_offsets(layer_id, layer_posx, layer_posy);
709           if (verboseLayersLabel) {
710             gimp_item_set_name(layer_id, verboseLayersLabel);
711           } else if (layer_name) {
712             gimp_item_set_name(layer_id, layer_name);
713           }
714           gimp_image_insert_layer(gmic_qt_gimp_image_id, layer_id, -1, layer_pos + p);
715 
716 #if (GIMP_MAJOR_VERSION < 2) || ((GIMP_MAJOR_VERSION == 2) && (GIMP_MINOR_VERSION <= 8))
717           GimpDrawable * drawable = gimp_drawable_get(layer_id);
718           GimpPixelRgn region;
719           gimp_pixel_rgn_init(&region, drawable, 0, 0, drawable->width, drawable->height, true, true);
720           GmicQt::image2uchar(img);
721           gimp_pixel_rgn_set_rect(&region, (guchar *)img.data(), 0, 0, img.width(), img.height());
722           gimp_drawable_flush(drawable);
723           gimp_drawable_merge_shadow(layer_id, true);
724           gimp_drawable_update(layer_id, 0, 0, drawable->width, drawable->height);
725           gimp_drawable_detach(drawable);
726 #else
727           GeglBuffer * buffer = gimp_drawable_get_shadow_buffer(layer_id);
728           const char * const format = img.spectrum() == 1 ? "Y' float" : img.spectrum() == 2 ? "Y'A float" : img.spectrum() == 3 ? "R'G'B' float" : "R'G'B'A float";
729           (img /= 255).permute_axes("cxyz");
730           gegl_buffer_set(buffer, NULL, 0, babl_format(format), img.data(), 0);
731           g_object_unref(buffer);
732           gimp_drawable_merge_shadow(layer_id, true);
733           gimp_drawable_update(layer_id, 0, 0, img.width(), img.height());
734 #endif
735           img.assign();
736         }
737       }
738       const unsigned int max_width = bottom_right.x - top_left.x;
739       const unsigned int max_height = bottom_right.y - top_left.y;
740       for (unsigned int p = images._width; p < inputLayers.size(); ++p) {
741         gimp_image_remove_layer(gmic_qt_gimp_image_id, inputLayers[p]);
742       }
743       if ((unsigned int)image_nb_layers == inputLayers.size()) {
744         gimp_image_resize(gmic_qt_gimp_image_id, max_width, max_height, -top_left.x, -top_left.y);
745       } else {
746         gimp_image_resize(gmic_qt_gimp_image_id, std::max(image_width, max_width), std::max(image_height, max_height), 0, 0);
747       }
748     }
749     gimp_image_undo_group_end(gmic_qt_gimp_image_id);
750   } else if (outputMode == GmicQt::NewActiveLayers || outputMode == GmicQt::NewLayers) {
751     const gint active_layer_id = gimp_image_get_active_layer(gmic_qt_gimp_image_id);
752     if (active_layer_id >= 0) {
753       gimp_image_undo_group_start(gmic_qt_gimp_image_id);
754       gint top_layer_id = 0, layer_id = 0;
755       top_left.x = top_left.y = 0;
756       bottom_right.x = bottom_right.y = 0;
757       for (unsigned int p = 0; p < images.size(); ++p) {
758         if (images[p]) {
759           layer_blendmode = GIMP_NORMAL_MODE;
760           layer_opacity = 100;
761           layer_posx = layer_posy = 0;
762           if (inputLayers.size() == 1) {
763             if (!is_selection) {
764               gimp_drawable_offsets(active_layer_id, &layer_posx, &layer_posy);
765             }
766             cimg_library::CImg<char>::string(gimp_item_get_name(active_layer_id)).move_to(layer_name);
767           } else {
768             layer_name.assign();
769           }
770           get_output_layer_props(imageNames[p], layer_blendmode, layer_opacity, layer_posx, layer_posy, layer_name);
771           top_left.x = std::min(top_left.x, layer_posx);
772           top_left.y = std::min(top_left.y, layer_posy);
773           bottom_right.x = std::max(bottom_right.x, (gint)(layer_posx + images[p]._width));
774           bottom_right.y = std::max(bottom_right.y, (gint)(layer_posy + images[p]._height));
775 
776           cimg_library::CImg<gmic_pixel_type> & img = images[p];
777           if (gimp_image_base_type(gmic_qt_gimp_image_id) == GIMP_GRAY) {
778             GmicQt::calibrate_image(img, !is_selection && (img.spectrum() == 1 || img.spectrum() == 3) ? 1 : 2, false);
779           } else {
780             GmicQt::calibrate_image(img, !is_selection && (img.spectrum() == 1 || img.spectrum() == 3) ? 3 : 4, false);
781           }
782           layer_id = gimp_layer_new(gmic_qt_gimp_image_id, verboseLayersLabel, img.width(), img.height(), spectrum2gimpImageTypes[std::min(img.spectrum(), 4)], layer_opacity, layer_blendmode);
783           if (!p) {
784             top_layer_id = layer_id;
785           }
786           gimp_layer_set_offsets(layer_id, layer_posx, layer_posy);
787           if (verboseLayersLabel) {
788             gimp_item_set_name(layer_id, verboseLayersLabel);
789           } else if (layer_name) {
790             gimp_item_set_name(layer_id, layer_name);
791           }
792           gimp_image_insert_layer(gmic_qt_gimp_image_id, layer_id, -1, p);
793 
794 #if (GIMP_MAJOR_VERSION < 2) || ((GIMP_MAJOR_VERSION == 2) && (GIMP_MINOR_VERSION <= 8))
795           GimpDrawable * drawable = gimp_drawable_get(layer_id);
796           GimpPixelRgn region;
797           gimp_pixel_rgn_init(&region, drawable, 0, 0, drawable->width, drawable->height, true, true);
798           GmicQt::image2uchar(img);
799           gimp_pixel_rgn_set_rect(&region, (guchar *)img.data(), 0, 0, img.width(), img.height());
800           gimp_drawable_flush(drawable);
801           gimp_drawable_merge_shadow(layer_id, true);
802           gimp_drawable_update(layer_id, 0, 0, drawable->width, drawable->height);
803           gimp_drawable_detach(drawable);
804 #else
805           GeglBuffer * buffer = gimp_drawable_get_shadow_buffer(layer_id);
806           const char * const format = img.spectrum() == 1 ? "Y' float" : img.spectrum() == 2 ? "Y'A float" : img.spectrum() == 3 ? "R'G'B' float" : "R'G'B'A float";
807           (img /= 255).permute_axes("cxyz");
808           gegl_buffer_set(buffer, NULL, 0, babl_format(format), img.data(), 0);
809           g_object_unref(buffer);
810           gimp_drawable_merge_shadow(layer_id, true);
811           gimp_drawable_update(layer_id, 0, 0, img.width(), img.height());
812 #endif
813           img.assign();
814         }
815         const unsigned int max_width = bottom_right.x - top_left.x;
816         const unsigned int max_height = bottom_right.y - top_left.y;
817         const unsigned int Mw = std::max(image_width, max_width);
818         const unsigned int Mh = std::max(image_height, max_height);
819         if (Mw && Mh) {
820           gimp_image_resize(gmic_qt_gimp_image_id, Mw, Mh, -top_left.x, -top_left.y);
821         }
822         if (outputMode == GmicQt::NewLayers) {
823           gimp_image_set_active_layer(gmic_qt_gimp_image_id, active_layer_id);
824         } else {
825           gimp_image_set_active_layer(gmic_qt_gimp_image_id, top_layer_id);
826         }
827       }
828       gimp_image_undo_group_end(gmic_qt_gimp_image_id);
829     }
830   } else if (outputMode == GmicQt::NewImage && images.size()) {
831     const gint active_layer_id = gimp_image_get_active_layer(gmic_qt_gimp_image_id);
832     const unsigned int max_width = (unsigned int)bottom_right.x;
833     const unsigned int max_height = (unsigned int)bottom_right.y;
834     if (active_layer_id >= 0) {
835 #if (GIMP_MAJOR_VERSION < 2) || ((GIMP_MAJOR_VERSION == 2) && (GIMP_MINOR_VERSION <= 8))
836       const int nimage_id = gimp_image_new(max_width, max_height, max_spectrum <= 2 ? GIMP_GRAY : GIMP_RGB);
837 #else
838       const int nimage_id = gimp_image_new_with_precision(max_width, max_height, max_spectrum <= 2 ? GIMP_GRAY : GIMP_RGB, gimp_image_get_precision(gmic_qt_gimp_image_id));
839 #endif
840       gimp_image_undo_group_start(nimage_id);
841       for (unsigned int p = 0; p < images.size(); ++p) {
842         if (images[p]) {
843           layer_blendmode = GIMP_NORMAL_MODE;
844           layer_opacity = 100;
845           layer_posx = layer_posy = 0;
846           if (inputLayers.size() == 1) {
847             if (!is_selection) {
848               gimp_drawable_offsets(active_layer_id, &layer_posx, &layer_posy);
849             }
850             cimg_library::CImg<char>::string(gimp_item_get_name(active_layer_id)).move_to(layer_name);
851           } else {
852             layer_name.assign();
853           }
854           get_output_layer_props(imageNames[p], layer_blendmode, layer_opacity, layer_posx, layer_posy, layer_name);
855           if (is_selection) {
856             layer_posx = 0;
857             layer_posy = 0;
858           }
859           cimg_library::CImg<gmic_pixel_type> & img = images[p];
860           if (gimp_image_base_type(nimage_id) != GIMP_GRAY) {
861             GmicQt::calibrate_image(img, (img.spectrum() == 1 || img.spectrum() == 3) ? 3 : 4, false);
862           }
863           gint layer_id = gimp_layer_new(nimage_id, verboseLayersLabel, img.width(), img.height(), spectrum2gimpImageTypes[std::min(img.spectrum(), 4)], layer_opacity, layer_blendmode);
864           gimp_layer_set_offsets(layer_id, layer_posx, layer_posy);
865           if (verboseLayersLabel) {
866             gimp_item_set_name(layer_id, verboseLayersLabel);
867           } else if (layer_name) {
868             gimp_item_set_name(layer_id, layer_name);
869           }
870           gimp_image_insert_layer(nimage_id, layer_id, -1, p);
871 
872 #if (GIMP_MAJOR_VERSION < 2) || ((GIMP_MAJOR_VERSION == 2) && (GIMP_MINOR_VERSION <= 8))
873           GimpDrawable * drawable = gimp_drawable_get(layer_id);
874           GimpPixelRgn region;
875           gimp_pixel_rgn_init(&region, drawable, 0, 0, drawable->width, drawable->height, true, true);
876           GmicQt::image2uchar(img);
877           gimp_pixel_rgn_set_rect(&region, (guchar *)img.data(), 0, 0, img.width(), img.height());
878           gimp_drawable_flush(drawable);
879           gimp_drawable_merge_shadow(layer_id, true);
880           gimp_drawable_update(layer_id, 0, 0, drawable->width, drawable->height);
881           gimp_drawable_detach(drawable);
882 #else
883           GeglBuffer * buffer = gimp_drawable_get_shadow_buffer(layer_id);
884           const char * const format = img.spectrum() == 1 ? "Y' float" : img.spectrum() == 2 ? "Y'A float" : img.spectrum() == 3 ? "R'G'B' float" : "R'G'B'A float";
885           (img /= 255).permute_axes("cxyz");
886           gegl_buffer_set(buffer, NULL, 0, babl_format(format), img.data(), 0);
887           g_object_unref(buffer);
888           gimp_drawable_merge_shadow(layer_id, true);
889           gimp_drawable_update(layer_id, 0, 0, img.width(), img.height());
890 #endif
891           img.assign();
892         }
893       }
894       gimp_display_new(nimage_id);
895       gimp_image_undo_group_end(nimage_id);
896     }
897   }
898 
899   gimp_displays_flush();
900 }
901 
902 /*
903  * 'Run' function, required by the GIMP plug-in API.
904  */
gmic_qt_run(const gchar *,gint,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)905 void gmic_qt_run(const gchar * /* name */, gint /* nparams */, const GimpParam * param, gint * nreturn_vals, GimpParam ** return_vals)
906 {
907   TIMING;
908 #if (GIMP_MAJOR_VERSION == 2 && GIMP_MINOR_VERSION > 8) || (GIMP_MAJOR_VERSION >= 3)
909   gegl_init(NULL, NULL);
910   gimp_plugin_enable_precision();
911 #endif
912 
913   static GimpParam return_values[1];
914   *return_vals = return_values;
915   *nreturn_vals = 1;
916   return_values[0].type = GIMP_PDB_STATUS;
917   int run_mode = (GimpRunMode)param[0].data.d_int32;
918   GimpPDBStatusType status = GIMP_PDB_SUCCESS;
919   switch (run_mode) {
920   case GIMP_RUN_INTERACTIVE:
921     gmic_qt_gimp_image_id = param[1].data.d_drawable;
922     launchPlugin();
923     break;
924   case GIMP_RUN_WITH_LAST_VALS:
925     gmic_qt_gimp_image_id = param[1].data.d_drawable;
926     launchPluginHeadlessUsingLastParameters();
927     break;
928   case GIMP_RUN_NONINTERACTIVE:
929     gmic_qt_gimp_image_id = param[1].data.d_drawable;
930     launchPluginHeadless(param[5].data.d_string, (GmicQt::InputMode)(param[3].data.d_int32 + GmicQt::NoInput), GmicQt::OutputMode(param[4].data.d_int32 + GmicQt::InPlace));
931     break;
932   }
933   return_values[0].data.d_status = status;
934 }
935 
gmic_qt_query()936 void gmic_qt_query()
937 {
938   static const GimpParamDef args[] = {{GIMP_PDB_INT32, (gchar *)"run_mode", (gchar *)"Interactive, non-interactive"},
939                                       {GIMP_PDB_IMAGE, (gchar *)"image", (gchar *)"Input image"},
940                                       {GIMP_PDB_DRAWABLE, (gchar *)"drawable", (gchar *)"Input drawable (unused)"},
941                                       {GIMP_PDB_INT32, (gchar *)"input",
942                                        (gchar *)"Input layers mode, when non-interactive"
943                                                 " (0=none, 1=active, 2=all, 3=active & below, 4=active & above, 5=all visibles, 6=all invisibles,"
944                                                 " 7=all visibles (decr.), 8=all invisibles (decr.), 9=all (decr.))"},
945                                       {GIMP_PDB_INT32, (gchar *)"output",
946                                        (gchar *)"Output mode, when non-interactive "
947                                                 "(0=in place,1=new layers,2=new active layers,3=new image)"},
948                                       {GIMP_PDB_STRING, (gchar *)"command", (gchar *)"G'MIC command string, when non-interactive"}};
949 
950   const char name[] = "plug-in-gmic-qt";
951   QByteArray blurb = QString("G'MIC-Qt (%1)").arg(GmicQt::gmicVersionString()).toLatin1();
952   QByteArray path("G'MIC-Qt...");
953   path.prepend("_");
954   gimp_install_procedure(name,                      // name
955                          blurb.constData(),         // blurb
956                          blurb.constData(),         // help
957                          "S\303\251bastien Fourey", // author
958                          "S\303\251bastien Fourey", // copyright
959                          "2017",                    // date
960                          path.constData(),          // menu_path
961                          "RGB*, GRAY*",             // image_types
962                          GIMP_PLUGIN,               // type
963                          G_N_ELEMENTS(args),        // nparams
964                          0,                         // nreturn_vals
965                          args,                      // params
966                          0);                        // return_vals
967   gimp_plugin_menu_register(name, "<Image>/Filters");
968 }
969 
970 GimpPlugInInfo PLUG_IN_INFO = {0, 0, gmic_qt_query, gmic_qt_run};
971 
972 MAIN()
973