Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
blend_gui.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2012 johannes hanika.
4 Copyright (C) 2012 Richard Wonka.
5 Copyright (C) 2012-2014, 2016-2019 Tobias Ellinghaus.
6 Copyright (C) 2012-2015, 2017, 2019 Ulrich Pegelow.
7 Copyright (C) 2013, 2016, 2022 Aldric Renaudin.
8 Copyright (C) 2013 Dennis Gnad.
9 Copyright (C) 2013 parafin.
10 Copyright (C) 2013, 2018-2022 Pascal Obry.
11 Copyright (C) 2013-2016 Roman Lebedev.
12 Copyright (C) 2013 Thomas Pryds.
13 Copyright (C) 2017-2018, 2020 Heiko Bauke.
14 Copyright (C) 2017, 2019, 2021 luzpaz.
15 Copyright (C) 2018-2019 Edgardo Hoszowski.
16 Copyright (C) 2018 rawfiner.
17 Copyright (C) 2019, 2022-2023, 2025-2026 Aurélien PIERRE.
18 Copyright (C) 2019-2022 Diederik Ter Rahe.
19 Copyright (C) 2019 Florian Wernert.
20 Copyright (C) 2019 mepi0011.
21 Copyright (C) 2020-2022 Chris Elston.
22 Copyright (C) 2020-2021 Harold le Clément de Saint-Marcq.
23 Copyright (C) 2020-2021 Marco.
24 Copyright (C) 2020-2021 Ralf Brown.
25 Copyright (C) 2021 Hanno Schwalm.
26 Copyright (C) 2021 Hubert Kowalski.
27 Copyright (C) 2021 Marco Carrarini.
28 Copyright (C) 2021 Mark-64.
29 Copyright (C) 2021-2022 Nicolas Auffray.
30 Copyright (C) 2022 Martin Bařinka.
31 Copyright (C) 2022 Philipp Lutz.
32 Copyright (C) 2023 Alynx Zhou.
33 Copyright (C) 2023 Luca Zulberti.
34 Copyright (C) 2025-2026 Guillaume Stutin.
35
36 darktable is free software: you can redistribute it and/or modify
37 it under the terms of the GNU General Public License as published by
38 the Free Software Foundation, either version 3 of the License, or
39 (at your option) any later version.
40
41 darktable is distributed in the hope that it will be useful,
42 but WITHOUT ANY WARRANTY; without even the implied warranty of
43 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44 GNU General Public License for more details.
45
46 You should have received a copy of the GNU General Public License
47 along with darktable. If not, see <http://www.gnu.org/licenses/>.
48*/
49#include "system/macros.h"
50#include "system/mem_alloc.h"
51#include "system/simd.h"
52#include "control/signal.h"
54#include "develop/blend.h"
55#include "develop/blend_gui.h"
56#include "widgets/bauhaus.h"
58#include "gui/bauhaus_conf.h"
59#include "system/dtpthread.h"
60#include "math/math.h"
61#include "develop/iop_profile.h"
62#include "common/conf.h"
63#include "control/control.h"
64#include "develop/develop.h"
65#include "develop/imageop.h"
66#include "develop/imageop_gui.h"
67#include "develop/masks.h"
68#include "develop/masks_group.h" // dt_masks_group_set_member_operation()
69#include "develop/masks_gui.h"
70#include "widgets/button.h"
72
73#include "gui/actions/menu.h"
74#include "gui/application.h"
75#include "libs/colorpicker.h"
76
77#include <assert.h>
78#include <stdlib.h>
79#include <string.h>
81#include "widgets/container.h"
82#include "widgets/label.h"
83#include "widgets/notebook.h"
84#include "widgets/popup.h"
85#include "widgets/scroll_wrap.h"
88
89#define NEUTRAL_GRAY 0.5
90#define BLEND_MASKMODE_CONF_KEY "plugins/darkroom/blending/mask_mode_tab"
91
92/* The seven blend name tables are DEFINED in blend.c now: backend consumers
93 * (develop/supervisor.c) read them, so they cannot live in a GUI-only file. */
95 = { { 0.0f, { 0, 0, 0, 1.0 } },
96 { 0.125f, { NEUTRAL_GRAY / 8, NEUTRAL_GRAY / 8, NEUTRAL_GRAY / 8, 1.0 } },
97 { 0.25f, { NEUTRAL_GRAY / 4, NEUTRAL_GRAY / 4, NEUTRAL_GRAY / 4, 1.0 } },
98 { 0.5f, { NEUTRAL_GRAY / 2, NEUTRAL_GRAY / 2, NEUTRAL_GRAY / 2, 1.0 } },
99 { 1.0f, { NEUTRAL_GRAY, NEUTRAL_GRAY, NEUTRAL_GRAY, 1.0 } } };
100
101// The values for "a" are generated in the following way:
102// Lab (with L=[90 to 68], b=0, and a=[-56 to 56] -> sRGB (D65 linear) -> normalize with MAX(R,G,B) = 0.75
104 { 0.000f, { 0.0112790f, 0.7500000f, 0.5609999f, 1.0f } },
105 { 0.250f, { 0.2888855f, 0.7500000f, 0.6318934f, 1.0f } },
106 { 0.375f, { 0.4872486f, 0.7500000f, 0.6825501f, 1.0f } },
107 { 0.500f, { 0.7500000f, 0.7499399f, 0.7496052f, 1.0f } },
108 { 0.625f, { 0.7500000f, 0.5054633f, 0.5676756f, 1.0f } },
109 { 0.750f, { 0.7500000f, 0.3423850f, 0.4463195f, 1.0f } },
110 { 1.000f, { 0.7500000f, 0.1399815f, 0.2956989f, 1.0f } },
111};
112
113// The values for "b" are generated in the following way:
114// Lab (with L=[58 to 62], a=0, and b=[-65 to 65] -> sRGB (D65 linear) -> normalize with MAX(R,G,B) = 0.75
116 { 0.000f, { 0.0162050f, 0.1968228f, 0.7500000f, 1.0f } },
117 { 0.250f, { 0.2027354f, 0.3168822f, 0.7500000f, 1.0f } },
118 { 0.375f, { 0.3645722f, 0.4210476f, 0.7500000f, 1.0f } },
119 { 0.500f, { 0.6167146f, 0.5833379f, 0.7500000f, 1.0f } },
120 { 0.625f, { 0.7500000f, 0.6172369f, 0.5412091f, 1.0f } },
121 { 0.750f, { 0.7500000f, 0.5590797f, 0.3071980f, 1.0f } },
122 { 1.000f, { 0.7500000f, 0.4963975f, 0.0549797f, 1.0f } },
123};
124
126 = { { 0.0f, { 0, 0, 0, 1.0 } },
127 { 0.125f, { NEUTRAL_GRAY / 8, NEUTRAL_GRAY / 8, NEUTRAL_GRAY / 8, 1.0 } },
128 { 0.25f, { NEUTRAL_GRAY / 4, NEUTRAL_GRAY / 4, NEUTRAL_GRAY / 4, 1.0 } },
129 { 0.5f, { NEUTRAL_GRAY / 2, NEUTRAL_GRAY / 2, NEUTRAL_GRAY / 2, 1.0 } },
130 { 1.0f, { NEUTRAL_GRAY, NEUTRAL_GRAY, NEUTRAL_GRAY, 1.0 } } };
131
133 { 0.000f, { 0.0000000f, 0.0000000f, 0.0000000f, 1.0f } },
134 { 0.125f, { 0.0937500f, 0.0000000f, 0.0000000f, 1.0f } },
135 { 0.250f, { 0.1875000f, 0.0000000f, 0.0000000f, 1.0f } },
136 { 0.500f, { 0.3750000f, 0.0000000f, 0.0000000f, 1.0f } },
137 { 1.000f, { 0.7500000f, 0.0000000f, 0.0000000f, 1.0f } }
138};
139
141 { 0.000f, { 0.0000000f, 0.0000000f, 0.0000000f, 1.0f } },
142 { 0.125f, { 0.0000000f, 0.0937500f, 0.0000000f, 1.0f } },
143 { 0.250f, { 0.0000000f, 0.1875000f, 0.0000000f, 1.0f } },
144 { 0.500f, { 0.0000000f, 0.3750000f, 0.0000000f, 1.0f } },
145 { 1.000f, { 0.0000000f, 0.7500000f, 0.0000000f, 1.0f } }
146};
147
149 { 0.000f, { 0.0000000f, 0.0000000f, 0.0000000f, 1.0f } },
150 { 0.125f, { 0.0000000f, 0.0000000f, 0.0937500f, 1.0f } },
151 { 0.250f, { 0.0000000f, 0.0000000f, 0.1875000f, 1.0f } },
152 { 0.500f, { 0.0000000f, 0.0000000f, 0.3750000f, 1.0f } },
153 { 1.000f, { 0.0000000f, 0.0000000f, 0.7500000f, 1.0f } }
154};
155
156// The chroma values are displayed in a gradient from {0.5,0.5,0.5} to {0.5,0.0,0.5} (pink)
158 { 0.000f, { 0.5000000f, 0.5000000f, 0.5000000f, 1.0f } },
159 { 0.125f, { 0.5000000f, 0.4375000f, 0.5000000f, 1.0f } },
160 { 0.250f, { 0.5000000f, 0.3750000f, 0.5000000f, 1.0f } },
161 { 0.500f, { 0.5000000f, 0.2500000f, 0.5000000f, 1.0f } },
162 { 1.000f, { 0.5000000f, 0.0000000f, 0.5000000f, 1.0f } }
163};
164
165// The hue values for LCh are generated in the following way:
166// LCh (with L=65 and C=37) -> sRGB (D65 linear) -> normalize with MAX(R,G,B) = 0.75
167// Please keep in sync with the display in the gamma module
169 { 0.000f, { 0.7500000f, 0.2200405f, 0.4480174f, 1.0f } },
170 { 0.104f, { 0.7500000f, 0.2475123f, 0.2488547f, 1.0f } },
171 { 0.200f, { 0.7500000f, 0.3921083f, 0.2017670f, 1.0f } },
172 { 0.295f, { 0.7500000f, 0.7440329f, 0.3011876f, 1.0f } },
173 { 0.377f, { 0.3813996f, 0.7500000f, 0.3799668f, 1.0f } },
174 { 0.503f, { 0.0747526f, 0.7500000f, 0.7489037f, 1.0f } },
175 { 0.650f, { 0.0282981f, 0.3736209f, 0.7500000f, 1.0f } },
176 { 0.803f, { 0.2583821f, 0.2591069f, 0.7500000f, 1.0f } },
177 { 0.928f, { 0.7500000f, 0.2788102f, 0.7492077f, 1.0f } },
178 { 1.000f, { 0.7500000f, 0.2200405f, 0.4480174f, 1.0f } },
179};
180
181// The hue values for HSL are generated in the following way:
182// HSL (with S=0.5 and L=0.5) -> any RGB(linear) -> (normalize with MAX(R,G,B) = 0.75)
183// Please keep in sync with the display in the gamma module
185 { 0.000f, { 0.7500000f, 0.2500000f, 0.2500000f, 1.0f } },
186 { 0.167f, { 0.7500000f, 0.7500000f, 0.2500000f, 1.0f } },
187 { 0.333f, { 0.2500000f, 0.7500000f, 0.2500000f, 1.0f } },
188 { 0.500f, { 0.2500000f, 0.7500000f, 0.7500000f, 1.0f } },
189 { 0.667f, { 0.2500000f, 0.2500000f, 0.7500000f, 1.0f } },
190 { 0.833f, { 0.7500000f, 0.2500000f, 0.7500000f, 1.0f } },
191 { 1.000f, { 0.7500000f, 0.2500000f, 0.2500000f, 1.0f } },
192};
193
194// The hue values for JzCzhz are generated in the following way:
195// JzCzhz (with Jz=0.011 and Cz=0.01) -> sRGB(D65 linear) -> normalize with MAX(R,G,B) = 0.75
196// Please keep in sync with the display in the gamma module
198 { 0.000f, { 0.7500000f, 0.1946971f, 0.3697612f, 1.0f } },
199 { 0.082f, { 0.7500000f, 0.2278141f, 0.2291548f, 1.0f } },
200 { 0.150f, { 0.7500000f, 0.3132381f, 0.1653960f, 1.0f } },
201 { 0.275f, { 0.7483232f, 0.7500000f, 0.1939316f, 1.0f } },
202 { 0.378f, { 0.2642865f, 0.7500000f, 0.2642768f, 1.0f } },
203 { 0.570f, { 0.0233180f, 0.7493543f, 0.7500000f, 1.0f } },
204 { 0.650f, { 0.1119025f, 0.5116763f, 0.7500000f, 1.0f } },
205 { 0.762f, { 0.3331225f, 0.3337235f, 0.7500000f, 1.0f } },
206 { 0.883f, { 0.7464700f, 0.2754816f, 0.7500000f, 1.0f } },
207 { 1.000f, { 0.7500000f, 0.1946971f, 0.3697612f, 1.0f } },
208};
209
228
229static void _blendop_blendif_update_tab(dt_iop_module_t *module, const int tab);
231
233{
235 if(cst == IOP_CS_NONE)
236 {
237 switch(data->channel_tabs_csp)
238 {
240 cst = IOP_CS_LAB;
241 break;
244 cst = IOP_CS_RGB;
245 break;
248 cst = IOP_CS_NONE;
249 break;
250 }
251 }
252 return cst;
253}
254
256{
258 {
259 switch(mode & ~DEVELOP_BLEND_REVERSE)
260 {
270 return TRUE;
271 default:
272 return FALSE;
273 }
274 }
275 return FALSE;
276}
277
278static inline float _get_boost_factor(const dt_iop_gui_blend_data_t *data, const int channel, const int in_out)
279{
280 return exp2f(data->module->blend_params->blendif_boost_factors[data->channel[channel].param_channels[in_out]]);
281}
282
284 float *out, const dt_iop_order_iccprofile_info_t *work_profile, int in_out)
285{
286 out[0] = out[1] = out[2] = out[3] = out[4] = out[5] = out[6] = out[7] = -1.0f;
287
288 switch(cst)
289 {
290 case IOP_CS_LAB:
291 out[CHANNEL_INDEX_L] = (in[0] / _get_boost_factor(data, 0, in_out)) / 100.0f;
292 out[CHANNEL_INDEX_a] = ((in[1] / _get_boost_factor(data, 1, in_out)) + 128.0f) / 256.0f;
293 out[CHANNEL_INDEX_b] = ((in[2] / _get_boost_factor(data, 2, in_out)) + 128.0f) / 256.0f;
294 break;
295 case IOP_CS_RGB:
297 if(IS_NULL_PTR(work_profile))
298 out[CHANNEL_INDEX_g] = 0.3f * in[0] + 0.59f * in[1] + 0.11f * in[2];
299 else
300 out[CHANNEL_INDEX_g] = dt_ioppr_get_rgb_matrix_luminance(in, work_profile->matrix_in,
301 work_profile->lut_in,
302 work_profile->unbounded_coeffs_in,
303 work_profile->lutsize,
304 work_profile->nonlinearlut);
306 out[CHANNEL_INDEX_R] = in[0] / _get_boost_factor(data, 1, in_out);
307 out[CHANNEL_INDEX_G] = in[1] / _get_boost_factor(data, 2, in_out);
308 out[CHANNEL_INDEX_B] = in[2] / _get_boost_factor(data, 3, in_out);
309 break;
310 case IOP_CS_LCH:
311 out[CHANNEL_INDEX_C] = (in[1] / _get_boost_factor(data, 3, in_out)) / (128.0f * sqrtf(2.0f));
312 out[CHANNEL_INDEX_h] = in[2] / _get_boost_factor(data, 4, in_out);
313 break;
314 case IOP_CS_HSL:
315 out[CHANNEL_INDEX_H] = in[0] / _get_boost_factor(data, 4, in_out);
316 out[CHANNEL_INDEX_S] = in[1] / _get_boost_factor(data, 5, in_out);
317 out[CHANNEL_INDEX_l] = in[2] / _get_boost_factor(data, 6, in_out);
318 break;
319 case IOP_CS_JZCZHZ:
320 out[CHANNEL_INDEX_Jz] = in[0] / _get_boost_factor(data, 4, in_out);
321 out[CHANNEL_INDEX_Cz] = in[1] / _get_boost_factor(data, 5, in_out);
322 out[CHANNEL_INDEX_hz] = in[2] / _get_boost_factor(data, 6, in_out);
323 break;
324 default:
325 break;
326 }
327}
328
329static void _blendif_cook(dt_iop_colorspace_type_t cst, const float *in, float *out,
330 const dt_iop_order_iccprofile_info_t *const work_profile)
331{
332 out[0] = out[1] = out[2] = out[3] = out[4] = out[5] = out[6] = out[7] = -1.0f;
333
334 switch(cst)
335 {
336 case IOP_CS_LAB:
337 out[CHANNEL_INDEX_L] = in[0];
338 out[CHANNEL_INDEX_a] = in[1];
339 out[CHANNEL_INDEX_b] = in[2];
340 break;
341 case IOP_CS_RGB:
343 if(IS_NULL_PTR(work_profile))
344 out[CHANNEL_INDEX_g] = (0.3f * in[0] + 0.59f * in[1] + 0.11f * in[2]) * 100.0f;
345 else
346 out[CHANNEL_INDEX_g] = dt_ioppr_get_rgb_matrix_luminance(in, work_profile->matrix_in,
347 work_profile->lut_in,
348 work_profile->unbounded_coeffs_in,
349 work_profile->lutsize,
350 work_profile->nonlinearlut) * 100.0f;
351 out[CHANNEL_INDEX_R] = in[0] * 100.0f;
352 out[CHANNEL_INDEX_G] = in[1] * 100.0f;
353 out[CHANNEL_INDEX_B] = in[2] * 100.0f;
354 break;
355 case IOP_CS_LCH:
356 out[CHANNEL_INDEX_C] = in[1] / (128.0f * sqrtf(2.0f)) * 100.0f;
357 out[CHANNEL_INDEX_h] = in[2] * 360.0f;
358 break;
359 case IOP_CS_HSL:
360 out[CHANNEL_INDEX_H] = in[0] * 360.0f;
361 out[CHANNEL_INDEX_S] = in[1] * 100.0f;
362 out[CHANNEL_INDEX_l] = in[2] * 100.0f;
363 break;
364 case IOP_CS_JZCZHZ:
365 out[CHANNEL_INDEX_Jz] = in[0] * 100.0f;
366 out[CHANNEL_INDEX_Cz] = in[1] * 100.0f;
367 out[CHANNEL_INDEX_hz] = in[2] * 360.0f;
368 break;
369 default:
370 break;
371 }
372}
373
374static inline int _blendif_print_digits_default(float value)
375{
376 int digits;
377 if(value < 0.0001f) digits = 0;
378 else if(value < 0.01f) digits = 2;
379 else if(value < 0.999f) digits = 1;
380 else digits = 0;
381
382 return digits;
383}
384
385static inline int _blendif_print_digits_ab(float value)
386{
387 int digits;
388 if(fabsf(value) < 10.0f) digits = 1;
389 else digits = 0;
390
391 return digits;
392}
393
394static void _blendif_scale_print_ab(float value, float boost_factor, char *string, int n)
395{
396 const float scaled = (value * 256.0f - 128.0f) * boost_factor;
397 snprintf(string, n, "%-5.*f", _blendif_print_digits_ab(scaled), scaled);
398}
399
400static void _blendif_scale_print_hue(float value, float boost_factor, char *string, int n)
401{
402 snprintf(string, n, "%-5.0f", value * 360.0f);
403}
404
405static void _blendif_scale_print_default(float value, float boost_factor, char *string, int n)
406{
407 const float scaled = value * boost_factor;
408 snprintf(string, n, "%-5.*f", _blendif_print_digits_default(scaled), scaled * 100.0f);
409}
410
413{
414 const gboolean mask_inclusive = blend->mask_combine & DEVELOP_COMBINE_INCL;
415 const uint32_t mask = cst == DEVELOP_BLEND_CS_LAB
418 const uint32_t active_channels = blend->blendif & mask;
419 const uint32_t inverted_channels = (blend->blendif >> 16) ^ (mask_inclusive ? mask : 0);
420 const uint32_t cancel_channels = inverted_channels & ~blend->blendif & mask;
421 return active_channels || cancel_channels;
422}
423
425{
427 if(IS_NULL_PTR(bd) || !bd->blendif_support || !bd->blendif_inited) return FALSE;
428
429 gboolean changed = FALSE;
430 if(!bd->output_channels_shown)
431 {
432 const uint32_t mask = bd->csp == DEVELOP_BLEND_CS_LAB
435
436 dt_develop_blend_params_t *const d = module->blend_params;
437
438 // clear the output channels and invert them when needed
439 const uint32_t old_blendif = d->blendif;
440 const uint32_t need_inversion = d->mask_combine & DEVELOP_COMBINE_INCL ? (mask << 16) : 0;
441
442 d->blendif = (d->blendif & ~(mask | (mask << 16))) | need_inversion;
443
444 changed = (d->blendif != old_blendif);
445
446 for (size_t ch = 0; ch < DEVELOP_BLENDIF_SIZE; ch++)
447 {
448 if ((DEVELOP_BLENDIF_OUTPUT_MASK & (1 << ch))
449 && ( d->blendif_parameters[ch * 4 + 0] != 0.0f
450 || d->blendif_parameters[ch * 4 + 1] != 0.0f
451 || d->blendif_parameters[ch * 4 + 2] != 1.0f
452 || d->blendif_parameters[ch * 4 + 3] != 1.0f))
453 {
454 changed = TRUE;
455 d->blendif_parameters[ch * 4 + 0] = 0.0f;
456 d->blendif_parameters[ch * 4 + 1] = 0.0f;
457 d->blendif_parameters[ch * 4 + 2] = 1.0f;
458 d->blendif_parameters[ch * 4 + 3] = 1.0f;
459 }
460 }
461 }
462 return changed;
463}
464
465static void _blendop_masks_mode_callback(const unsigned int mask_mode, dt_iop_gui_blend_data_t *data)
466{
467 data->module->blend_params->mask_mode = mask_mode;
468
469 dt_iop_set_mask_mode(data->module, mask_mode);
470
471 if(data->blending_box)
472 dt_iop_gui_update_blending(data->module);
473}
474
476{
477 if(dt_gui_widgets_suppressed()) return;
478
479 dt_develop_blend_params_t *bp = data->module->blend_params;
480 dt_develop_blend_mode_t new_blend_mode = GPOINTER_TO_INT(dt_bauhaus_combobox_get_data(combo));
481 if(new_blend_mode != (bp->blend_mode & DEVELOP_BLEND_MODE_MASK))
482 {
483 bp->blend_mode = new_blend_mode | (bp->blend_mode & DEVELOP_BLEND_REVERSE);
485 {
486 gtk_widget_set_visible(data->blend_mode_parameter_slider, TRUE);
487 }
488 else
489 {
490 bp->blend_parameter = 0.0f;
492 gtk_widget_set_visible(data->blend_mode_parameter_slider, FALSE);
493 }
494 dt_dev_add_history_item(data->module->dev, data->module, TRUE, TRUE);
495 dt_iop_gui_update_header(data->module);
496 }
497}
498
500{
501 if(dt_gui_widgets_suppressed()) return;
502
503 dt_develop_blend_params_t *bp = data->module->blend_params;
504 const int val = GPOINTER_TO_INT(dt_bauhaus_combobox_get_data(combobox));
505 if(val == 0)
506 bp->blend_mode &= ~DEVELOP_BLEND_REVERSE;
507 else
509
510 dt_dev_add_history_item(data->module->dev, data->module, TRUE, TRUE);
511 dt_iop_gui_update_header(data->module);
512 dt_control_queue_redraw_widget(GTK_WIDGET(combobox));
513}
514
516{
517 dt_develop_blend_params_t *const d = data->module->blend_params;
518
519 const unsigned combine = GPOINTER_TO_UINT(dt_bauhaus_combobox_get_data(data->masks_combine_combo));
520 d->mask_combine &= ~(DEVELOP_COMBINE_INV | DEVELOP_COMBINE_INCL);
521 d->mask_combine |= combine;
522
523 // inverts the parametric mask channels that are not used
524 if(data->blendif_support && data->blendif_inited)
525 {
527 const uint32_t unused_channels = mask & ~d->blendif;
528 d->blendif &= ~(unused_channels << 16);
529 if(d->mask_combine & DEVELOP_COMBINE_INCL)
530 {
531 d->blendif |= unused_channels << 16;
532 }
533 _blendop_blendif_update_tab(data->module, data->tab);
534 }
535
536 _blendif_clean_output_channels(data->module);
537 dt_dev_add_history_item(data->module->dev, data->module, TRUE, TRUE);
538 dt_iop_gui_update_header(data->module);
539}
540
541static void _blendop_masks_invert_toggled(GtkToggleButton *togglebutton, dt_iop_gui_blend_data_t *data)
542{
543 if(dt_gui_widgets_suppressed()) return;
544 const gboolean active = gtk_toggle_button_get_active(togglebutton);
545 if(active)
546 data->module->blend_params->mask_combine |= DEVELOP_COMBINE_INV;
547 else
548 data->module->blend_params->mask_combine &= ~DEVELOP_COMBINE_INV;
549 _blendif_clean_output_channels(data->module);
550 dt_dev_add_history_item(data->module->dev, data->module, TRUE, TRUE);
551 dt_iop_gui_update_header(data->module);
552}
553
555{
556 if(dt_gui_widgets_suppressed()) return;
557
558 dt_develop_blend_params_t *bp = data->module->blend_params;
559
560 const dt_iop_gui_blendif_channel_t *channel = &data->channel[data->tab];
561
562 const int in_out = (slider == data->filter[1].slider) ? 1 : 0;
564 GtkLabel **label = data->filter[in_out].label;
565
566 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->colorpicker))
567 && !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->colorpicker_set_values)))
568 {
569 dt_iop_color_picker_reset(data->module, FALSE);
570 }
571
572 float *parameters = &(bp->blendif_parameters[4 * ch]);
573
575 for(int k = 0; k < 4; k++) parameters[k] = dtgtk_gradient_slider_multivalue_get_value(slider, k);
577
578 const float boost_factor = _get_boost_factor(data, data->tab, in_out);
579 for(int k = 0; k < 4; k++)
580 {
581 char text[256];
582 (channel->scale_print)(parameters[k], boost_factor, text, sizeof(text));
583 gtk_label_set_text(label[k], text);
584 }
585
587 if(parameters[1] == 0.0f && parameters[2] == 1.0f)
588 bp->blendif &= ~(1 << ch);
589 else
590 bp->blendif |= (1 << ch);
591
592 dt_dev_add_history_item(data->module->dev, data->module, TRUE, TRUE);
593 dt_iop_gui_update_header(data->module);
594}
595
598{
599 if(dt_gui_widgets_suppressed()) return;
600
601 dt_develop_blend_params_t *bp = data->module->blend_params;
602
603 const dt_iop_gui_blendif_channel_t *channel = &data->channel[data->tab];
604
605 const int in_out = (slider == data->filter[1].slider) ? 1 : 0;
607
608 // invert the parametric mask if needed
610 bp->blendif |= (1 << (16 + ch));
611 else
612 bp->blendif &= ~(1 << (16 + ch));
613
614 dt_dev_add_history_item(data->module->dev, data->module, TRUE, TRUE);
615 dt_iop_gui_update_header(data->module);
616 _blendop_blendif_update_tab(data->module, data->tab);
617}
618
619static void _blendop_blendif_polarity_callback(GtkToggleButton *togglebutton, dt_iop_gui_blend_data_t *data)
620{
621 if(dt_gui_widgets_suppressed()) return;
622
623 int active = gtk_toggle_button_get_active(togglebutton);
624
625 dt_develop_blend_params_t *bp = data->module->blend_params;
626
627 const dt_iop_gui_blendif_channel_t *channel = &data->channel[data->tab];
628
629 const int in_out = (GTK_WIDGET(togglebutton) == data->filter[1].polarity) ? 1 : 0;
631 GtkDarktableGradientSlider *slider = data->filter[in_out].slider;
632
633 if(!active)
634 bp->blendif |= (1 << (ch + 16));
635 else
636 bp->blendif &= ~(1 << (ch + 16));
637
646
647 dt_dev_add_history_item(data->module->dev, data->module, TRUE, TRUE);
648 dt_iop_gui_update_header(data->module);
649 dt_control_queue_redraw_widget(GTK_WIDGET(togglebutton));
650}
651
652static float log10_scale_callback(GtkWidget *self, float inval, int dir)
653{
654 float outval;
655 const float tiny = 1.0e-4f;
656 switch(dir)
657 {
659 outval = (log10(CLAMP(inval, 0.0001f, 1.0f)) + 4.0f) / 4.0f;
660 break;
662 outval = CLAMP(exp(M_LN10 * (4.0f * inval - 4.0f)), 0.0f, 1.0f);
663 if(outval <= tiny) outval = 0.0f;
664 if(outval >= 1.0f - tiny) outval = 1.0f;
665 break;
666 default:
667 outval = inval;
668 }
669 return outval;
670}
671
672
673static float magnifier_scale_callback(GtkWidget *self, float inval, int dir)
674{
675 float outval;
676 const float range = 6.0f;
677 const float invrange = 1.0f/range;
678 const float scale = tanh(range * 0.5f);
679 const float invscale = 1.0f/scale;
680 const float eps = 1.0e-6f;
681 const float tiny = 1.0e-4f;
682 switch(dir)
683 {
685 outval = (invscale * tanh(range * (CLAMP(inval, 0.0f, 1.0f) - 0.5f)) + 1.0f) * 0.5f;
686 if(outval <= tiny) outval = 0.0f;
687 if(outval >= 1.0f - tiny) outval = 1.0f;
688 break;
690 outval = invrange * atanh((2.0f * CLAMP(inval, eps, 1.0f - eps) - 1.0f) * scale) + 0.5f;
691 if(outval <= tiny) outval = 0.0f;
692 if(outval >= 1.0f - tiny) outval = 1.0f;
693 break;
694 default:
695 outval = inval;
696 }
697 return outval;
698}
699
701 float (*scale_callback)(GtkWidget*, float, int), const char *label)
702{
703 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
705
706 int in_out = (slider == data->filter[1].slider) ? 1 : 0;
707
708 dtgtk_gradient_slider_multivalue_set_scale_callback(slider, (mode == 1) ? scale_callback : NULL);
709 gchar *text = g_strdup_printf("%s%s",
710 (in_out == 0) ? _("input") : _("output"),
711 (mode == 1) ? label : "");
712 gtk_label_set_text(data->filter[in_out].head, text);
713 dt_free(text);
714
715 return (mode == 1) ? 1 : 0;
716}
717
718
720{
721 return _blendop_blendif_disp_alternative_worker(widget, module, mode, magnifier_scale_callback, _(" (zoom)"));
722}
723
725{
726 return _blendop_blendif_disp_alternative_worker(widget, module, mode, log10_scale_callback, _(" (log)"));
727}
728
730{
731 (void) _blendop_blendif_disp_alternative_worker(widget, module, 0, NULL, "");
732}
733
741static void _blendop_blendif_log_scale_toggled(GtkToggleButton *button, dt_iop_module_t *module)
742{
743 if(dt_gui_widgets_suppressed()) return;
744
745 dt_iop_gui_blend_data_t *data = !IS_NULL_PTR(module) ? module->gui->blend_data : NULL;
746 if(IS_NULL_PTR(data) || IS_NULL_PTR(data->channel)) return;
747
748 const int in_out = GTK_WIDGET(button) == data->filter[1].log_scale ? 1 : 0;
749 const dt_iop_gui_blendif_channel_t *channel = &data->channel[data->tab];
750 if(IS_NULL_PTR(channel->altdisplay)) return;
751
752 const gboolean active = gtk_toggle_button_get_active(button);
753 gtk_button_set_label(GTK_BUTTON(button), active ? _("Linear scale") : _("Log scale"));
754
755 const int requested_mode = active ? 1 : 0;
756 data->altmode[data->tab][in_out]
757 = channel->altdisplay(GTK_WIDGET(data->filter[in_out].slider), module, requested_mode);
758}
759
760
762{
764
766 {
767 if(bd->tab < 4)
768 picker_cst = IOP_CS_RGB;
769 else
770 picker_cst = IOP_CS_HSL;
771 }
773 {
774 if(bd->tab < 4)
775 picker_cst = IOP_CS_RGB;
776 else
777 picker_cst = IOP_CS_JZCZHZ;
778 }
780 {
781 if(bd->tab < 3)
782 picker_cst = IOP_CS_LAB;
783 else
784 picker_cst = IOP_CS_LCH;
785 }
786
787 return picker_cst;
788}
789
790static inline int _blendif_print_digits_picker(float value)
791{
792 int digits;
793 if(value < 10.0f) digits = 2;
794 else digits = 1;
795
796 return digits;
797}
798
799static void _update_gradient_slider_pickers(GtkWidget *callback_dummy, dt_iop_module_t *module)
800{
801 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
802 if(callback_dummy == data->colorpicker_set_values
803 && !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->colorpicker_set_values)))
804 {
807 }
808
810
811 float *raw_mean, *raw_min, *raw_max;
812
814
815 for(int in_out = 1; in_out >= 0; in_out--)
816 {
817 if(in_out)
818 {
819 raw_mean = module->picked_output_color;
820 raw_min = module->picked_output_color_min;
821 raw_max = module->picked_output_color_max;
822 }
823 else
824 {
825 raw_mean = module->picked_color;
826 raw_min = module->picked_color_min;
827 raw_max = module->picked_color_max;
828 }
829
830 if((gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->colorpicker)) ||
831 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->colorpicker_set_values))) &&
832 (raw_min[0] != INFINITY))
833 {
834 float picker_mean[8], picker_min[8], picker_max[8];
835 float cooked[8];
836
837 const dt_develop_blend_colorspace_t blend_csp = data->channel_tabs_csp;
839 const dt_iop_order_iccprofile_info_t *work_profile = (blend_csp == DEVELOP_BLEND_CS_RGB_SCENE)
841 : dt_ioppr_get_iop_work_profile_info(module, module->dev->iop);
842
843 _blendif_scale(data, cst, raw_mean, picker_mean, work_profile, in_out);
844 _blendif_scale(data, cst, raw_min, picker_min, work_profile, in_out);
845 _blendif_scale(data, cst, raw_max, picker_max, work_profile, in_out);
846 _blendif_cook(cst, raw_mean, cooked, work_profile);
847
848 gchar *text = g_strdup_printf("(%.*f)", _blendif_print_digits_picker(cooked[data->tab]), cooked[data->tab]);
849
851 data->filter[in_out].slider,
852 CLAMP(picker_mean[data->tab], 0.0f, 1.0f),
853 CLAMP(picker_min[data->tab], 0.0f, 1.0f),
854 CLAMP(picker_max[data->tab], 0.0f, 1.0f));
855 gtk_label_set_text(data->filter[in_out].picker_label, text);
856
857 dt_free(text);
858 }
859 else
860 {
862 gtk_label_set_text(data->filter[in_out].picker_label, "");
863 }
864 }
865
867}
868
869
870static void _blendop_blendif_update_tab(dt_iop_module_t *module, const int tab)
871{
872 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
873 dt_develop_blend_params_t *bp = module->blend_params;
874 dt_develop_blend_params_t *dp = module->default_blendop_params;
875 const float epsilon = 1e-6f;
876
878
879 const dt_iop_gui_blendif_channel_t *channel = &data->channel[tab];
880
881 for(int in_out = 1; in_out >= 0; in_out--)
882 {
883 const dt_develop_blendif_channels_t ch = channel->param_channels[in_out];
884 dt_iop_gui_blendif_filter_t *sl = &data->filter[in_out];
885
886 float *parameters = &(bp->blendif_parameters[4 * ch]);
887 float *defaults = &(dp->blendif_parameters[4 * ch]);
888
889 const int polarity = !(bp->blendif & (1 << (ch + 16)));
890
891 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sl->polarity), polarity);
892
894 sl->slider,
897 sl->slider,
900 sl->slider,
903 sl->slider,
905
907 for(int k = 0; k < 4; k++)
908 {
911 }
913
914 const float boost_factor = _get_boost_factor(data, tab, in_out);
915 for(int k = 0; k < 4; k++)
916 {
917 char text[256];
918 channel->scale_print(parameters[k], boost_factor, text, sizeof(text));
919 gtk_label_set_text(sl->label[k], text);
920 }
921
923
924 for(int k = 0; k < channel->numberstops; k++)
925 {
927 channel->colorstops[k].color);
928 }
929
931
932 if(channel->altdisplay)
933 {
934 data->altmode[tab][in_out] = channel->altdisplay(GTK_WIDGET(sl->slider), module, data->altmode[tab][in_out]);
935 gtk_widget_set_sensitive(sl->log_scale, TRUE);
936 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sl->log_scale), data->altmode[tab][in_out] == 1);
937 gtk_button_set_label(GTK_BUTTON(sl->log_scale),
938 data->altmode[tab][in_out] == 1 ? _("Linear scale") : _("Log scale"));
939 }
940 else
941 {
942 _blendop_blendif_disp_alternative_reset(GTK_WIDGET(sl->slider), module);
943 data->altmode[tab][in_out] = 0;
944 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sl->log_scale), FALSE);
945 gtk_button_set_label(GTK_BUTTON(sl->log_scale), _("Log scale"));
946 gtk_widget_set_sensitive(sl->log_scale, FALSE);
947 }
948 }
949
951
952 const gboolean boost_factor_enabled = channel->boost_factor_enabled;
953 float boost_factor = 0.0f;
954 if(boost_factor_enabled)
955 {
956 boost_factor = bp->blendif_boost_factors[channel->param_channels[0]] - channel->boost_factor_offset;
957 }
958 gtk_widget_set_sensitive(GTK_WIDGET(data->channel_boost_factor_slider), boost_factor_enabled);
959 dt_bauhaus_slider_set(GTK_WIDGET(data->channel_boost_factor_slider), boost_factor);
960
961 for(int page_num = 0; !IS_NULL_PTR(data->channel[page_num].label); page_num++)
962 {
963 gboolean modified = FALSE;
964 const dt_iop_gui_blendif_channel_t *page_channel = &data->channel[page_num];
965
966 for(int in_out = 0; in_out < 2 && !modified; in_out++)
967 {
968 const dt_develop_blendif_channels_t ch = page_channel->param_channels[in_out];
969
970 const float *params = &bp->blendif_parameters[4 * ch];
971 const float *defaults = &dp->blendif_parameters[4 * ch];
972 for(int k = 0; k < 4; k++)
973 {
974 if(fabsf(params[k] - defaults[k]) > epsilon)
975 {
976 modified = TRUE;
977 break;
978 }
979 }
980 }
981
982 GtkWidget *page = gtk_notebook_get_nth_page(data->channel_tabs, page_num);
983 GtkWidget *label = GTK_IS_WIDGET(page) ? gtk_notebook_get_tab_label(data->channel_tabs, page) : NULL;
984 if(GTK_IS_LABEL(label))
985 {
986 gchar *tab_title = modified
987 ? g_markup_printf_escaped("<b>%s</b>", _(page_channel->label))
988 : g_markup_printf_escaped("%s", _(page_channel->label));
989 gtk_label_set_markup(GTK_LABEL(label), tab_title);
990 dt_free(tab_title);
991 }
992 }
993
995 {
996 const gboolean output
997 = (module->request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_OUTPUT) != 0;
998 dt_dev_pixelpipe_display_mask_t new_request_mask_display
999 = module->request_mask_display & ~DT_DEV_PIXELPIPE_DISPLAY_ANY;
1000 new_request_mask_display |= channel->display_channel;
1001 if(output) new_request_mask_display |= DT_DEV_PIXELPIPE_DISPLAY_OUTPUT;
1002
1003 if(new_request_mask_display != module->request_mask_display)
1004 {
1005 module->request_mask_display = new_request_mask_display;
1008 }
1009 }
1010
1013}
1014
1015
1016static void _blendop_blendif_tab_switch(GtkNotebook *notebook, GtkWidget *page, guint page_num,
1018{
1019 if(dt_gui_widgets_suppressed() || IS_NULL_PTR(data) || !data->blendif_inited) return;
1020 const int cst_old = _blendop_blendif_get_picker_colorspace(data);
1021 dt_iop_color_picker_reset(data->module, FALSE);
1022
1023 data->tab = page_num;
1024
1025 if(cst_old != _blendop_blendif_get_picker_colorspace(data)
1026 && (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->colorpicker))
1027 || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->colorpicker_set_values))))
1028 {
1030 dt_dev_pixelpipe_update_history_all(data->module->dev);
1031 }
1032
1033 _blendop_blendif_update_tab(data->module, data->tab);
1034}
1035
1042static void _blendop_blending_notebook_switch(GtkNotebook *notebook, GtkWidget *page, guint page_num,
1044{
1045 (void)notebook;
1046 (void)page;
1047 if(IS_NULL_PTR(data)) return;
1048
1050}
1051
1053{
1054 if(dt_gui_widgets_suppressed() || IS_NULL_PTR(data) || !data->blendif_inited) return;
1055 dt_develop_blend_params_t *bp = data->module->blend_params;
1056 const int tab = data->tab;
1057
1058 const float value = dt_bauhaus_slider_get(slider);
1059 for(int in_out = 1; in_out >= 0; in_out--)
1060 {
1061 const int ch = data->channel[tab].param_channels[in_out];
1062 float off = 0.0f;
1065 {
1066 off = 0.5f;
1067 }
1068 const float new_value = value + data->channel[tab].boost_factor_offset;
1069 const float old_value = bp->blendif_boost_factors[ch];
1070 const float factor = exp2f(old_value) / exp2f(new_value);
1071 float *parameters = &(bp->blendif_parameters[4 * ch]);
1072 if(parameters[0] > 0.0f) parameters[0] = clamp_range_f((parameters[0] - off) * factor + off, 0.0f, 1.0f);
1073 if(parameters[1] > 0.0f) parameters[1] = clamp_range_f((parameters[1] - off) * factor + off, 0.0f, 1.0f);
1074 if(parameters[2] < 1.0f) parameters[2] = clamp_range_f((parameters[2] - off) * factor + off, 0.0f, 1.0f);
1075 if(parameters[3] < 1.0f) parameters[3] = clamp_range_f((parameters[3] - off) * factor + off, 0.0f, 1.0f);
1076 if(parameters[1] == 0.0f && parameters[2] == 1.0f)
1077 bp->blendif &= ~(1 << ch);
1078 bp->blendif_boost_factors[ch] = new_value;
1079 }
1080
1081 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->colorpicker_set_values)))
1082 {
1084 }
1085
1086 _blendop_blendif_update_tab(data->module, tab);
1087
1088 dt_dev_add_history_item(data->module->dev, data->module, TRUE, TRUE);
1089 dt_iop_gui_update_header(data->module);
1090}
1091
1093{
1094 if(dt_gui_widgets_suppressed() || IS_NULL_PTR(data) || !data->blendif_inited) return;
1095 dt_develop_blend_params_t *bp = data->module->blend_params;
1096 const float oldval = bp->details;
1097 bp->details = dt_bauhaus_slider_get(slider);
1098 dt_dev_add_history_item(data->module->dev, data->module, TRUE, TRUE);
1099 dt_iop_gui_update_header(data->module);
1100
1101 if((oldval == 0.0f) && (bp->details != 0.0f))
1102 {
1103 dt_dev_pixelpipe_update_history_all(data->module->dev);
1104 }
1105}
1106
1107static gboolean _blendop_blendif_showmask_clicked(GtkToggleButton *button, GdkEventButton *event, dt_iop_module_t *module)
1108{
1109 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
1110 // These should all use an uniform setter function.
1111 //
1112 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
1113 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
1114 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
1115 // that changes over the pipe lifecycle.
1116 //
1117 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
1118 // (but not all pipes are connected to a GUI, so you need to cover all cases all the time and don't forget to test everything),
1119 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
1120 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
1121 // planning for pipeline execution.
1122 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
1123 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
1124 // the preview at the far end of the pipe.
1125 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
1126 // which flushed lines that could be reused later and were only temporarily not needed.
1127 if(dt_gui_widgets_suppressed()) return TRUE;
1128
1129 if(event->button == 1)
1130 {
1131 const int has_mask_display = module->request_mask_display & (DT_DEV_PIXELPIPE_DISPLAY_MASK | DT_DEV_PIXELPIPE_DISPLAY_CHANNEL);
1132
1133 module->request_mask_display
1134 &= ~(DT_DEV_PIXELPIPE_DISPLAY_MASK | DT_DEV_PIXELPIPE_DISPLAY_CHANNEL
1135 | DT_DEV_PIXELPIPE_DISPLAY_ANY | DT_DEV_PIXELPIPE_DISPLAY_STICKY);
1136
1137 if(dt_modifier_is(event->state, DT_PRIMARY_MASK | GDK_SHIFT_MASK))
1138 module->request_mask_display |= (DT_DEV_PIXELPIPE_DISPLAY_MASK | DT_DEV_PIXELPIPE_DISPLAY_CHANNEL);
1139 else if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
1140 module->request_mask_display |= DT_DEV_PIXELPIPE_DISPLAY_CHANNEL;
1141 else if(dt_modifier_is(event->state, DT_PRIMARY_MASK))
1142 module->request_mask_display |= DT_DEV_PIXELPIPE_DISPLAY_MASK;
1143 else
1144 module->request_mask_display |= (has_mask_display ? 0 : DT_DEV_PIXELPIPE_DISPLAY_MASK);
1145
1146 gtk_toggle_button_set_active(button,
1148
1149 module->enabled = TRUE;
1150 if(module->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->gui->off), TRUE);
1151
1153 // (re)set the header mask indicator too
1154 if(module->gui->mask_indicator)
1155 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->gui->mask_indicator),
1158
1161 dt_iop_request_focus(module);
1162
1163 // We don't want to re-read the history here
1165 }
1166
1167 return TRUE;
1168}
1169
1170static void _blendop_masks_mode_changed(GtkToggleButton *togglebutton, dt_iop_module_t *module)
1171{
1172 if(dt_gui_widgets_suppressed()) return;
1173
1174 const unsigned int bit = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(togglebutton), "mask-bit"));
1175 if(!bit) return;
1176 if(IS_NULL_PTR(module)) return;
1177 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->blend_data)) return;
1178
1179 uint32_t mask_mode = module->blend_params->mask_mode;
1180 const gboolean active = gtk_toggle_button_get_active(togglebutton);
1181 const gboolean is_disable_toggle = bit == DEVELOP_MASK_RASTER
1182 || bit == DEVELOP_MASK_SHAPE
1183 || bit == DEVELOP_MASK_PARAMETRIC;
1184 const gboolean enable_bit = is_disable_toggle ? !active : active;
1185
1186 switch(bit)
1187 {
1189 if(enable_bit)
1190 mask_mode |= DEVELOP_MASK_ENABLED;
1191 else
1192 mask_mode &= ~DEVELOP_MASK_ENABLED;
1193 break;
1194
1196 if(enable_bit)
1198 else
1199 mask_mode &= ~DEVELOP_MASK_RASTER;
1200 break;
1201
1202 case DEVELOP_MASK_SHAPE:
1203 if(enable_bit)
1205 else
1206 mask_mode &= ~DEVELOP_MASK_SHAPE;
1207 break;
1208
1210 if(enable_bit)
1212 else
1213 mask_mode &= ~DEVELOP_MASK_PARAMETRIC;
1214 break;
1215
1216 default:
1217 break;
1218 }
1219
1220 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
1221 _blendop_masks_mode_callback(mask_mode, data);
1224 dt_dev_add_history_item(module->dev, data->module, TRUE, TRUE);
1225 dt_iop_gui_update_header(data->module);
1226}
1227
1228static gboolean _blendop_blendif_reset(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
1229{
1230 module->blend_params->blendif = module->default_blendop_params->blendif;
1232 4 * DEVELOP_BLENDIF_SIZE * sizeof(float));
1233 module->blend_params->details = module->default_blendop_params->details;
1234
1237 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
1239
1240 return TRUE;
1241}
1242
1243static gboolean _blendop_blendif_invert(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
1244{
1245 if(dt_gui_widgets_suppressed()) return TRUE;
1246
1247 const dt_iop_gui_blend_data_t *data = module->gui->blend_data;
1248
1249 unsigned int toggle_mask = 0;
1250
1251 switch(data->channel_tabs_csp)
1252 {
1254 toggle_mask = DEVELOP_BLENDIF_Lab_MASK << 16;
1255 break;
1258 toggle_mask = DEVELOP_BLENDIF_RGB_MASK << 16;
1259 break;
1262 toggle_mask = 0;
1263 break;
1264 }
1265
1266 module->blend_params->blendif ^= toggle_mask;
1267 module->blend_params->mask_combine ^= DEVELOP_COMBINE_MASKS_POS;
1268 module->blend_params->mask_combine ^= DEVELOP_COMBINE_INCL;
1270 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
1272
1273 return TRUE;
1274}
1275
1277 dt_masks_type_t type, gpointer user_data)
1278{
1280 if(IS_NULL_PTR(module) || IS_NULL_PTR(bd)) return FALSE;
1281
1282 // we want to be sure that the iop has focus
1283 dt_iop_request_focus(module);
1286 if(GTK_IS_TOGGLE_BUTTON(bd->masks_edit))
1287 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit), FALSE);
1288 return TRUE;
1289}
1290
1291static gboolean _blendop_masks_show_and_edit(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1292{
1293 if(dt_gui_widgets_suppressed()) return FALSE;
1295
1296 if(event->button == 1)
1297 {
1299
1302
1304 if(grp && (grp->type & DT_MASKS_GROUP) && grp->points)
1305 {
1306 const gboolean control_button_pressed = dt_modifier_is(event->state, DT_PRIMARY_MASK);
1307
1308 switch(bd->masks_shown)
1309 {
1310 case DT_MASKS_EDIT_FULL:
1311 bd->masks_shown = control_button_pressed ? DT_MASKS_EDIT_RESTRICTED : DT_MASKS_EDIT_OFF;
1312 break;
1313
1315 bd->masks_shown = !control_button_pressed ? DT_MASKS_EDIT_FULL : DT_MASKS_EDIT_OFF;
1316 break;
1317
1318 default:
1319 case DT_MASKS_EDIT_OFF:
1320 bd->masks_shown = control_button_pressed ? DT_MASKS_EDIT_RESTRICTED : DT_MASKS_EDIT_FULL;
1321 }
1322 }
1323 else
1324 {
1326 /* remove hinter messages */
1328 }
1329
1330 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit), bd->masks_shown != DT_MASKS_EDIT_OFF);
1332
1333 // Deactivate every masks shape toolbar because edit mode replaces creation mode.
1335
1337
1338 return TRUE;
1339 }
1340
1341 return FALSE;
1342}
1343
1344static gboolean _blendop_masks_polarity_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
1345{
1346 if(dt_gui_widgets_suppressed()) return TRUE;
1347
1348 const int active = !gtk_toggle_button_get_active(togglebutton);
1349 gtk_toggle_button_set_active(togglebutton, active);
1350
1352
1353 if(active)
1355 else
1356 bp->mask_combine &= ~DEVELOP_COMBINE_MASKS_POS;
1357
1358 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1360 dt_control_queue_redraw_widget(GTK_WIDGET(togglebutton));
1361
1362 return TRUE;
1363}
1364
1375
1387
1389{
1390 if(IS_NULL_PTR(module)) return NULL;
1391 if(IS_NULL_PTR(module->blend_params)) return NULL;
1392 return dt_masks_get_from_id(module->dev, module->blend_params->mask_id);
1393}
1394
1396{
1397 if(IS_NULL_PTR(mask_form)) return;
1398
1399 int new_form_id = 100;
1400 for(GList *form_node = dev->forms; form_node;)
1401 {
1402 const dt_masks_form_t *existing_form = (dt_masks_form_t *)form_node->data;
1403 if(existing_form->formid == mask_form->formid)
1404 {
1405 mask_form->formid = new_form_id++;
1406 form_node = dev->forms;
1407 }
1408 else
1409 {
1410 form_node = g_list_next(form_node);
1411 }
1412 }
1413}
1414
1416{
1417 if(IS_NULL_PTR(module)) return NULL;
1418
1420 if(IS_NULL_PTR(group_form)) return NULL;
1421
1422 _blendop_masks_check_id(module->dev, group_form);
1423 dt_masks_append_form(module->dev, group_form);
1424 module->blend_params->mask_id = group_form->formid;
1425
1426 // Named by id, once the group is in dev->forms and its id is settled: the masks module owns the
1427 // naming convention and the copy-on-write, and nothing here writes into the form. The touch may
1428 // replace the group with a clone, so the live one is resolved again rather than returned from
1429 // the pointer created above.
1431 group_form = dt_masks_get_from_id(module->dev, module->blend_params->mask_id);
1432
1433 return group_form;
1434}
1435
1437{
1438 if(IS_NULL_PTR(bd)) return;
1439
1440 // Only initialize icons if they haven't been created yet
1443 return;
1444
1445 const int icon_size = DT_PIXEL_APPLY_DPI(13);
1456}
1457
1458static const GdkPixbuf *_blendop_masks_get_op_icon(const dt_iop_gui_blend_data_t *bd, const int state,
1459 const int index)
1460{
1461 if(IS_NULL_PTR(bd) || index == 0) return NULL;
1462
1463 if(state & DT_MASKS_STATE_UNION) return bd->masks_ic_union;
1467 return NULL;
1468}
1469
1470static const GdkPixbuf *_blendop_masks_get_inverse_icon(const dt_iop_gui_blend_data_t *bd, const int state)
1471{
1472 return (bd && (state & DT_MASKS_STATE_INVERSE)) ? bd->masks_ic_inverse : NULL;
1473}
1474
1475static int _blendop_masks_group_tree_append(const dt_iop_gui_blend_data_t *bd, GtkTreeStore *tree_store,
1476 GtkTreeIter *parent_iter, const dt_masks_form_t *parent_group);
1477
1478// Check if this is a parent mask reusing a single parent mask (and no shapes).
1479// Such a wrapper is redundant and should be hidden: the child mask should be used directly instead.
1481{
1482 if(IS_NULL_PTR(mask_form)) return FALSE;
1483 if(!(mask_form->type & DT_MASKS_GROUP)) return FALSE;
1484 if(IS_NULL_PTR(mask_form->points)) return FALSE;
1485
1486 // Check if there is exactly one item
1487 if(g_list_length(mask_form->points) != 1) return FALSE;
1488
1489 // Get the single child
1490 const dt_masks_form_group_t *group_entry = (const dt_masks_form_group_t *)mask_form->points->data;
1491 if(IS_NULL_PTR(group_entry)) return FALSE;
1492
1493 const dt_masks_form_t *child_form = dt_masks_get_from_id((dt_develop_t *)dev, group_entry->formid);
1494 if(IS_NULL_PTR(child_form)) return FALSE;
1495
1496 // Check if the single child is a group
1497 return (child_form->type & DT_MASKS_GROUP) ? TRUE : FALSE;
1498}
1499
1500static gboolean _blendop_masks_is_group_with_shapes(const dt_develop_t *dev, const dt_masks_form_t *mask_form)
1501{
1502 if(IS_NULL_PTR(mask_form)) return FALSE;
1503 if(!(mask_form->type & DT_MASKS_GROUP)) return FALSE;
1504 if(IS_NULL_PTR(mask_form->points)) return FALSE;
1505
1506 for(const GList *group_node = mask_form->points; group_node; group_node = g_list_next(group_node))
1507 {
1508 const dt_masks_form_group_t *group_entry = (const dt_masks_form_group_t *)group_node->data;
1509 if(IS_NULL_PTR(group_entry)) continue;
1510
1511 const dt_masks_form_t *child_form = dt_masks_get_from_id((dt_develop_t *)dev, group_entry->formid);
1512 if(IS_NULL_PTR(child_form)) continue;
1513 if(!(child_form->type & DT_MASKS_GROUP)) return TRUE;
1514 if(_blendop_masks_is_group_with_shapes(dev, child_form)) return TRUE;
1515 }
1516
1517 return FALSE;
1518}
1519
1520static int _blendop_masks_group_tree_append_entry(const dt_iop_gui_blend_data_t *bd, GtkTreeStore *tree_store,
1521 GtkTreeIter *parent_iter,
1522 const dt_masks_form_group_t *group_entry,
1523 const dt_masks_form_t *mask_form, const int index)
1524{
1525 if(IS_NULL_PTR(bd) || IS_NULL_PTR(tree_store) || IS_NULL_PTR(group_entry) || IS_NULL_PTR(mask_form)) return 0;
1526
1527 GtkTreeIter iter;
1528
1529 // Append the opacity to the displayed name, same as the masks library treeview.
1530 char display_name[256] = "";
1531 if(group_entry->opacity != 1.0f)
1532 g_snprintf(display_name, sizeof(display_name), "%s %d%%", mask_form->name,
1533 (int)(group_entry->opacity * 100));
1534 else
1535 g_strlcpy(display_name, mask_form->name, sizeof(display_name));
1536
1537 gtk_tree_store_append(tree_store, &iter, parent_iter);
1538 gtk_tree_store_set(tree_store, &iter, BLENDOP_MASKS_GROUP_COL_OP_ICON,
1539 _blendop_masks_get_op_icon(bd, group_entry->state, index),
1541 _blendop_masks_get_inverse_icon(bd, group_entry->state),
1542 BLENDOP_MASKS_GROUP_COL_NAME, display_name,
1547
1548 int row_count = 1;
1549 if(mask_form->type & DT_MASKS_GROUP)
1550 row_count += _blendop_masks_group_tree_append(bd, tree_store, &iter, mask_form);
1551
1552 return row_count;
1553}
1554
1555static int _blendop_masks_group_tree_append(const dt_iop_gui_blend_data_t *bd, GtkTreeStore *tree_store,
1556 GtkTreeIter *parent_iter, const dt_masks_form_t *parent_group)
1557{
1558 if(IS_NULL_PTR(bd)) return 0;
1559 if(IS_NULL_PTR(tree_store)) return 0;
1560 if(IS_NULL_PTR(parent_group)) return 0;
1561 if(!(parent_group->type & DT_MASKS_GROUP)) return 0;
1562
1563 int row_count = 0;
1564
1565 /* One pass, in the group's own membership order. This used to run twice -- the members that
1566 * are groups holding shapes, then everything else -- which put every sub-group at the top of
1567 * the mask whatever its rank inside it. The rank is what the combine operators are read
1568 * against, so the list has to show it. */
1569 int index = 0;
1570 for(const GList *group_node = parent_group->points; group_node; group_node = g_list_next(group_node))
1571 {
1572 const dt_masks_form_group_t *group_entry = (const dt_masks_form_group_t *)group_node->data;
1573 const dt_masks_form_t *mask_form = group_entry
1574 ? dt_masks_get_from_id(bd->module->dev, group_entry->formid)
1575 : NULL;
1576 if(mask_form)
1577 row_count += _blendop_masks_group_tree_append_entry(bd, tree_store, parent_iter, group_entry, mask_form,
1578 index);
1579 index++;
1580 }
1581
1582 return row_count;
1583}
1584
1585static gboolean _blendop_masks_find_iter_by_formid(GtkTreeModel *model, GtkTreeIter *iter,
1586 const int formid_col, const int formid)
1587{
1588 if(IS_NULL_PTR(model) || IS_NULL_PTR(iter)) return FALSE;
1589
1590 gboolean valid = gtk_tree_model_get_iter_first(model, iter);
1591 while(valid)
1592 {
1593 int current_formid = -1;
1594 gtk_tree_model_get(model, iter, formid_col, &current_formid, -1);
1595 if(current_formid == formid) return TRUE;
1596 valid = gtk_tree_model_iter_next(model, iter);
1597 }
1598
1599 return FALSE;
1600}
1601
1602static void _blendop_masks_all_selection_changed(GtkTreeSelection *selection, dt_iop_module_t *module);
1603static gboolean _blendop_masks_all_button_pressed(GtkWidget *treeview, GdkEventButton *event,
1604 dt_iop_module_t *module);
1605static void _blendop_masks_group_selection_changed(GtkTreeSelection *selection, dt_iop_module_t *module);
1606static gboolean _blendop_masks_group_button_pressed(GtkWidget *treeview, GdkEventButton *event,
1607 dt_iop_module_t *module);
1608static void _blendop_masks_group_name_activate(GtkEntry *entry, dt_iop_module_t *module);
1609static gboolean _blendop_masks_group_name_focus_out(GtkWidget *widget, GdkEventFocus *event,
1610 dt_iop_module_t *module);
1611
1613{
1614 if(IS_NULL_PTR(module)) return;
1615 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->blend_data)) return;
1617 if(IS_NULL_PTR(bd)) return;
1618 if(!GTK_IS_TREE_VIEW(bd->masks_treeview)) return;
1619 if(!GTK_IS_TREE_VIEW(bd->masks_group_treeview)) return;
1620
1621 GtkTreeModel *all_model = gtk_tree_view_get_model(GTK_TREE_VIEW(bd->masks_treeview));
1622 GtkTreeModel *group_model = gtk_tree_view_get_model(GTK_TREE_VIEW(bd->masks_group_treeview));
1623 if(!GTK_IS_LIST_STORE(all_model)) return;
1624 if(!GTK_IS_TREE_STORE(group_model)) return;
1625
1626 // Block signals during model updates to prevent callbacks from accessing inconsistent state
1627 GtkTreeSelection *all_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(bd->masks_treeview));
1628 GtkTreeSelection *group_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(bd->masks_group_treeview));
1629 g_signal_handlers_block_by_func(all_selection, G_CALLBACK(_blendop_masks_all_selection_changed), module);
1630 g_signal_handlers_block_by_func(group_selection, G_CALLBACK(_blendop_masks_group_selection_changed), module);
1631 g_signal_handlers_block_by_func(bd->masks_treeview, G_CALLBACK(_blendop_masks_all_button_pressed), module);
1632 g_signal_handlers_block_by_func(bd->masks_group_treeview, G_CALLBACK(_blendop_masks_group_button_pressed), module);
1633
1634 gtk_list_store_clear(GTK_LIST_STORE(all_model));
1635 gtk_tree_store_clear(GTK_TREE_STORE(group_model));
1636
1638
1639 /* The available shapes keep the groups at the top, ahead of the loose shapes: this list is a
1640 * catalogue to pick from rather than a rendering order, and a group is the coarser thing to
1641 * pick. The mask's own tree, above, is the one that must show rank. */
1642
1643 // First pass: groups containing shapes first.
1644 for(const GList *form_node = module->dev->forms; form_node; form_node = g_list_next(form_node))
1645 {
1646 dt_masks_form_t *mask_form = (dt_masks_form_t *)form_node->data;
1647 if(IS_NULL_PTR(mask_form)) continue;
1648 if(mask_form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE)) continue;
1649 if(!IS_NULL_PTR(group_form) && mask_form->formid == group_form->formid) continue;
1650 if(!_blendop_masks_is_group_with_shapes(module->dev, mask_form)) continue;
1651 // Skip groups containing only a single group
1652 if(_blendop_masks_is_single_group_wrapper(module->dev, mask_form)) continue;
1653
1654 const gboolean active = dt_masks_form_group_find_entry(group_form, mask_form->formid, NULL) != NULL;
1655 GtkTreeIter iter;
1656 gtk_list_store_append(GTK_LIST_STORE(all_model), &iter);
1657 gchar *display_markup = g_markup_printf_escaped("%s", mask_form->name);
1658 gtk_list_store_set(GTK_LIST_STORE(all_model), &iter, BLENDOP_MASKS_ALL_COL_ACTIVE, active,
1661 BLENDOP_MASKS_ALL_COL_MARKUP, display_markup,
1663 -1);
1664 g_free(display_markup);
1665 }
1666
1667 // Second pass: then all non-group (and empty-group) entries.
1668 for(const GList *form_node = module->dev->forms; form_node; form_node = g_list_next(form_node))
1669 {
1670 dt_masks_form_t *mask_form = (dt_masks_form_t *)form_node->data;
1671 if(IS_NULL_PTR(mask_form)) continue;
1672 if(mask_form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE)) continue;
1673 if(!IS_NULL_PTR(group_form) && mask_form->formid == group_form->formid) continue;
1674 if(_blendop_masks_is_group_with_shapes(module->dev, mask_form)) continue;
1675
1676 const gboolean active = dt_masks_form_group_find_entry(group_form, mask_form->formid, NULL) != NULL;
1677 gboolean sensitive = TRUE;
1678 const gchar *locked_group_name = NULL;
1679
1680 // Check if this shape belongs to an active group
1681 for(const GList *parent_node = module->dev->forms; parent_node; parent_node = g_list_next(parent_node))
1682 {
1683 dt_masks_form_t *parent_form = (dt_masks_form_t *)parent_node->data;
1684 if(IS_NULL_PTR(parent_form) || !_blendop_masks_is_group_with_shapes(module->dev, parent_form)) continue;
1685
1686 const gboolean parent_active = dt_masks_form_group_find_entry(group_form, parent_form->formid, NULL) != NULL;
1687 if(parent_active && dt_masks_form_group_find_entry(parent_form, mask_form->formid, NULL))
1688 {
1689 sensitive = FALSE;
1690 locked_group_name = parent_form->name;
1691 break;
1692 }
1693 }
1694
1695 const gboolean display_active = active || !sensitive;
1696 gchar *display_markup = NULL;
1697 gchar *status_markup = NULL;
1698 display_markup = g_markup_printf_escaped("%s", mask_form->name);
1699
1700 if(!sensitive && !IS_NULL_PTR(locked_group_name) && *locked_group_name)
1701 {
1702 gchar *already_in = g_strdup_printf(_("Already in '%s'"), locked_group_name);
1703 status_markup = g_markup_printf_escaped("<i>%s</i>", already_in);
1704 g_free(already_in);
1705 }
1706 else
1707 status_markup = g_strdup("");
1708
1709 GtkTreeIter iter;
1710 gtk_list_store_append(GTK_LIST_STORE(all_model), &iter);
1711 gtk_list_store_set(GTK_LIST_STORE(all_model), &iter, BLENDOP_MASKS_ALL_COL_ACTIVE, display_active,
1715 -1);
1716 g_free(display_markup);
1717 g_free(status_markup);
1718 }
1719
1720 if(!IS_NULL_PTR(group_form) && (group_form->type & DT_MASKS_GROUP))
1721 {
1722 if(GTK_IS_ENTRY(bd->group_shapes_label))
1723 gtk_entry_set_text(GTK_ENTRY(bd->group_shapes_label), group_form->name);
1724 _blendop_masks_group_tree_append(bd, GTK_TREE_STORE(group_model), NULL, group_form);
1725 }
1726
1727 // Unblock signals after model updates are complete
1728 g_signal_handlers_unblock_by_func(all_selection, G_CALLBACK(_blendop_masks_all_selection_changed), module);
1729 g_signal_handlers_unblock_by_func(group_selection, G_CALLBACK(_blendop_masks_group_selection_changed), module);
1730 g_signal_handlers_unblock_by_func(bd->masks_treeview, G_CALLBACK(_blendop_masks_all_button_pressed), module);
1731 g_signal_handlers_unblock_by_func(bd->masks_group_treeview, G_CALLBACK(_blendop_masks_group_button_pressed), module);
1732}
1733
1735{
1736 if(IS_NULL_PTR(module)) return;
1737 if(IS_NULL_PTR(module->dev)) return;
1738
1740 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
1743}
1744
1745gboolean dt_iop_gui_blend_set_drawn_mask_group(dt_iop_module_t *module, const int group_id)
1746{
1747 if(!dt_iop_module_supports_drawn_mask(module)) return FALSE;
1748
1749 const dt_masks_form_t *group = dt_masks_get_from_id(module->dev, group_id);
1750 if(IS_NULL_PTR(group) || !(group->type & DT_MASKS_GROUP)) return FALSE;
1751
1752 const uint32_t drawn = DEVELOP_MASK_ENABLED | DEVELOP_MASK_SHAPE;
1753 const gboolean changed = (module->blend_params->mask_id != group_id)
1754 || ((module->blend_params->mask_mode & drawn) != drawn);
1755 if(!changed) return FALSE;
1756
1757 module->blend_params->mask_id = group_id;
1758 module->blend_params->mask_mode |= drawn;
1759
1760 // Reads blend_params->mask_mode back rather than taking it as an argument, so it has to come
1761 // after the write above.
1763
1764 /* A module the user has never expanded has no blend GUI to refresh, and one whose blending
1765 * body was never built has a dt_iop_gui_blend_data_t but no widgets in it -- which is exactly
1766 * what blending_box records, the same gate _blendop_masks_mode_callback() applies. */
1767 if(!IS_NULL_PTR(module->gui) && !IS_NULL_PTR(module->gui->blend_data))
1768 {
1769 const dt_iop_gui_blend_data_t *bd = (const dt_iop_gui_blend_data_t *)module->gui->blend_data;
1772 }
1773
1776
1777 return TRUE;
1778}
1779
1780static void _blendop_masks_group_name_commit(dt_iop_module_t *module, const gchar *new_text)
1781{
1782 if(IS_NULL_PTR(module)) return;
1784 if(IS_NULL_PTR(group_form)) return;
1785
1786 gchar *mask_default_name = dt_masks_group_name_for_module(module);
1787 gchar *text = (new_text && *new_text) ? g_strdup(new_text) : g_strdup(mask_default_name);
1788 g_free(mask_default_name);
1789
1790 g_strlcpy(group_form->name, text, sizeof(group_form->name));
1792 g_free(text);
1793
1797}
1798
1799static void _blendop_masks_group_name_activate(GtkEntry *entry, dt_iop_module_t *module)
1800{
1801 if(!GTK_IS_ENTRY(entry)) return;
1802 _blendop_masks_group_name_commit(module, gtk_entry_get_text(entry));
1803}
1804
1805static gboolean _blendop_masks_group_name_focus_out(GtkWidget *widget, GdkEventFocus *event,
1806 dt_iop_module_t *module)
1807{
1808 (void)event;
1809 if(GTK_IS_ENTRY(widget))
1810 _blendop_masks_group_name_commit(module, gtk_entry_get_text(GTK_ENTRY(widget)));
1811 return FALSE;
1812}
1813
1814static void _blendop_masks_all_name_edited(GtkCellRendererText *cell, gchar *path_string,
1815 gchar *new_text, dt_iop_module_t *module)
1816{
1817 (void)cell;
1818 if(IS_NULL_PTR(module)) return;
1819 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->blend_data)) return;
1820
1822 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(bd->masks_treeview));
1823 if(!GTK_IS_TREE_MODEL(model)) return;
1824
1825 GtkTreeIter iter;
1826 if(!gtk_tree_model_get_iter_from_string(model, &iter, path_string)) return;
1827
1828 int formid = -1;
1829 gtk_tree_model_get(model, &iter, BLENDOP_MASKS_ALL_COL_FORMID, &formid, -1);
1830 dt_masks_form_t *mask_form = dt_masks_get_from_id(module->dev, formid);
1831 if(IS_NULL_PTR(mask_form)) return;
1832
1833 const gchar *text = (new_text && *new_text) ? new_text : " ";
1834 g_strlcpy(mask_form->name, text, sizeof(mask_form->name));
1835
1836 dt_dev_add_history_item(module->dev, NULL, FALSE, TRUE);
1840}
1841
1842static void _blendop_masks_all_selection_changed(GtkTreeSelection *selection, dt_iop_module_t *module)
1843{
1844 if(dt_gui_widgets_suppressed()) return;
1845 if(IS_NULL_PTR(module)) return;
1846 if(IS_NULL_PTR(selection)) return;
1847
1848 GtkTreeModel *model = NULL;
1849 GtkTreeIter iter;
1850 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
1851 if(!GTK_IS_TREE_MODEL(model)) return;
1852
1853 int formid = -1;
1854 gtk_tree_model_get(model, &iter, BLENDOP_MASKS_ALL_COL_FORMID, &formid, -1);
1855 if(formid <= 0) return;
1856 dt_masks_form_t *mask_form = dt_masks_get_from_id(module->dev, formid);
1857 if(IS_NULL_PTR(mask_form)) return;
1858
1859 // Keep the global shape manager in sync without firing its selection handler.
1860 // That handler rebuilds the visible mask GUI and can be re-entered while this
1861 // blend list selection is still being processed.
1862 dt_dev_masks_selection_change(module->dev, NULL, formid, FALSE);
1863 dt_masks_change_form_gui(module->dev, mask_form);
1864 if(module->dev && module->dev->form_gui)
1865 {
1866 module->dev->form_gui->group_selected = 0;
1867 module->dev->form_gui->form_selected = TRUE;
1868 }
1869 dt_masks_center_view_on_form(module->dev, mask_form);
1870}
1871
1872static void _blendop_masks_all_toggled(GtkCellRendererToggle *cell, gchar *path_string, dt_iop_module_t *module)
1873{
1874 (void)cell;
1875 if(dt_gui_widgets_suppressed()) return;
1876 if(IS_NULL_PTR(module)) return;
1877 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->blend_data)) return;
1878
1880 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(bd->masks_treeview));
1881 if(!GTK_IS_TREE_MODEL(model)) return;
1882
1883 GtkTreeIter iter;
1884 if(!gtk_tree_model_get_iter_from_string(model, &iter, path_string)) return;
1885
1886 gboolean active = FALSE;
1887 gboolean sensitive = TRUE;
1888 int formid = -1;
1889 gtk_tree_model_get(model, &iter, BLENDOP_MASKS_ALL_COL_ACTIVE, &active,
1891 BLENDOP_MASKS_ALL_COL_SENSITIVE, &sensitive, -1);
1892
1893 if(!sensitive) return;
1894
1895 dt_masks_form_t *mask_form = dt_masks_get_from_id(module->dev, formid);
1896 if(IS_NULL_PTR(mask_form)) return;
1897
1899 const int parentid_before = group_form ? group_form->formid : 0;
1900
1901 if(active)
1902 {
1903 if(!IS_NULL_PTR(group_form))
1904 dt_masks_form_delete(module->dev, module, group_form, mask_form);
1905 }
1906 else
1907 {
1908 if(IS_NULL_PTR(group_form))
1909 group_form = _blendop_masks_group_create(module);
1910 if(IS_NULL_PTR(group_form)) return;
1911
1912 if(!dt_masks_form_group_find_entry(group_form, mask_form->formid, NULL))
1913 {
1914 group_form = dt_masks_cow_touch(module->dev, group_form);
1915 dt_masks_group_add_form(module->dev, group_form, mask_form);
1917 }
1918 }
1919
1920 // If this is a group with shapes, update sensitive state of child shapes
1921 if(_blendop_masks_is_group_with_shapes(module->dev, mask_form))
1922 {
1923 const gboolean new_active = !active; // Toggle state
1924 GtkTreeIter search_iter;
1925 gboolean valid = gtk_tree_model_get_iter_first(model, &search_iter);
1926
1927 while(valid)
1928 {
1929 int child_formid = -1;
1930 gtk_tree_model_get(model, &search_iter, BLENDOP_MASKS_ALL_COL_FORMID, &child_formid, -1);
1931
1932 // Check if this child belongs to the toggled group
1933 if(dt_masks_form_group_find_entry(mask_form, child_formid, NULL))
1934 {
1935 // Lock (gray out) if group is being activated, unlock if deactivated
1936 gtk_list_store_set(GTK_LIST_STORE(model), &search_iter,
1937 BLENDOP_MASKS_ALL_COL_SENSITIVE, !new_active, -1);
1938 }
1939
1940 valid = gtk_tree_model_iter_next(model, &search_iter);
1941 }
1942 }
1943
1945
1946 const int parentid_after = group_form ? group_form->formid : parentid_before;
1948 active ? parentid_before : parentid_after,
1950}
1951
1952// Permanently delete a shape from the "all shapes" list: the row's trash icon is the only
1953// entry point, gated by dt_masks_gui_confirm_permanent_delete().
1954static void _blendop_masks_all_delete(dt_iop_module_t *module, const int formid)
1955{
1956 if(IS_NULL_PTR(module)) return;
1957
1958 dt_masks_form_t *mask_form = dt_masks_get_from_id(module->dev, formid);
1959 if(IS_NULL_PTR(mask_form)) return;
1960
1961 if(!dt_masks_gui_confirm_permanent_delete(mask_form->name)) return;
1962
1963 dt_masks_change_form_gui(module->dev, NULL);
1964 dt_masks_form_delete(module->dev, module, NULL, mask_form);
1967}
1968
1970{
1971 if(IS_NULL_PTR(module)) return;
1972
1973 const int formid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "blend-formid"));
1974 if(dt_masks_form_duplicate(module->dev, formid) <= 0) return;
1975
1978}
1979
1981{
1982 if(IS_NULL_PTR(module)) return;
1983 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->blend_data)) return;
1984
1985 const int formid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "blend-formid"));
1987 GtkTreeView *view = GTK_TREE_VIEW(bd->masks_treeview);
1988 GtkTreeModel *model = gtk_tree_view_get_model(view);
1989 if(!GTK_IS_TREE_MODEL(model)) return;
1990
1991 GtkTreeIter iter;
1993
1994 GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
1995 GtkTreeViewColumn *column = gtk_tree_view_get_column(view, 1);
1996 GtkCellRenderer *renderer = g_object_get_data(G_OBJECT(view), "blendop-masks-name-renderer");
1997 if(path && column && renderer)
1998 gtk_tree_view_set_cursor_on_cell(view, path, column, renderer, TRUE);
1999 if(path) gtk_tree_path_free(path);
2000}
2001
2002static gboolean _blendop_masks_all_handle_left_click(GtkWidget *treeview, GtkTreePath *path,
2003 GtkTreeViewColumn *column, dt_iop_module_t *module)
2004{
2005 if(IS_NULL_PTR(module)) return FALSE;
2006 if(IS_NULL_PTR(path) || IS_NULL_PTR(column)) return FALSE;
2007
2009 if(IS_NULL_PTR(bd) || column != bd->all_shapes_col) return FALSE;
2010
2011 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
2012 GtkTreeIter iter;
2013 if(!gtk_tree_model_get_iter(model, &iter, path)) return FALSE;
2014
2015 gboolean sensitive = TRUE;
2016 int formid = -1;
2017 gtk_tree_model_get(model, &iter, BLENDOP_MASKS_ALL_COL_SENSITIVE, &sensitive,
2018 BLENDOP_MASKS_ALL_COL_FORMID, &formid, -1);
2019
2020 if(formid <= 0) return FALSE;
2021
2022 if(!sensitive)
2023 {
2024 gtk_tree_path_free(path);
2025 return TRUE;
2026 }
2027
2028 // Toggle the checkbox value.
2029 gchar *path_string = gtk_tree_path_to_string(path);
2030 _blendop_masks_all_toggled(NULL, path_string, module);
2031 dt_free(path_string);
2032 gtk_tree_path_free(path);
2033 return TRUE; // Block default selection behavior.
2034}
2035
2036static gboolean _blendop_masks_all_button_pressed(GtkWidget *treeview, GdkEventButton *event,
2037 dt_iop_module_t *module)
2038{
2039 if(IS_NULL_PTR(event) || event->type != GDK_BUTTON_PRESS) return FALSE;
2040
2041 GtkTreePath *path = NULL;
2042 GtkTreeViewColumn *column = NULL;
2043 if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), (gint)event->x, (gint)event->y, &path,
2044 &column, NULL, NULL))
2045 return FALSE;
2046
2047 // Handle left click on the per-row delete icon column.
2048 if(event->button == GDK_BUTTON_PRIMARY && !IS_NULL_PTR(column))
2049 {
2051 if(!IS_NULL_PTR(bd) && column == bd->all_shapes_delete_col)
2052 {
2053 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
2054 GtkTreeIter iter;
2055 int formid = -1;
2056 if(gtk_tree_model_get_iter(model, &iter, path))
2057 gtk_tree_model_get(model, &iter, BLENDOP_MASKS_ALL_COL_FORMID, &formid, -1);
2058 gtk_tree_path_free(path);
2059 if(formid > 0) _blendop_masks_all_delete(module, formid);
2060 return TRUE;
2061 }
2062 }
2063
2064 // Handle left click on checkbox column - toggle directly without requiring selection
2065 if(event->button == GDK_BUTTON_PRIMARY && !IS_NULL_PTR(column))
2066 {
2067 if(_blendop_masks_all_handle_left_click(treeview, path, column, module))
2068 return TRUE;
2069 }
2070
2071 // Handle right click for context menu
2072 if(event->button != GDK_BUTTON_SECONDARY)
2073 {
2074 gtk_tree_path_free(path);
2075 return FALSE;
2076 }
2077
2078 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
2079 gtk_tree_selection_unselect_all(selection);
2080 gtk_tree_selection_select_path(selection, path);
2081
2082 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
2083 GtkTreeIter iter;
2084 int formid = -1;
2085 if(gtk_tree_model_get_iter(model, &iter, path))
2086 gtk_tree_model_get(model, &iter, BLENDOP_MASKS_ALL_COL_FORMID, &formid, -1);
2087 gtk_tree_path_free(path);
2088 if(formid <= 0) return TRUE;
2089
2090 GtkWidget *menu = gtk_menu_new();
2091 GtkWidget *item = gtk_menu_item_new_with_label(_("Duplicate"));
2092 g_object_set_data(G_OBJECT(item), "blend-formid", GINT_TO_POINTER(formid));
2093 g_signal_connect(item, "activate", G_CALLBACK(_blendop_masks_all_duplicate_callback), module);
2094 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
2095
2096 item = gtk_menu_item_new_with_label(_("Rename"));
2097 g_object_set_data(G_OBJECT(item), "blend-formid", GINT_TO_POINTER(formid));
2098 g_signal_connect(item, "activate", G_CALLBACK(_blendop_masks_all_rename_callback), module);
2099 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
2100
2101 gtk_widget_show_all(menu);
2102 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
2103 return TRUE;
2104}
2105
2106static void _blendop_masks_group_operation_callback(GtkWidget *menu_item, gpointer user_data)
2107{
2108 dt_iop_module_t *module = (dt_iop_module_t *)user_data;
2109 if(IS_NULL_PTR(module)) return;
2110
2111 const int formid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "blend-formid"));
2112 const int parentid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "blend-parentid"));
2113 const dt_masks_state_t state = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "blend-state"));
2114
2115 /* The touch-then-resolve dance this used to spell out by hand -- and which every other caller
2116 * of it got wrong -- now lives inside the module, where it cannot be omitted. UNCHANGED means
2117 * the row already had this operator, so there is nothing to commit. */
2118 if(dt_masks_group_set_member_operation(module->dev, parentid, formid, state, NULL) != DT_MASKS_OK)
2119 return;
2120
2124}
2125
2126static void _blendop_masks_group_selection_changed(GtkTreeSelection *selection, dt_iop_module_t *module)
2127{
2128 if(dt_gui_widgets_suppressed()) return;
2129 if(IS_NULL_PTR(module)) return;
2130 if(IS_NULL_PTR(selection)) return;
2131
2132 GtkTreeModel *model = NULL;
2133 GtkTreeIter iter;
2134 if(!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
2135 if(!GTK_IS_TREE_MODEL(model)) return;
2136
2137 int formid = -1;
2138 gtk_tree_model_get(model, &iter, BLENDOP_MASKS_GROUP_COL_FORMID, &formid, -1);
2139 if(formid <= 0) return;
2140 dt_masks_form_t *mask_form = dt_masks_get_from_id(module->dev, formid);
2141 if(IS_NULL_PTR(mask_form)) return;
2142
2143 // Switching edit mode rebuilds the visible module group. Do it only when the
2144 // current overlay does not already contain the selected row, because this
2145 // callback runs inside a GtkTreeSelection::changed emission.
2146 dt_masks_form_gui_t *gui = module->dev ? module->dev->form_gui : NULL;
2147 dt_masks_form_t *visible_form = module->dev ? dt_masks_get_visible_form(module->dev) : NULL;
2148 if(IS_NULL_PTR(gui) || gui->edit_mode != DT_MASKS_EDIT_FULL
2149 || dt_masks_group_index_from_formid(visible_form, formid) < 0)
2150 {
2151 // Rebuilding the visible group may update Gtk widgets. Hold the GUI reset
2152 // guard so selection callbacks emitted during that rebuild don't recurse.
2156 }
2157
2158 // Keep lib/masks tree selection in sync without re-triggering its selection handler,
2159 // otherwise the visible overlay can be replaced by only the clicked shape.
2160 dt_dev_masks_selection_change(module->dev, module, formid, FALSE);
2161
2162 // Mark the selected shape as active in the central mask GUI state.
2163 if(module->dev && module->dev->form_gui)
2164 {
2165 gui = module->dev->form_gui;
2166 visible_form = dt_masks_get_visible_form(module->dev);
2167 const int selected_index = dt_masks_group_index_from_formid(visible_form, formid);
2168 if(selected_index >= 0)
2169 {
2170 gui->group_selected = selected_index;
2171 gui->form_selected = TRUE;
2172 gui->border_selected = FALSE;
2173 gui->source_selected = FALSE;
2174 gui->node_selected = FALSE;
2175 gui->handle_selected = FALSE;
2176 gui->seg_selected = FALSE;
2178 gui->node_selected_idx = -1;
2179 gui->form_dragging = FALSE;
2180 gui->source_dragging = FALSE;
2181 gui->form_rotating = FALSE;
2182 gui->pivot_selected = FALSE;
2183 }
2184 }
2185 dt_masks_center_view_on_form(module->dev, mask_form);
2186}
2187
2189 const int index, const gboolean move_up)
2190{
2191 if(IS_NULL_PTR(group_form)) return FALSE;
2192 if(!(group_form->type & DT_MASKS_GROUP)) return FALSE;
2193 if(index < 0) return FALSE;
2194
2195 const guint length = g_list_length(group_form->points);
2196 if(length == 0 || (guint)index >= length) return FALSE;
2197 if(move_up && index == 0) return FALSE;
2198 if(!move_up && (guint)index >= length - 1) return FALSE;
2199
2200 // Touch before resolving `entry`: cloning group_form also clones its points (fresh
2201 // dt_masks_form_group_t blocks), so an entry resolved from the pre-touch list would not
2202 // be a member of the (possibly cloned) list touched below.
2203 group_form = dt_masks_cow_touch(dev, group_form);
2204 dt_masks_form_group_t *entry = (dt_masks_form_group_t *)g_list_nth_data(group_form->points, index);
2205 if(IS_NULL_PTR(entry)) return FALSE;
2206
2207 const int new_index = move_up ? index - 1 : index + 1;
2208 group_form->points = g_list_remove(group_form->points, entry);
2209 group_form->points = g_list_insert(group_form->points, entry, new_index);
2210 return TRUE;
2211}
2212
2214{
2215 if(IS_NULL_PTR(module)) return;
2216
2217 const int parentid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "blend-parentid"));
2218 const int index = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "blend-index"));
2219 const gboolean move_up = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "blend-move-up"));
2220 dt_masks_form_t *group_form = dt_masks_get_from_id(module->dev, parentid);
2221 if(!_blendop_masks_group_move_by_index(module->dev, group_form, index, move_up)) return;
2222
2223 dt_masks_change_form_gui(module->dev, NULL);
2226}
2227
2229{
2230 if(IS_NULL_PTR(module)) return;
2231
2232 const int formid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "blend-formid"));
2233 const int parentid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu_item), "blend-parentid"));
2234
2235 // dt_masks_form_duplicate_in_group also attaches the duplicate to the group right away,
2236 // inheriting the source entry's state/opacity -- same helper used by the shape manager's
2237 // own "Duplicate shape" action (libs/shape_manager.c).
2238 const int nid = dt_masks_form_duplicate_in_group(module->dev, parentid, formid);
2239 if(nid <= 0) return;
2240
2244}
2245
2246static void _blendop_masks_edit_list_toggle(GtkToggleButton *togglebutton, dt_iop_module_t *module)
2247{
2249
2250 if(!GTK_IS_TOGGLE_BUTTON(togglebutton)) return;
2251
2252 // The edit toggle swaps the visible list between:
2253 // - current module mask tree
2254 // - global all-shapes list
2255 const gboolean edit_mode = gtk_toggle_button_get_active(togglebutton);
2256
2257 if(edit_mode)
2258 gtk_button_set_label(GTK_BUTTON(togglebutton), _("OK"));
2259 else
2260 gtk_button_set_label(GTK_BUTTON(togglebutton), _("Attach shapes"));
2261
2262 if(GTK_IS_STACK(bd->lists_stack))
2263 gtk_stack_set_visible_child_name(GTK_STACK(bd->lists_stack), edit_mode ? "all" : "group");
2264
2265 if(GTK_IS_ENTRY(bd->group_shapes_label))
2266 gtk_widget_set_sensitive(bd->group_shapes_label, !edit_mode);
2267}
2268
2270{
2271 if(IS_NULL_PTR(module) || IS_NULL_PTR(bd)) return NULL;
2272
2273 const dt_masks_shape_buttons_config_t config = {
2274 .dev = module->dev,
2275 .owner_module = module,
2276 .creation_module = module,
2277 .buttons = bd->masks_shapes,
2278 .types = bd->masks_type,
2279 .action_section = "blend_shapes",
2280 .flags = DT_MASKS_SHAPE_BUTTONS_ALL,
2281 .register_flags = DT_MASKS_SHAPE_BUTTONS_POLYGON,
2282 .local = FALSE,
2283 .user_data = bd,
2284 .can_start = _blendop_masks_shape_can_start,
2285 .form_type = NULL,
2286 .started = NULL,
2287 .exited = NULL
2288 };
2289 GtkWidget *widget = dt_masks_shape_buttons_create(&config);
2290
2291 return widget;
2292}
2293
2295 const int formid, const int parentid, const int state,
2296 const int index, const int list_length)
2297{
2298 if(IS_NULL_PTR(bd) || IS_NULL_PTR(module)) return NULL;
2299
2300 // Initialize mask operation icons if needed
2302
2303 GtkWidget *menu = gtk_menu_new();
2304 gtk_style_context_add_class(gtk_widget_get_style_context(menu), "dt-masks-context-menu");
2305
2306 // Same shape-parameter sliders (size/fading/rotation/opacity) as the darkroom canvas's own
2307 // shape context menu (dt_masks_create_menu). The sliders name the row by identity and let the
2308 // write API copy-on-write per step, so this no longer has to touch the parent group up front --
2309 // which never worked anyway once a slider started committing history mid-drag.
2310 dt_masks_form_t *form = dt_masks_get_from_id(module->dev, formid);
2311
2312 if(!IS_NULL_PTR(form))
2313 {
2314 dt_masks_gui_populate_interaction_sliders(menu, module->dev, form, parentid, module->dev->form_gui, module);
2315 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
2316 }
2317
2318 GtkWidget *op_item = gtk_menu_item_new_with_label(_("Operation"));
2319 GtkWidget *op_submenu = gtk_menu_new();
2320 gtk_menu_item_set_submenu(GTK_MENU_ITEM(op_item), op_submenu);
2321 gtk_menu_shell_append(GTK_MENU_SHELL(menu), op_item);
2322
2324 op_submenu,
2326 module,
2328 g_object_set_data(G_OBJECT(item), "blend-formid", GINT_TO_POINTER(formid));
2329 g_object_set_data(G_OBJECT(item), "blend-parentid", GINT_TO_POINTER(parentid));
2330 g_object_set_data(G_OBJECT(item), "blend-state", GINT_TO_POINTER(DT_MASKS_STATE_INVERSE));
2331
2332 gtk_menu_shell_append(GTK_MENU_SHELL(op_submenu), gtk_separator_menu_item_new());
2333
2334 item = ctx_gtk_check_menu_item_new_with_markup_and_pixbuf(_("Union"), bd->masks_ic_union, op_submenu,
2336 (state & DT_MASKS_STATE_UNION) != 0, FALSE);
2337 gtk_widget_set_sensitive(item, index > 0);
2338 g_object_set_data(G_OBJECT(item), "blend-formid", GINT_TO_POINTER(formid));
2339 g_object_set_data(G_OBJECT(item), "blend-parentid", GINT_TO_POINTER(parentid));
2340 g_object_set_data(G_OBJECT(item), "blend-state", GINT_TO_POINTER(DT_MASKS_STATE_UNION));
2341
2342 item = ctx_gtk_check_menu_item_new_with_markup_and_pixbuf(_("Intersection"), bd->masks_ic_intersection, op_submenu,
2345 gtk_widget_set_sensitive(item, index > 0);
2346 g_object_set_data(G_OBJECT(item), "blend-formid", GINT_TO_POINTER(formid));
2347 g_object_set_data(G_OBJECT(item), "blend-parentid", GINT_TO_POINTER(parentid));
2348 g_object_set_data(G_OBJECT(item), "blend-state", GINT_TO_POINTER(DT_MASKS_STATE_INTERSECTION));
2349
2350 item = ctx_gtk_check_menu_item_new_with_markup_and_pixbuf(_("Difference"), bd->masks_ic_difference, op_submenu,
2353 gtk_widget_set_sensitive(item, index > 0);
2354 g_object_set_data(G_OBJECT(item), "blend-formid", GINT_TO_POINTER(formid));
2355 g_object_set_data(G_OBJECT(item), "blend-parentid", GINT_TO_POINTER(parentid));
2356 g_object_set_data(G_OBJECT(item), "blend-state", GINT_TO_POINTER(DT_MASKS_STATE_DIFFERENCE));
2357
2358 item = ctx_gtk_check_menu_item_new_with_markup_and_pixbuf(_("Exclusion"), bd->masks_ic_exclusion, op_submenu,
2361 gtk_widget_set_sensitive(item, index > 0);
2362 g_object_set_data(G_OBJECT(item), "blend-formid", GINT_TO_POINTER(formid));
2363 g_object_set_data(G_OBJECT(item), "blend-parentid", GINT_TO_POINTER(parentid));
2364 g_object_set_data(G_OBJECT(item), "blend-state", GINT_TO_POINTER(DT_MASKS_STATE_EXCLUSION));
2365
2366 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
2367
2368 item = gtk_menu_item_new_with_label(_("Move Up"));
2369 gtk_widget_set_sensitive(item, index > 0);
2370 g_object_set_data(G_OBJECT(item), "blend-parentid", GINT_TO_POINTER(parentid));
2371 g_object_set_data(G_OBJECT(item), "blend-index", GINT_TO_POINTER(index));
2372 g_object_set_data(G_OBJECT(item), "blend-move-up", GINT_TO_POINTER(TRUE));
2373 g_signal_connect(item, "activate", G_CALLBACK(_blendop_masks_group_move_callback), module);
2374 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
2375
2376 item = gtk_menu_item_new_with_label(_("Move Down"));
2377 gtk_widget_set_sensitive(item, index >= 0 && index < list_length - 1);
2378 g_object_set_data(G_OBJECT(item), "blend-parentid", GINT_TO_POINTER(parentid));
2379 g_object_set_data(G_OBJECT(item), "blend-index", GINT_TO_POINTER(index));
2380 g_object_set_data(G_OBJECT(item), "blend-move-up", GINT_TO_POINTER(FALSE));
2381 g_signal_connect(item, "activate", G_CALLBACK(_blendop_masks_group_move_callback), module);
2382 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
2383
2384 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
2385
2386 item = gtk_menu_item_new_with_label(_("Duplicate shape"));
2387 g_object_set_data(G_OBJECT(item), "blend-formid", GINT_TO_POINTER(formid));
2388 g_object_set_data(G_OBJECT(item), "blend-parentid", GINT_TO_POINTER(parentid));
2389 g_signal_connect(item, "activate", G_CALLBACK(_blendop_masks_group_duplicate_callback), module);
2390 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
2391
2392 return menu;
2393}
2394
2395// Detach the form from its parent group, but keep it in dev->forms for potential reuse.
2396static void _blendop_masks_group_unlink(dt_iop_module_t *module, const int formid, const int parentid)
2397{
2398 if(IS_NULL_PTR(module)) return;
2399 dt_masks_form_t *parent_group = dt_masks_get_from_id(module->dev, parentid);
2400 dt_masks_form_t *form = dt_masks_get_from_id(module->dev, formid);
2401 if(IS_NULL_PTR(parent_group) || !(parent_group->type & DT_MASKS_GROUP) || IS_NULL_PTR(form)) return;
2402
2403 // Discard any visible overlay before mutating the group.
2404 dt_masks_change_form_gui(module->dev, NULL);
2405
2406 // Passing the parent group only detaches the form from this group.
2407 dt_masks_form_delete(module->dev, module, parent_group, form);
2408
2413}
2414
2415// Detach the form from its parent group and remove it from dev->forms altogether.
2416static void _blendop_masks_group_delete(dt_iop_module_t *module, const int formid, const int parentid)
2417{
2418 if(IS_NULL_PTR(module)) return;
2419 dt_masks_form_t *form = dt_masks_get_from_id(module->dev, formid);
2420 if(IS_NULL_PTR(form)) return;
2421
2423
2424 // Discard any visible overlay before mutating the masks.
2425 dt_masks_change_form_gui(module->dev, NULL);
2426
2427 // Passing no group permanently deletes the form from every module and dev->forms.
2428 dt_masks_form_delete(module->dev, module, NULL, form);
2429
2434}
2435
2436// Handle a left click on the per-row unlink / delete icon columns.
2437// Returns TRUE if the click was consumed (so row selection is not triggered).
2438static gboolean _blendop_masks_group_handle_action_click(GtkWidget *treeview, GtkTreePath *path,
2439 GtkTreeViewColumn *column, dt_iop_module_t *module)
2440{
2441 if(IS_NULL_PTR(module) || IS_NULL_PTR(path) || IS_NULL_PTR(column)) return FALSE;
2443 if(IS_NULL_PTR(bd)) return FALSE;
2444 if(column != bd->group_unlink_col && column != bd->group_delete_col) return FALSE;
2445
2446 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
2447 GtkTreeIter iter;
2448 if(!gtk_tree_model_get_iter(model, &iter, path)) return TRUE;
2449
2450 int formid = -1;
2451 int parentid = -1;
2452 gtk_tree_model_get(model, &iter, BLENDOP_MASKS_GROUP_COL_FORMID, &formid,
2453 BLENDOP_MASKS_GROUP_COL_PARENTID, &parentid, -1);
2454 if(formid <= 0 || parentid <= 0) return TRUE;
2455
2456 if(column == bd->group_unlink_col)
2457 _blendop_masks_group_unlink(module, formid, parentid);
2458 else
2459 _blendop_masks_group_delete(module, formid, parentid);
2460
2461 return TRUE;
2462}
2463
2464static gboolean _blendop_masks_group_button_pressed(GtkWidget *treeview, GdkEventButton *event,
2465 dt_iop_module_t *module)
2466{
2467 if(IS_NULL_PTR(event) || event->type != GDK_BUTTON_PRESS) return FALSE;
2468
2469 GtkTreePath *path = NULL;
2470 GtkTreeViewColumn *column = NULL;
2471 if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), (gint)event->x, (gint)event->y, &path,
2472 &column, NULL, NULL))
2473 return FALSE;
2474
2475 // Left click on the unlink / delete icon columns triggers the row action.
2476 if(event->button == GDK_BUTTON_PRIMARY)
2477 {
2478 const gboolean consumed = _blendop_masks_group_handle_action_click(treeview, path, column, module);
2479 gtk_tree_path_free(path);
2480 return consumed;
2481 }
2482
2483 if(event->button != GDK_BUTTON_SECONDARY)
2484 {
2485 gtk_tree_path_free(path);
2486 return FALSE;
2487 }
2488
2489 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
2490 gtk_tree_selection_unselect_all(selection);
2491 gtk_tree_selection_select_path(selection, path);
2492
2493 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
2494 GtkTreeIter iter;
2495 int formid = -1;
2496 int parentid = -1;
2497 int state = 0;
2498 int index = -1;
2499 if(gtk_tree_model_get_iter(model, &iter, path))
2500 gtk_tree_model_get(model, &iter, BLENDOP_MASKS_GROUP_COL_FORMID, &formid,
2503 BLENDOP_MASKS_GROUP_COL_INDEX, &index, -1);
2504 gtk_tree_path_free(path);
2505
2506 if(formid <= 0 || parentid <= 0) return TRUE;
2507
2509 dt_masks_form_t *parent_group = dt_masks_get_from_id(module->dev, parentid);
2510 const int list_length = (parent_group && (parent_group->type & DT_MASKS_GROUP))
2511 ? g_list_length(parent_group->points)
2512 : 0;
2513
2514 GtkWidget *menu = _blendop_masks_group_ctx_menu(bd, module, formid, parentid, state, index, list_length);
2515 if(IS_NULL_PTR(menu)) return TRUE;
2516 gtk_widget_show_all(menu);
2517 gtk_menu_popup_at_pointer(GTK_MENU(menu), (GdkEvent *)event);
2518 return TRUE;
2519}
2520
2521// Per-column tooltips for the unlink / delete action icons.
2522static gboolean _blendop_masks_group_query_tooltip(GtkWidget *treeview, gint x, gint y, gboolean keyboard_tip,
2523 GtkTooltip *tooltip, dt_iop_module_t *module)
2524{
2525 if(IS_NULL_PTR(module) || keyboard_tip) return FALSE;
2527 if(IS_NULL_PTR(bd)) return FALSE;
2528
2529 gint bx = 0, by = 0;
2530 gtk_tree_view_convert_widget_to_bin_window_coords(GTK_TREE_VIEW(treeview), x, y, &bx, &by);
2531
2532 GtkTreePath *path = NULL;
2533 GtkTreeViewColumn *column = NULL;
2534 if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), bx, by, &path, &column, NULL, NULL))
2535 return FALSE;
2536 gtk_tree_path_free(path);
2537
2538 const char *text = NULL;
2539 if(column == bd->group_unlink_col)
2540 text = _("Detach this shape from the mask. The shape is kept and stays available for reuse.");
2541 else if(column == bd->group_delete_col)
2542 text = _("Permanently delete this shape. It is detached from the mask and removed from the list of available shapes.");
2543
2544 if(IS_NULL_PTR(text)) return FALSE;
2545
2546 gtk_tooltip_set_text(tooltip, text);
2547 return TRUE;
2548}
2549
2550// Depth-first search for the row matching (formid, parentid) anywhere in the group tree.
2551static gboolean _blendop_masks_group_find_row(GtkTreeModel *model, GtkTreeIter *parent,
2552 const int formid, const int parentid, GtkTreeIter *out)
2553{
2554 GtkTreeIter iter;
2555 gboolean valid = gtk_tree_model_iter_children(model, &iter, parent);
2556 while(valid)
2557 {
2558 int fid = -1;
2559 int pid = -1;
2560 gtk_tree_model_get(model, &iter, BLENDOP_MASKS_GROUP_COL_FORMID, &fid,
2562 if(fid == formid && pid == parentid)
2563 {
2564 *out = iter;
2565 return TRUE;
2566 }
2567 if(gtk_tree_model_iter_has_child(model, &iter)
2568 && _blendop_masks_group_find_row(model, &iter, formid, parentid, out))
2569 return TRUE;
2570 valid = gtk_tree_model_iter_next(model, &iter);
2571 }
2572 return FALSE;
2573}
2574
2575// Refresh a single row in place (name + opacity + operation/inverse icons), the same way
2576// the masks library treeview does, so live opacity changes don't rebuild and collapse the tree.
2577static void _blendop_masks_group_update_row(dt_iop_module_t *module, const int formid, const int parentid)
2578{
2579 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->blend_data)) return;
2581 if(!GTK_IS_TREE_VIEW(bd->masks_group_treeview)) return;
2582 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(bd->masks_group_treeview));
2583 if(!GTK_IS_TREE_STORE(model)) return;
2584
2585 GtkTreeIter iter;
2586 if(!_blendop_masks_group_find_row(model, NULL, formid, parentid, &iter)) return;
2587
2588 dt_masks_form_t *form = dt_masks_get_from_id(module->dev, formid);
2589 dt_masks_form_t *parent_group = dt_masks_get_from_id(module->dev, parentid);
2590 int index = -1;
2591 dt_masks_form_group_t *entry = dt_masks_form_group_find_entry(parent_group, formid, &index);
2592 if(IS_NULL_PTR(form) || IS_NULL_PTR(entry)) return;
2593
2594 char display_name[256] = "";
2595 if(entry->opacity != 1.0f)
2596 g_snprintf(display_name, sizeof(display_name), "%s %d%%", form->name, (int)(entry->opacity * 100));
2597 else
2598 g_strlcpy(display_name, form->name, sizeof(display_name));
2599
2600 gtk_tree_store_set(GTK_TREE_STORE(model), &iter, BLENDOP_MASKS_GROUP_COL_NAME, display_name,
2604}
2605
2606static void _blendop_masks_handler_callback(gpointer instance, const int formid, const int parentid,
2607 const dt_masks_event_t event, dt_iop_module_t *module)
2608{
2609 (void)instance;
2610 // A plain value update (e.g. opacity) only needs the affected row refreshed in place.
2611 // Structural changes rebuild the whole list.
2612 if(event == DT_MASKS_EVENT_UPDATE)
2613 _blendop_masks_group_update_row(module, formid, parentid);
2614 else
2616}
2617
2620{
2621 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
2622
2623 if(picker == data->colorpicker_set_values)
2624 {
2625 if(dt_gui_widgets_suppressed()) return TRUE;
2626
2628
2629 dt_develop_blend_params_t *bp = module->blend_params;
2630
2631 const int tab = data->tab;
2632 dt_aligned_pixel_t raw_min, raw_max;
2633 float picker_min[8] DT_ALIGNED_PIXEL, picker_max[8] DT_ALIGNED_PIXEL;
2634 dt_aligned_pixel_t picker_values;
2635
2636 const int in_out = (dt_modifier_is(dt_key_modifier_state(), DT_PRIMARY_MASK) && data->output_channels_shown) ? 1 : 0;
2637
2638 if(in_out)
2639 {
2640 for(size_t i = 0; i < 4; i++)
2641 {
2642 raw_min[i] = module->picked_output_color_min[i];
2643 raw_max[i] = module->picked_output_color_max[i];
2644 }
2645 }
2646 else
2647 {
2648 for(size_t i = 0; i < 4; i++)
2649 {
2650 raw_min[i] = module->picked_color_min[i];
2651 raw_max[i] = module->picked_color_max[i];
2652 }
2653 }
2654
2655 const dt_iop_gui_blendif_channel_t *channel = &data->channel[data->tab];
2657 dt_iop_gui_blendif_filter_t *sl = &data->filter[in_out];
2658
2659 float *parameters = &(bp->blendif_parameters[4 * ch]);
2660
2661 const dt_develop_blend_colorspace_t blend_csp = data->channel_tabs_csp;
2663 const dt_iop_order_iccprofile_info_t *work_profile = (blend_csp == DEVELOP_BLEND_CS_RGB_SCENE)
2665 : dt_ioppr_get_iop_work_profile_info(module, module->dev->iop);
2666
2667 gboolean reverse_hues = FALSE;
2668 if(cst == IOP_CS_HSL && tab == CHANNEL_INDEX_H)
2669 {
2670 if((raw_max[3] - raw_min[3]) < (raw_max[0] - raw_min[0]) && raw_min[3] < 0.5f && raw_max[3] > 0.5f)
2671 {
2672 raw_max[0] = raw_max[3] < 0.5f ? raw_max[3] + 0.5f : raw_max[3] - 0.5f;
2673 raw_min[0] = raw_min[3] < 0.5f ? raw_min[3] + 0.5f : raw_min[3] - 0.5f;
2674 reverse_hues = TRUE;
2675 }
2676 }
2677 else if((cst == IOP_CS_LCH && tab == CHANNEL_INDEX_h) || (cst == IOP_CS_JZCZHZ && tab == CHANNEL_INDEX_hz))
2678 {
2679 if((raw_max[3] - raw_min[3]) < (raw_max[2] - raw_min[2]) && raw_min[3] < 0.5f && raw_max[3] > 0.5f)
2680 {
2681 raw_max[2] = raw_max[3] < 0.5f ? raw_max[3] + 0.5f : raw_max[3] - 0.5f;
2682 raw_min[2] = raw_min[3] < 0.5f ? raw_min[3] + 0.5f : raw_min[3] - 0.5f;
2683 reverse_hues = TRUE;
2684 }
2685 }
2686
2687 _blendif_scale(data, cst, raw_max, picker_max, work_profile, in_out);
2688
2689 gboolean picker_box_changed = FALSE;
2690 const dt_colorpicker_sample_t *const sample = module->dev->color_picker.primary_sample;
2691 if(!IS_NULL_PTR(sample) && sample->size == DT_LIB_COLORPICKER_SIZE_BOX)
2692 {
2694 {
2695 picker_box_changed = TRUE;
2696 }
2697 else
2698 {
2699 const float epsilon = 1e-6f;
2700 for(int k = 0; k < 4; k++)
2701 {
2702 if(fabsf(sample->box[k] - data->picker_set_values_box[k]) > epsilon)
2703 {
2704 picker_box_changed = TRUE;
2705 break;
2706 }
2707 }
2708 }
2709
2710 if(picker_box_changed)
2711 {
2712 for(int k = 0; k < 4; k++) data->picker_set_values_box[k] = sample->box[k];
2715 }
2716 }
2717 else
2718 {
2721 }
2722
2734 if(channel->boost_factor_enabled && picker_box_changed
2736 {
2737 const float picker_margin = 0.02f;
2738 const float boost_step = 0.25f;
2739 const int max_boost_iters = 64;
2740 const float boost_min = channel->boost_factor_offset;
2741 const float boost_max = channel->boost_factor_offset + 18.0f;
2742 const float target_max = (picker_max[tab] > (1.0f - picker_margin)) ? 1.0f : (1.0f - picker_margin);
2743 float trial = bp->blendif_boost_factors[ch];
2744 gboolean within_bounds = picker_max[tab] <= target_max;
2745
2746 if(!within_bounds)
2747 {
2748 for(int iter = 0; iter < max_boost_iters && trial < boost_max; iter++)
2749 {
2750 trial = fminf(trial + boost_step, boost_max);
2751 bp->blendif_boost_factors[ch] = trial;
2752 _blendif_scale(data, cst, raw_max, picker_max, work_profile, in_out);
2753 within_bounds = picker_max[tab] <= target_max;
2754 if(within_bounds) break;
2755 }
2756 }
2757
2758 if(within_bounds)
2759 {
2760 for(int iter = 0; iter < max_boost_iters && trial > boost_min; iter++)
2761 {
2762 const float candidate = fmaxf(trial - boost_step, boost_min);
2763 bp->blendif_boost_factors[ch] = candidate;
2764 _blendif_scale(data, cst, raw_max, picker_max, work_profile, in_out);
2765 if(picker_max[tab] > target_max)
2766 break;
2767 trial = candidate;
2768 }
2769 }
2770
2771 bp->blendif_boost_factors[ch] = trial;
2772 _blendif_scale(data, cst, raw_max, picker_max, work_profile, in_out);
2773 }
2774
2775 _blendif_scale(data, cst, raw_min, picker_min, work_profile, in_out);
2776
2777 const float feather = 0.01f;
2778
2779 if(picker_min[tab] > picker_max[tab])
2780 {
2781 const float tmp = picker_min[tab];
2782 picker_min[tab] = picker_max[tab];
2783 picker_max[tab] = tmp;
2784 }
2785
2786 picker_values[0] = CLAMP(picker_min[tab] - feather, 0.f, 1.f);
2787 picker_values[1] = CLAMP(picker_min[tab] + feather, 0.f, 1.f);
2788 picker_values[2] = CLAMP(picker_max[tab] - feather, 0.f, 1.f);
2789 picker_values[3] = CLAMP(picker_max[tab] + feather, 0.f, 1.f);
2790
2791 if(picker_values[1] > picker_values[2])
2792 {
2793 picker_values[1] = CLAMP(picker_min[tab], 0.f, 1.f);
2794 picker_values[2] = CLAMP(picker_max[tab], 0.f, 1.f);
2795 }
2796
2797 picker_values[0] = CLAMP(picker_values[0], 0.f, picker_values[1]);
2798 picker_values[3] = CLAMP(picker_values[3], picker_values[2], 1.f);
2799
2801 for(int k = 0; k < 4; k++)
2804
2805 // update picked values
2806 _update_gradient_slider_pickers(NULL, module);
2807
2808 const float boost_factor = _get_boost_factor(data, data->tab, in_out);
2809 for(int k = 0; k < 4; k++)
2810 {
2811 char text[256];
2813 text, sizeof(text));
2814 gtk_label_set_text(sl->label[k], text);
2815 }
2816
2818
2819 // save values to parameters
2821 for(int k = 0; k < 4; k++)
2824
2825 // de-activate processing of this channel if maximum span is selected
2826 if(parameters[1] == 0.0f && parameters[2] == 1.0f)
2827 bp->blendif &= ~(1 << ch);
2828 else
2829 bp->blendif |= (1 << ch);
2830
2831 // set the polarity of the channel to include the picked values
2832 if(reverse_hues == ((bp->mask_combine & DEVELOP_COMBINE_INV) == DEVELOP_COMBINE_INV))
2833 bp->blendif &= ~(1 << (16 + ch));
2834 else
2835 bp->blendif |= 1 << (16 + ch);
2836
2837 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
2838
2840 _blendop_blendif_update_tab(module, tab);
2841
2842 return TRUE;
2843 }
2844 else if(picker == data->colorpicker)
2845 {
2846 if(dt_gui_widgets_suppressed()) return TRUE;
2847
2848 _update_gradient_slider_pickers(NULL, module);
2849
2850 return TRUE;
2851 }
2852 else return FALSE; // needs to be handled by module
2853}
2854
2856{
2857 switch(cst)
2858 {
2863 break;
2864 default:
2866 break;
2867 }
2868 if(!dt_develop_blend_colorspace_is_compatible(module, cst)) return FALSE;
2869 if(cst != module->blend_params->blend_cst)
2870 {
2872
2873 // look for last history item for this module with the selected blending mode to copy parametric mask settings
2874 for(const GList *history = g_list_last(module->dev->history); history; history = g_list_previous(history))
2875 {
2876 const dt_dev_history_item_t *data = (dt_dev_history_item_t *)(history->data);
2877 if(data->module == module && data->blend_params->blend_cst == cst)
2878 {
2879 const dt_develop_blend_params_t *hp = data->blend_params;
2880 dt_develop_blend_params_t *np = module->blend_params;
2881
2882 np->blend_mode = hp->blend_mode;
2884 np->blendif = hp->blendif;
2885 memcpy(np->blendif_parameters, hp->blendif_parameters, sizeof(hp->blendif_parameters));
2887 break;
2888 }
2889 }
2890
2891 dt_iop_gui_blend_data_t *bd = module->gui->blend_data;
2892 const int cst_old = _blendop_blendif_get_picker_colorspace(bd);
2893 dt_dev_add_history_item(module->dev, module, FALSE, TRUE);
2894 dt_iop_gui_update(module);
2895
2896 if(cst_old != _blendop_blendif_get_picker_colorspace(bd) &&
2897 (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bd->colorpicker)) ||
2898 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bd->colorpicker_set_values))))
2899 {
2901 dt_dev_pixelpipe_update_history_all(bd->module->dev);
2902 }
2903
2904 return TRUE;
2905 }
2906 return FALSE;
2907}
2908
2909static void _blendif_select_colorspace(GtkMenuItem *menuitem, dt_iop_module_t *module)
2910{
2911 dt_develop_blend_colorspace_t cst = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menuitem), "dt-blend-cst"));
2912 if(_blendif_change_blend_colorspace(module, cst))
2913 {
2914 gtk_widget_queue_draw(module->gui->widget);
2915 }
2916}
2917
2918static void _blendif_show_output_channels(GtkMenuItem *menuitem, dt_iop_module_t *module)
2919{
2921 if(IS_NULL_PTR(bd) || !bd->blendif_support || !bd->blendif_inited) return;
2922 if(!bd->output_channels_shown)
2923 {
2925 dt_iop_gui_update(module);
2926 }
2927}
2928
2929static void _blendif_hide_output_channels(GtkMenuItem *menuitem, dt_iop_module_t *module)
2930{
2932 if(IS_NULL_PTR(bd) || !bd->blendif_support || !bd->blendif_inited) return;
2933 if(bd->output_channels_shown)
2934 {
2937 {
2938 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
2939 }
2940 dt_iop_gui_update(module);
2941 }
2942}
2943
2944static void _blendif_append_colorspace_item(GtkMenu *menu, dt_iop_module_t *module, const char *label,
2946{
2947 GtkWidget *mi = gtk_check_menu_item_new_with_label(label);
2948 const gboolean current = module->blend_params->blend_cst == cst;
2949 if(current)
2950 {
2951 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mi), TRUE);
2952 dt_gui_add_class(mi, "active_menu_item");
2953 }
2954 // the space an edit already uses stays readable as such, even where it would not be offered
2955 gtk_widget_set_sensitive(mi, current || dt_develop_blend_colorspace_is_compatible(module, cst));
2956 g_object_set_data_full(G_OBJECT(mi), "dt-blend-cst", GINT_TO_POINTER(cst), NULL);
2957 g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_blendif_select_colorspace), module);
2958 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
2959}
2960
2961static gboolean _blendif_options_callback(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
2962{
2963 if(event->button != 1 && event->button != 2) return FALSE;
2965 if(IS_NULL_PTR(bd) || !bd->blendif_support || !bd->blendif_inited) return FALSE;
2966
2967 GtkWidget *mi;
2968 GtkMenu *menu = dt_gui_get_global()->presets_popup_menu;
2969 if(menu) gtk_widget_destroy(GTK_WIDGET(menu));
2970 dt_gui_get_global()->presets_popup_menu = GTK_MENU(gtk_menu_new());
2972
2973 // add a section to switch blending color spaces
2975 if(module_cst == DEVELOP_BLEND_CS_LAB || module_cst == DEVELOP_BLEND_CS_RGB_DISPLAY
2976 || module_cst == DEVELOP_BLEND_CS_RGB_SCENE)
2977 {
2978
2979 mi = gtk_menu_item_new_with_label(_("reset to default blend colorspace"));
2980 g_object_set_data_full(G_OBJECT(mi), "dt-blend-cst", GINT_TO_POINTER(DEVELOP_BLEND_CS_NONE), NULL);
2981 g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_blendif_select_colorspace), module);
2982 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
2983
2984 // every blending space is listed, the ones this module cannot blend in are greyed out
2986 _blendif_append_colorspace_item(menu, module, _("RGB (display)"), DEVELOP_BLEND_CS_RGB_DISPLAY);
2987 _blendif_append_colorspace_item(menu, module, _("RGB (scene)"), DEVELOP_BLEND_CS_RGB_SCENE);
2988
2989 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
2990
2991 if(bd->output_channels_shown)
2992 {
2993 mi = gtk_menu_item_new_with_label(_("reset and hide output channels"));
2994 g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_blendif_hide_output_channels), module);
2995 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
2996 }
2997 else
2998 {
2999 mi = gtk_menu_item_new_with_label(_("show output channels"));
3000 g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(_blendif_show_output_channels), module);
3001 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
3002 }
3003 }
3004
3005 dt_gui_menu_popup(dt_gui_get_global()->presets_popup_menu, GTK_WIDGET(button), GDK_GRAVITY_SOUTH_EAST, GDK_GRAVITY_NORTH_EAST);
3006
3008 return TRUE;
3009}
3010
3011// activate channel/mask view
3013{
3014 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
3015 // These should all use an uniform setter function.
3016 //
3017 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
3018 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
3019 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
3020 // that changes over the pipe lifecycle.
3021 //
3022 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
3023 // (but not all pipes are connected to a GUI, so you need to cover all cases all the time and don't forget to test everything),
3024 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
3025 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
3026 // planning for pipeline execution.
3027 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
3028 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
3029 // the preview at the far end of the pipe.
3030 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
3031 // which flushed lines that could be reused later and were only temporarily not needed.
3032
3033 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
3034
3035 dt_dev_pixelpipe_display_mask_t new_request_mask_display = module->request_mask_display | mode;
3036
3037 // in case user requests channel display: get the cannel
3038 if(new_request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_CHANNEL)
3039 {
3041
3042 if(widget == GTK_WIDGET(data->filter[1].slider))
3044
3045 new_request_mask_display &= ~DT_DEV_PIXELPIPE_DISPLAY_ANY;
3046 new_request_mask_display |= channel;
3047 }
3048
3049 // only if something has changed: reprocess center view
3050 if(new_request_mask_display != module->request_mask_display)
3051 {
3052 module->request_mask_display = new_request_mask_display;
3056 }
3057}
3058
3059// toggle channel/mask view
3061{
3062 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
3063 // These should all use an uniform setter function.
3064 //
3065 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
3066 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
3067 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
3068 // that changes over the pipe lifecycle.
3069 //
3070 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
3071 // (but not all pipes are connected to a GUI, so you need to cover all cases all the time and don't forget to test everything),
3072 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
3073 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
3074 // planning for pipeline execution.
3075 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
3076 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
3077 // the preview at the far end of the pipe.
3078 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
3079 // which flushed lines that could be reused later and were only temporarily not needed.
3080
3081 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
3082
3083 dt_dev_pixelpipe_display_mask_t new_request_mask_display = module->request_mask_display & ~DT_DEV_PIXELPIPE_DISPLAY_STICKY;
3084
3085 // toggle mode
3086 if(module->request_mask_display & mode)
3087 new_request_mask_display &= ~mode;
3088 else
3089 new_request_mask_display |= mode;
3090
3092 if(new_request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_STICKY)
3094 else
3095 data->save_for_leave &= ~DT_DEV_PIXELPIPE_DISPLAY_STICKY;
3097
3098 new_request_mask_display &= ~DT_DEV_PIXELPIPE_DISPLAY_ANY;
3099
3100 // in case user requests channel display: get the cannel
3101 if(new_request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_CHANNEL)
3102 {
3104
3105 if(widget == GTK_WIDGET(data->filter[1].slider))
3107
3108 new_request_mask_display &= ~DT_DEV_PIXELPIPE_DISPLAY_ANY;
3109 new_request_mask_display |= channel;
3110 }
3111
3112 if(new_request_mask_display != module->request_mask_display)
3113 {
3114 module->request_mask_display = new_request_mask_display;
3118 }
3119}
3120
3130{
3131 dt_iop_gui_blend_data_t *data = !IS_NULL_PTR(module) ? module->gui->blend_data : NULL;
3132 if(IS_NULL_PTR(data)) return;
3133
3134 const gboolean channel_active
3135 = (module->request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_CHANNEL) != 0;
3136 const gboolean output_active
3137 = channel_active && (module->request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_OUTPUT) != 0;
3138
3140 if(GTK_IS_TOGGLE_BUTTON(data->filter[0].channel_display))
3141 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->filter[0].channel_display),
3142 channel_active && !output_active);
3143 if(GTK_IS_TOGGLE_BUTTON(data->filter[1].channel_display))
3144 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->filter[1].channel_display),
3145 output_active);
3147}
3148
3157static void _blendop_blendif_channel_display_toggled(GtkToggleButton *button, dt_iop_module_t *module)
3158{
3159 if(dt_gui_widgets_suppressed()) return;
3160
3161 dt_iop_gui_blend_data_t *data = !IS_NULL_PTR(module) ? module->gui->blend_data : NULL;
3162 if(IS_NULL_PTR(data) || IS_NULL_PTR(data->channel)) return;
3163
3164 const int in_out = GTK_WIDGET(button) == data->filter[1].channel_display ? 1 : 0;
3165 const gboolean active = gtk_toggle_button_get_active(button);
3166 GtkWidget *const other_button = data->filter[!in_out].channel_display;
3167
3169 if(active && GTK_IS_TOGGLE_BUTTON(other_button))
3170 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(other_button), FALSE);
3172
3173 dt_dev_pixelpipe_display_mask_t new_request_mask_display
3174 = module->request_mask_display
3175 & ~(DT_DEV_PIXELPIPE_DISPLAY_CHANNEL | DT_DEV_PIXELPIPE_DISPLAY_OUTPUT
3176 | DT_DEV_PIXELPIPE_DISPLAY_ANY | DT_DEV_PIXELPIPE_DISPLAY_STICKY);
3177
3178 if(active)
3179 {
3180 new_request_mask_display |= DT_DEV_PIXELPIPE_DISPLAY_CHANNEL
3182 | data->channel[data->tab].display_channel;
3183 if(in_out) new_request_mask_display |= DT_DEV_PIXELPIPE_DISPLAY_OUTPUT;
3184 }
3185
3187 if(active)
3189 else
3190 data->save_for_leave &= ~DT_DEV_PIXELPIPE_DISPLAY_STICKY;
3192
3193 if(new_request_mask_display == module->request_mask_display) return;
3194
3195 module->request_mask_display = new_request_mask_display;
3197
3198 dt_iop_request_focus(module);
3200}
3201
3202
3203// magic mode: if mouse cursor enters a gradient slider with shift and/or control pressed we
3204// enter channel display and/or mask display mode
3205static gboolean _blendop_blendif_enter(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *module)
3206{
3207 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
3208 // These should all use an uniform setter function.
3209 //
3210 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
3211 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
3212 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
3213 // that changes over the pipe lifecycle.
3214 //
3215 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
3216 // (but not all pipes are connected to a GUI, so you need to cover all cases all the time and don't forget to test everything),
3217 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
3218 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
3219 // planning for pipeline execution.
3220 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
3221 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
3222 // the preview at the far end of the pipe.
3223 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
3224 // which flushed lines that could be reused later and were only temporarily not needed.
3225
3226 if(dt_gui_widgets_suppressed()) return FALSE;
3227 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
3228
3230
3231 // depending on shift modifiers we activate channel and/or mask display
3232 if(dt_modifier_is(event->state, GDK_SHIFT_MASK | DT_PRIMARY_MASK))
3233 {
3235 }
3236 else if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
3237 {
3239 }
3240 else if(dt_modifier_is(event->state, DT_PRIMARY_MASK))
3241 {
3243 }
3244
3246 if(mode && data->timeout_handle)
3247 {
3248 // purge any remaining timeout handlers
3249 g_source_remove(data->timeout_handle);
3250 data->timeout_handle = 0;
3251 }
3253 {
3254 // save request_mask_display to restore later
3255 data->save_for_leave = module->request_mask_display & ~DT_DEV_PIXELPIPE_DISPLAY_STICKY;
3256 }
3258
3259 /* A plain hover only gives keyboard focus to the slider. Reapplying an
3260 * already-active CHANNEL request here would derive INPUT/OUTPUT again from
3261 * the hovered widget and switch the persistent channel toggle without a
3262 * click. Only modifier-assisted hover is allowed to change the preview. */
3264 _blendop_blendif_channel_mask_view(widget, module, mode);
3265
3266 gtk_widget_grab_focus(widget);
3267 return FALSE;
3268}
3269
3270
3271// handler for delayed mask/channel display mode switch-off
3272static gboolean _blendop_blendif_leave_delayed(gpointer data)
3273{
3274 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
3275 // These should all use an uniform setter function.
3276 //
3277 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
3278 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
3279 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
3280 // that changes over the pipe lifecycle.
3281 //
3282 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
3283 // (but not all pipes are connected to a GUI, so you need to cover all cases all the time and don't forget to test everything),
3284 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
3285 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
3286 // planning for pipeline execution.
3287 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
3288 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
3289 // the preview at the far end of the pipe.
3290 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
3291 // which flushed lines that could be reused later and were only temporarily not needed.
3292
3293 dt_iop_module_t *module = (dt_iop_module_t *)data;
3294 dt_iop_gui_blend_data_t *bd = module->gui->blend_data;
3295 int reprocess = 0;
3296
3297 dt_pthread_mutex_lock(&bd->lock);
3298 // restore saved request_mask_display and reprocess image
3299 if(bd->timeout_handle && (module->request_mask_display != (bd->save_for_leave & ~DT_DEV_PIXELPIPE_DISPLAY_STICKY)))
3300 {
3301 module->request_mask_display = bd->save_for_leave & ~DT_DEV_PIXELPIPE_DISPLAY_STICKY;
3303 reprocess = 1;
3304 }
3305 bd->timeout_handle = 0;
3306 dt_pthread_mutex_unlock(&bd->lock);
3307
3308 if(reprocess)
3309 {
3312 }
3313 // return FALSE and thereby terminate the handler
3314 return FALSE;
3315}
3316
3317// de-activate magic mode when leaving the gradient slider
3318static gboolean _blendop_blendif_leave(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *module)
3319{
3320 if(dt_gui_widgets_suppressed()) return FALSE;
3321 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
3322
3323 // do not immediately switch-off mask/channel display in case user leaves gradient only briefly.
3324 // instead we activate a handler function that gets triggered after some timeout
3328 data->timeout_handle = g_timeout_add(1000, _blendop_blendif_leave_delayed, module);
3330
3331 return FALSE;
3332}
3333
3334
3335static gboolean _blendop_blendif_key_press(GtkWidget *widget, GdkEventKey *event, dt_iop_module_t *module)
3336{
3337 if(dt_gui_widgets_suppressed()) return FALSE;
3338
3339 gboolean handled = FALSE;
3340
3341 switch(event->keyval)
3342 {
3343 case GDK_KEY_m:
3344 case GDK_KEY_M:
3346 handled = TRUE;
3347 }
3348
3349 if(handled)
3350 dt_iop_request_focus(module);
3351
3352 return handled;
3353}
3354
3355
3356#define COLORSTOPS(gradient) sizeof(gradient) / sizeof(dt_iop_gui_blendif_colorstop_t), gradient
3357
3359 = { { N_("L"), N_("sliders for L channel"), 1.0f / 100.0f, COLORSTOPS(_gradient_L), TRUE, 0.0f,
3362 { N_("a"), N_("sliders for a channel"), 1.0f / 256.0f, COLORSTOPS(_gradient_a), TRUE, 0.0f,
3365 { N_("b"), N_("sliders for b channel"), 1.0f / 256.0f, COLORSTOPS(_gradient_b), TRUE, 0.0f,
3368 { N_("C"), N_("sliders for chroma channel (of LCh)"), 1.0f / 100.0f, COLORSTOPS(_gradient_chroma),
3369 TRUE, 0.0f,
3372 { N_("h"), N_("sliders for hue channel (of LCh)"), 1.0f / 360.0f, COLORSTOPS(_gradient_LCh_hue),
3373 FALSE, 0.0f,
3375 _blendif_scale_print_hue, NULL, N_("hue") },
3376 { NULL } };
3377
3379 = { { N_("g"), N_("sliders for gray value"), 1.0f / 255.0f, COLORSTOPS(_gradient_gray), TRUE, 0.0f,
3382 { N_("R"), N_("sliders for red channel"), 1.0f / 255.0f, COLORSTOPS(_gradient_red), TRUE, 0.0f,
3385 { N_("G"), N_("sliders for green channel"), 1.0f / 255.0f, COLORSTOPS(_gradient_green), TRUE, 0.0f,
3388 { N_("B"), N_("sliders for blue channel"), 1.0f / 255.0f, COLORSTOPS(_gradient_blue), TRUE, 0.0f,
3391 { N_("H"), N_("sliders for hue channel (of HSL)"), 1.0f / 360.0f, COLORSTOPS(_gradient_HSL_hue),
3392 FALSE, 0.0f,
3394 _blendif_scale_print_hue, NULL, N_("hue") },
3395 { N_("S"), N_("sliders for chroma channel (of HSL)"), 1.0f / 100.0f, COLORSTOPS(_gradient_chroma),
3396 FALSE, 0.0f,
3399 { N_("L"), N_("sliders for value channel (of HSL)"), 1.0f / 100.0f, COLORSTOPS(_gradient_gray),
3400 FALSE, 0.0f,
3403 { NULL } };
3404
3406 = { { N_("g"), N_("sliders for gray value"), 1.0f / 255.0f, COLORSTOPS(_gradient_gray), TRUE, 0.0f,
3409 { N_("R"), N_("sliders for red channel"), 1.0f / 255.0f, COLORSTOPS(_gradient_red), TRUE, 0.0f,
3412 { N_("G"), N_("sliders for green channel"), 1.0f / 255.0f, COLORSTOPS(_gradient_green), TRUE, 0.0f,
3415 { N_("B"), N_("sliders for blue channel"), 1.0f / 255.0f, COLORSTOPS(_gradient_blue), TRUE, 0.0f,
3418 { N_("Jz"), N_("sliders for value channel (of JzCzhz)"), 1.0f / 100.0f, COLORSTOPS(_gradient_gray),
3419 TRUE, -6.64385619f, // cf. _blend_init_blendif_boost_parameters
3422 { N_("Cz"), N_("sliders for chroma channel (of JzCzhz)"), 1.0f / 100.0f, COLORSTOPS(_gradient_chroma),
3423 TRUE, -6.64385619f, // cf. _blend_init_blendif_boost_parameters
3426 { N_("hz"), N_("sliders for hue channel (of JzCzhz)"), 1.0f / 360.0f, COLORSTOPS(_gradient_JzCzhz_hue),
3427 FALSE, 0.0f,
3429 _blendif_scale_print_hue, NULL, N_("hue") },
3430 { NULL } };
3431
3432const char *slider_tooltip[] = { N_("adjustment based on input received by this module:\n* range defined by upper markers: "
3433 "blend fully\n* range defined by lower markers: do not blend at all\n* range between "
3434 "adjacent upper/lower markers: blend gradually"),
3435 N_("adjustment based on unblended output of this module:\n* range defined by upper "
3436 "markers: blend fully\n* range defined by lower markers: do not blend at all\n* range "
3437 "between adjacent upper/lower markers: blend gradually") };
3438
3440{
3441 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
3442 // These should all use an uniform setter function.
3443 //
3444 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
3445 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
3446 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
3447 // that changes over the pipe lifecycle.
3448 //
3449 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
3450 // (but not all pipes are connected to a GUI, so you need to cover all cases all the time and don't forget to test everything),
3451 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
3452 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
3453 // planning for pipeline execution.
3454 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
3455 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
3456 // the preview at the far end of the pipe.
3457 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
3458 // which flushed lines that could be reused later and were only temporarily not needed.
3459 dt_iop_gui_blend_data_t *bd = module->gui->blend_data;
3460
3461 if(IS_NULL_PTR(bd) || !bd->blendif_support || !bd->blendif_inited) return;
3462
3464
3466 if(bd->timeout_handle)
3467 {
3468 g_source_remove(bd->timeout_handle);
3469 bd->timeout_handle = 0;
3471 {
3472 module->request_mask_display = bd->save_for_leave & ~DT_DEV_PIXELPIPE_DISPLAY_STICKY;
3475 }
3476 }
3478
3479 /* update output channel mask visibility */
3480 gtk_widget_set_visible(GTK_WIDGET(bd->filter[1].box), bd->output_channels_shown);
3481
3482 /* update tabs */
3483 if(bd->channel_tabs_csp != bd->csp)
3484 {
3485 bd->channel = NULL;
3486
3487 switch(bd->csp)
3488 {
3490 bd->channel = Lab_channels;
3491 break;
3493 bd->channel = rgb_channels;
3494 break;
3496 bd->channel = rgbj_channels;
3497 break;
3498 default:
3499 assert(FALSE); // blendif not supported for RAW, which is already caught upstream; we should not get
3500 // here
3501 }
3502
3504
3505 /* remove tabs before adding others */
3507
3508 bd->channel_tabs_csp = bd->csp;
3509
3510 int index = 0;
3511 for(const dt_iop_gui_blendif_channel_t *ch = bd->channel; !IS_NULL_PTR(ch->label); ch++, index++)
3512 {
3513 dt_ui_notebook_page(bd->channel_tabs, ch->label, _(ch->tooltip));
3514 gtk_widget_show_all(GTK_WIDGET(gtk_notebook_get_nth_page(bd->channel_tabs, index)));
3515 }
3516
3517 bd->tab = 0;
3518 gtk_notebook_set_current_page(GTK_NOTEBOOK(bd->channel_tabs), bd->tab);
3519 }
3520
3521 _blendop_blendif_update_tab(module, bd->tab);
3523
3525}
3526
3527
3528void dt_iop_gui_init_blendif(GtkBox *blendw, dt_iop_module_t *module, GtkWidget *blendif_header)
3529{
3531
3532 bd->blendif_box = blendw;
3533
3534 /* create and add blendif support if module supports it */
3535 if(bd->blendif_support)
3536 {
3537 dt_iop_togglebutton_new_no_register(module, "blend`tools", N_("reset blend mask settings"), NULL,
3538 G_CALLBACK(_blendop_blendif_reset), FALSE, 0, 0,
3539 dtgtk_cairo_paint_reset, blendif_header);
3540
3541 GtkWidget *header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3542 gtk_widget_set_name(header, "blendif-pickers");
3543
3544 bd->tab = 0;
3546 bd->channel_tabs = GTK_NOTEBOOK(gtk_notebook_new());
3547 gtk_notebook_set_scrollable(bd->channel_tabs, TRUE);
3548 gtk_box_pack_start(GTK_BOX(bd->blendif_box), GTK_WIDGET(bd->channel_tabs), FALSE, FALSE, 0);
3549 gtk_notebook_set_action_widget(GTK_NOTEBOOK(bd->channel_tabs), header, GTK_PACK_END);
3550 dt_gui_add_class(GTK_WIDGET(bd->channel_tabs), "empty");
3551
3553 gtk_widget_set_tooltip_text(bd->colorpicker, _("pick GUI color from image\nctrl+click or right-click to select an area"));
3554
3558
3559 gtk_widget_set_tooltip_text(bd->colorpicker_set_values, _("set the range based on an area from the image\n"
3560 "drag to use the input image\n"
3561 "ctrl+drag to use the output image"));
3562
3563 dt_iop_togglebutton_new_no_register(module, "blend`tools", N_("invert all channel's polarities"), NULL,
3564 G_CALLBACK(_blendop_blendif_invert), FALSE, 0, 0,
3565 dtgtk_cairo_paint_invert, header);
3566
3567 gtk_widget_show_all(header);
3568
3569 for(int in_out = 1; in_out >= 0; in_out--)
3570 {
3571 dt_iop_gui_blendif_filter_t *sl = &bd->filter[in_out];
3572
3573 GtkWidget *slider_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3574
3576 in_out ? "blend-upper" : "blend-lower"));
3577 gtk_box_pack_start(GTK_BOX(slider_box), GTK_WIDGET(sl->slider), TRUE, TRUE, 0);
3578
3580 dt_gui_add_class(sl->polarity, "dt_ignore_fg_state");
3581
3582 gtk_widget_set_tooltip_text(sl->polarity, _("toggle polarity. best seen by enabling 'display mask'"));
3583 gtk_box_pack_end(GTK_BOX(slider_box), GTK_WIDGET(sl->polarity), FALSE, FALSE, 0);
3584
3585 GtkWidget *label_box = gtk_grid_new();
3586 gtk_grid_set_column_homogeneous(GTK_GRID(label_box), TRUE);
3587
3588 sl->head = GTK_LABEL(dt_ui_label_new(in_out ? _("output") : _("input")));
3589 gtk_grid_attach(GTK_GRID(label_box), GTK_WIDGET(sl->head), 0, 0, 1, 1);
3590
3591 GtkWidget *overlay = gtk_overlay_new();
3592 gtk_grid_attach(GTK_GRID(label_box), overlay, 1, 0, 3, 1);
3593
3594 sl->picker_label = GTK_LABEL(gtk_label_new(""));
3595 gtk_widget_set_name(GTK_WIDGET(sl->picker_label), "blend-data");
3596 gtk_label_set_xalign(sl->picker_label, .0);
3597 gtk_label_set_yalign(sl->picker_label, 1.0);
3598 gtk_container_add(GTK_CONTAINER(overlay), GTK_WIDGET(sl->picker_label));
3599
3600 for(int k = 0; k < 4; k++)
3601 {
3602 sl->label[k] = GTK_LABEL(gtk_label_new(NULL));
3603 gtk_widget_set_name(GTK_WIDGET(sl->label[k]), "blend-data");
3604 gtk_label_set_xalign(sl->label[k], .35 + k * .65/3);
3605 gtk_label_set_yalign(sl->label[k], k % 2);
3606 gtk_overlay_add_overlay(GTK_OVERLAY(overlay), GTK_WIDGET(sl->label[k]));
3607 }
3608
3609 gtk_widget_set_tooltip_text(GTK_WIDGET(sl->slider),
3610 _("double-click to reset.\npress 'm' to toggle mask view."));
3611 gtk_widget_set_tooltip_text(GTK_WIDGET(sl->head), _(slider_tooltip[in_out]));
3612
3613 g_signal_connect(G_OBJECT(sl->slider), "value-changed", G_CALLBACK(_blendop_blendif_sliders_callback), bd);
3614 g_signal_connect(G_OBJECT(sl->slider), "value-reset", G_CALLBACK(_blendop_blendif_sliders_reset_callback), bd);
3615 g_signal_connect(G_OBJECT(sl->slider), "leave-notify-event", G_CALLBACK(_blendop_blendif_leave), module);
3616 g_signal_connect(G_OBJECT(sl->slider), "enter-notify-event", G_CALLBACK(_blendop_blendif_enter), module);
3617 g_signal_connect(G_OBJECT(sl->slider), "key-press-event", G_CALLBACK(_blendop_blendif_key_press), module);
3618 g_signal_connect(G_OBJECT(sl->polarity), "toggled", G_CALLBACK(_blendop_blendif_polarity_callback), bd);
3619
3620 // The label is semantically "part of" the slider, even though it's a different widget,
3621 // so the parent box has no internal spacing between children to keep them grouped.
3622 sl->box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
3623 GtkWidget *channel_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
3624 {
3625 gtk_box_pack_start(GTK_BOX(channel_box), GTK_WIDGET(label_box), FALSE, FALSE, 0);
3626 gtk_box_pack_start(GTK_BOX(channel_box), GTK_WIDGET(slider_box), FALSE, FALSE, 0);
3627 }
3628 gtk_box_pack_start(GTK_BOX(sl->box), channel_box, FALSE, FALSE, 0);
3629
3630 GtkWidget *display_controls = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3631 {
3632 sl->log_scale = gtk_toggle_button_new_with_label(_("Log scale"));
3633 gtk_widget_set_tooltip_text(sl->log_scale,
3634 _("toggle the alternative logarithmic or magnified scale for this channel"));
3635 g_signal_connect(G_OBJECT(sl->log_scale), "toggled",
3636 G_CALLBACK(_blendop_blendif_log_scale_toggled), module);
3637 gtk_box_pack_start(GTK_BOX(display_controls), sl->log_scale, FALSE, FALSE, 0);
3638
3640 gtk_widget_set_tooltip_text(
3641 sl->channel_display,
3642 in_out ? _("display the current channel from the unblended module output")
3643 : _("display the current channel from the module input"));
3644 g_signal_connect(G_OBJECT(sl->channel_display), "toggled",
3645 G_CALLBACK(_blendop_blendif_channel_display_toggled), module);
3646 gtk_box_pack_start(GTK_BOX(display_controls), sl->channel_display, FALSE, FALSE, 0);
3647 }
3648 gtk_box_pack_start(GTK_BOX(sl->box), display_controls, FALSE, FALSE, 0);
3649 gtk_box_pack_start(GTK_BOX(bd->blendif_box), GTK_WIDGET(sl->box), FALSE, FALSE, 0);
3650
3651 }
3652
3659 gtk_widget_set_tooltip_text(bd->channel_boost_factor_slider, _("adjust the boost factor of the channel mask"));
3660 gtk_widget_set_sensitive(bd->channel_boost_factor_slider, FALSE);
3661
3662 g_signal_connect(G_OBJECT(bd->channel_boost_factor_slider), "value-changed",
3664
3665 gtk_box_pack_start(GTK_BOX(bd->blendif_box), GTK_WIDGET(bd->channel_boost_factor_slider), FALSE, FALSE, 0);
3666
3667 g_signal_connect(G_OBJECT(bd->channel_tabs), "switch_page", G_CALLBACK(_blendop_blendif_tab_switch), bd);
3668 g_signal_connect(G_OBJECT(bd->colorpicker), "toggled", G_CALLBACK(_update_gradient_slider_pickers), module);
3669 g_signal_connect(G_OBJECT(bd->colorpicker_set_values), "toggled", G_CALLBACK(_update_gradient_slider_pickers), module);
3670
3671 bd->blendif_inited = 1;
3672 }
3673 else
3674 {
3675 gchar *module_name = dt_history_item_get_name(module);
3676 gchar *markup = g_markup_printf_escaped(
3677 _("<i>Parametric masking is disabled because the <b>%s</b> module does not provide compatible color channels.</i>"),
3678 module_name);
3679 GtkWidget *label = gtk_label_new(NULL);
3680 gtk_label_set_markup(GTK_LABEL(label), markup);
3681 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
3682 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
3683 gtk_box_pack_start(GTK_BOX(bd->blendif_box), label, FALSE, FALSE, 0);
3684 dt_free(markup);
3685 dt_free(module_name);
3686 }
3687}
3688
3689void dt_iop_gui_init_contours(GtkBox *blendw, dt_iop_module_t *module)
3690{
3692
3693 gtk_box_pack_start(blendw, bd->details_slider, FALSE, FALSE, 0);
3694 gtk_box_pack_start(blendw, bd->blur_radius_slider, FALSE, FALSE, 0);
3695 gtk_box_pack_start(blendw, bd->masks_feathering_guide_combo, FALSE, FALSE, 0);
3696 gtk_box_pack_start(blendw, bd->feathering_radius_slider, FALSE, FALSE, 0);
3697 gtk_box_pack_start(blendw, bd->brightness_slider, FALSE, FALSE, 0);
3698 gtk_box_pack_start(blendw, bd->contrast_slider, FALSE, FALSE, 0);
3699}
3700
3702{
3703 dt_iop_gui_blend_data_t *bd = module->gui ? (dt_iop_gui_blend_data_t *)module->gui->blend_data : NULL;
3704 dt_develop_blend_params_t *bp = module->blend_params;
3705
3706 if(IS_NULL_PTR(bd) || !bd->masks_support || !bd->masks_inited) return;
3707
3709
3710 /* update masks state */
3711 dt_masks_form_gui_t *form_gui = module->dev->form_gui;
3712 dt_masks_form_t *visible_form = dt_masks_get_visible_form(module->dev);
3713 const gboolean module_creation = !IS_NULL_PTR(form_gui) && form_gui->creation
3714 && form_gui->creation_module == module;
3715 const dt_masks_type_t creation_type = module_creation && !IS_NULL_PTR(visible_form)
3716 ? visible_form->type
3717 : (module_creation ? form_gui->creation_type : DT_MASKS_NONE);
3719 const gboolean has_group_shapes = grp && (grp->type & DT_MASKS_GROUP) && grp->points;
3720 if(GTK_IS_WIDGET(bd->masks_combo))
3721 {
3723 if(has_group_shapes)
3724 {
3725 char txt[512];
3726 const guint n = g_list_length(grp->points);
3727 snprintf(txt, sizeof(txt), ngettext("%d shape used", "%d shapes used", n), n);
3729 }
3730 else
3731 {
3732 dt_bauhaus_combobox_add(bd->masks_combo, _("no mask used"));
3733 }
3735 gtk_widget_queue_draw(bd->masks_combo);
3736 }
3737
3738 if(!has_group_shapes)
3739 {
3741 // reset the gui
3743 }
3744
3745 if(bd->masks_support)
3746 {
3747 if(GTK_IS_TOGGLE_BUTTON(bd->masks_edit))
3748 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit),
3749 !module_creation && bd->masks_shown != DT_MASKS_EDIT_OFF);
3750
3751 if(GTK_IS_TOGGLE_BUTTON(bd->masks_polarity))
3752 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_polarity),
3754 }
3755
3756 // update buttons status
3757 for(int n = 0; n < DEVELOP_MASKS_NB_SHAPES; n++)
3758 {
3759 if(!GTK_IS_TOGGLE_BUTTON(bd->masks_shapes[n])) continue;
3760
3761 if(module_creation && (creation_type & bd->masks_type[n]))
3762 {
3763 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_shapes[n]), TRUE);
3764 }
3765 else
3766 {
3767 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_shapes[n]), FALSE);
3768 }
3769 }
3770
3773
3775}
3776
3777/* Mouse-wheel mapping grid.
3778 *
3779 * One radio group per row: a wheel/modifier combination edits exactly one mask property, or
3780 * nothing at all. The mapping lives in conf and is read back by the shapes through
3781 * dt_masks_scroll_get_interaction() (masks_gui.h); this grid only displays and writes it, and
3782 * holds no state of its own. Hence the re-read on "map": every module's blending panel shows
3783 * the same application-wide mapping and any of them can change it, so the grid that becomes
3784 * visible must show what is stored, not what it was built with.
3785 */
3786#define BLENDOP_SCROLL_DATA_ROW "blendop-scroll-row"
3787#define BLENDOP_SCROLL_DATA_INTERACTION "blendop-scroll-interaction"
3788
3789/* Property columns, in display order. DT_MASKS_INTERACTION_UNDEF ("nothing") is a column like
3790 * the others -- it is how a row is unmapped -- and comes last, after what it opts out of. */
3794
3795static void _blendop_masks_scroll_toggled(GtkToggleButton *button, gpointer user_data)
3796{
3797 // A radio group emits "toggled" twice per change, once for each side. Only the winner writes.
3798 if(!gtk_toggle_button_get_active(button)) return;
3799
3800 const dt_masks_scroll_modifier_t modifier
3801 = (dt_masks_scroll_modifier_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), BLENDOP_SCROLL_DATA_ROW));
3802 const dt_masks_interaction_t interaction
3803 = (dt_masks_interaction_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), BLENDOP_SCROLL_DATA_INTERACTION));
3804
3805 dt_masks_scroll_mapping_set(modifier, interaction);
3806}
3807
3808static void _blendop_masks_scroll_sync(GtkWidget *grid, gpointer user_data)
3809{
3810 GList *children = gtk_container_get_children(GTK_CONTAINER(grid));
3811 for(GList *child = children; child; child = g_list_next(child))
3812 {
3813 GtkWidget *cell = GTK_WIDGET(child->data);
3814 if(!GTK_IS_RADIO_BUTTON(cell)) continue;
3815
3816 const dt_masks_scroll_modifier_t modifier
3817 = (dt_masks_scroll_modifier_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), BLENDOP_SCROLL_DATA_ROW));
3818 const dt_masks_interaction_t interaction
3819 = (dt_masks_interaction_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), BLENDOP_SCROLL_DATA_INTERACTION));
3820 const gboolean active = (dt_masks_scroll_mapping_get(modifier) == interaction);
3821 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cell)) == active) continue;
3822
3823 // Writing conf back from a display refresh would turn a stale panel into the authority.
3824 g_signal_handlers_block_by_func(cell, G_CALLBACK(_blendop_masks_scroll_toggled), NULL);
3825 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cell), active);
3826 g_signal_handlers_unblock_by_func(cell, G_CALLBACK(_blendop_masks_scroll_toggled), NULL);
3827 }
3828 g_list_free(children);
3829}
3830
3832{
3833 GtkWidget *grid = gtk_grid_new();
3834 gtk_grid_set_row_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
3835 gtk_grid_set_column_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
3836 /* Natural width, not filled: handed more width than it needs, GtkGrid spreads the surplus
3837 * over its columns, which pushes the cells apart and re-centres every rotated title over a
3838 * cell wider than itself. What keeps the columns tight is this plus the spanning titles
3839 * below -- not the column spacing, which is free to stay at the panel's usual gutter
3840 * (measured: 24px columns either way, only the gutters change). */
3841 gtk_widget_set_halign(grid, GTK_ALIGN_START);
3842
3843 for(size_t column = 0; column < G_N_ELEMENTS(_blendop_scroll_columns); column++)
3844 {
3845 const dt_masks_interaction_t interaction = _blendop_scroll_columns[column];
3846 const char *alias = dt_masks_interaction_alias_name(interaction);
3847 // Second word on its own line: rotated 90 degrees the lines stack sideways, so the column
3848 // stays as tall as the longest single word instead of as tall as both of them.
3849 gchar *title = alias ? g_strdup_printf("%s/%s", _(dt_masks_interaction_name(interaction)), _(alias))
3850 : g_strdup(_(dt_masks_interaction_name(interaction)));
3851 GtkWidget *header = gtk_label_new(title);
3852 dt_free(title);
3853 gtk_label_set_angle(GTK_LABEL(header), 90.0 * 0.5);
3854 gtk_label_set_justify(GTK_LABEL(header), GTK_JUSTIFY_LEFT);
3855 // Anchor the rotated title to the bottom left of its cell, where the column it names
3856 // starts, instead of letting it float in the middle of whatever width the cell ends up with.
3857 gtk_label_set_xalign(GTK_LABEL(header), 0.0f);
3858 gtk_label_set_yalign(GTK_LABEL(header), 1.0f);
3859 gtk_widget_set_halign(header, GTK_ALIGN_START);
3860 gtk_widget_set_valign(header, GTK_ALIGN_END);
3861 /* Spanning, not one cell: a label rotated 45 degrees requests the width of its slanted
3862 * bounding box (measured: 102px for "Fading/Curvature" against 24px for a radio), and a
3863 * single-cell title makes its whole column that wide. Letting each title span the columns
3864 * to its right -- the direction it leans into, whose header space is free -- constrains the
3865 * sum instead of one column, and every column falls back to the width of its radio. */
3866 gtk_grid_attach(GTK_GRID(grid), header, (int)column + 1, 0,
3867 (int)(G_N_ELEMENTS(_blendop_scroll_columns) - column) + 1, 1);
3868 }
3869
3870 /* The rightmost title has no column to lean into, so it would widen its own the same way.
3871 * This trailing column exists only to absorb that overhang; it expands so the grid hands it
3872 * whatever width the titles need, rather than spreading it over the real columns. */
3873 GtkWidget *overhang = gtk_label_new("");
3874 gtk_widget_set_hexpand(overhang, TRUE);
3875 gtk_grid_attach(GTK_GRID(grid), overhang, (int)G_N_ELEMENTS(_blendop_scroll_columns) + 1, 0, 1, 1);
3876
3877 for(int row = 0; row < DT_MASKS_SCROLL_MODIFIER_LAST; row++)
3878 {
3880 GtkWidget *row_label = gtk_label_new(_(dt_masks_scroll_modifier_name(modifier)));
3881 gtk_label_set_xalign(GTK_LABEL(row_label), 0.0f);
3882 gtk_grid_attach(GTK_GRID(grid), row_label, 0, row + 1, 1, 1);
3883
3884 GtkWidget *group = NULL;
3885 for(size_t column = 0; column < G_N_ELEMENTS(_blendop_scroll_columns); column++)
3886 {
3887 const dt_masks_interaction_t interaction = _blendop_scroll_columns[column];
3888 GtkWidget *cell = gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(group));
3889 if(IS_NULL_PTR(group)) group = cell;
3890
3891 g_object_set_data(G_OBJECT(cell), BLENDOP_SCROLL_DATA_ROW, GINT_TO_POINTER(modifier));
3892 g_object_set_data(G_OBJECT(cell), BLENDOP_SCROLL_DATA_INTERACTION, GINT_TO_POINTER(interaction));
3893
3894 const char *alias = dt_masks_interaction_alias_name(interaction);
3895 gchar *tooltip;
3896 if(interaction == DT_MASKS_INTERACTION_UNDEF)
3897 tooltip = g_strdup_printf(_("%s does nothing"), _(dt_masks_scroll_modifier_name(modifier)));
3898 else if(alias)
3899 tooltip = g_strdup_printf(_("%s edits the %s of the hovered shape, or its %s on a gradient"),
3900 _(dt_masks_scroll_modifier_name(modifier)),
3901 _(dt_masks_interaction_name(interaction)), _(alias));
3902 else
3903 tooltip = g_strdup_printf(_("%s edits the %s of the hovered shape"),
3904 _(dt_masks_scroll_modifier_name(modifier)),
3905 _(dt_masks_interaction_name(interaction)));
3906 gtk_widget_set_tooltip_text(cell, tooltip);
3908
3909 // State first, handler after: building the group already emits "toggled" for every cell
3910 // it deactivates, and those must not be mistaken for the user picking something.
3911 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cell),
3912 dt_masks_scroll_mapping_get(modifier) == interaction);
3913 g_signal_connect(cell, "toggled", G_CALLBACK(_blendop_masks_scroll_toggled), NULL);
3914
3915 gtk_grid_attach(GTK_GRID(grid), cell, (int)column + 1, row + 1, 1, 1);
3916 }
3917 }
3918
3919 g_signal_connect(grid, "map", G_CALLBACK(_blendop_masks_scroll_sync), NULL);
3920
3921 return grid;
3922}
3923
3924void dt_iop_gui_init_masks(GtkBox *blendw, dt_iop_module_t *module)
3925{
3927
3928 bd->masks_box = blendw;
3929
3930 /* create and add masks support if module supports it */
3931 if(bd->masks_support)
3932 {
3933 bd->masks_combo_ids = NULL;
3935
3936 bd->lists_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3937
3938 GtkCellRenderer *renderer = NULL;
3939 GtkTreeSelection *selection = NULL;
3940
3941 GtkWidget *group_shapes_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3942 bd->group_shapes_label = gtk_entry_new();
3943 gchar *group_placeholder = dt_masks_group_name_for_module(module);
3944 gtk_entry_set_placeholder_text(GTK_ENTRY(bd->group_shapes_label), group_placeholder);
3945 g_free(group_placeholder);
3946 gtk_widget_set_tooltip_text(bd->group_shapes_label, _("Edit current module's group name"));
3947 gtk_widget_set_halign(bd->group_shapes_label, GTK_ALIGN_FILL);
3948 gtk_widget_set_hexpand(bd->group_shapes_label, TRUE);
3949 g_signal_connect(bd->group_shapes_label, "activate", G_CALLBACK(_blendop_masks_group_name_activate), module);
3950 g_signal_connect(bd->group_shapes_label, "focus-out-event",
3951 G_CALLBACK(_blendop_masks_group_name_focus_out), module);
3952 gtk_box_pack_start(GTK_BOX(group_shapes_header), bd->group_shapes_label, TRUE, TRUE, 0);
3953
3954 // buttons for mask polarity and edit mode
3955 bd->masks_polarity = dt_iop_togglebutton_new_no_register(module, "blend`tools", N_("toggle polarity of drawn mask"), NULL,
3957 FALSE, 0, 0, dtgtk_cairo_paint_invert, group_shapes_header);
3959 dt_gui_add_class(bd->masks_polarity, "dt_ignore_fg_state");
3960
3961 bd->masks_edit = dt_iop_togglebutton_new_no_register(module, "blend`tools", N_("show and edit mask elements"),
3962 N_("show and edit in restricted mode"),
3963 G_CALLBACK(_blendop_masks_show_and_edit),
3964 FALSE, 0, 0, dtgtk_cairo_paint_masks_edit, group_shapes_header);
3965
3966 gtk_box_pack_start(GTK_BOX(bd->lists_box), group_shapes_header, FALSE, FALSE, 0);
3967
3968 // Current mask list
3969 bd->masks_group_treeview = gtk_tree_view_new();
3970 bd->group_shapes_store = gtk_tree_store_new(BLENDOP_MASKS_GROUP_COL_COUNT, GDK_TYPE_PIXBUF,
3971 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_INT,
3972 G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
3973 gtk_tree_view_set_model(GTK_TREE_VIEW(bd->masks_group_treeview), GTK_TREE_MODEL(bd->group_shapes_store));
3974
3975 // Initialize mask operation icons for treeview display
3977
3978 bd->group_shapes_col = gtk_tree_view_column_new();
3979 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_group_treeview), bd->group_shapes_col);
3980
3981 renderer = gtk_cell_renderer_pixbuf_new();
3982 gtk_tree_view_column_pack_start(bd->group_shapes_col, renderer, FALSE);
3983 gtk_tree_view_column_add_attribute(bd->group_shapes_col, renderer, "pixbuf", BLENDOP_MASKS_GROUP_COL_OP_ICON);
3984
3985 renderer = gtk_cell_renderer_pixbuf_new();
3986 gtk_tree_view_column_pack_start(bd->group_shapes_col, renderer, FALSE);
3987 gtk_tree_view_column_add_attribute(bd->group_shapes_col, renderer, "pixbuf", BLENDOP_MASKS_GROUP_COL_INV_ICON);
3988
3989 renderer = gtk_cell_renderer_text_new();
3990 gtk_tree_view_column_pack_start(bd->group_shapes_col, renderer, TRUE);
3991 gtk_tree_view_column_add_attribute(bd->group_shapes_col, renderer, "text", BLENDOP_MASKS_GROUP_COL_NAME);
3992
3993 // Per-row action icons: unlink (detach from this mask) and delete (remove altogether).
3994 // Clicks are handled in _blendop_masks_group_button_pressed by matching the column.
3995 bd->group_unlink_col = gtk_tree_view_column_new();
3996 renderer = gtk_cell_renderer_pixbuf_new();
3997 g_object_set(renderer, "icon-name", "list-remove-symbolic", "stock-size", GTK_ICON_SIZE_MENU, NULL);
3998 gtk_tree_view_column_pack_start(bd->group_unlink_col, renderer, FALSE);
3999 gtk_tree_view_column_set_sizing(bd->group_unlink_col, GTK_TREE_VIEW_COLUMN_FIXED);
4000 gtk_tree_view_column_set_fixed_width(bd->group_unlink_col, DT_PIXEL_APPLY_DPI(24));
4001 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_group_treeview), bd->group_unlink_col);
4002
4003 bd->group_delete_col = gtk_tree_view_column_new();
4004 renderer = gtk_cell_renderer_pixbuf_new();
4005 g_object_set(renderer, "icon-name", "user-trash-symbolic", "stock-size", GTK_ICON_SIZE_MENU, NULL);
4006 gtk_tree_view_column_pack_start(bd->group_delete_col, renderer, FALSE);
4007 gtk_tree_view_column_set_sizing(bd->group_delete_col, GTK_TREE_VIEW_COLUMN_FIXED);
4008 gtk_tree_view_column_set_fixed_width(bd->group_delete_col, DT_PIXEL_APPLY_DPI(24));
4009 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_group_treeview), bd->group_delete_col);
4010
4011 // Keep the name column expanding so the action icons stay flush right.
4012 gtk_tree_view_column_set_expand(bd->group_shapes_col, TRUE);
4013
4014 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(bd->masks_group_treeview));
4015 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
4016 g_signal_connect(selection, "changed", G_CALLBACK(_blendop_masks_group_selection_changed), module);
4017 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(bd->masks_group_treeview), FALSE);
4018 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(bd->masks_group_treeview), TRUE);
4019 g_signal_connect(bd->masks_group_treeview, "button-press-event",
4020 G_CALLBACK(_blendop_masks_group_button_pressed), module);
4021
4022 gtk_widget_set_has_tooltip(bd->masks_group_treeview, TRUE);
4023 g_signal_connect(bd->masks_group_treeview, "query-tooltip",
4024 G_CALLBACK(_blendop_masks_group_query_tooltip), module);
4025
4027 "plugins/darkroom/masks/group_list_height", DT_UI_RESIZE_DYNAMIC);
4028 gtk_widget_set_vexpand(bd->group_shapes_sw, TRUE);
4029
4030 // Creating shapes buttons (circle, ellipse ....)
4032 if(!GTK_IS_WIDGET(bd->all_shapes_buttons)) return;
4033
4034 // Wire shapes toggle button
4035 bd->wire_shape_toggle = gtk_toggle_button_new_with_label(_("Attach shapes"));
4036 gtk_widget_set_tooltip_text(bd->wire_shape_toggle, _("Show all shapes and groups to choose which ones to connect to or disconnect from the mask."));
4037
4038 GtkWidget *bottom_bar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4039 gtk_box_pack_start(GTK_BOX(bottom_bar), bd->wire_shape_toggle, FALSE, FALSE, 0);
4040 gtk_box_pack_end(GTK_BOX(bottom_bar), bd->all_shapes_buttons, FALSE, FALSE, 0);
4041 gtk_box_pack_end(GTK_BOX(bd->lists_box), bottom_bar, FALSE, FALSE, 0);
4042
4043
4044 // All shapes list
4045 bd->masks_treeview = gtk_tree_view_new();
4046 bd->all_shapes_store = gtk_list_store_new(BLENDOP_MASKS_ALL_COL_COUNT, G_TYPE_BOOLEAN,
4047 G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN,
4048 G_TYPE_STRING, G_TYPE_STRING);
4049 gtk_tree_view_set_model(GTK_TREE_VIEW(bd->masks_treeview), GTK_TREE_MODEL(bd->all_shapes_store));
4050
4051 bd->all_shapes_col = gtk_tree_view_column_new();
4052 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_treeview), bd->all_shapes_col);
4053
4054 renderer = gtk_cell_renderer_toggle_new();
4055 g_object_set(renderer, "activatable", TRUE, NULL);
4056 g_signal_connect(renderer, "toggled", G_CALLBACK(_blendop_masks_all_toggled), module);
4057 gtk_tree_view_column_pack_start(bd->all_shapes_col, renderer, FALSE);
4058 gtk_tree_view_column_add_attribute(bd->all_shapes_col, renderer, "active", BLENDOP_MASKS_ALL_COL_ACTIVE);
4059 gtk_tree_view_column_add_attribute(bd->all_shapes_col, renderer, "sensitive", BLENDOP_MASKS_ALL_COL_SENSITIVE);
4060
4061 GtkTreeViewColumn *all_shapes_name_col = gtk_tree_view_column_new();
4062 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_treeview), all_shapes_name_col);
4063
4064 renderer = gtk_cell_renderer_text_new();
4065 g_object_set(renderer, "editable", TRUE, NULL);
4066 g_signal_connect(renderer, "edited", G_CALLBACK(_blendop_masks_all_name_edited), module);
4067 gtk_tree_view_column_pack_start(all_shapes_name_col, renderer, TRUE);
4068 gtk_tree_view_column_add_attribute(all_shapes_name_col, renderer, "markup", BLENDOP_MASKS_ALL_COL_MARKUP);
4069 gtk_tree_view_column_add_attribute(all_shapes_name_col, renderer, "sensitive", BLENDOP_MASKS_ALL_COL_SENSITIVE);
4070 g_object_set_data(G_OBJECT(bd->masks_treeview), "blendop-masks-name-renderer", renderer);
4071
4072 GtkTreeViewColumn *all_shapes_status_col = gtk_tree_view_column_new();
4073 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_treeview), all_shapes_status_col);
4074
4075 renderer = gtk_cell_renderer_text_new();
4076 g_object_set(renderer, "xalign", 1.0f, NULL);
4077 gtk_tree_view_column_pack_end(all_shapes_status_col, renderer, TRUE);
4078 gtk_tree_view_column_add_attribute(all_shapes_status_col, renderer, "markup",
4080 gtk_tree_view_column_add_attribute(all_shapes_status_col, renderer, "sensitive",
4082
4083 // Per-row delete icon, mirroring group_delete_col above. Clicks are handled in
4084 // _blendop_masks_all_button_pressed by matching the column.
4085 bd->all_shapes_delete_col = gtk_tree_view_column_new();
4086 renderer = gtk_cell_renderer_pixbuf_new();
4087 g_object_set(renderer, "icon-name", "user-trash-symbolic", "stock-size", GTK_ICON_SIZE_MENU, NULL);
4088 gtk_tree_view_column_pack_start(bd->all_shapes_delete_col, renderer, FALSE);
4089 gtk_tree_view_column_set_sizing(bd->all_shapes_delete_col, GTK_TREE_VIEW_COLUMN_FIXED);
4090 gtk_tree_view_column_set_fixed_width(bd->all_shapes_delete_col, DT_PIXEL_APPLY_DPI(24));
4091 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_treeview), bd->all_shapes_delete_col);
4092
4093 // Keep the name column expanding so the status text and trash icon stay flush right,
4094 // same as group_shapes_col above.
4095 gtk_tree_view_column_set_expand(all_shapes_name_col, TRUE);
4096
4097 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(bd->masks_treeview));
4098 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
4099 g_signal_connect(selection, "changed", G_CALLBACK(_blendop_masks_all_selection_changed), module);
4100 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(bd->masks_treeview), FALSE);
4101 g_signal_connect(bd->masks_treeview, "button-press-event",
4102 G_CALLBACK(_blendop_masks_all_button_pressed), module);
4103
4105 "plugins/darkroom/masks/all_list_height", DT_UI_RESIZE_DYNAMIC);
4106 gtk_widget_set_vexpand(bd->all_shapes_sw, TRUE);
4107
4108 // The two lists share the same slot; Edit selects which one is visible.
4109 bd->lists_stack = gtk_stack_new();
4110 gtk_stack_set_transition_type(GTK_STACK(bd->lists_stack), GTK_STACK_TRANSITION_TYPE_NONE);
4111 gtk_stack_set_homogeneous(GTK_STACK(bd->lists_stack), FALSE);
4112 gtk_stack_add_named(GTK_STACK(bd->lists_stack), bd->group_shapes_sw, "group");
4113 gtk_stack_add_named(GTK_STACK(bd->lists_stack), bd->all_shapes_sw, "all");
4114 gtk_box_pack_start(GTK_BOX(bd->lists_box), bd->lists_stack, TRUE, TRUE, 0);
4115
4116 // Default state:
4117 // - current-module tree is visible
4118 // - all-shapes tree is hidden
4119 // The toggle callback flips these two scrolled windows.
4120 g_signal_connect(bd->wire_shape_toggle, "toggled", G_CALLBACK(_blendop_masks_edit_list_toggle), module);
4121
4122 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->wire_shape_toggle), FALSE);
4123 _blendop_masks_edit_list_toggle(GTK_TOGGLE_BUTTON(bd->wire_shape_toggle), module);
4124
4125 gtk_box_pack_start(GTK_BOX(bd->masks_box), bd->lists_box, TRUE, TRUE, 0);
4126
4128 (&bd->masks_cs,
4129 "plugins/darkroom/masks/wacom",
4130 _("Brush options"),
4131 GTK_BOX(bd->masks_box), GTK_PACK_END);
4132
4133 GtkWidget *brush_smoothing = dt_bauhaus_combobox_from_conf(dt_bauhaus_get_global(), DT_GUI_MODULE(NULL), "brush_smoothing");
4134 gtk_box_pack_start(GTK_BOX(bd->masks_cs.container), brush_smoothing, TRUE, TRUE, 0);
4135
4136 GtkWidget *pressure_mapping = dt_bauhaus_combobox_from_conf(dt_bauhaus_get_global(), DT_GUI_MODULE(NULL), "pressure_sensitivity");
4137 gtk_box_pack_start(GTK_BOX(bd->masks_cs.container), pressure_mapping, TRUE, TRUE, 0);
4138
4140 (&bd->scroll_cs,
4141 "plugins/darkroom/masks/scroll_mapping",
4142 _("Mouse wheel"),
4143 GTK_BOX(bd->masks_box), GTK_PACK_END);
4144 gtk_widget_set_tooltip_text(bd->scroll_cs.expander,
4145 _("Choose which property of the hovered shape each mouse wheel "
4146 "combination edits. Shared by every mask of every module."));
4147 // Natural size, not filled: the grid must not be handed width it would spread between its
4148 // own columns (see _blendop_masks_create_scroll_grid).
4149 gtk_box_pack_start(GTK_BOX(bd->scroll_cs.container), _blendop_masks_create_scroll_grid(), FALSE, FALSE, 0);
4150
4151 bd->masks_inited = 1;
4153 }
4154 else
4155 {
4156 gchar *module_name = dt_history_item_get_name(module);
4157 gchar *markup = g_markup_printf_escaped(
4158 _("<i>Drawn masking is disabled because the <b>%s</b> module manages drawn shapes internally.</i>"),
4159 module_name);
4160 GtkWidget *label = gtk_label_new(NULL);
4161 gtk_label_set_markup(GTK_LABEL(label), markup);
4162 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
4163 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
4164 gtk_box_pack_start(GTK_BOX(bd->masks_box), label, FALSE, FALSE, 0);
4165 dt_free(markup);
4166 dt_free(module_name);
4167 }
4168}
4169
4175
4176static void _raster_combo_populate(GtkWidget *w, void *m)
4177{
4178 dt_iop_module_t *module = (dt_iop_module_t *)m;
4179 dt_iop_request_focus(module);
4180
4182
4183 raster_combo_entry_t *entry = (raster_combo_entry_t *)calloc(1, sizeof(raster_combo_entry_t));
4184 if(IS_NULL_PTR(entry)) return;
4185 dt_bauhaus_combobox_add_full(w, _("no mask used"), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, entry, free, TRUE);
4186
4187 int i = 1;
4188
4189 for(GList* iter = module->dev->iop; iter; iter = g_list_next(iter))
4190 {
4191 dt_iop_module_t *iop = (dt_iop_module_t *)iter->data;
4192 if(iop == module)
4193 break;
4194
4195 GHashTableIter masks_iter;
4196 gpointer key, value;
4197
4198 g_hash_table_iter_init(&masks_iter, iop->raster_mask.source.masks);
4199 while(g_hash_table_iter_next(&masks_iter, &key, &value))
4200 {
4201 const int id = GPOINTER_TO_INT(key);
4202 const char *modulename = (char *)value;
4203 entry = (raster_combo_entry_t *)malloc(sizeof(raster_combo_entry_t));
4204 entry->module = iop;
4205 entry->id = id;
4207 if(iop == module->raster_mask.sink.source && module->raster_mask.sink.id == id)
4209 i++;
4210 }
4211 }
4212}
4213
4215{
4217
4218 // nothing to do
4219 if(entry->module == module->raster_mask.sink.source && entry->id == module->raster_mask.sink.id)
4220 return;
4221
4222 if(module->raster_mask.sink.source)
4223 {
4224 // we no longer use this one
4225 g_hash_table_remove(module->raster_mask.sink.source->raster_mask.source.users, module);
4226 }
4227
4228 module->raster_mask.sink.source = entry->module;
4229 module->raster_mask.sink.id = entry->id;
4230
4231 if(entry->module)
4232 {
4233 // The value is the provider-local mask id. dt_iop_is_raster_mask_used()
4234 // reads it to decide which side-band mask buffers the pixelpipe must
4235 // publish. g_hash_table_add() stores the consumer pointer as both key and
4236 // value, making an existing mask visible in the GUI but impossible to
4237 // retrieve reliably from the pipeline.
4238 g_hash_table_insert(entry->module->raster_mask.source.users, module,
4239 GINT_TO_POINTER(entry->id));
4240
4241 // update blend_params!
4242 memcpy(module->blend_params->raster_mask_source, entry->module->op, sizeof(module->blend_params->raster_mask_source));
4243 module->blend_params->raster_mask_instance = entry->module->multi_priority;
4244 module->blend_params->raster_mask_id = entry->id;
4245 }
4246 else
4247 {
4248 memset(module->blend_params->raster_mask_source, 0, sizeof(module->blend_params->raster_mask_source));
4249 module->blend_params->raster_mask_instance = 0;
4250 module->blend_params->raster_mask_id = 0;
4251 }
4252
4253 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
4255}
4256
4258{
4260 dt_develop_blend_params_t *bp = module->blend_params;
4261
4262 if(IS_NULL_PTR(bd) || !bd->masks_support || !bd->raster_inited) return;
4263
4264 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->raster_polarity), bp->raster_mask_invert);
4265
4267}
4268
4269static void _raster_polarity_callback(GtkToggleButton *togglebutton, dt_iop_module_t *self)
4270{
4271 if(dt_gui_widgets_suppressed()) return;
4272
4274
4275 bp->raster_mask_invert = gtk_toggle_button_get_active(togglebutton);
4276
4277 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
4279 dt_control_queue_redraw_widget(GTK_WIDGET(togglebutton));
4280}
4281
4282void dt_iop_gui_init_raster(GtkBox *blendw, dt_iop_module_t *module)
4283{
4285
4286 bd->raster_box = blendw;
4287
4288 /* create and add raster support if module supports it (it's coupled to masks at the moment) */
4289 if(bd->masks_support)
4290 {
4291 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4292
4296 dt_bauhaus_widget_set_label(bd->raster_combo, N_("raster mask"));
4297 dt_bauhaus_combobox_add(bd->raster_combo, _("no mask used"));
4298 g_signal_connect(G_OBJECT(bd->raster_combo), "value-changed",
4299 G_CALLBACK(_raster_value_changed_callback), module);
4301 gtk_box_pack_start(GTK_BOX(hbox), bd->raster_combo, TRUE, TRUE, 0);
4302
4304 dt_gui_add_class(bd->raster_polarity, "dt_ignore_fg_state");
4305 gtk_widget_set_tooltip_text(bd->raster_polarity, _("toggle polarity of raster mask"));
4306 g_signal_connect(G_OBJECT(bd->raster_polarity), "toggled", G_CALLBACK(_raster_polarity_callback), module);
4307 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->raster_polarity), FALSE);
4308 gtk_box_pack_start(GTK_BOX(hbox), bd->raster_polarity, FALSE, FALSE, 0);
4309
4310 gtk_box_pack_start(GTK_BOX(bd->raster_box), GTK_WIDGET(hbox), FALSE, FALSE, 0);
4311
4312 bd->raster_inited = 1;
4313 }
4314}
4315
4317{
4318 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->blend_data)) return;
4320
4323
4325 if(bd->timeout_handle)
4326 g_source_remove(bd->timeout_handle);
4327
4331
4332 dt_free(module->gui->blend_data);
4333 module->gui->blend_data = NULL;
4334}
4335
4336
4338{
4339 for(const dt_develop_name_value_t *bm = dt_develop_blend_mode_names; *bm->name; bm++)
4340 {
4341 if(bm->value == mode)
4342 {
4343 dt_bauhaus_combobox_add_full(combobox, g_dpgettext2(NULL, "blendmode", bm->name), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, GUINT_TO_POINTER(bm->value), NULL, TRUE);
4344
4345 return TRUE;
4346 }
4347 }
4348
4349 return FALSE;
4350}
4351
4352static GtkWidget *_combobox_new_from_list(dt_iop_module_t *module, const gchar *label,
4353 const dt_develop_name_value_t *list, uint32_t *field, const gchar *tooltip)
4354{
4359
4360 if(field)
4362 dt_bauhaus_widget_set_label(combo, label);
4363 gtk_widget_set_tooltip_text(combo, tooltip);
4364 for(; *list->name; list++)
4366 GUINT_TO_POINTER(list->value), NULL, TRUE);
4367
4368 return combo;
4369}
4370
4371static void _notebook_append_full_width_page(GtkWidget *notebook, GtkWidget *page, const gchar *label)
4372{
4373 GtkWidget *tab = gtk_label_new(label);
4374 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, tab);
4375 gtk_container_child_set(GTK_CONTAINER(notebook), page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
4376}
4377
4378static GtkWidget *_blendop_create_enable_toggle(dt_iop_module_t *module, const unsigned int mask_bit)
4379{
4380 const gboolean is_disable_toggle = mask_bit == DEVELOP_MASK_RASTER
4381 || mask_bit == DEVELOP_MASK_SHAPE
4382 || mask_bit == DEVELOP_MASK_PARAMETRIC;
4383 GtkWidget *toggle = gtk_check_button_new_with_label(is_disable_toggle ? _("Disable") : _("Enable"));
4384 g_object_set_data(G_OBJECT(toggle), "mask-bit", GUINT_TO_POINTER(mask_bit));
4385 g_signal_connect(G_OBJECT(toggle), "toggled", G_CALLBACK(_blendop_masks_mode_changed), module);
4386 return toggle;
4387}
4388
4390{
4391 if(IS_NULL_PTR(module) || !module->gui->blend_data) return;
4392
4394 if(!GTK_IS_BUTTON(bd->top_enable)) return;
4395
4396 gchar *clean_name = delete_underscore(module->name());
4397 const char *multi_name = module->multi_name[0] ? module->multi_name : "0";
4398 gchar *multi_name_dup = ((g_strcmp0(multi_name, "0") == 0) || (g_strcmp0(multi_name, "") == 0)) ? g_strdup("") : g_strdup_printf(" (%s)", multi_name);
4399 GtkWidget *child = gtk_bin_get_child(GTK_BIN(bd->top_enable));
4400 gchar *label = g_markup_printf_escaped(_("Enable in <b>%s%s</b>"), clean_name, multi_name_dup);
4401 if(GTK_IS_LABEL(child))
4402 {
4403 gtk_label_set_markup(GTK_LABEL(child), label);
4404 gtk_label_set_line_wrap(GTK_LABEL(child), TRUE);
4405 gtk_label_set_xalign(GTK_LABEL(child), 0.0f);
4406 }
4407 dt_free(label);
4408 dt_free(clean_name);
4409 dt_free(multi_name_dup);
4410}
4411
4413 gchar *help_url, GtkWidget **content)
4414{
4415 GtkWidget *page = gtk_event_box_new();
4416 if(help_url) dt_gui_add_help_link(page, help_url);
4417 *content = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4418 gtk_container_add(GTK_CONTAINER(page), *content);
4420 return page;
4421}
4422
4424 gchar *help_url,
4425 dt_iop_module_t *module, const unsigned int mask_bit,
4426 GtkWidget **toggle, GtkWidget **content)
4427{
4428 GtkWidget *page = gtk_event_box_new();
4429 if(help_url) dt_gui_add_help_link(page, help_url);
4430 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4431 gtk_container_add(GTK_CONTAINER(page), vbox);
4432
4433 GtkWidget *header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4434 *toggle = _blendop_create_enable_toggle(module, mask_bit);
4435 gtk_box_pack_start(GTK_BOX(header), *toggle, FALSE, FALSE, 0);
4436 gtk_box_pack_start(GTK_BOX(vbox), header, FALSE, FALSE, 0);
4437
4438 *content = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4439 gtk_box_pack_start(GTK_BOX(vbox), *content, TRUE, TRUE, 0);
4440
4442 return header;
4443}
4444
4445static void _blendop_toggle_button_set_active(GtkWidget *toggle, const gboolean enabled)
4446{
4447 if(GTK_IS_TOGGLE_BUTTON(toggle))
4448 {
4449 const unsigned int bit = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(toggle), "mask-bit"));
4450 const gboolean is_disable_toggle = bit == DEVELOP_MASK_RASTER
4451 || bit == DEVELOP_MASK_SHAPE
4452 || bit == DEVELOP_MASK_PARAMETRIC;
4453 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), is_disable_toggle ? !enabled : enabled);
4454 }
4455}
4456
4457static void _blendop_sync_toggle_state(GtkWidget *toggle, const gboolean available,
4458 const gboolean enabled, GtkWidget *content)
4459{
4461 if(GTK_IS_WIDGET(toggle))
4462 gtk_widget_set_sensitive(toggle, available);
4463 if(GTK_IS_WIDGET(content))
4464 gtk_widget_set_sensitive(content, available && enabled);
4465}
4466
4468{
4470
4471 if(!(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) || IS_NULL_PTR(bd)) return;
4472
4474
4475 // update color space from parameters
4477 switch(default_csp)
4478 {
4481 break;
4485 switch(module->blend_params->blend_cst)
4486 {
4490 bd->csp = module->blend_params->blend_cst;
4491 break;
4492 default:
4493 bd->csp = default_csp;
4494 break;
4495 }
4496 break;
4498 default:
4500 break;
4501 }
4502
4503 // (un)set the mask indicator
4505
4506 if(!bd->blending_box)
4507 {
4509 return;
4510 }
4511
4512 // initialization of blending modes
4513 if(bd->csp != bd->blend_modes_csp)
4514 {
4516
4517 if(bd->csp == DEVELOP_BLEND_CS_LAB
4519 || bd->csp == DEVELOP_BLEND_CS_RAW )
4520 {
4537
4538 if(bd->csp == DEVELOP_BLEND_CS_LAB)
4539 {
4549 }
4550 else if(bd->csp == DEVELOP_BLEND_CS_RGB_DISPLAY)
4551 {
4562 }
4563 }
4564 else if(bd->csp == DEVELOP_BLEND_CS_RGB_SCENE)
4565 {
4580 }
4581 bd->blend_modes_csp = bd->csp;
4582 }
4583
4584 dt_develop_blend_mode_t blend_mode = module->blend_params->blend_mode & DEVELOP_BLEND_MODE_MASK;
4585 // module->blend_params->mask_mode
4587 {
4588 // add deprecated blend mode
4589 if(!_add_blendmode_combo(bd->blend_modes_combo, blend_mode))
4590 {
4591 // should never happen: unknown blend mode
4592 dt_control_log("unknown blend mode '%d' in module '%s'", blend_mode, module->op);
4593 module->blend_params->blend_mode = DEVELOP_BLEND_NORMAL2;
4594 blend_mode = DEVELOP_BLEND_NORMAL2;
4595 }
4596
4598 }
4599
4600 gboolean blend_mode_reversed = (module->blend_params->blend_mode & DEVELOP_BLEND_REVERSE) == DEVELOP_BLEND_REVERSE;
4602
4604 const gboolean show_param = (bd->blend_modes_csp == DEVELOP_BLEND_CS_RGB_SCENE) &&
4606 if(!show_param)
4607 {
4608 module->blend_params->blend_parameter = 0.0f;
4610 }
4611 gtk_widget_set_visible(bd->blend_mode_parameter_slider, show_param);
4612
4625
4626 /* reset all alternative display modes for blendif */
4627 memset(bd->altmode, 0, sizeof(bd->altmode));
4628
4629 // force the visibility of output channels if they contain some setting
4632
4636
4637 /* sync page states from mask mode */
4638 uint32_t mask_mode = module->blend_params->mask_mode;
4639 const gboolean top_enabled = (mask_mode & DEVELOP_MASK_ENABLED) != 0;
4640 const gboolean masks_enabled = (mask_mode & DEVELOP_MASK_SHAPE) != 0;
4641 const gboolean raster_enabled = (mask_mode & DEVELOP_MASK_RASTER) != 0;
4642 const gboolean blendif_enabled = (mask_mode & DEVELOP_MASK_PARAMETRIC) != 0;
4643 const gboolean bottom_enabled = top_enabled && ((bd->masks_inited && masks_enabled)
4644 || (bd->raster_inited && raster_enabled)
4645 || (bd->blendif_inited && blendif_enabled));
4646
4649
4650 if(GTK_IS_WIDGET(bd->blending_box))
4651 {
4652 gtk_widget_set_sensitive(bd->blending_box, top_enabled);
4653 // Note: the showmask toggle is outside the blending box.
4654 gtk_widget_set_sensitive(bd->showmask, top_enabled);
4655 }
4656
4660
4661 // The Contours page has no enable/disable toggle of its own (unlike the three pages above),
4662 // so unlike them it never gets its own explicit gtk_widget_set_sensitive() call and relies
4663 // entirely on inherited state propagated from bd->blending_box. Give it the same explicit
4664 // sync so its tab content doesn't get stuck rendering with a stale insensitive (dimmed) style.
4665 // It refines whatever mask raster/drawn/parametric produces, so it has nothing to act on
4666 // unless at least one of those three is actually enabled.
4667 if(GTK_IS_WIDGET(bd->contours_content))
4668 gtk_widget_set_sensitive(bd->contours_content, bottom_enabled);
4669
4670 if(bd->blendif_inited && !IS_NULL_PTR(bd->channel))
4671 {
4672 /* The parametric page may have made the whole content insensitive before
4673 * `_blendop_blendif_update_tab()` restored the channel-local state.
4674 * Reapply the final contract after the page toggle is synchronized:
4675 * boost factor is editable only for an enabled parametric mask and a
4676 * channel whose descriptor explicitly supports boosting. */
4677 gtk_widget_set_sensitive(
4679 blendif_enabled && bd->channel[bd->tab].boost_factor_enabled);
4680 }
4681
4682 if(bd->blendif_inited && blendif_enabled)
4683 {
4684 gtk_widget_hide(GTK_WIDGET(bd->masks_invert_combo));
4685 gtk_widget_show(GTK_WIDGET(bd->masks_combine_combo));
4686 }
4687 else
4688 {
4689 gtk_widget_show(GTK_WIDGET(bd->masks_invert_combo));
4690 gtk_widget_hide(GTK_WIDGET(bd->masks_combine_combo));
4691 }
4692
4693 /*
4694 * if this iop is operating in raw space, it has only 1 channel per pixel,
4695 * thus there is no alpha channel where we would normally store mask
4696 * that would get displayed if following button have been pressed.
4697 */
4698 if(module->blend_colorspace(module, NULL, NULL) == IOP_CS_RAW)
4699 {
4700 module->request_mask_display = DT_DEV_PIXELPIPE_DISPLAY_NONE;
4703 gtk_widget_hide(GTK_WIDGET(bd->showmask));
4704
4705 // disable also guided filters on RAW based color space
4706 gtk_widget_set_sensitive(bd->masks_feathering_guide_combo, FALSE);
4707 gtk_widget_hide(bd->masks_feathering_guide_combo);
4708 gtk_widget_set_sensitive(bd->feathering_radius_slider, FALSE);
4709 gtk_widget_hide(bd->feathering_radius_slider);
4710 gtk_widget_set_sensitive(bd->brightness_slider, FALSE);
4711 gtk_widget_hide(bd->brightness_slider);
4712 gtk_widget_set_sensitive(bd->contrast_slider, FALSE);
4713 gtk_widget_hide(bd->contrast_slider);
4714 gtk_widget_set_sensitive(bd->details_slider, FALSE);
4715 gtk_widget_hide(bd->details_slider);
4716 }
4717 else
4718 {
4719 gtk_widget_show(GTK_WIDGET(bd->showmask));
4720 gtk_widget_set_sensitive(bd->masks_feathering_guide_combo, TRUE);
4721 gtk_widget_show(bd->masks_feathering_guide_combo);
4722 gtk_widget_set_sensitive(bd->feathering_radius_slider, TRUE);
4723 gtk_widget_show(bd->feathering_radius_slider);
4724 gtk_widget_set_sensitive(bd->brightness_slider, TRUE);
4725 gtk_widget_show(bd->brightness_slider);
4726 gtk_widget_set_sensitive(bd->contrast_slider, TRUE);
4727 gtk_widget_show(bd->contrast_slider);
4728 /* show details slider only if it was used in an old edit (non-zero) */
4729 const gboolean show_details = (module->blend_params->details != 0.0f);
4730 gtk_widget_set_visible(bd->details_slider, show_details);
4731 }
4732
4733 if(!bottom_enabled)
4734 {
4735 module->request_mask_display = DT_DEV_PIXELPIPE_DISPLAY_NONE;
4738 }
4739
4740 if(bd->masks_inited && !masks_enabled)
4741 {
4744 }
4745
4746 if(bd->masks_support && !masks_enabled)
4747 {
4749 }
4750
4751 if(bd->blendif_inited && !blendif_enabled)
4752 {
4754 }
4755
4757
4758 // Re-evaluate header state after notebook toggle synchronization.
4760
4762}
4763
4765{
4766 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
4767 // These should all use an uniform setter function.
4768 //
4769 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
4770 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
4771 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
4772 // that changes over the pipe lifecycle.
4773 //
4774 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
4775 // (but not all pipes are connected to a GUI, so you need to cover all cases all the time and don't forget to test everything),
4776 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
4777 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
4778 // planning for pipeline execution.
4779 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
4780 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
4781 // the preview at the far end of the pipe.
4782 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
4783 // which flushed lines that could be reused later and were only temporarily not needed.
4784
4785 if(dt_gui_widgets_suppressed()) return;
4786 if(IS_NULL_PTR(module)) return;
4787
4788 const int has_mask_display = module->request_mask_display & (DT_DEV_PIXELPIPE_DISPLAY_MASK | DT_DEV_PIXELPIPE_DISPLAY_CHANNEL);
4789
4790 if((module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && module->gui->blend_data)
4791 {
4793 if(bd->showmask)
4795 module->request_mask_display = DT_DEV_PIXELPIPE_DISPLAY_NONE;
4798
4799 // (re)set the header mask indicator too
4800 if(bd->masks_support && bd->masks_edit)
4801 {
4802 // unselect all tools
4805
4807 }
4808
4811 if(bd->timeout_handle)
4812 {
4813 // purge any remaining timeout handlers
4814 g_source_remove(bd->timeout_handle);
4815 bd->timeout_handle = 0;
4816 }
4818
4819 // reprocess main center image if needed
4820 if (has_mask_display)
4822 }
4823}
4824
4826{
4827 // reached from dt_iop_load_default_params() -- the module-defaults path, which runs
4828 // headless too; every other entry in this file exists only under a live GUI
4829 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui)) return;
4830 dt_iop_gui_blend_data_t *bd = module->gui->blend_data;
4831 if(IS_NULL_PTR(bd) || !bd->blendif_support || !bd->blendif_inited) return;
4833}
4834
4836{
4837 if(IS_NULL_PTR(module) || !module->gui->blend_data) return;
4838
4840 if(IS_NULL_PTR(bd->blending_box)) return;
4842 if(bd->timeout_handle)
4843 {
4844 g_source_remove(bd->timeout_handle);
4845 bd->timeout_handle = 0;
4846 }
4849
4850 if(bd->masks_ic_inverse) g_object_unref(bd->masks_ic_inverse);
4851 if(bd->masks_ic_union) g_object_unref(bd->masks_ic_union);
4852 if(bd->masks_ic_intersection) g_object_unref(bd->masks_ic_intersection);
4853 if(bd->masks_ic_difference) g_object_unref(bd->masks_ic_difference);
4854 if(bd->masks_ic_exclusion) g_object_unref(bd->masks_ic_exclusion);
4855 if(bd->group_shapes_store) g_object_unref(bd->group_shapes_store);
4856 if(bd->all_shapes_store) g_object_unref(bd->all_shapes_store);
4857
4858 gtk_widget_destroy(bd->blending_box);
4859
4860 bd->blending_box = NULL;
4861 bd->blending_notebook = NULL;
4862 bd->top_enable = NULL;
4863 bd->masks_enable = NULL;
4864 bd->raster_enable = NULL;
4865 bd->blendif_enable = NULL;
4866 bd->blending_box = NULL;
4867 bd->masks_content = NULL;
4868 bd->raster_content = NULL;
4869 bd->blendif_content = NULL;
4870 bd->contours_content = NULL;
4871 bd->blendif_box = NULL;
4872 bd->masks_box = NULL;
4873 bd->raster_box = NULL;
4874 bd->colorpicker = NULL;
4875 bd->colorpicker_set_values = NULL;
4876 memset(bd->filter, 0, sizeof(bd->filter));
4877 bd->showmask = NULL;
4878 bd->masks_combine_combo = NULL;
4879 bd->blend_modes_combo = NULL;
4880 bd->blend_modes_blend_order = NULL;
4881 bd->blend_mode_parameter_slider = NULL;
4882 bd->masks_invert_combo = NULL;
4883 bd->opacity_slider = NULL;
4885 bd->feathering_radius_slider = NULL;
4886 bd->blur_radius_slider = NULL;
4887 bd->contrast_slider = NULL;
4888 bd->brightness_slider = NULL;
4889 bd->channel_boost_factor_slider = NULL;
4890 bd->details_slider = NULL;
4891 bd->masks_combo = NULL;
4892 memset(bd->masks_shapes, 0, sizeof(bd->masks_shapes));
4893 bd->masks_edit = NULL;
4894 bd->group_shapes_label = NULL;
4895 bd->masks_polarity = NULL;
4896 bd->wire_shape_toggle = NULL;
4897 bd->masks_treeview = NULL;
4898 bd->masks_group_treeview = NULL;
4899 bd->group_shapes_store = NULL;
4900 bd->group_shapes_col = NULL;
4901 bd->group_unlink_col = NULL;
4902 bd->group_delete_col = NULL;
4903 bd->all_shapes_store = NULL;
4904 bd->group_shapes_sw = NULL;
4905 bd->all_shapes_col = NULL;
4906 bd->all_shapes_delete_col = NULL;
4907 bd->all_shapes_sw = NULL;
4908 bd->lists_stack = NULL;
4909 bd->all_shapes_buttons = NULL;
4910 bd->lists_box = NULL;
4911 bd->masks_ic_inverse = NULL;
4912 bd->masks_ic_union = NULL;
4913 bd->masks_ic_intersection = NULL;
4914 bd->masks_ic_difference = NULL;
4915 bd->masks_ic_exclusion = NULL;
4916 bd->raster_combo = NULL;
4917 bd->raster_polarity = NULL;
4919 memset(bd->picker_set_values_box, 0, sizeof(bd->picker_set_values_box));
4921 bd->channel_tabs = NULL;
4922 bd->blendif_inited = 0;
4923 bd->masks_inited = 0;
4924 bd->raster_inited = 0;
4927 module->gui->fusion_slider = NULL;
4928}
4929
4931{
4932 if(IS_NULL_PTR(container) || !GTK_IS_WIDGET(container)) return;
4933 if(!(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)) return;
4934 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->blend_data)) return;
4935
4937 if(bd->blending_box) return;
4938
4940
4941 /* NOTE: we attach 2 modules to the parent container:
4942 1. the on/off global switch,
4943 2. the box containing all other options (recursively nested).
4944 This is just a trick so we can hide/insensitize the whole content
4945 in one shot, on main disabling, except for the main switch.
4946 */
4947
4948 /* Global ON/OFF switch + show/hide toggle */
4949 GtkWidget *display_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4950 dt_gui_add_help_link(display_box, dt_get_help_url("masks_blending"));
4951 gtk_widget_set_name(display_box, "blending-main-switch");
4952
4955 gtk_box_pack_start(GTK_BOX(display_box), bd->top_enable, FALSE, FALSE, 0);
4956
4957 bd->showmask = dt_iop_togglebutton_new_no_register(module, "blend`tools", N_("display mask and/or color channel"), NULL,
4959 FALSE, 0, 0, dtgtk_cairo_paint_showmask, display_box);
4960 gtk_widget_set_tooltip_text(bd->showmask, _("display mask and/or color channel. ctrl+click to display mask, "
4961 "shift+click to display channel. hover over parametric mask slider to "
4962 "select channel for display"));
4963
4964 gtk_box_pack_start(GTK_BOX(container), display_box, FALSE, FALSE, 0);
4965 gtk_widget_show_all(display_box);
4966
4967 /* Main widget container */
4968 bd->blending_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4969 gtk_box_pack_start(GTK_BOX(container), bd->blending_box, TRUE, TRUE, 0);
4970 gtk_widget_set_name(bd->blending_box, "blending-box");
4971
4972 /* Blend mode */
4977 dt_bauhaus_widget_set_label(bd->blend_modes_combo, N_("Blend mode"));
4978 g_signal_connect(G_OBJECT(bd->blend_modes_combo), "value-changed",
4979 G_CALLBACK(_blendop_blend_mode_callback), bd);
4980 dt_gui_add_help_link(GTK_WIDGET(bd->blend_modes_combo), dt_get_help_url("masks_blending_op"));
4981 gtk_box_pack_start(GTK_BOX(bd->blending_box), bd->blend_modes_combo, TRUE, TRUE, 0);
4982
4983 /* Compositing combobox: choose compositing order */
4987 GINT_TO_POINTER(0), NULL, TRUE);
4989 GINT_TO_POINTER(1), NULL, TRUE);
4992 gtk_widget_set_tooltip_text(bd->blend_modes_blend_order, _("choose compositing order: which layer is placed on top"));
4993 g_signal_connect(G_OBJECT(bd->blend_modes_blend_order), "value-changed",
4994 G_CALLBACK(_blendop_compositing_changed), bd);
4995 gtk_box_pack_start(GTK_BOX(bd->blending_box), bd->blend_modes_blend_order, TRUE, TRUE, 0);
4996
4997 /* Optional fulcrum parameter for blend modes supporting it */
5006 gtk_widget_set_tooltip_text(bd->blend_mode_parameter_slider, _("adjust the fulcrum used by some blending"
5007 " operations"));
5008 g_object_set_data(G_OBJECT(bd->blend_mode_parameter_slider), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5009 gtk_widget_set_visible(bd->blend_mode_parameter_slider, FALSE);
5010 gtk_box_pack_start(GTK_BOX(bd->blending_box), bd->blend_mode_parameter_slider, TRUE, TRUE, 0);
5011
5012 /* mask combine control: how to combine drawn/parametric masks */
5013 bd->masks_combine_combo = _combobox_new_from_list(module, _("Combine drawn & parametric"), dt_develop_combine_masks_names, NULL,
5014 _("how to combine drawn and parametric masks"));
5015 g_signal_connect(G_OBJECT(bd->masks_combine_combo), "value-changed",
5016 G_CALLBACK(_blendop_masks_combine_callback), bd);
5017 dt_gui_add_help_link(GTK_WIDGET(bd->masks_combine_combo), dt_get_help_url("masks_combined"));
5018 gtk_box_pack_start(GTK_BOX(bd->blending_box), GTK_WIDGET(bd->masks_combine_combo), TRUE, TRUE, 0);
5019
5020 /* mask invert control: single checkbox to invert masks */
5021 bd->masks_invert_combo = gtk_check_button_new_with_label(_("Invert mask"));
5022 gtk_widget_set_tooltip_text(bd->masks_invert_combo, _("invert the resulting mask (apply mask in inverted mode)"));
5023 g_signal_connect(G_OBJECT(bd->masks_invert_combo), "toggled",
5024 G_CALLBACK(_blendop_masks_invert_toggled), bd);
5025 gtk_box_pack_start(GTK_BOX(bd->blending_box), GTK_WIDGET(bd->masks_invert_combo), TRUE, TRUE, 0);
5026
5027 /* Global mask output opacity */
5033 dt_bauhaus_widget_set_label(bd->opacity_slider, N_("opacity"));
5035 module->gui->fusion_slider = bd->opacity_slider;
5036 gtk_widget_set_tooltip_text(bd->opacity_slider, _("set the opacity of the blending"));
5037 g_object_set_data(G_OBJECT(bd->opacity_slider), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5038 gtk_box_pack_start(GTK_BOX(bd->blending_box), bd->opacity_slider, TRUE, TRUE, 0);
5039
5044 dt_bauhaus_widget_set_label(bd->details_slider, N_("details threshold"));
5046 gtk_widget_set_tooltip_text(bd->details_slider, _("adjust the threshold for the details mask, "
5047 "\npositive values selects areas with strong details, "
5048 "\nnegative values select flat areas"));
5049 g_signal_connect(G_OBJECT(bd->details_slider), "value-changed", G_CALLBACK(_blendop_blendif_details_callback), bd);
5050 gtk_widget_hide(bd->details_slider);
5051
5057 dt_bauhaus_widget_set_label(bd->blur_radius_slider, N_("blurring radius"));
5059 gtk_widget_set_tooltip_text(bd->blur_radius_slider, _("radius for gaussian blur of blend mask"));
5060 g_object_set_data(G_OBJECT(bd->blur_radius_slider), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5061
5064 _("choose to guide mask by input or output image and"
5065 "\nchoose to apply feathering before or after mask blur"));
5066 g_object_set_data(G_OBJECT(bd->masks_feathering_guide_combo), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5067
5073 dt_bauhaus_widget_set_label(bd->feathering_radius_slider, N_("feathering radius"));
5075 gtk_widget_set_tooltip_text(bd->feathering_radius_slider, _("spatial radius of feathering"));
5076 g_object_set_data(G_OBJECT(bd->feathering_radius_slider), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5077
5083 dt_bauhaus_widget_set_label(bd->brightness_slider, N_("Feathering mask opacity"));
5085 gtk_widget_set_tooltip_text(bd->brightness_slider, _("shifts and tilts the tone curve of the blend mask to adjust its "
5086 "brightness without affecting fully transparent/fully opaque "
5087 "regions"));
5088 g_object_set_data(G_OBJECT(bd->brightness_slider), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5089
5095 dt_bauhaus_widget_set_label(bd->contrast_slider, N_("Feathering mask contrast"));
5097 gtk_widget_set_tooltip_text(bd->contrast_slider, _("gives the tone curve of the blend mask an s-like shape to "
5098 "adjust its contrast"));
5099 g_object_set_data(G_OBJECT(bd->contrast_slider), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5100
5101 /* separator between global blending options and the masking sub-modes notebook */
5102 GtkWidget *blend_separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
5103 gtk_widget_set_name(blend_separator, "blending-separator");
5104 gtk_box_pack_start(GTK_BOX(bd->blending_box), blend_separator, FALSE, FALSE, 0);
5105
5106 GtkWidget *presets_button = dtgtk_button_new(dtgtk_cairo_paint_presets, 0, NULL);
5107 gtk_widget_set_tooltip_text(presets_button, _("blending options"));
5108 if(bd->blendif_support)
5109 g_signal_connect(G_OBJECT(presets_button), "button-press-event", G_CALLBACK(_blendif_options_callback), module);
5110 else
5111 gtk_widget_set_sensitive(GTK_WIDGET(presets_button), FALSE);
5112
5113 /* Blending submodes notebook */
5114 bd->blending_notebook = gtk_notebook_new();
5115 gtk_notebook_set_scrollable(GTK_NOTEBOOK(bd->blending_notebook), TRUE);
5116 gtk_notebook_set_action_widget(GTK_NOTEBOOK(bd->blending_notebook), presets_button, GTK_PACK_END);
5117 gtk_widget_show(presets_button);
5118 gtk_box_pack_start(GTK_BOX(bd->blending_box), bd->blending_notebook, TRUE, TRUE, 0);
5119
5120 /* Raster page */
5121 _blendop_create_toggle_page(bd->blending_notebook, _("Raster"), dt_get_help_url("masks_raster"),
5123 dt_iop_gui_init_raster(GTK_BOX(bd->raster_content), module);
5124
5125 /* Drawn page */
5126 _blendop_create_toggle_page(bd->blending_notebook, _("Drawn"), dt_get_help_url("masks_drawn"),
5127 module, DEVELOP_MASK_SHAPE, &bd->masks_enable, &bd->masks_content);
5128 dt_iop_gui_init_masks(GTK_BOX(bd->masks_content), module);
5129
5130 /* Parametric page */
5131 GtkWidget *blendif_header = _blendop_create_toggle_page(bd->blending_notebook, _("Parametric"),
5132 dt_get_help_url("masks_parametric"),
5134 &bd->blendif_content);
5135 dt_iop_gui_init_blendif(GTK_BOX(bd->blendif_content), module, blendif_header);
5136
5137 /* Contours page */
5138 _blendop_create_notebook_page(bd->blending_notebook, _("Contours"), dt_get_help_url("masks_refinement"), &bd->contours_content);
5139 dt_iop_gui_init_contours(GTK_BOX(bd->contours_content), module);
5140
5141 /* Init all GUI states from module params */
5142 const unsigned int mask_mode = module->blend_params->mask_mode;
5147
5148 gtk_widget_show_all(GTK_WIDGET(bd->blending_box));
5149 // show_all overrides the visibility the collapsible section set at init; re-apply config state.
5150 if(bd->masks_inited)
5151 {
5154 }
5155 gtk_widget_set_sensitive(bd->blending_box, (mask_mode & DEVELOP_MASK_ENABLED) != 0);
5156
5157 g_signal_connect(G_OBJECT(bd->blending_notebook), "switch_page", G_CALLBACK(_blendop_blending_notebook_switch), bd);
5158 const int page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(bd->blending_notebook));
5159 if(page_count > 0)
5160 {
5161 // Restore the last-used tab after the notebook has been fully realized.
5163 page = CLAMP(page, 0, page_count - 1);
5164 gtk_notebook_set_current_page(GTK_NOTEBOOK(bd->blending_notebook), page);
5165 }
5166
5168}
5169
5171{
5172 if(!(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) || module->gui->blend_data) return;
5173
5174 module->gui->blend_data = g_malloc0(sizeof(dt_iop_gui_blend_data_t));
5176
5177 bd->module = module;
5181
5182 const dt_iop_colorspace_type_t cst = module->blend_colorspace(module, NULL, NULL);
5184 bd->masks_support = !(module->flags() & IOP_FLAGS_NO_MASKS);
5185
5186 dt_pthread_mutex_init(&bd->lock, NULL);
5189
5190 if(bd->masks_support)
5191 {
5193 G_CALLBACK(_blendop_masks_handler_callback), module);
5194 }
5195}
5196
5197// clang-format off
5198// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
5199// vim: shiftwidth=2 expandtab tabstop=2 cindent
5200// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
5201// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
void dt_gui_add_help_link(GtkWidget *widget, char *link)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
uint32_t container(dt_lib_module_t *self)
#define m
Definition basecurve.c:283
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1498
void dt_bauhaus_combobox_clear(GtkWidget *widget)
Definition bauhaus.c:2000
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3283
gboolean dt_bauhaus_combobox_set_from_value(GtkWidget *widget, int value)
Definition bauhaus.c:2122
void dt_bauhaus_widget_set_field(GtkWidget *widget, gpointer field, dt_introspection_type_t field_type)
Definition bauhaus.c:1565
void dt_bauhaus_disable_module_list(GtkWidget *widget)
Definition bauhaus.c:3743
void dt_bauhaus_set_use_default_callback(GtkWidget *widget)
Tell the widget to use the globally-defined default callback in the bauhaus structure This callback n...
Definition bauhaus.c:3749
void dt_bauhaus_combobox_add_full(GtkWidget *widget, const char *text, dt_bauhaus_combobox_alignment_t align, gpointer data, void(free_func)(void *data), gboolean sensitive)
Definition bauhaus.c:1849
gpointer dt_bauhaus_combobox_get_data(GtkWidget *widget)
Definition bauhaus.c:1990
void dt_bauhaus_combobox_add_populate_fct(GtkWidget *widget, void(*fct)(GtkWidget *w, void *module))
Definition bauhaus.c:1813
void dt_bauhaus_disable_accels(GtkWidget *widget)
Definition bauhaus.c:3737
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3334
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2093
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1635
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1701
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3410
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1827
@ DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT
Definition bauhaus.h:127
GtkWidget * dt_bauhaus_combobox_from_conf(dt_bauhaus_t *bh, dt_gui_module_t *self, const char *confkey)
gboolean dt_develop_blend_colorspace_is_compatible(dt_iop_module_t *module, dt_develop_blend_colorspace_t cst)
Whether the given module may blend in the given color space.
Definition blend.c:158
dt_develop_blend_colorspace_t dt_develop_blend_default_module_blend_colorspace(dt_iop_module_t *module)
Definition blend.c:153
const dt_develop_name_value_t dt_develop_combine_masks_names[]
Definition blend.c:2372
const dt_develop_name_value_t dt_develop_blend_mode_names[]
Definition blend.c:2308
void dt_develop_blend_init_blendif_parameters(dt_develop_blend_params_t *blend_params, dt_develop_blend_colorspace_t cst)
Definition blend.c:220
const dt_develop_name_value_t dt_develop_feathering_guide_names[]
Definition blend.c:2379
dt_develop_blend_colorspace_t
Definition blend.h:53
@ DEVELOP_BLEND_CS_NONE
Definition blend.h:54
@ DEVELOP_BLEND_CS_LAB
Definition blend.h:56
@ DEVELOP_BLEND_CS_RGB_SCENE
Definition blend.h:58
@ DEVELOP_BLEND_CS_RGB_DISPLAY
Definition blend.h:57
@ DEVELOP_BLEND_CS_RAW
Definition blend.h:55
@ DEVELOP_COMBINE_INV
Definition blend.h:123
@ DEVELOP_COMBINE_INCL
Definition blend.h:125
@ DEVELOP_COMBINE_MASKS_POS
Definition blend.h:126
dt_develop_blendif_channels_t
Definition blend.h:142
@ DEVELOP_BLENDIF_GRAY_out
Definition blend.h:156
@ DEVELOP_BLENDIF_hz_in
Definition blend.h:177
@ DEVELOP_BLENDIF_Cz_in
Definition blend.h:176
@ DEVELOP_BLENDIF_S_out
Definition blend.h:172
@ DEVELOP_BLENDIF_RED_in
Definition blend.h:152
@ DEVELOP_BLENDIF_C_out
Definition blend.h:164
@ DEVELOP_BLENDIF_A_in
Definition blend.h:144
@ DEVELOP_BLENDIF_C_in
Definition blend.h:161
@ DEVELOP_BLENDIF_l_in
Definition blend.h:169
@ DEVELOP_BLENDIF_L_out
Definition blend.h:147
@ DEVELOP_BLENDIF_GREEN_in
Definition blend.h:153
@ DEVELOP_BLENDIF_h_in
Definition blend.h:162
@ DEVELOP_BLENDIF_H_in
Definition blend.h:167
@ DEVELOP_BLENDIF_RED_out
Definition blend.h:157
@ DEVELOP_BLENDIF_Lab_MASK
Definition blend.h:190
@ DEVELOP_BLENDIF_Jz_out
Definition blend.h:179
@ DEVELOP_BLENDIF_B_in
Definition blend.h:145
@ DEVELOP_BLENDIF_Cz_out
Definition blend.h:180
@ DEVELOP_BLENDIF_Jz_in
Definition blend.h:175
@ DEVELOP_BLENDIF_H_out
Definition blend.h:171
@ DEVELOP_BLENDIF_A_out
Definition blend.h:148
@ DEVELOP_BLENDIF_B_out
Definition blend.h:149
@ DEVELOP_BLENDIF_h_out
Definition blend.h:165
@ DEVELOP_BLENDIF_OUTPUT_MASK
Definition blend.h:192
@ DEVELOP_BLENDIF_BLUE_in
Definition blend.h:154
@ DEVELOP_BLENDIF_L_in
Definition blend.h:143
@ DEVELOP_BLENDIF_hz_out
Definition blend.h:181
@ DEVELOP_BLENDIF_BLUE_out
Definition blend.h:159
@ DEVELOP_BLENDIF_RGB_MASK
Definition blend.h:191
@ DEVELOP_BLENDIF_S_in
Definition blend.h:168
@ DEVELOP_BLENDIF_GRAY_in
Definition blend.h:151
@ DEVELOP_BLENDIF_GREEN_out
Definition blend.h:158
@ DEVELOP_BLENDIF_l_out
Definition blend.h:173
dt_develop_blend_mode_t
Definition blend.h:62
@ DEVELOP_BLEND_LIGHTEN
Definition blend.h:65
@ DEVELOP_BLEND_COLOR
Definition blend.h:82
@ DEVELOP_BLEND_CHROMATICITY
Definition blend.h:80
@ DEVELOP_BLEND_RGB_B
Definition blend.h:98
@ DEVELOP_BLEND_LIGHTNESS
Definition blend.h:79
@ DEVELOP_BLEND_BOUNDED
Definition blend.h:88
@ DEVELOP_BLEND_SUBTRACT
Definition blend.h:70
@ DEVELOP_BLEND_MODE_MASK
Definition blend.h:107
@ DEVELOP_BLEND_DIVIDE_INVERSE
Definition blend.h:102
@ DEVELOP_BLEND_NORMAL2
Definition blend.h:87
@ DEVELOP_BLEND_HARDLIGHT
Definition blend.h:75
@ DEVELOP_BLEND_HUE
Definition blend.h:81
@ DEVELOP_BLEND_OVERLAY
Definition blend.h:73
@ DEVELOP_BLEND_RGB_R
Definition blend.h:96
@ DEVELOP_BLEND_LAB_A
Definition blend.h:94
@ DEVELOP_BLEND_LAB_COLOR
Definition blend.h:90
@ DEVELOP_BLEND_REVERSE
Definition blend.h:106
@ DEVELOP_BLEND_AVERAGE
Definition blend.h:68
@ DEVELOP_BLEND_DIVIDE
Definition blend.h:101
@ DEVELOP_BLEND_HSV_COLOR
Definition blend.h:92
@ DEVELOP_BLEND_MULTIPLY
Definition blend.h:67
@ DEVELOP_BLEND_SCREEN
Definition blend.h:72
@ DEVELOP_BLEND_HARMONIC_MEAN
Definition blend.h:104
@ DEVELOP_BLEND_PINLIGHT
Definition blend.h:78
@ DEVELOP_BLEND_HSV_VALUE
Definition blend.h:91
@ DEVELOP_BLEND_LINEARLIGHT
Definition blend.h:77
@ DEVELOP_BLEND_ADD
Definition blend.h:69
@ DEVELOP_BLEND_VIVIDLIGHT
Definition blend.h:76
@ DEVELOP_BLEND_GEOMETRIC_MEAN
Definition blend.h:103
@ DEVELOP_BLEND_SOFTLIGHT
Definition blend.h:74
@ DEVELOP_BLEND_COLORADJUST
Definition blend.h:85
@ DEVELOP_BLEND_DARKEN
Definition blend.h:66
@ DEVELOP_BLEND_LAB_B
Definition blend.h:95
@ DEVELOP_BLEND_LAB_LIGHTNESS
Definition blend.h:89
@ DEVELOP_BLEND_DIFFERENCE2
Definition blend.h:86
@ DEVELOP_BLEND_SUBTRACT_INVERSE
Definition blend.h:100
@ DEVELOP_BLEND_RGB_G
Definition blend.h:97
@ DEVELOP_MASK_RASTER
Definition blend.h:116
@ DEVELOP_MASK_PARAMETRIC
Definition blend.h:115
@ DEVELOP_MASK_ENABLED
Definition blend.h:113
@ DEVELOP_MASK_SHAPE
Definition blend.h:114
static int _blendop_blendif_disp_alternative_worker(GtkWidget *widget, dt_iop_module_t *module, int mode, float(*scale_callback)(GtkWidget *, float, int), const char *label)
Definition blend_gui.c:700
static void _blendop_blendif_update_tab(dt_iop_module_t *module, const int tab)
Definition blend_gui.c:870
static void _blendop_masks_group_selection_changed(GtkTreeSelection *selection, dt_iop_module_t *module)
Definition blend_gui.c:2126
static void _blendop_masks_group_duplicate_callback(GtkWidget *menu_item, dt_iop_module_t *module)
Definition blend_gui.c:2228
const dt_iop_gui_blendif_colorstop_t _gradient_HSL_hue[]
Definition blend_gui.c:184
const dt_iop_gui_blendif_colorstop_t _gradient_JzCzhz_hue[]
Definition blend_gui.c:197
static const GdkPixbuf * _blendop_masks_get_inverse_icon(const dt_iop_gui_blend_data_t *bd, const int state)
Definition blend_gui.c:1470
static void _blendop_masks_mode_changed(GtkToggleButton *togglebutton, dt_iop_module_t *module)
Definition blend_gui.c:1170
const dt_iop_gui_blendif_colorstop_t _gradient_blue[]
Definition blend_gui.c:148
void dt_iop_gui_init_masks(GtkBox *blendw, dt_iop_module_t *module)
Definition blend_gui.c:3924
static void _blendop_masks_all_selection_changed(GtkTreeSelection *selection, dt_iop_module_t *module)
Definition blend_gui.c:1842
static void _blendif_scale_print_default(float value, float boost_factor, char *string, int n)
Definition blend_gui.c:405
void dt_iop_gui_init_blending(dt_iop_module_t *module)
Definition blend_gui.c:5170
static void _blendif_hide_output_channels(GtkMenuItem *menuitem, dt_iop_module_t *module)
Definition blend_gui.c:2929
static GtkWidget * _blendop_masks_group_ctx_menu(dt_iop_gui_blend_data_t *bd, dt_iop_module_t *module, const int formid, const int parentid, const int state, const int index, const int list_length)
Definition blend_gui.c:2294
void dt_iop_gui_blend_masks_update(dt_iop_module_t *module)
Definition blend_gui.c:3701
static int _blendif_print_digits_picker(float value)
Definition blend_gui.c:790
static gboolean _blendop_masks_group_handle_action_click(GtkWidget *treeview, GtkTreePath *path, GtkTreeViewColumn *column, dt_iop_module_t *module)
Definition blend_gui.c:2438
#define BLEND_MASKMODE_CONF_KEY
Definition blend_gui.c:90
void dt_iop_gui_init_raster(GtkBox *blendw, dt_iop_module_t *module)
Definition blend_gui.c:4282
static void _blendif_cook(dt_iop_colorspace_type_t cst, const float *in, float *out, const dt_iop_order_iccprofile_info_t *const work_profile)
Definition blend_gui.c:329
static GtkWidget * _blendop_create_enable_toggle(dt_iop_module_t *module, const unsigned int mask_bit)
Definition blend_gui.c:4378
static void _blendif_select_colorspace(GtkMenuItem *menuitem, dt_iop_module_t *module)
Definition blend_gui.c:2909
static void _blendop_masks_group_name_commit(dt_iop_module_t *module, const gchar *new_text)
Definition blend_gui.c:1780
static gboolean _blendif_options_callback(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
Definition blend_gui.c:2961
static void _blendop_masks_all_rename_callback(GtkWidget *menu_item, dt_iop_module_t *module)
Definition blend_gui.c:1980
static gboolean _blendif_clean_output_channels(dt_iop_module_t *module)
Definition blend_gui.c:424
static gboolean _blendop_blendif_invert(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
Definition blend_gui.c:1243
static void _blendop_masks_handler_callback(gpointer instance, const int formid, const int parentid, const dt_masks_event_t event, dt_iop_module_t *module)
Definition blend_gui.c:2606
static int _blendop_masks_group_tree_append(const dt_iop_gui_blend_data_t *bd, GtkTreeStore *tree_store, GtkTreeIter *parent_iter, const dt_masks_form_t *parent_group)
Definition blend_gui.c:1555
static void _blendif_show_output_channels(GtkMenuItem *menuitem, dt_iop_module_t *module)
Definition blend_gui.c:2918
static void _blendop_masks_scroll_toggled(GtkToggleButton *button, gpointer user_data)
Definition blend_gui.c:3795
_blendop_masks_all_cols_t
Definition blend_gui.c:1366
@ BLENDOP_MASKS_ALL_COL_STATUS_MARKUP
Definition blend_gui.c:1372
@ BLENDOP_MASKS_ALL_COL_MARKUP
Definition blend_gui.c:1371
@ BLENDOP_MASKS_ALL_COL_SENSITIVE
Definition blend_gui.c:1370
@ BLENDOP_MASKS_ALL_COL_NAME
Definition blend_gui.c:1368
@ BLENDOP_MASKS_ALL_COL_COUNT
Definition blend_gui.c:1373
@ BLENDOP_MASKS_ALL_COL_FORMID
Definition blend_gui.c:1369
@ BLENDOP_MASKS_ALL_COL_ACTIVE
Definition blend_gui.c:1367
void dt_iop_gui_init_blendif(GtkBox *blendw, dt_iop_module_t *module, GtkWidget *blendif_header)
Definition blend_gui.c:3528
static void _blendop_sync_toggle_state(GtkWidget *toggle, const gboolean available, const gboolean enabled, GtkWidget *content)
Definition blend_gui.c:4457
static void _blendop_blendif_channel_display_toggled(GtkToggleButton *button, dt_iop_module_t *module)
Persistently display the current parametric input or output channel.
Definition blend_gui.c:3157
static gboolean _blendop_blendif_enter(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *module)
Definition blend_gui.c:3205
static void _blendop_masks_mode_callback(const unsigned int mask_mode, dt_iop_gui_blend_data_t *data)
Definition blend_gui.c:465
static GtkWidget * _combobox_new_from_list(dt_iop_module_t *module, const gchar *label, const dt_develop_name_value_t *list, uint32_t *field, const gchar *tooltip)
Definition blend_gui.c:4352
static gboolean _blendop_masks_group_button_pressed(GtkWidget *treeview, GdkEventButton *event, dt_iop_module_t *module)
Definition blend_gui.c:2464
static GtkWidget * _blendop_create_toggle_page(GtkWidget *notebook, const gchar *label, gchar *help_url, dt_iop_module_t *module, const unsigned int mask_bit, GtkWidget **toggle, GtkWidget **content)
Definition blend_gui.c:4423
static gboolean _blendop_masks_is_group_with_shapes(const dt_develop_t *dev, const dt_masks_form_t *mask_form)
Definition blend_gui.c:1500
#define BLENDOP_SCROLL_DATA_ROW
Definition blend_gui.c:3786
void dt_iop_gui_init_blending_body(GtkWidget *container, dt_iop_module_t *module)
Definition blend_gui.c:4930
static void _blendop_masks_init_icons(dt_iop_gui_blend_data_t *bd)
Definition blend_gui.c:1436
static GtkWidget * _blendop_create_notebook_page(GtkWidget *notebook, const gchar *label, gchar *help_url, GtkWidget **content)
Definition blend_gui.c:4412
static void _blendop_blendif_details_callback(GtkWidget *slider, dt_iop_gui_blend_data_t *data)
Definition blend_gui.c:1092
static int _blendop_masks_group_tree_append_entry(const dt_iop_gui_blend_data_t *bd, GtkTreeStore *tree_store, GtkTreeIter *parent_iter, const dt_masks_form_group_t *group_entry, const dt_masks_form_t *mask_form, const int index)
Definition blend_gui.c:1520
static void _blendop_masks_scroll_sync(GtkWidget *grid, gpointer user_data)
Definition blend_gui.c:3808
static void _blendif_scale(dt_iop_gui_blend_data_t *data, dt_iop_colorspace_type_t cst, const float *in, float *out, const dt_iop_order_iccprofile_info_t *work_profile, int in_out)
Definition blend_gui.c:283
static void _blendop_blendif_channel_mask_view_toggle(GtkWidget *widget, dt_iop_module_t *module, dt_dev_pixelpipe_display_mask_t mode)
Definition blend_gui.c:3060
static void _blendop_masks_combine_callback(GtkWidget *combo, dt_iop_gui_blend_data_t *data)
Definition blend_gui.c:515
static gboolean _blendop_masks_show_and_edit(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
Definition blend_gui.c:1291
const dt_iop_gui_blendif_colorstop_t _gradient_green[]
Definition blend_gui.c:140
static void _raster_polarity_callback(GtkToggleButton *togglebutton, dt_iop_module_t *self)
Definition blend_gui.c:4269
const char * slider_tooltip[]
Definition blend_gui.c:3432
static void _blendop_blendif_log_scale_toggled(GtkToggleButton *button, dt_iop_module_t *module)
Toggle the alternative scale of one parametric-mask slider.
Definition blend_gui.c:741
static void _blendop_blendif_sync_channel_display_buttons(dt_iop_module_t *module)
Synchronize input/output channel toggles from the active preview request.
Definition blend_gui.c:3129
static gboolean _blendop_blendif_leave_delayed(gpointer data)
Definition blend_gui.c:3272
#define COLORSTOPS(gradient)
Definition blend_gui.c:3356
static gboolean _blendop_masks_is_single_group_wrapper(dt_develop_t *dev, const dt_masks_form_t *mask_form)
Definition blend_gui.c:1480
static void _blendif_append_colorspace_item(GtkMenu *menu, dt_iop_module_t *module, const char *label, const dt_develop_blend_colorspace_t cst)
Definition blend_gui.c:2944
static gboolean _blendop_blendif_leave(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *module)
Definition blend_gui.c:3318
const dt_iop_gui_blendif_channel_t rgb_channels[]
Definition blend_gui.c:3379
static gboolean _blendop_masks_all_button_pressed(GtkWidget *treeview, GdkEventButton *event, dt_iop_module_t *module)
Definition blend_gui.c:2036
static gboolean _blendop_masks_group_query_tooltip(GtkWidget *treeview, gint x, gint y, gboolean keyboard_tip, GtkTooltip *tooltip, dt_iop_module_t *module)
Definition blend_gui.c:2522
static void _blendop_compositing_changed(GtkWidget *combobox, dt_iop_gui_blend_data_t *data)
Definition blend_gui.c:499
static gboolean _blendif_are_output_channels_used(const dt_develop_blend_params_t *const blend, const dt_develop_blend_colorspace_t cst)
Definition blend_gui.c:411
static void _blendop_masks_group_name_activate(GtkEntry *entry, dt_iop_module_t *module)
Definition blend_gui.c:1799
_channel_indexes
Definition blend_gui.c:211
@ CHANNEL_INDEX_l
Definition blend_gui.c:223
@ CHANNEL_INDEX_hz
Definition blend_gui.c:226
@ CHANNEL_INDEX_Cz
Definition blend_gui.c:225
@ CHANNEL_INDEX_b
Definition blend_gui.c:214
@ CHANNEL_INDEX_g
Definition blend_gui.c:217
@ CHANNEL_INDEX_H
Definition blend_gui.c:221
@ CHANNEL_INDEX_Jz
Definition blend_gui.c:224
@ CHANNEL_INDEX_S
Definition blend_gui.c:222
@ CHANNEL_INDEX_L
Definition blend_gui.c:212
@ CHANNEL_INDEX_C
Definition blend_gui.c:215
@ CHANNEL_INDEX_a
Definition blend_gui.c:213
@ CHANNEL_INDEX_G
Definition blend_gui.c:219
@ CHANNEL_INDEX_B
Definition blend_gui.c:220
@ CHANNEL_INDEX_h
Definition blend_gui.c:216
@ CHANNEL_INDEX_R
Definition blend_gui.c:218
const dt_iop_gui_blendif_colorstop_t _gradient_b[]
Definition blend_gui.c:115
static gboolean _blendop_masks_find_iter_by_formid(GtkTreeModel *model, GtkTreeIter *iter, const int formid_col, const int formid)
Definition blend_gui.c:1585
static void _blendop_masks_group_operation_callback(GtkWidget *menu_item, gpointer user_data)
Definition blend_gui.c:2106
static gboolean _blendop_blendif_key_press(GtkWidget *widget, GdkEventKey *event, dt_iop_module_t *module)
Definition blend_gui.c:3335
static void _blendop_blendif_sliders_reset_callback(GtkDarktableGradientSlider *slider, dt_iop_gui_blend_data_t *data)
Definition blend_gui.c:596
static void _blendop_masks_invert_toggled(GtkToggleButton *togglebutton, dt_iop_gui_blend_data_t *data)
Definition blend_gui.c:541
static gboolean _blendop_masks_group_move_by_index(dt_develop_t *dev, dt_masks_form_t *group_form, const int index, const gboolean move_up)
Definition blend_gui.c:2188
_blendop_masks_group_cols_t
Definition blend_gui.c:1377
@ BLENDOP_MASKS_GROUP_COL_INDEX
Definition blend_gui.c:1384
@ BLENDOP_MASKS_GROUP_COL_OP_ICON
Definition blend_gui.c:1378
@ BLENDOP_MASKS_GROUP_COL_STATE
Definition blend_gui.c:1383
@ BLENDOP_MASKS_GROUP_COL_COUNT
Definition blend_gui.c:1385
@ BLENDOP_MASKS_GROUP_COL_FORMID
Definition blend_gui.c:1381
@ BLENDOP_MASKS_GROUP_COL_NAME
Definition blend_gui.c:1380
@ BLENDOP_MASKS_GROUP_COL_INV_ICON
Definition blend_gui.c:1379
@ BLENDOP_MASKS_GROUP_COL_PARENTID
Definition blend_gui.c:1382
static void _raster_value_changed_callback(GtkWidget *widget, struct dt_iop_module_t *module)
Definition blend_gui.c:4214
static void _blendop_masks_all_toggled(GtkCellRendererToggle *cell, gchar *path_string, dt_iop_module_t *module)
Definition blend_gui.c:1872
static void _blendop_blendif_polarity_callback(GtkToggleButton *togglebutton, dt_iop_gui_blend_data_t *data)
Definition blend_gui.c:619
#define BLENDOP_SCROLL_DATA_INTERACTION
Definition blend_gui.c:3787
static void _blendif_scale_print_hue(float value, float boost_factor, char *string, int n)
Definition blend_gui.c:400
static gboolean _blendop_masks_shape_can_start(GtkWidget *button, dt_iop_module_t *module, dt_masks_type_t type, gpointer user_data)
Definition blend_gui.c:1276
static gboolean _blendif_blend_parameter_enabled(dt_develop_blend_colorspace_t csp, dt_develop_blend_mode_t mode)
Definition blend_gui.c:255
static gboolean _blendop_masks_polarity_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
Definition blend_gui.c:1344
static gboolean _blendop_masks_all_handle_left_click(GtkWidget *treeview, GtkTreePath *path, GtkTreeViewColumn *column, dt_iop_module_t *module)
Definition blend_gui.c:2002
static void _blendop_blendif_tab_switch(GtkNotebook *notebook, GtkWidget *page, guint page_num, dt_iop_gui_blend_data_t *data)
Definition blend_gui.c:1016
static GtkWidget * _blendop_masks_create_shape_buttons(dt_iop_module_t *module, dt_iop_gui_blend_data_t *bd)
Definition blend_gui.c:2269
const dt_iop_gui_blendif_channel_t Lab_channels[]
Definition blend_gui.c:3359
static float magnifier_scale_callback(GtkWidget *self, float inval, int dir)
Definition blend_gui.c:673
static void _blendop_masks_all_name_edited(GtkCellRendererText *cell, gchar *path_string, gchar *new_text, dt_iop_module_t *module)
Definition blend_gui.c:1814
void dt_iop_gui_update_blending(dt_iop_module_t *module)
Definition blend_gui.c:4467
const dt_iop_gui_blendif_colorstop_t _gradient_chroma[]
Definition blend_gui.c:157
static void _blendop_masks_check_id(dt_develop_t *dev, dt_masks_form_t *mask_form)
Definition blend_gui.c:1395
static void _blendop_blend_mode_callback(GtkWidget *combo, dt_iop_gui_blend_data_t *data)
Definition blend_gui.c:475
static float log10_scale_callback(GtkWidget *self, float inval, int dir)
Definition blend_gui.c:652
static dt_iop_colorspace_type_t _blendop_blendif_get_picker_colorspace(dt_iop_gui_blend_data_t *bd)
Definition blend_gui.c:761
static int _blendif_print_digits_default(float value)
Definition blend_gui.c:374
static void _blendop_masks_apply_and_commit(dt_iop_module_t *module)
Definition blend_gui.c:1734
static int _blendif_print_digits_ab(float value)
Definition blend_gui.c:385
static void _update_gradient_slider_pickers(GtkWidget *callback_dummy, dt_iop_module_t *module)
Definition blend_gui.c:799
const dt_iop_gui_blendif_channel_t rgbj_channels[]
Definition blend_gui.c:3406
static gboolean _add_blendmode_combo(GtkWidget *combobox, dt_develop_blend_mode_t mode)
Definition blend_gui.c:4337
void dt_iop_gui_blending_lose_focus(dt_iop_module_t *module)
Definition blend_gui.c:4764
static void _blendop_masks_all_duplicate_callback(GtkWidget *menu_item, dt_iop_module_t *module)
Definition blend_gui.c:1969
static gboolean _blendif_change_blend_colorspace(dt_iop_module_t *module, dt_develop_blend_colorspace_t cst)
Definition blend_gui.c:2855
const dt_iop_gui_blendif_colorstop_t _gradient_L[]
Definition blend_gui.c:95
const dt_iop_gui_blendif_colorstop_t _gradient_LCh_hue[]
Definition blend_gui.c:168
static dt_masks_form_t * _blendop_masks_group_create(dt_iop_module_t *module)
Definition blend_gui.c:1415
static int _blendop_blendif_disp_alternative_log(GtkWidget *widget, dt_iop_module_t *module, int mode)
Definition blend_gui.c:724
static int _blendop_blendif_disp_alternative_mag(GtkWidget *widget, dt_iop_module_t *module, int mode)
Definition blend_gui.c:719
static const dt_masks_interaction_t _blendop_scroll_columns[]
Definition blend_gui.c:3792
static float _get_boost_factor(const dt_iop_gui_blend_data_t *data, const int channel, const int in_out)
Definition blend_gui.c:278
static const GdkPixbuf * _blendop_masks_get_op_icon(const dt_iop_gui_blend_data_t *bd, const int state, const int index)
Definition blend_gui.c:1458
static gboolean _blendop_blendif_showmask_clicked(GtkToggleButton *button, GdkEventButton *event, dt_iop_module_t *module)
Definition blend_gui.c:1107
static void _blendop_masks_edit_list_toggle(GtkToggleButton *togglebutton, dt_iop_module_t *module)
Definition blend_gui.c:2246
static void _blendop_masks_group_update_row(dt_iop_module_t *module, const int formid, const int parentid)
Definition blend_gui.c:2577
static void _blendop_blendif_channel_mask_view(GtkWidget *widget, dt_iop_module_t *module, dt_dev_pixelpipe_display_mask_t mode)
Definition blend_gui.c:3012
static void _raster_combo_populate(GtkWidget *w, void *m)
Definition blend_gui.c:4176
void dt_iop_gui_cleanup_blending_body(dt_iop_module_t *module)
Definition blend_gui.c:4835
static GtkWidget * _blendop_masks_create_scroll_grid(void)
Definition blend_gui.c:3831
static void _blendop_blendif_disp_alternative_reset(GtkWidget *widget, dt_iop_module_t *module)
Definition blend_gui.c:729
static void _blendop_update_top_enable_label(dt_iop_module_t *module)
Definition blend_gui.c:4389
void dt_iop_gui_update_raster(dt_iop_module_t *module)
Definition blend_gui.c:4257
static void _blendop_masks_group_delete(dt_iop_module_t *module, const int formid, const int parentid)
Definition blend_gui.c:2416
static void _blendop_masks_refresh_lists(dt_iop_module_t *module)
Definition blend_gui.c:1612
static void _notebook_append_full_width_page(GtkWidget *notebook, GtkWidget *page, const gchar *label)
Definition blend_gui.c:4371
void dt_iop_gui_cleanup_blending(dt_iop_module_t *module)
Definition blend_gui.c:4316
const dt_iop_gui_blendif_colorstop_t _gradient_gray[]
Definition blend_gui.c:126
static gboolean _blendop_masks_group_name_focus_out(GtkWidget *widget, GdkEventFocus *event, dt_iop_module_t *module)
Definition blend_gui.c:1805
void dt_iop_gui_blending_reload_defaults(dt_iop_module_t *module)
Definition blend_gui.c:4825
static dt_iop_colorspace_type_t _blendif_colorpicker_cst(dt_iop_gui_blend_data_t *data)
Definition blend_gui.c:232
const dt_iop_gui_blendif_colorstop_t _gradient_red[]
Definition blend_gui.c:132
void dt_iop_gui_init_contours(GtkBox *blendw, dt_iop_module_t *module)
Definition blend_gui.c:3689
const dt_iop_gui_blendif_colorstop_t _gradient_a[]
Definition blend_gui.c:103
static void _blendop_blendif_boost_factor_callback(GtkWidget *slider, dt_iop_gui_blend_data_t *data)
Definition blend_gui.c:1052
gboolean dt_iop_gui_blend_set_drawn_mask_group(dt_iop_module_t *module, const int group_id)
Definition blend_gui.c:1745
static dt_masks_form_t * _blendop_masks_group_from_module(dt_iop_module_t *module)
Definition blend_gui.c:1388
static void _blendop_blending_notebook_switch(GtkNotebook *notebook, GtkWidget *page, guint page_num, dt_iop_gui_blend_data_t *data)
Persist the active blending notebook tab in anselrc.
Definition blend_gui.c:1042
static gboolean _blendop_masks_group_find_row(GtkTreeModel *model, GtkTreeIter *parent, const int formid, const int parentid, GtkTreeIter *out)
Definition blend_gui.c:2551
static gboolean _blendop_blendif_reset(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
Definition blend_gui.c:1228
static void _blendop_blendif_sliders_callback(GtkDarktableGradientSlider *slider, dt_iop_gui_blend_data_t *data)
Definition blend_gui.c:554
static void _blendif_scale_print_ab(float value, float boost_factor, char *string, int n)
Definition blend_gui.c:394
static void _blendop_masks_all_delete(dt_iop_module_t *module, const int formid)
Definition blend_gui.c:1954
static void _blendop_masks_group_move_callback(GtkWidget *menu_item, dt_iop_module_t *module)
Definition blend_gui.c:2213
#define NEUTRAL_GRAY
Definition blend_gui.c:89
static void _blendop_toggle_button_set_active(GtkWidget *toggle, const gboolean enabled)
Definition blend_gui.c:4445
static void _blendop_masks_group_unlink(dt_iop_module_t *module, const int formid, const int parentid)
Definition blend_gui.c:2396
void dt_iop_gui_update_blendif(dt_iop_module_t *module)
Definition blend_gui.c:3439
The blending panel's GUI state and API, cut out of develop/blend.h.
gboolean blend_color_picker_apply(dt_iop_module_t *module, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
void dtgtk_button_set_active(GtkDarktableButton *button, gboolean active)
Definition button.c:177
GtkWidget * dtgtk_button_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
Definition button.c:135
#define DTGTK_BUTTON(obj)
Definition button.h:40
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_gui_new_collapsible_section(dt_gui_collapsible_section_t *cs, const char *confname, const char *label, GtkBox *parent, GtkPackType pack)
Create a collapsible section and pack it into the parent box.
void dt_gui_update_collapsible_section(dt_gui_collapsible_section_t *cs)
dt_iop_colorspace_type_t
@ IOP_CS_RAW
@ IOP_CS_LCH
@ IOP_CS_JZCZHZ
@ IOP_CS_RGB
@ IOP_CS_HSL
@ IOP_CS_LAB
@ IOP_CS_NONE
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
void dt_iop_color_picker_set_cst(dt_iop_module_t *module, const dt_iop_colorspace_type_t picker_cst)
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
dt_iop_colorspace_type_t dt_iop_color_picker_get_active_cst(dt_iop_module_t *module)
@ DT_COLOR_PICKER_AREA
@ DT_COLOR_PICKER_POINT_AREA
@ DT_LIB_COLORPICKER_SIZE_BOX
Definition colorpicker.h:39
static const float x
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const dt_colormatrix_t dt_aligned_pixel_t out
static const int row
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_gui_container_destroy_children(GtkContainer *container)
Definition container.c:80
void dt_control_log(const char *msg,...)
Definition control.c:839
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:970
void dt_control_queue_redraw_widget(GtkWidget *widget)
threadsafe request of redraw of specific widget. Use this function if you need to redraw a specific w...
Definition control.c:1015
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:1027
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:654
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:526
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:649
#define dt_dev_add_history_item(dev, module, enable, redraw)
#define dt_dev_pixelpipe_update_history_main(dev)
#define dt_dev_pixelpipe_update_zoom_main(dev)
#define dt_dev_pixelpipe_update_history_all(dev)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_current_profile_info(dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe)
__DT_CLONE_TARGETS__ dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
void dt_dev_masks_selection_change(dt_develop_t *dev, struct dt_iop_module_t *module, const int selectid, const int throw_event)
Definition develop.c:1422
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1712
dt_dev_pixelpipe_display_mask_t
Definition develop.h:121
@ DT_DEV_PIXELPIPE_DISPLAY_a
Definition develop.h:127
@ DT_DEV_PIXELPIPE_DISPLAY_OUTPUT
Definition develop.h:125
@ DT_DEV_PIXELPIPE_DISPLAY_G
Definition develop.h:130
@ DT_DEV_PIXELPIPE_DISPLAY_L
Definition develop.h:126
@ DT_DEV_PIXELPIPE_DISPLAY_CHANNEL
Definition develop.h:124
@ DT_DEV_PIXELPIPE_DISPLAY_LCH_h
Definition develop.h:134
@ DT_DEV_PIXELPIPE_DISPLAY_JzCzhz_hz
Definition develop.h:140
@ DT_DEV_PIXELPIPE_DISPLAY_HSL_H
Definition develop.h:135
@ DT_DEV_PIXELPIPE_DISPLAY_HSL_S
Definition develop.h:136
@ DT_DEV_PIXELPIPE_DISPLAY_JzCzhz_Cz
Definition develop.h:139
@ DT_DEV_PIXELPIPE_DISPLAY_LCH_C
Definition develop.h:133
@ DT_DEV_PIXELPIPE_DISPLAY_b
Definition develop.h:128
@ DT_DEV_PIXELPIPE_DISPLAY_STICKY
Definition develop.h:144
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:123
@ DT_DEV_PIXELPIPE_DISPLAY_JzCzhz_Jz
Definition develop.h:138
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:122
@ DT_DEV_PIXELPIPE_DISPLAY_HSL_l
Definition develop.h:137
@ DT_DEV_PIXELPIPE_DISPLAY_GRAY
Definition develop.h:132
@ DT_DEV_PIXELPIPE_DISPLAY_B
Definition develop.h:131
@ DT_DEV_PIXELPIPE_DISPLAY_R
Definition develop.h:129
gboolean enabled
–doc was passed on the command line
GtkWidget * view
the tree view
static GdkPixbuf * dt_draw_get_pixbuf_from_cairo(DTGTKCairoPaintIconFunc paint, const int width, const int height)
Definition draw.h:1116
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:127
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Initialise a mutex. With mutexattr NULL – which is how 54 of the 56 call sites in this tree spell it ...
Definition dtpthread.h:104
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:132
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
static gboolean dt_iop_colorspace_is_rgb(const dt_iop_colorspace_type_t cst)
Definition format.h:75
@ IOP_CS_RGB_DISPLAY
Definition format.h:71
void dtgtk_gradient_slider_multivalue_set_resetvalue(GtkDarktableGradientSlider *gslider, gdouble value, gint pos)
void dtgtk_gradient_slider_multivalue_set_value(GtkDarktableGradientSlider *gslider, gdouble value, gint pos)
void dtgtk_gradient_slider_multivalue_set_increment(GtkDarktableGradientSlider *gslider, gdouble value)
void dtgtk_gradient_slider_multivalue_set_picker(GtkDarktableGradientSlider *gslider, gdouble value)
gdouble dtgtk_gradient_slider_multivalue_get_value(GtkDarktableGradientSlider *gslider, gint pos)
void dtgtk_gradient_slider_multivalue_set_picker_meanminmax(GtkDarktableGradientSlider *gslider, gdouble mean, gdouble min, gdouble max)
void dtgtk_gradient_slider_multivalue_clear_stops(GtkDarktableGradientSlider *gslider)
void dtgtk_gradient_slider_multivalue_set_scale_callback(GtkDarktableGradientSlider *gslider, float(*callback)(GtkWidget *self, float value, int dir))
void dtgtk_gradient_slider_multivalue_set_stop(GtkDarktableGradientSlider *gslider, gfloat position, GdkRGBA color)
void dtgtk_gradient_slider_multivalue_set_marker(GtkDarktableGradientSlider *gslider, gint mark, gint pos)
GtkWidget * dtgtk_gradient_slider_multivalue_new_with_name(gint positions, gchar *name)
#define DTGTK_GRADIENT_SLIDER_MULTIVALUE(obj)
@ GRADIENT_SLIDER_MARKER_UPPER_FILLED_BIG
@ GRADIENT_SLIDER_MARKER_LOWER_OPEN_BIG
@ GRADIENT_SLIDER_MARKER_UPPER_OPEN_BIG
@ GRADIENT_SLIDER_MARKER_LOWER_FILLED_BIG
@ GRADIENT_SLIDER_GET
@ GRADIENT_SLIDER_SET
#define DT_GUI_MODULE(x)
const char * tooltip
Definition image.h:319
gboolean dt_iop_module_supports_drawn_mask(dt_iop_module_t *iop)
Definition imageop.c:785
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:1685
void dt_iop_set_mask_mode(dt_iop_module_t *module, int mask_mode)
Definition imageop.c:1174
void dt_iop_add_remove_mask_indicator(dt_iop_module_t *module)
void dt_iop_gui_update(dt_iop_module_t *module)
void dt_iop_gui_update_header(dt_iop_module_t *module)
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
GtkWidget * dt_iop_togglebutton_new_no_register(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
@ DT_INTROSPECTION_TYPE_ENUM
@ DT_INTROSPECTION_TYPE_FLOAT
const char * model
gchar * delete_underscore(const char *s)
Definition label.c:75
GtkWidget * dt_ui_label_new(const gchar *str)
Definition label.c:125
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
#define DEVELOP_BLENDIF_SIZE
Definition lightroom.c:236
_lib_location_type_t type
Definition location.c:1
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
gchar * dt_masks_group_name_for_module(const dt_iop_module_t *module)
Definition masks.c:143
dt_masks_result_t dt_masks_group_set_member_operation(dt_develop_t *dev, const int group_id, const int formid, const dt_masks_state_t operation, dt_masks_member_t *out)
Set a group member's combination operator, or toggle its inversion.
Definition masks.c:1804
dt_masks_result_t dt_masks_group_set_name_from_module(dt_develop_t *dev, const int group_id, dt_iop_module_t *module)
Name a group after the module that renders it, by id.
Definition masks.c:1827
int dt_masks_form_duplicate_in_group(dt_develop_t *dev, int group_id, int form_id)
Duplicate a shape (dt_masks_form_duplicate) and, if group_id names a valid group, attach the duplicat...
Definition masks.c:210
dt_masks_event_t
Definition masks.h:143
@ DT_MASKS_EVENT_UPDATE
Definition masks.h:147
@ DT_MASKS_EVENT_RESET
Definition masks.h:150
@ DT_MASKS_EVENT_ADD
Definition masks.h:145
@ DT_MASKS_EVENT_DELETE
Definition masks.h:148
@ DT_MASKS_EVENT_REMOVE
Definition masks.h:146
@ DT_MASKS_EVENT_CHANGE
Definition masks.h:149
dt_masks_form_t * dt_masks_create(dt_masks_type_t type)
Definition masks.c:867
void dt_masks_form_delete(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *grp, dt_masks_form_t *form)
Definition masks.c:1217
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:925
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
Definition masks_gui.c:5809
int dt_masks_form_duplicate(dt_develop_t *dev, int formid)
Definition masks.c:186
Ask the masks module about a shape or a group, instead of reading its structs.
void dt_masks_scroll_mapping_set(dt_masks_scroll_modifier_t modifier, dt_masks_interaction_t interaction)
Definition masks_gui.c:3140
void dt_masks_gui_populate_interaction_sliders(GtkWidget *menu, dt_develop_t *dev, dt_masks_form_t *form, const int group_id, dt_masks_form_gui_t *gui, dt_iop_module_t *module)
Append the full set of shape-parameter sliders (size/fading/rotation or curvature/fade/rotation depen...
Definition masks_gui.c:798
int dt_masks_group_index_from_formid(const dt_masks_form_t *group_form, int form_id)
Find any group that currently references formid, searching every top-level group in dev->forms and th...
Definition masks_gui.c:1388
void dt_masks_change_form_gui(dt_develop_t *dev, dt_masks_form_t *new_form)
Definition masks_gui.c:4840
gboolean dt_masks_gui_confirm_permanent_delete(const char *form_name)
Ask the user to confirm a permanent shape deletion, gated by the "ask_before_delete_mask_shape" pref....
Definition masks_gui.c:602
const char * dt_masks_interaction_alias_name(dt_masks_interaction_t interaction)
The other word for a property, when a shape family spells it its own way.
Definition masks_gui.c:3183
void dt_masks_append_form(dt_develop_t *develop, dt_masks_form_t *mask_form)
Definition masks_gui.c:2505
const char * dt_masks_interaction_name(dt_masks_interaction_t interaction)
Definition masks_gui.c:3165
void dt_masks_shape_buttons_deactivate_all(GtkWidget *active_button)
Definition masks_gui.c:113
GtkWidget * dt_masks_shape_buttons_create(const dt_masks_shape_buttons_config_t *config)
Build a synchronized toolbar for creating masks shapes.
Definition masks_gui.c:308
dt_masks_form_group_t * dt_masks_group_add_form(dt_develop_t *dev, dt_masks_form_t *group_form, dt_masks_form_t *mask_form)
Definition masks_gui.c:5347
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
Definition masks_gui.c:1408
const char * dt_masks_scroll_modifier_name(dt_masks_scroll_modifier_t modifier)
Definition masks_gui.c:3149
int dt_masks_center_view_on_form(dt_develop_t *dev, const dt_masks_form_t *mask_form)
Center the darkroom ROI on a mask form gravity center.
Definition masks_gui.c:5177
dt_masks_form_group_t * dt_masks_form_group_find_entry(dt_masks_form_t *group_form, const int form_id, int *out_index)
Find the group-membership entry for form_id inside group_form's own points list (not recursive into s...
Definition masks_gui.c:1368
dt_masks_interaction_t dt_masks_scroll_mapping_get(dt_masks_scroll_modifier_t modifier)
Definition masks_gui.c:3127
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
dt_masks_scroll_modifier_t
Definition masks_gui.h:774
@ DT_MASKS_SCROLL_MODIFIER_LAST
Definition masks_gui.h:779
dt_masks_form_t * dt_masks_cow_touch(dt_develop_t *dev, dt_masks_form_t *form)
@ DT_MASKS_EDIT_RESTRICTED
@ DT_MASKS_EDIT_OFF
@ DT_MASKS_EDIT_FULL
dt_masks_state_t
Definition masks_types.h:87
@ DT_MASKS_STATE_DIFFERENCE
Definition masks_types.h:94
@ DT_MASKS_STATE_INVERSE
Definition masks_types.h:91
@ DT_MASKS_STATE_INTERSECTION
Definition masks_types.h:93
@ DT_MASKS_STATE_EXCLUSION
Definition masks_types.h:95
@ DT_MASKS_STATE_UNION
Definition masks_types.h:92
dt_masks_type_t
Definition masks_types.h:62
@ DT_MASKS_NON_CLONE
Definition masks_types.h:71
@ DT_MASKS_CLONE
Definition masks_types.h:67
@ DT_MASKS_NONE
Definition masks_types.h:63
@ DT_MASKS_GROUP
Definition masks_types.h:66
dt_masks_interaction_t
@ DT_MASKS_INTERACTION_OPACITY
@ DT_MASKS_INTERACTION_SIZE
@ DT_MASKS_INTERACTION_FADING
@ DT_MASKS_INTERACTION_UNDEF
@ DT_MASKS_INTERACTION_ROTATION
@ DT_MASKS_OK
#define DEVELOP_MASKS_NB_SHAPES
Definition masks_types.h:58
#define M_LN10
Definition math.h:42
static float clamp_range_f(const float x, const float low, const float high)
Definition math.h:98
#define DT_ALIGNED_PIXEL
Align a 4-float pixel on 16 bytes, enough for SSE. Same struct-member caveat as DT_ALIGNED_ARRAY,...
Definition mem_alloc.h:85
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
GtkWidget * ctx_gtk_check_menu_item_new_with_markup_and_pixbuf(const char *label, GdkPixbuf *icon, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data, const gboolean checked, const gboolean show_checkbox)
Definition menu.c:280
char * key
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition notebook.c:88
const float factor
Definition pdf.h:91
void dt_gui_menu_popup(GtkMenu *menu, GtkWidget *button, GdkGravity widget_anchor, GdkGravity menu_anchor)
Definition popup.c:53
#define eps
Definition rcd.c:87
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, const char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
@ DT_UI_RESIZE_DYNAMIC
Definition scroll_wrap.h:43
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:411
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:390
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:619
@ DT_SIGNAL_MASK_CHANGED
Definition signal.h:316
@ DT_SIGNAL_DEVELOP_MASKS_GUI_CHANGED
Definition signal.h:324
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:400
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
dt_lib_colorpicker_size_t size
Definition colorpicker.h:64
dt_boundingbox_t box
Definition colorpicker.h:63
struct dt_develop_blend_params_t * blend_params
Definition dev_history.h:57
uint32_t feathering_guide
Definition blend.h:219
dt_dev_operation_t raster_mask_source
Definition blend.h:233
float blendif_parameters[4 *DEVELOP_BLENDIF_SIZE]
Definition blend.h:231
float blendif_boost_factors[DEVELOP_BLENDIF_SIZE]
Definition blend.h:232
gboolean raster_mask_invert
Definition blend.h:236
GList * iop
Definition develop.h:269
GList * history
Definition develop.h:259
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
GList * forms
Definition develop.h:304
GtkMenu * presets_popup_menu
GtkWidget * raster_enable
Definition blend_gui.h:98
GtkWidget * blendif_enable
Definition blend_gui.h:99
dt_gui_collapsible_section_t scroll_cs
Definition blend_gui.h:169
GtkWidget * masks_content
Definition blend_gui.h:100
GtkWidget * masks_treeview
Definition blend_gui.h:147
GtkWidget * channel_boost_factor_slider
Definition blend_gui.h:135
GtkWidget * opacity_slider
Definition blend_gui.h:117
GtkWidget * raster_polarity
Definition blend_gui.h:173
GtkWidget * all_shapes_sw
Definition blend_gui.h:157
GtkWidget * blur_radius_slider
Definition blend_gui.h:120
GdkPixbuf * masks_ic_intersection
Definition blend_gui.h:161
GtkWidget * feathering_radius_slider
Definition blend_gui.h:119
GtkWidget * details_slider
Definition blend_gui.h:136
dt_iop_gui_blendif_filter_t filter[2]
Definition blend_gui.h:110
GtkWidget * all_shapes_buttons
Definition blend_gui.h:164
GtkWidget * colorpicker
Definition blend_gui.h:108
GtkWidget * colorpicker_set_values
Definition blend_gui.h:109
GdkPixbuf * masks_ic_union
Definition blend_gui.h:160
GdkPixbuf * masks_ic_inverse
Definition blend_gui.h:159
GtkWidget * masks_group_treeview
Definition blend_gui.h:148
GtkWidget * masks_combo
Definition blend_gui.h:138
int masks_type[DEVELOP_MASKS_NB_SHAPES]
Definition blend_gui.h:140
GdkPixbuf * masks_ic_difference
Definition blend_gui.h:162
GtkTreeViewColumn * group_shapes_col
Definition blend_gui.h:150
GtkWidget * lists_stack
Definition blend_gui.h:158
dt_boundingbox_t picker_set_values_box
Definition blend_gui.h:176
GtkWidget * contrast_slider
Definition blend_gui.h:121
GtkWidget * blend_modes_blend_order
Definition blend_gui.h:114
GtkWidget * masks_combine_combo
Definition blend_gui.h:112
dt_develop_blend_colorspace_t channel_tabs_csp
Definition blend_gui.h:125
gboolean picker_set_values_manual_boost_lock
Definition blend_gui.h:177
GtkWidget * blend_mode_parameter_slider
Definition blend_gui.h:115
GtkWidget * group_shapes_label
Definition blend_gui.h:142
GtkWidget * masks_feathering_guide_combo
Definition blend_gui.h:118
const dt_iop_gui_blendif_channel_t * channel
Definition blend_gui.h:127
dt_pthread_mutex_t lock
Definition blend_gui.h:180
GtkWidget * brightness_slider
Definition blend_gui.h:122
GtkWidget * masks_shapes[DEVELOP_MASKS_NB_SHAPES]
Definition blend_gui.h:139
GtkWidget * raster_content
Definition blend_gui.h:101
GtkWidget * blend_modes_combo
Definition blend_gui.h:113
GtkWidget * contours_content
Definition blend_gui.h:103
dt_develop_blend_colorspace_t csp
Definition blend_gui.h:91
dt_iop_module_t *GtkWidget * blending_box
Definition blend_gui.h:94
GtkTreeViewColumn * group_delete_col
Definition blend_gui.h:152
GtkWidget * group_shapes_sw
Definition blend_gui.h:154
dt_gui_collapsible_section_t masks_cs
Definition blend_gui.h:166
GtkWidget * blending_notebook
Definition blend_gui.h:95
GtkWidget * blendif_content
Definition blend_gui.h:102
GtkWidget * masks_invert_combo
Definition blend_gui.h:116
gboolean output_channels_shown
Definition blend_gui.h:133
GdkPixbuf * masks_ic_exclusion
Definition blend_gui.h:163
GtkWidget * raster_combo
Definition blend_gui.h:172
GtkListStore * all_shapes_store
Definition blend_gui.h:153
GtkWidget * masks_polarity
Definition blend_gui.h:143
GtkTreeViewColumn * all_shapes_col
Definition blend_gui.h:155
GtkWidget * top_enable
Definition blend_gui.h:96
GtkWidget * masks_enable
Definition blend_gui.h:97
GtkTreeStore * group_shapes_store
Definition blend_gui.h:149
dt_dev_pixelpipe_display_mask_t save_for_leave
Definition blend_gui.h:130
GtkWidget * wire_shape_toggle
Definition blend_gui.h:144
GtkTreeViewColumn * all_shapes_delete_col
Definition blend_gui.h:156
GtkNotebook * channel_tabs
Definition blend_gui.h:132
gboolean picker_set_values_box_valid
Definition blend_gui.h:175
dt_develop_blend_colorspace_t blend_modes_csp
Definition blend_gui.h:124
GtkTreeViewColumn * group_unlink_col
Definition blend_gui.h:151
const dt_iop_gui_blendif_colorstop_t * colorstops
Definition blend_gui.h:59
void(* scale_print)(float value, float boost_factor, char *string, int n)
Definition blend_gui.h:64
int(* altdisplay)(GtkWidget *, dt_iop_module_t *, int)
Definition blend_gui.h:65
dt_develop_blendif_channels_t param_channels[2]
Definition blend_gui.h:62
dt_dev_pixelpipe_display_mask_t display_channel
Definition blend_gui.h:63
GtkDarktableGradientSlider * slider
Definition blend_gui.h:71
GtkWidget * mask_indicator
Definition imageop_gui.h:55
GtkWidget * widget
Definition imageop_gui.h:47
GHashTable * masks
Definition imageop.h:362
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:349
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
int request_mask_display
Definition imageop.h:276
struct dt_iop_module_t::@23::@24 source
struct dt_iop_module_t::@23::@25 sink
GHashTable * users
Definition imageop.h:358
struct dt_develop_blend_params_t * default_blendop_params
Definition imageop.h:349
struct dt_iop_module_t::@23 raster_mask
dt_dev_operation_t op
Definition imageop.h:259
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
int nonlinearlut
Non-zero when the profile has tone curves at all; tested as a boolean everywhere, but it is really th...
int lutsize
Entry count of each of the six LUTs. Always 65536 in practice: both callers of dt_ioppr_init_profile_...
float * lut_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....
dt_colormatrix_t matrix_in
RGB -> XYZ (D50), row-major. matrix_in[1][*] is the luminance row. NaN in [0][0] marks the whole prof...
gboolean node_selected
Definition masks_gui.h:145
gboolean handle_border_selected
Definition masks_gui.h:148
gboolean source_selected
Definition masks_gui.h:153
dt_masks_type_t creation_type
Definition masks_gui.h:182
gboolean source_dragging
Definition masks_gui.h:161
dt_masks_edit_mode_t edit_mode
Definition masks_gui.h:138
dt_iop_module_t * creation_module
Definition masks_gui.h:180
gboolean seg_selected
Definition masks_gui.h:147
gboolean form_dragging
Definition masks_gui.h:160
gboolean form_selected
Definition masks_gui.h:151
gboolean handle_selected
Definition masks_gui.h:146
gboolean form_rotating
Definition masks_gui.h:162
gboolean border_selected
Definition masks_gui.h:152
gboolean pivot_selected
Definition masks_gui.h:154
dt_masks_type_t type
Definition masks.h:254
char name[128]
Definition masks.h:277
GList * points
Definition masks.h:253
struct dt_develop_t * dev
Definition masks_gui.h:514
Definition blend_gui.c:4171
dt_iop_module_t *int id
Definition blend_gui.c:4173
GtkWidget * notebook
void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
#define DTGTK_TOGGLEBUTTON(obj)
char * dt_get_help_url(char *name)
GdkModifierType dt_key_modifier_state()
gboolean dt_gui_widgets_suppressed(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
void dtgtk_cairo_paint_masks_inverse(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_difference(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_presets(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_showmask(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_union(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_colorpicker(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_exclusion(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_eye_toggle(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_reset(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_intersection(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_invert(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_edit(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ CPF_ALTER