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(®ion, drawable, ix, iy, iw, ih, false, false);
540 CImg<unsigned char> img(spectrum, iw, ih);
541 gimp_pixel_rgn_get_rect(®ion, 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(®ion, drawable, rgn_x, rgn_y, rgn_width, rgn_height, true, true);
642 GmicQt::image2uchar(img);
643 gimp_pixel_rgn_set_rect(®ion, (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(®ion, drawable, 0, 0, drawable->width, drawable->height, true, true);
720 GmicQt::image2uchar(img);
721 gimp_pixel_rgn_set_rect(®ion, (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(®ion, drawable, 0, 0, drawable->width, drawable->height, true, true);
798 GmicQt::image2uchar(img);
799 gimp_pixel_rgn_set_rect(®ion, (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(®ion, drawable, 0, 0, drawable->width, drawable->height, true, true);
876 GmicQt::image2uchar(img);
877 gimp_pixel_rgn_set_rect(®ion, (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