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)
2664 ? dt_ioppr_get_pipe_current_profile_info(piece->module, pipe)
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 void _blendif_options_callback(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
2962{
2963 if(event->button != 1 && event->button != 2) return;
2965 if(IS_NULL_PTR(bd) || !bd->blendif_support || !bd->blendif_inited) return;
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}
3009
3010// activate channel/mask view
3012{
3013 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
3014 // These should all use an uniform setter function.
3015 //
3016 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
3017 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
3018 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
3019 // that changes over the pipe lifecycle.
3020 //
3021 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
3022 // (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),
3023 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
3024 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
3025 // planning for pipeline execution.
3026 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
3027 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
3028 // the preview at the far end of the pipe.
3029 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
3030 // which flushed lines that could be reused later and were only temporarily not needed.
3031
3032 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
3033
3034 dt_dev_pixelpipe_display_mask_t new_request_mask_display = module->request_mask_display | mode;
3035
3036 // in case user requests channel display: get the cannel
3037 if(new_request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_CHANNEL)
3038 {
3040
3041 if(widget == GTK_WIDGET(data->filter[1].slider))
3043
3044 new_request_mask_display &= ~DT_DEV_PIXELPIPE_DISPLAY_ANY;
3045 new_request_mask_display |= channel;
3046 }
3047
3048 // only if something has changed: reprocess center view
3049 if(new_request_mask_display != module->request_mask_display)
3050 {
3051 module->request_mask_display = new_request_mask_display;
3055 }
3056}
3057
3058// toggle channel/mask view
3060{
3061 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
3062 // These should all use an uniform setter function.
3063 //
3064 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
3065 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
3066 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
3067 // that changes over the pipe lifecycle.
3068 //
3069 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
3070 // (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),
3071 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
3072 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
3073 // planning for pipeline execution.
3074 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
3075 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
3076 // the preview at the far end of the pipe.
3077 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
3078 // which flushed lines that could be reused later and were only temporarily not needed.
3079
3080 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
3081
3082 dt_dev_pixelpipe_display_mask_t new_request_mask_display = module->request_mask_display & ~DT_DEV_PIXELPIPE_DISPLAY_STICKY;
3083
3084 // toggle mode
3085 if(module->request_mask_display & mode)
3086 new_request_mask_display &= ~mode;
3087 else
3088 new_request_mask_display |= mode;
3089
3091 if(new_request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_STICKY)
3093 else
3094 data->save_for_leave &= ~DT_DEV_PIXELPIPE_DISPLAY_STICKY;
3096
3097 new_request_mask_display &= ~DT_DEV_PIXELPIPE_DISPLAY_ANY;
3098
3099 // in case user requests channel display: get the cannel
3100 if(new_request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_CHANNEL)
3101 {
3103
3104 if(widget == GTK_WIDGET(data->filter[1].slider))
3106
3107 new_request_mask_display &= ~DT_DEV_PIXELPIPE_DISPLAY_ANY;
3108 new_request_mask_display |= channel;
3109 }
3110
3111 if(new_request_mask_display != module->request_mask_display)
3112 {
3113 module->request_mask_display = new_request_mask_display;
3117 }
3118}
3119
3129{
3130 dt_iop_gui_blend_data_t *data = !IS_NULL_PTR(module) ? module->gui->blend_data : NULL;
3131 if(IS_NULL_PTR(data)) return;
3132
3133 const gboolean channel_active
3134 = (module->request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_CHANNEL) != 0;
3135 const gboolean output_active
3136 = channel_active && (module->request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_OUTPUT) != 0;
3137
3139 if(GTK_IS_TOGGLE_BUTTON(data->filter[0].channel_display))
3140 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->filter[0].channel_display),
3141 channel_active && !output_active);
3142 if(GTK_IS_TOGGLE_BUTTON(data->filter[1].channel_display))
3143 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->filter[1].channel_display),
3144 output_active);
3146}
3147
3156static void _blendop_blendif_channel_display_toggled(GtkToggleButton *button, dt_iop_module_t *module)
3157{
3158 if(dt_gui_widgets_suppressed()) return;
3159
3160 dt_iop_gui_blend_data_t *data = !IS_NULL_PTR(module) ? module->gui->blend_data : NULL;
3161 if(IS_NULL_PTR(data) || IS_NULL_PTR(data->channel)) return;
3162
3163 const int in_out = GTK_WIDGET(button) == data->filter[1].channel_display ? 1 : 0;
3164 const gboolean active = gtk_toggle_button_get_active(button);
3165 GtkWidget *const other_button = data->filter[!in_out].channel_display;
3166
3168 if(active && GTK_IS_TOGGLE_BUTTON(other_button))
3169 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(other_button), FALSE);
3171
3172 dt_dev_pixelpipe_display_mask_t new_request_mask_display
3173 = module->request_mask_display
3174 & ~(DT_DEV_PIXELPIPE_DISPLAY_CHANNEL | DT_DEV_PIXELPIPE_DISPLAY_OUTPUT
3175 | DT_DEV_PIXELPIPE_DISPLAY_ANY | DT_DEV_PIXELPIPE_DISPLAY_STICKY);
3176
3177 if(active)
3178 {
3179 new_request_mask_display |= DT_DEV_PIXELPIPE_DISPLAY_CHANNEL
3181 | data->channel[data->tab].display_channel;
3182 if(in_out) new_request_mask_display |= DT_DEV_PIXELPIPE_DISPLAY_OUTPUT;
3183 }
3184
3186 if(active)
3188 else
3189 data->save_for_leave &= ~DT_DEV_PIXELPIPE_DISPLAY_STICKY;
3191
3192 if(new_request_mask_display == module->request_mask_display) return;
3193
3194 module->request_mask_display = new_request_mask_display;
3196
3197 dt_iop_request_focus(module);
3199}
3200
3201
3202// magic mode: if mouse cursor enters a gradient slider with shift and/or control pressed we
3203// enter channel display and/or mask display mode
3204static gboolean _blendop_blendif_enter(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *module)
3205{
3206 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
3207 // These should all use an uniform setter function.
3208 //
3209 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
3210 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
3211 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
3212 // that changes over the pipe lifecycle.
3213 //
3214 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
3215 // (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),
3216 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
3217 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
3218 // planning for pipeline execution.
3219 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
3220 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
3221 // the preview at the far end of the pipe.
3222 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
3223 // which flushed lines that could be reused later and were only temporarily not needed.
3224
3225 if(dt_gui_widgets_suppressed()) return FALSE;
3226 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
3227
3229
3230 // depending on shift modifiers we activate channel and/or mask display
3231 if(dt_modifier_is(event->state, GDK_SHIFT_MASK | DT_PRIMARY_MASK))
3232 {
3234 }
3235 else if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
3236 {
3238 }
3239 else if(dt_modifier_is(event->state, DT_PRIMARY_MASK))
3240 {
3242 }
3243
3245 if(mode && data->timeout_handle)
3246 {
3247 // purge any remaining timeout handlers
3248 g_source_remove(data->timeout_handle);
3249 data->timeout_handle = 0;
3250 }
3252 {
3253 // save request_mask_display to restore later
3254 data->save_for_leave = module->request_mask_display & ~DT_DEV_PIXELPIPE_DISPLAY_STICKY;
3255 }
3257
3258 /* A plain hover only gives keyboard focus to the slider. Reapplying an
3259 * already-active CHANNEL request here would derive INPUT/OUTPUT again from
3260 * the hovered widget and switch the persistent channel toggle without a
3261 * click. Only modifier-assisted hover is allowed to change the preview. */
3263 _blendop_blendif_channel_mask_view(widget, module, mode);
3264
3265 gtk_widget_grab_focus(widget);
3266 return FALSE;
3267}
3268
3269
3270// handler for delayed mask/channel display mode switch-off
3271static gboolean _blendop_blendif_leave_delayed(gpointer data)
3272{
3273 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
3274 // These should all use an uniform setter function.
3275 //
3276 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
3277 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
3278 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
3279 // that changes over the pipe lifecycle.
3280 //
3281 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
3282 // (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),
3283 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
3284 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
3285 // planning for pipeline execution.
3286 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
3287 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
3288 // the preview at the far end of the pipe.
3289 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
3290 // which flushed lines that could be reused later and were only temporarily not needed.
3291
3292 dt_iop_module_t *module = (dt_iop_module_t *)data;
3293 dt_iop_gui_blend_data_t *bd = module->gui->blend_data;
3294 int reprocess = 0;
3295
3296 dt_pthread_mutex_lock(&bd->lock);
3297 // restore saved request_mask_display and reprocess image
3298 if(bd->timeout_handle && (module->request_mask_display != (bd->save_for_leave & ~DT_DEV_PIXELPIPE_DISPLAY_STICKY)))
3299 {
3300 module->request_mask_display = bd->save_for_leave & ~DT_DEV_PIXELPIPE_DISPLAY_STICKY;
3302 reprocess = 1;
3303 }
3304 bd->timeout_handle = 0;
3305 dt_pthread_mutex_unlock(&bd->lock);
3306
3307 if(reprocess)
3308 {
3311 }
3312 // return FALSE and thereby terminate the handler
3313 return FALSE;
3314}
3315
3316// de-activate magic mode when leaving the gradient slider
3317static gboolean _blendop_blendif_leave(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *module)
3318{
3319 if(dt_gui_widgets_suppressed()) return FALSE;
3320 dt_iop_gui_blend_data_t *data = module->gui->blend_data;
3321
3322 // do not immediately switch-off mask/channel display in case user leaves gradient only briefly.
3323 // instead we activate a handler function that gets triggered after some timeout
3327 data->timeout_handle = g_timeout_add(1000, _blendop_blendif_leave_delayed, module);
3329
3330 return FALSE;
3331}
3332
3333
3334static gboolean _blendop_blendif_key_press(GtkWidget *widget, GdkEventKey *event, dt_iop_module_t *module)
3335{
3336 if(dt_gui_widgets_suppressed()) return FALSE;
3337
3338 gboolean handled = FALSE;
3339
3340 switch(event->keyval)
3341 {
3342 case GDK_KEY_m:
3343 case GDK_KEY_M:
3345 handled = TRUE;
3346 }
3347
3348 if(handled)
3349 dt_iop_request_focus(module);
3350
3351 return handled;
3352}
3353
3354
3355#define COLORSTOPS(gradient) sizeof(gradient) / sizeof(dt_iop_gui_blendif_colorstop_t), gradient
3356
3358 = { { N_("L"), N_("sliders for L channel"), 1.0f / 100.0f, COLORSTOPS(_gradient_L), TRUE, 0.0f,
3361 { N_("a"), N_("sliders for a channel"), 1.0f / 256.0f, COLORSTOPS(_gradient_a), TRUE, 0.0f,
3364 { N_("b"), N_("sliders for b channel"), 1.0f / 256.0f, COLORSTOPS(_gradient_b), TRUE, 0.0f,
3367 { N_("C"), N_("sliders for chroma channel (of LCh)"), 1.0f / 100.0f, COLORSTOPS(_gradient_chroma),
3368 TRUE, 0.0f,
3371 { N_("h"), N_("sliders for hue channel (of LCh)"), 1.0f / 360.0f, COLORSTOPS(_gradient_LCh_hue),
3372 FALSE, 0.0f,
3374 _blendif_scale_print_hue, NULL, N_("hue") },
3375 { NULL } };
3376
3378 = { { N_("g"), N_("sliders for gray value"), 1.0f / 255.0f, COLORSTOPS(_gradient_gray), TRUE, 0.0f,
3381 { N_("R"), N_("sliders for red channel"), 1.0f / 255.0f, COLORSTOPS(_gradient_red), TRUE, 0.0f,
3384 { N_("G"), N_("sliders for green channel"), 1.0f / 255.0f, COLORSTOPS(_gradient_green), TRUE, 0.0f,
3387 { N_("B"), N_("sliders for blue channel"), 1.0f / 255.0f, COLORSTOPS(_gradient_blue), TRUE, 0.0f,
3390 { N_("H"), N_("sliders for hue channel (of HSL)"), 1.0f / 360.0f, COLORSTOPS(_gradient_HSL_hue),
3391 FALSE, 0.0f,
3393 _blendif_scale_print_hue, NULL, N_("hue") },
3394 { N_("S"), N_("sliders for chroma channel (of HSL)"), 1.0f / 100.0f, COLORSTOPS(_gradient_chroma),
3395 FALSE, 0.0f,
3398 { N_("L"), N_("sliders for value channel (of HSL)"), 1.0f / 100.0f, COLORSTOPS(_gradient_gray),
3399 FALSE, 0.0f,
3402 { NULL } };
3403
3405 = { { N_("g"), N_("sliders for gray value"), 1.0f / 255.0f, COLORSTOPS(_gradient_gray), TRUE, 0.0f,
3408 { N_("R"), N_("sliders for red channel"), 1.0f / 255.0f, COLORSTOPS(_gradient_red), TRUE, 0.0f,
3411 { N_("G"), N_("sliders for green channel"), 1.0f / 255.0f, COLORSTOPS(_gradient_green), TRUE, 0.0f,
3414 { N_("B"), N_("sliders for blue channel"), 1.0f / 255.0f, COLORSTOPS(_gradient_blue), TRUE, 0.0f,
3417 { N_("Jz"), N_("sliders for value channel (of JzCzhz)"), 1.0f / 100.0f, COLORSTOPS(_gradient_gray),
3418 TRUE, -6.64385619f, // cf. _blend_init_blendif_boost_parameters
3421 { N_("Cz"), N_("sliders for chroma channel (of JzCzhz)"), 1.0f / 100.0f, COLORSTOPS(_gradient_chroma),
3422 TRUE, -6.64385619f, // cf. _blend_init_blendif_boost_parameters
3425 { N_("hz"), N_("sliders for hue channel (of JzCzhz)"), 1.0f / 360.0f, COLORSTOPS(_gradient_JzCzhz_hue),
3426 FALSE, 0.0f,
3428 _blendif_scale_print_hue, NULL, N_("hue") },
3429 { NULL } };
3430
3431const char *slider_tooltip[] = { N_("adjustment based on input received by this module:\n* range defined by upper markers: "
3432 "blend fully\n* range defined by lower markers: do not blend at all\n* range between "
3433 "adjacent upper/lower markers: blend gradually"),
3434 N_("adjustment based on unblended output of this module:\n* range defined by upper "
3435 "markers: blend fully\n* range defined by lower markers: do not blend at all\n* range "
3436 "between adjacent upper/lower markers: blend gradually") };
3437
3439{
3440 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
3441 // These should all use an uniform setter function.
3442 //
3443 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
3444 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
3445 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
3446 // that changes over the pipe lifecycle.
3447 //
3448 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
3449 // (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),
3450 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
3451 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
3452 // planning for pipeline execution.
3453 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
3454 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
3455 // the preview at the far end of the pipe.
3456 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
3457 // which flushed lines that could be reused later and were only temporarily not needed.
3458 dt_iop_gui_blend_data_t *bd = module->gui->blend_data;
3459
3460 if(IS_NULL_PTR(bd) || !bd->blendif_support || !bd->blendif_inited) return;
3461
3463
3465 if(bd->timeout_handle)
3466 {
3467 g_source_remove(bd->timeout_handle);
3468 bd->timeout_handle = 0;
3470 {
3471 module->request_mask_display = bd->save_for_leave & ~DT_DEV_PIXELPIPE_DISPLAY_STICKY;
3474 }
3475 }
3477
3478 /* update output channel mask visibility */
3479 gtk_widget_set_visible(GTK_WIDGET(bd->filter[1].box), bd->output_channels_shown);
3480
3481 /* update tabs */
3482 if(bd->channel_tabs_csp != bd->csp)
3483 {
3484 bd->channel = NULL;
3485
3486 switch(bd->csp)
3487 {
3489 bd->channel = Lab_channels;
3490 break;
3492 bd->channel = rgb_channels;
3493 break;
3495 bd->channel = rgbj_channels;
3496 break;
3497 default:
3498 assert(FALSE); // blendif not supported for RAW, which is already caught upstream; we should not get
3499 // here
3500 }
3501
3503
3504 /* remove tabs before adding others */
3506
3507 bd->channel_tabs_csp = bd->csp;
3508
3509 int index = 0;
3510 for(const dt_iop_gui_blendif_channel_t *ch = bd->channel; !IS_NULL_PTR(ch->label); ch++, index++)
3511 {
3512 dt_ui_notebook_page(bd->channel_tabs, ch->label, _(ch->tooltip));
3513 gtk_widget_show_all(GTK_WIDGET(gtk_notebook_get_nth_page(bd->channel_tabs, index)));
3514 }
3515
3516 bd->tab = 0;
3517 gtk_notebook_set_current_page(GTK_NOTEBOOK(bd->channel_tabs), bd->tab);
3518 }
3519
3520 _blendop_blendif_update_tab(module, bd->tab);
3522
3524}
3525
3526
3527void dt_iop_gui_init_blendif(GtkBox *blendw, dt_iop_module_t *module, GtkWidget *blendif_header)
3528{
3530
3531 bd->blendif_box = blendw;
3532
3533 /* create and add blendif support if module supports it */
3534 if(bd->blendif_support)
3535 {
3536 dt_iop_togglebutton_new_no_register(module, "blend`tools", N_("reset blend mask settings"), NULL,
3537 G_CALLBACK(_blendop_blendif_reset), FALSE, 0, 0,
3538 dtgtk_cairo_paint_reset, blendif_header);
3539
3540 GtkWidget *header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3541 gtk_widget_set_name(header, "blendif-pickers");
3542
3543 bd->tab = 0;
3545 bd->channel_tabs = GTK_NOTEBOOK(gtk_notebook_new());
3546 gtk_notebook_set_scrollable(bd->channel_tabs, TRUE);
3547 gtk_box_pack_start(GTK_BOX(bd->blendif_box), GTK_WIDGET(bd->channel_tabs), FALSE, FALSE, 0);
3548 gtk_notebook_set_action_widget(GTK_NOTEBOOK(bd->channel_tabs), header, GTK_PACK_END);
3549 dt_gui_add_class(GTK_WIDGET(bd->channel_tabs), "empty");
3550
3552 gtk_widget_set_tooltip_text(bd->colorpicker, _("pick GUI color from image\nctrl+click or right-click to select an area"));
3553
3557
3558 gtk_widget_set_tooltip_text(bd->colorpicker_set_values, _("set the range based on an area from the image\n"
3559 "drag to use the input image\n"
3560 "ctrl+drag to use the output image"));
3561
3562 dt_iop_togglebutton_new_no_register(module, "blend`tools", N_("invert all channel's polarities"), NULL,
3563 G_CALLBACK(_blendop_blendif_invert), FALSE, 0, 0,
3564 dtgtk_cairo_paint_invert, header);
3565
3566 gtk_widget_show_all(header);
3567
3568 for(int in_out = 1; in_out >= 0; in_out--)
3569 {
3570 dt_iop_gui_blendif_filter_t *sl = &bd->filter[in_out];
3571
3572 GtkWidget *slider_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3573
3575 in_out ? "blend-upper" : "blend-lower"));
3576 gtk_box_pack_start(GTK_BOX(slider_box), GTK_WIDGET(sl->slider), TRUE, TRUE, 0);
3577
3579 dt_gui_add_class(sl->polarity, "dt_ignore_fg_state");
3580
3581 gtk_widget_set_tooltip_text(sl->polarity, _("toggle polarity. best seen by enabling 'display mask'"));
3582 gtk_box_pack_end(GTK_BOX(slider_box), GTK_WIDGET(sl->polarity), FALSE, FALSE, 0);
3583
3584 GtkWidget *label_box = gtk_grid_new();
3585 gtk_grid_set_column_homogeneous(GTK_GRID(label_box), TRUE);
3586
3587 sl->head = GTK_LABEL(dt_ui_label_new(in_out ? _("output") : _("input")));
3588 gtk_grid_attach(GTK_GRID(label_box), GTK_WIDGET(sl->head), 0, 0, 1, 1);
3589
3590 GtkWidget *overlay = gtk_overlay_new();
3591 gtk_grid_attach(GTK_GRID(label_box), overlay, 1, 0, 3, 1);
3592
3593 sl->picker_label = GTK_LABEL(gtk_label_new(""));
3594 gtk_widget_set_name(GTK_WIDGET(sl->picker_label), "blend-data");
3595 gtk_label_set_xalign(sl->picker_label, .0);
3596 gtk_label_set_yalign(sl->picker_label, 1.0);
3597 gtk_container_add(GTK_CONTAINER(overlay), GTK_WIDGET(sl->picker_label));
3598
3599 for(int k = 0; k < 4; k++)
3600 {
3601 sl->label[k] = GTK_LABEL(gtk_label_new(NULL));
3602 gtk_widget_set_name(GTK_WIDGET(sl->label[k]), "blend-data");
3603 gtk_label_set_xalign(sl->label[k], .35 + k * .65/3);
3604 gtk_label_set_yalign(sl->label[k], k % 2);
3605 gtk_overlay_add_overlay(GTK_OVERLAY(overlay), GTK_WIDGET(sl->label[k]));
3606 }
3607
3608 gtk_widget_set_tooltip_text(GTK_WIDGET(sl->slider),
3609 _("double-click to reset.\npress 'm' to toggle mask view."));
3610 gtk_widget_set_tooltip_text(GTK_WIDGET(sl->head), _(slider_tooltip[in_out]));
3611
3612 g_signal_connect(G_OBJECT(sl->slider), "value-changed", G_CALLBACK(_blendop_blendif_sliders_callback), bd);
3613 g_signal_connect(G_OBJECT(sl->slider), "value-reset", G_CALLBACK(_blendop_blendif_sliders_reset_callback), bd);
3614 g_signal_connect(G_OBJECT(sl->slider), "leave-notify-event", G_CALLBACK(_blendop_blendif_leave), module);
3615 g_signal_connect(G_OBJECT(sl->slider), "enter-notify-event", G_CALLBACK(_blendop_blendif_enter), module);
3616 g_signal_connect(G_OBJECT(sl->slider), "key-press-event", G_CALLBACK(_blendop_blendif_key_press), module);
3617 g_signal_connect(G_OBJECT(sl->polarity), "toggled", G_CALLBACK(_blendop_blendif_polarity_callback), bd);
3618
3619 // The label is semantically "part of" the slider, even though it's a different widget,
3620 // so the parent box has no internal spacing between children to keep them grouped.
3621 sl->box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
3622 GtkWidget *channel_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
3623 {
3624 gtk_box_pack_start(GTK_BOX(channel_box), GTK_WIDGET(label_box), FALSE, FALSE, 0);
3625 gtk_box_pack_start(GTK_BOX(channel_box), GTK_WIDGET(slider_box), FALSE, FALSE, 0);
3626 }
3627 gtk_box_pack_start(GTK_BOX(sl->box), channel_box, FALSE, FALSE, 0);
3628
3629 GtkWidget *display_controls = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3630 {
3631 sl->log_scale = gtk_toggle_button_new_with_label(_("Log scale"));
3632 gtk_widget_set_tooltip_text(sl->log_scale,
3633 _("toggle the alternative logarithmic or magnified scale for this channel"));
3634 g_signal_connect(G_OBJECT(sl->log_scale), "toggled",
3635 G_CALLBACK(_blendop_blendif_log_scale_toggled), module);
3636 gtk_box_pack_start(GTK_BOX(display_controls), sl->log_scale, FALSE, FALSE, 0);
3637
3639 gtk_widget_set_tooltip_text(
3640 sl->channel_display,
3641 in_out ? _("display the current channel from the unblended module output")
3642 : _("display the current channel from the module input"));
3643 g_signal_connect(G_OBJECT(sl->channel_display), "toggled",
3644 G_CALLBACK(_blendop_blendif_channel_display_toggled), module);
3645 gtk_box_pack_start(GTK_BOX(display_controls), sl->channel_display, FALSE, FALSE, 0);
3646 }
3647 gtk_box_pack_start(GTK_BOX(sl->box), display_controls, FALSE, FALSE, 0);
3648 gtk_box_pack_start(GTK_BOX(bd->blendif_box), GTK_WIDGET(sl->box), FALSE, FALSE, 0);
3649
3650 }
3651
3658 gtk_widget_set_tooltip_text(bd->channel_boost_factor_slider, _("adjust the boost factor of the channel mask"));
3659 gtk_widget_set_sensitive(bd->channel_boost_factor_slider, FALSE);
3660
3661 g_signal_connect(G_OBJECT(bd->channel_boost_factor_slider), "value-changed",
3663
3664 gtk_box_pack_start(GTK_BOX(bd->blendif_box), GTK_WIDGET(bd->channel_boost_factor_slider), FALSE, FALSE, 0);
3665
3666 g_signal_connect(G_OBJECT(bd->channel_tabs), "switch_page", G_CALLBACK(_blendop_blendif_tab_switch), bd);
3667 g_signal_connect(G_OBJECT(bd->colorpicker), "toggled", G_CALLBACK(_update_gradient_slider_pickers), module);
3668 g_signal_connect(G_OBJECT(bd->colorpicker_set_values), "toggled", G_CALLBACK(_update_gradient_slider_pickers), module);
3669
3670 bd->blendif_inited = 1;
3671 }
3672 else
3673 {
3674 gchar *module_name = dt_history_item_get_name(module);
3675 gchar *markup = g_markup_printf_escaped(
3676 _("<i>Parametric masking is disabled because the <b>%s</b> module does not provide compatible color channels.</i>"),
3677 module_name);
3678 GtkWidget *label = gtk_label_new(NULL);
3679 gtk_label_set_markup(GTK_LABEL(label), markup);
3680 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
3681 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
3682 gtk_box_pack_start(GTK_BOX(bd->blendif_box), label, FALSE, FALSE, 0);
3683 dt_free(markup);
3684 dt_free(module_name);
3685 }
3686}
3687
3688void dt_iop_gui_init_contours(GtkBox *blendw, dt_iop_module_t *module)
3689{
3691
3692 gtk_box_pack_start(blendw, bd->details_slider, FALSE, FALSE, 0);
3693 gtk_box_pack_start(blendw, bd->blur_radius_slider, FALSE, FALSE, 0);
3694 gtk_box_pack_start(blendw, bd->masks_feathering_guide_combo, FALSE, FALSE, 0);
3695 gtk_box_pack_start(blendw, bd->feathering_radius_slider, FALSE, FALSE, 0);
3696 gtk_box_pack_start(blendw, bd->brightness_slider, FALSE, FALSE, 0);
3697 gtk_box_pack_start(blendw, bd->contrast_slider, FALSE, FALSE, 0);
3698}
3699
3701{
3702 dt_iop_gui_blend_data_t *bd = module->gui ? (dt_iop_gui_blend_data_t *)module->gui->blend_data : NULL;
3703 dt_develop_blend_params_t *bp = module->blend_params;
3704
3705 if(IS_NULL_PTR(bd) || !bd->masks_support || !bd->masks_inited) return;
3706
3708
3709 /* update masks state */
3710 dt_masks_form_gui_t *form_gui = module->dev->form_gui;
3711 dt_masks_form_t *visible_form = dt_masks_get_visible_form(module->dev);
3712 const gboolean module_creation = !IS_NULL_PTR(form_gui) && form_gui->creation
3713 && form_gui->creation_module == module;
3714 const dt_masks_type_t creation_type = module_creation && !IS_NULL_PTR(visible_form)
3715 ? visible_form->type
3716 : (module_creation ? form_gui->creation_type : DT_MASKS_NONE);
3718 const gboolean has_group_shapes = grp && (grp->type & DT_MASKS_GROUP) && grp->points;
3719 if(GTK_IS_WIDGET(bd->masks_combo))
3720 {
3722 if(has_group_shapes)
3723 {
3724 char txt[512];
3725 const guint n = g_list_length(grp->points);
3726 snprintf(txt, sizeof(txt), ngettext("%d shape used", "%d shapes used", n), n);
3728 }
3729 else
3730 {
3731 dt_bauhaus_combobox_add(bd->masks_combo, _("no mask used"));
3732 }
3734 gtk_widget_queue_draw(bd->masks_combo);
3735 }
3736
3737 if(!has_group_shapes)
3738 {
3740 // reset the gui
3742 }
3743
3744 if(bd->masks_support)
3745 {
3746 if(GTK_IS_TOGGLE_BUTTON(bd->masks_edit))
3747 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_edit),
3748 !module_creation && bd->masks_shown != DT_MASKS_EDIT_OFF);
3749
3750 if(GTK_IS_TOGGLE_BUTTON(bd->masks_polarity))
3751 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_polarity),
3753 }
3754
3755 // update buttons status
3756 for(int n = 0; n < DEVELOP_MASKS_NB_SHAPES; n++)
3757 {
3758 if(!GTK_IS_TOGGLE_BUTTON(bd->masks_shapes[n])) continue;
3759
3760 if(module_creation && (creation_type & bd->masks_type[n]))
3761 {
3762 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_shapes[n]), TRUE);
3763 }
3764 else
3765 {
3766 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->masks_shapes[n]), FALSE);
3767 }
3768 }
3769
3772
3774}
3775
3776/* Mouse-wheel mapping grid.
3777 *
3778 * One radio group per row: a wheel/modifier combination edits exactly one mask property, or
3779 * nothing at all. The mapping lives in conf and is read back by the shapes through
3780 * dt_masks_scroll_get_interaction() (masks_gui.h); this grid only displays and writes it, and
3781 * holds no state of its own. Hence the re-read on "map": every module's blending panel shows
3782 * the same application-wide mapping and any of them can change it, so the grid that becomes
3783 * visible must show what is stored, not what it was built with.
3784 */
3785#define BLENDOP_SCROLL_DATA_ROW "blendop-scroll-row"
3786#define BLENDOP_SCROLL_DATA_INTERACTION "blendop-scroll-interaction"
3787
3788/* Property columns, in display order. DT_MASKS_INTERACTION_UNDEF ("nothing") is a column like
3789 * the others -- it is how a row is unmapped -- and comes last, after what it opts out of. */
3793
3794static void _blendop_masks_scroll_toggled(GtkToggleButton *button, gpointer user_data)
3795{
3796 // A radio group emits "toggled" twice per change, once for each side. Only the winner writes.
3797 if(!gtk_toggle_button_get_active(button)) return;
3798
3799 const dt_masks_scroll_modifier_t modifier
3800 = (dt_masks_scroll_modifier_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), BLENDOP_SCROLL_DATA_ROW));
3801 const dt_masks_interaction_t interaction
3802 = (dt_masks_interaction_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), BLENDOP_SCROLL_DATA_INTERACTION));
3803
3804 dt_masks_scroll_mapping_set(modifier, interaction);
3805}
3806
3807static void _blendop_masks_scroll_sync(GtkWidget *grid, gpointer user_data)
3808{
3809 GList *children = gtk_container_get_children(GTK_CONTAINER(grid));
3810 for(GList *child = children; child; child = g_list_next(child))
3811 {
3812 GtkWidget *cell = GTK_WIDGET(child->data);
3813 if(!GTK_IS_RADIO_BUTTON(cell)) continue;
3814
3815 const dt_masks_scroll_modifier_t modifier
3816 = (dt_masks_scroll_modifier_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), BLENDOP_SCROLL_DATA_ROW));
3817 const dt_masks_interaction_t interaction
3818 = (dt_masks_interaction_t)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(cell), BLENDOP_SCROLL_DATA_INTERACTION));
3819 const gboolean active = (dt_masks_scroll_mapping_get(modifier) == interaction);
3820 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cell)) == active) continue;
3821
3822 // Writing conf back from a display refresh would turn a stale panel into the authority.
3823 g_signal_handlers_block_by_func(cell, G_CALLBACK(_blendop_masks_scroll_toggled), NULL);
3824 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cell), active);
3825 g_signal_handlers_unblock_by_func(cell, G_CALLBACK(_blendop_masks_scroll_toggled), NULL);
3826 }
3827 g_list_free(children);
3828}
3829
3831{
3832 GtkWidget *grid = gtk_grid_new();
3833 gtk_grid_set_row_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
3834 gtk_grid_set_column_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
3835 /* Natural width, not filled: handed more width than it needs, GtkGrid spreads the surplus
3836 * over its columns, which pushes the cells apart and re-centres every rotated title over a
3837 * cell wider than itself. What keeps the columns tight is this plus the spanning titles
3838 * below -- not the column spacing, which is free to stay at the panel's usual gutter
3839 * (measured: 24px columns either way, only the gutters change). */
3840 gtk_widget_set_halign(grid, GTK_ALIGN_START);
3841
3842 for(size_t column = 0; column < G_N_ELEMENTS(_blendop_scroll_columns); column++)
3843 {
3844 const dt_masks_interaction_t interaction = _blendop_scroll_columns[column];
3845 const char *alias = dt_masks_interaction_alias_name(interaction);
3846 // Second word on its own line: rotated 90 degrees the lines stack sideways, so the column
3847 // stays as tall as the longest single word instead of as tall as both of them.
3848 gchar *title = alias ? g_strdup_printf("%s/%s", _(dt_masks_interaction_name(interaction)), _(alias))
3849 : g_strdup(_(dt_masks_interaction_name(interaction)));
3850 GtkWidget *header = gtk_label_new(title);
3851 dt_free(title);
3852 gtk_label_set_angle(GTK_LABEL(header), 90.0 * 0.5);
3853 gtk_label_set_justify(GTK_LABEL(header), GTK_JUSTIFY_LEFT);
3854 // Anchor the rotated title to the bottom left of its cell, where the column it names
3855 // starts, instead of letting it float in the middle of whatever width the cell ends up with.
3856 gtk_label_set_xalign(GTK_LABEL(header), 0.0f);
3857 gtk_label_set_yalign(GTK_LABEL(header), 1.0f);
3858 gtk_widget_set_halign(header, GTK_ALIGN_START);
3859 gtk_widget_set_valign(header, GTK_ALIGN_END);
3860 /* Spanning, not one cell: a label rotated 45 degrees requests the width of its slanted
3861 * bounding box (measured: 102px for "Fading/Curvature" against 24px for a radio), and a
3862 * single-cell title makes its whole column that wide. Letting each title span the columns
3863 * to its right -- the direction it leans into, whose header space is free -- constrains the
3864 * sum instead of one column, and every column falls back to the width of its radio. */
3865 gtk_grid_attach(GTK_GRID(grid), header, (int)column + 1, 0,
3866 (int)(G_N_ELEMENTS(_blendop_scroll_columns) - column) + 1, 1);
3867 }
3868
3869 /* The rightmost title has no column to lean into, so it would widen its own the same way.
3870 * This trailing column exists only to absorb that overhang; it expands so the grid hands it
3871 * whatever width the titles need, rather than spreading it over the real columns. */
3872 GtkWidget *overhang = gtk_label_new("");
3873 gtk_widget_set_hexpand(overhang, TRUE);
3874 gtk_grid_attach(GTK_GRID(grid), overhang, (int)G_N_ELEMENTS(_blendop_scroll_columns) + 1, 0, 1, 1);
3875
3876 for(int row = 0; row < DT_MASKS_SCROLL_MODIFIER_LAST; row++)
3877 {
3879 GtkWidget *row_label = gtk_label_new(_(dt_masks_scroll_modifier_name(modifier)));
3880 gtk_label_set_xalign(GTK_LABEL(row_label), 0.0f);
3881 gtk_grid_attach(GTK_GRID(grid), row_label, 0, row + 1, 1, 1);
3882
3883 GtkWidget *group = NULL;
3884 for(size_t column = 0; column < G_N_ELEMENTS(_blendop_scroll_columns); column++)
3885 {
3886 const dt_masks_interaction_t interaction = _blendop_scroll_columns[column];
3887 GtkWidget *cell = gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(group));
3888 if(IS_NULL_PTR(group)) group = cell;
3889
3890 g_object_set_data(G_OBJECT(cell), BLENDOP_SCROLL_DATA_ROW, GINT_TO_POINTER(modifier));
3891 g_object_set_data(G_OBJECT(cell), BLENDOP_SCROLL_DATA_INTERACTION, GINT_TO_POINTER(interaction));
3892
3893 const char *alias = dt_masks_interaction_alias_name(interaction);
3894 gchar *tooltip;
3895 if(interaction == DT_MASKS_INTERACTION_UNDEF)
3896 tooltip = g_strdup_printf(_("%s does nothing"), _(dt_masks_scroll_modifier_name(modifier)));
3897 else if(alias)
3898 tooltip = g_strdup_printf(_("%s edits the %s of the hovered shape, or its %s on a gradient"),
3899 _(dt_masks_scroll_modifier_name(modifier)),
3900 _(dt_masks_interaction_name(interaction)), _(alias));
3901 else
3902 tooltip = g_strdup_printf(_("%s edits the %s of the hovered shape"),
3903 _(dt_masks_scroll_modifier_name(modifier)),
3904 _(dt_masks_interaction_name(interaction)));
3905 gtk_widget_set_tooltip_text(cell, tooltip);
3907
3908 // State first, handler after: building the group already emits "toggled" for every cell
3909 // it deactivates, and those must not be mistaken for the user picking something.
3910 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cell),
3911 dt_masks_scroll_mapping_get(modifier) == interaction);
3912 g_signal_connect(cell, "toggled", G_CALLBACK(_blendop_masks_scroll_toggled), NULL);
3913
3914 gtk_grid_attach(GTK_GRID(grid), cell, (int)column + 1, row + 1, 1, 1);
3915 }
3916 }
3917
3918 g_signal_connect(grid, "map", G_CALLBACK(_blendop_masks_scroll_sync), NULL);
3919
3920 return grid;
3921}
3922
3923void dt_iop_gui_init_masks(GtkBox *blendw, dt_iop_module_t *module)
3924{
3926
3927 bd->masks_box = blendw;
3928
3929 /* create and add masks support if module supports it */
3930 if(bd->masks_support)
3931 {
3932 bd->masks_combo_ids = NULL;
3934
3935 bd->lists_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
3936
3937 GtkCellRenderer *renderer = NULL;
3938 GtkTreeSelection *selection = NULL;
3939
3940 GtkWidget *group_shapes_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
3941 bd->group_shapes_label = gtk_entry_new();
3942 gchar *group_placeholder = dt_masks_group_name_for_module(module);
3943 gtk_entry_set_placeholder_text(GTK_ENTRY(bd->group_shapes_label), group_placeholder);
3944 g_free(group_placeholder);
3945 gtk_widget_set_tooltip_text(bd->group_shapes_label, _("Edit current module's group name"));
3946 gtk_widget_set_halign(bd->group_shapes_label, GTK_ALIGN_FILL);
3947 gtk_widget_set_hexpand(bd->group_shapes_label, TRUE);
3948 g_signal_connect(bd->group_shapes_label, "activate", G_CALLBACK(_blendop_masks_group_name_activate), module);
3949 g_signal_connect(bd->group_shapes_label, "focus-out-event",
3950 G_CALLBACK(_blendop_masks_group_name_focus_out), module);
3951 gtk_box_pack_start(GTK_BOX(group_shapes_header), bd->group_shapes_label, TRUE, TRUE, 0);
3952
3953 // buttons for mask polarity and edit mode
3954 bd->masks_polarity = dt_iop_togglebutton_new_no_register(module, "blend`tools", N_("toggle polarity of drawn mask"), NULL,
3956 FALSE, 0, 0, dtgtk_cairo_paint_invert, group_shapes_header);
3958 dt_gui_add_class(bd->masks_polarity, "dt_ignore_fg_state");
3959
3960 bd->masks_edit = dt_iop_togglebutton_new_no_register(module, "blend`tools", N_("show and edit mask elements"),
3961 N_("show and edit in restricted mode"),
3962 G_CALLBACK(_blendop_masks_show_and_edit),
3963 FALSE, 0, 0, dtgtk_cairo_paint_masks_edit, group_shapes_header);
3964
3965 gtk_box_pack_start(GTK_BOX(bd->lists_box), group_shapes_header, FALSE, FALSE, 0);
3966
3967 // Current mask list
3968 bd->masks_group_treeview = gtk_tree_view_new();
3969 bd->group_shapes_store = gtk_tree_store_new(BLENDOP_MASKS_GROUP_COL_COUNT, GDK_TYPE_PIXBUF,
3970 GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_INT,
3971 G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
3972 gtk_tree_view_set_model(GTK_TREE_VIEW(bd->masks_group_treeview), GTK_TREE_MODEL(bd->group_shapes_store));
3973
3974 // Initialize mask operation icons for treeview display
3976
3977 bd->group_shapes_col = gtk_tree_view_column_new();
3978 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_group_treeview), bd->group_shapes_col);
3979
3980 renderer = gtk_cell_renderer_pixbuf_new();
3981 gtk_tree_view_column_pack_start(bd->group_shapes_col, renderer, FALSE);
3982 gtk_tree_view_column_add_attribute(bd->group_shapes_col, renderer, "pixbuf", BLENDOP_MASKS_GROUP_COL_OP_ICON);
3983
3984 renderer = gtk_cell_renderer_pixbuf_new();
3985 gtk_tree_view_column_pack_start(bd->group_shapes_col, renderer, FALSE);
3986 gtk_tree_view_column_add_attribute(bd->group_shapes_col, renderer, "pixbuf", BLENDOP_MASKS_GROUP_COL_INV_ICON);
3987
3988 renderer = gtk_cell_renderer_text_new();
3989 gtk_tree_view_column_pack_start(bd->group_shapes_col, renderer, TRUE);
3990 gtk_tree_view_column_add_attribute(bd->group_shapes_col, renderer, "text", BLENDOP_MASKS_GROUP_COL_NAME);
3991
3992 // Per-row action icons: unlink (detach from this mask) and delete (remove altogether).
3993 // Clicks are handled in _blendop_masks_group_button_pressed by matching the column.
3994 bd->group_unlink_col = gtk_tree_view_column_new();
3995 renderer = gtk_cell_renderer_pixbuf_new();
3996 g_object_set(renderer, "icon-name", "list-remove-symbolic", "stock-size", GTK_ICON_SIZE_MENU, NULL);
3997 gtk_tree_view_column_pack_start(bd->group_unlink_col, renderer, FALSE);
3998 gtk_tree_view_column_set_sizing(bd->group_unlink_col, GTK_TREE_VIEW_COLUMN_FIXED);
3999 gtk_tree_view_column_set_fixed_width(bd->group_unlink_col, DT_PIXEL_APPLY_DPI(24));
4000 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_group_treeview), bd->group_unlink_col);
4001
4002 bd->group_delete_col = gtk_tree_view_column_new();
4003 renderer = gtk_cell_renderer_pixbuf_new();
4004 g_object_set(renderer, "icon-name", "user-trash-symbolic", "stock-size", GTK_ICON_SIZE_MENU, NULL);
4005 gtk_tree_view_column_pack_start(bd->group_delete_col, renderer, FALSE);
4006 gtk_tree_view_column_set_sizing(bd->group_delete_col, GTK_TREE_VIEW_COLUMN_FIXED);
4007 gtk_tree_view_column_set_fixed_width(bd->group_delete_col, DT_PIXEL_APPLY_DPI(24));
4008 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_group_treeview), bd->group_delete_col);
4009
4010 // Keep the name column expanding so the action icons stay flush right.
4011 gtk_tree_view_column_set_expand(bd->group_shapes_col, TRUE);
4012
4013 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(bd->masks_group_treeview));
4014 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
4015 g_signal_connect(selection, "changed", G_CALLBACK(_blendop_masks_group_selection_changed), module);
4016 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(bd->masks_group_treeview), FALSE);
4017 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(bd->masks_group_treeview), TRUE);
4018 g_signal_connect(bd->masks_group_treeview, "button-press-event",
4019 G_CALLBACK(_blendop_masks_group_button_pressed), module);
4020
4021 gtk_widget_set_has_tooltip(bd->masks_group_treeview, TRUE);
4022 g_signal_connect(bd->masks_group_treeview, "query-tooltip",
4023 G_CALLBACK(_blendop_masks_group_query_tooltip), module);
4024
4026 "plugins/darkroom/masks/group_list_height", DT_UI_RESIZE_DYNAMIC);
4027 gtk_widget_set_vexpand(bd->group_shapes_sw, TRUE);
4028
4029 // Creating shapes buttons (circle, ellipse ....)
4031 if(!GTK_IS_WIDGET(bd->all_shapes_buttons)) return;
4032
4033 // Wire shapes toggle button
4034 bd->wire_shape_toggle = gtk_toggle_button_new_with_label(_("Attach shapes"));
4035 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."));
4036
4037 GtkWidget *bottom_bar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4038 gtk_box_pack_start(GTK_BOX(bottom_bar), bd->wire_shape_toggle, FALSE, FALSE, 0);
4039 gtk_box_pack_end(GTK_BOX(bottom_bar), bd->all_shapes_buttons, FALSE, FALSE, 0);
4040 gtk_box_pack_end(GTK_BOX(bd->lists_box), bottom_bar, FALSE, FALSE, 0);
4041
4042
4043 // All shapes list
4044 bd->masks_treeview = gtk_tree_view_new();
4045 bd->all_shapes_store = gtk_list_store_new(BLENDOP_MASKS_ALL_COL_COUNT, G_TYPE_BOOLEAN,
4046 G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN,
4047 G_TYPE_STRING, G_TYPE_STRING);
4048 gtk_tree_view_set_model(GTK_TREE_VIEW(bd->masks_treeview), GTK_TREE_MODEL(bd->all_shapes_store));
4049
4050 bd->all_shapes_col = gtk_tree_view_column_new();
4051 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_treeview), bd->all_shapes_col);
4052
4053 renderer = gtk_cell_renderer_toggle_new();
4054 g_object_set(renderer, "activatable", TRUE, NULL);
4055 g_signal_connect(renderer, "toggled", G_CALLBACK(_blendop_masks_all_toggled), module);
4056 gtk_tree_view_column_pack_start(bd->all_shapes_col, renderer, FALSE);
4057 gtk_tree_view_column_add_attribute(bd->all_shapes_col, renderer, "active", BLENDOP_MASKS_ALL_COL_ACTIVE);
4058 gtk_tree_view_column_add_attribute(bd->all_shapes_col, renderer, "sensitive", BLENDOP_MASKS_ALL_COL_SENSITIVE);
4059
4060 GtkTreeViewColumn *all_shapes_name_col = gtk_tree_view_column_new();
4061 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_treeview), all_shapes_name_col);
4062
4063 renderer = gtk_cell_renderer_text_new();
4064 g_object_set(renderer, "editable", TRUE, NULL);
4065 g_signal_connect(renderer, "edited", G_CALLBACK(_blendop_masks_all_name_edited), module);
4066 gtk_tree_view_column_pack_start(all_shapes_name_col, renderer, TRUE);
4067 gtk_tree_view_column_add_attribute(all_shapes_name_col, renderer, "markup", BLENDOP_MASKS_ALL_COL_MARKUP);
4068 gtk_tree_view_column_add_attribute(all_shapes_name_col, renderer, "sensitive", BLENDOP_MASKS_ALL_COL_SENSITIVE);
4069 g_object_set_data(G_OBJECT(bd->masks_treeview), "blendop-masks-name-renderer", renderer);
4070
4071 GtkTreeViewColumn *all_shapes_status_col = gtk_tree_view_column_new();
4072 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_treeview), all_shapes_status_col);
4073
4074 renderer = gtk_cell_renderer_text_new();
4075 g_object_set(renderer, "xalign", 1.0f, NULL);
4076 gtk_tree_view_column_pack_end(all_shapes_status_col, renderer, TRUE);
4077 gtk_tree_view_column_add_attribute(all_shapes_status_col, renderer, "markup",
4079 gtk_tree_view_column_add_attribute(all_shapes_status_col, renderer, "sensitive",
4081
4082 // Per-row delete icon, mirroring group_delete_col above. Clicks are handled in
4083 // _blendop_masks_all_button_pressed by matching the column.
4084 bd->all_shapes_delete_col = gtk_tree_view_column_new();
4085 renderer = gtk_cell_renderer_pixbuf_new();
4086 g_object_set(renderer, "icon-name", "user-trash-symbolic", "stock-size", GTK_ICON_SIZE_MENU, NULL);
4087 gtk_tree_view_column_pack_start(bd->all_shapes_delete_col, renderer, FALSE);
4088 gtk_tree_view_column_set_sizing(bd->all_shapes_delete_col, GTK_TREE_VIEW_COLUMN_FIXED);
4089 gtk_tree_view_column_set_fixed_width(bd->all_shapes_delete_col, DT_PIXEL_APPLY_DPI(24));
4090 gtk_tree_view_append_column(GTK_TREE_VIEW(bd->masks_treeview), bd->all_shapes_delete_col);
4091
4092 // Keep the name column expanding so the status text and trash icon stay flush right,
4093 // same as group_shapes_col above.
4094 gtk_tree_view_column_set_expand(all_shapes_name_col, TRUE);
4095
4096 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(bd->masks_treeview));
4097 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
4098 g_signal_connect(selection, "changed", G_CALLBACK(_blendop_masks_all_selection_changed), module);
4099 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(bd->masks_treeview), FALSE);
4100 g_signal_connect(bd->masks_treeview, "button-press-event",
4101 G_CALLBACK(_blendop_masks_all_button_pressed), module);
4102
4104 "plugins/darkroom/masks/all_list_height", DT_UI_RESIZE_DYNAMIC);
4105 gtk_widget_set_vexpand(bd->all_shapes_sw, TRUE);
4106
4107 // The two lists share the same slot; Edit selects which one is visible.
4108 bd->lists_stack = gtk_stack_new();
4109 gtk_stack_set_transition_type(GTK_STACK(bd->lists_stack), GTK_STACK_TRANSITION_TYPE_NONE);
4110 gtk_stack_set_homogeneous(GTK_STACK(bd->lists_stack), FALSE);
4111 gtk_stack_add_named(GTK_STACK(bd->lists_stack), bd->group_shapes_sw, "group");
4112 gtk_stack_add_named(GTK_STACK(bd->lists_stack), bd->all_shapes_sw, "all");
4113 gtk_box_pack_start(GTK_BOX(bd->lists_box), bd->lists_stack, TRUE, TRUE, 0);
4114
4115 // Default state:
4116 // - current-module tree is visible
4117 // - all-shapes tree is hidden
4118 // The toggle callback flips these two scrolled windows.
4119 g_signal_connect(bd->wire_shape_toggle, "toggled", G_CALLBACK(_blendop_masks_edit_list_toggle), module);
4120
4121 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->wire_shape_toggle), FALSE);
4122 _blendop_masks_edit_list_toggle(GTK_TOGGLE_BUTTON(bd->wire_shape_toggle), module);
4123
4124 gtk_box_pack_start(GTK_BOX(bd->masks_box), bd->lists_box, TRUE, TRUE, 0);
4125
4127 (&bd->masks_cs,
4128 "plugins/darkroom/masks/wacom",
4129 _("Brush options"),
4130 GTK_BOX(bd->masks_box), GTK_PACK_END);
4131
4132 GtkWidget *brush_smoothing = dt_bauhaus_combobox_from_conf(dt_bauhaus_get_global(), DT_GUI_MODULE(NULL), "brush_smoothing");
4133 gtk_box_pack_start(GTK_BOX(bd->masks_cs.container), brush_smoothing, TRUE, TRUE, 0);
4134
4135 GtkWidget *pressure_mapping = dt_bauhaus_combobox_from_conf(dt_bauhaus_get_global(), DT_GUI_MODULE(NULL), "pressure_sensitivity");
4136 gtk_box_pack_start(GTK_BOX(bd->masks_cs.container), pressure_mapping, TRUE, TRUE, 0);
4137
4139 (&bd->scroll_cs,
4140 "plugins/darkroom/masks/scroll_mapping",
4141 _("Mouse wheel"),
4142 GTK_BOX(bd->masks_box), GTK_PACK_END);
4143 gtk_widget_set_tooltip_text(bd->scroll_cs.expander,
4144 _("Choose which property of the hovered shape each mouse wheel "
4145 "combination edits. Shared by every mask of every module."));
4146 // Natural size, not filled: the grid must not be handed width it would spread between its
4147 // own columns (see _blendop_masks_create_scroll_grid).
4148 gtk_box_pack_start(GTK_BOX(bd->scroll_cs.container), _blendop_masks_create_scroll_grid(), FALSE, FALSE, 0);
4149
4150 bd->masks_inited = 1;
4152 }
4153 else
4154 {
4155 gchar *module_name = dt_history_item_get_name(module);
4156 gchar *markup = g_markup_printf_escaped(
4157 _("<i>Drawn masking is disabled because the <b>%s</b> module manages drawn shapes internally.</i>"),
4158 module_name);
4159 GtkWidget *label = gtk_label_new(NULL);
4160 gtk_label_set_markup(GTK_LABEL(label), markup);
4161 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
4162 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
4163 gtk_box_pack_start(GTK_BOX(bd->masks_box), label, FALSE, FALSE, 0);
4164 dt_free(markup);
4165 dt_free(module_name);
4166 }
4167}
4168
4174
4175static void _raster_combo_populate(GtkWidget *w, void *m)
4176{
4177 dt_iop_module_t *module = (dt_iop_module_t *)m;
4178 dt_iop_request_focus(module);
4179
4181
4182 raster_combo_entry_t *entry = (raster_combo_entry_t *)calloc(1, sizeof(raster_combo_entry_t));
4183 if(IS_NULL_PTR(entry)) return;
4184 dt_bauhaus_combobox_add_full(w, _("no mask used"), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, entry, free, TRUE);
4185
4186 int i = 1;
4187
4188 for(GList* iter = module->dev->iop; iter; iter = g_list_next(iter))
4189 {
4190 dt_iop_module_t *iop = (dt_iop_module_t *)iter->data;
4191 if(iop == module)
4192 break;
4193
4194 GHashTableIter masks_iter;
4195 gpointer key, value;
4196
4197 g_hash_table_iter_init(&masks_iter, iop->raster_mask.source.masks);
4198 while(g_hash_table_iter_next(&masks_iter, &key, &value))
4199 {
4200 const int id = GPOINTER_TO_INT(key);
4201 const char *modulename = (char *)value;
4202 entry = (raster_combo_entry_t *)malloc(sizeof(raster_combo_entry_t));
4203 entry->module = iop;
4204 entry->id = id;
4206 if(iop == module->raster_mask.sink.source && module->raster_mask.sink.id == id)
4208 i++;
4209 }
4210 }
4211}
4212
4214{
4216
4217 // nothing to do
4218 if(entry->module == module->raster_mask.sink.source && entry->id == module->raster_mask.sink.id)
4219 return;
4220
4221 if(module->raster_mask.sink.source)
4222 {
4223 // we no longer use this one
4224 g_hash_table_remove(module->raster_mask.sink.source->raster_mask.source.users, module);
4225 }
4226
4227 module->raster_mask.sink.source = entry->module;
4228 module->raster_mask.sink.id = entry->id;
4229
4230 if(entry->module)
4231 {
4232 // The value is the provider-local mask id. dt_iop_is_raster_mask_used()
4233 // reads it to decide which side-band mask buffers the pixelpipe must
4234 // publish. g_hash_table_add() stores the consumer pointer as both key and
4235 // value, making an existing mask visible in the GUI but impossible to
4236 // retrieve reliably from the pipeline.
4237 g_hash_table_insert(entry->module->raster_mask.source.users, module,
4238 GINT_TO_POINTER(entry->id));
4239
4240 // update blend_params!
4241 memcpy(module->blend_params->raster_mask_source, entry->module->op, sizeof(module->blend_params->raster_mask_source));
4242 module->blend_params->raster_mask_instance = entry->module->multi_priority;
4243 module->blend_params->raster_mask_id = entry->id;
4244 }
4245 else
4246 {
4247 memset(module->blend_params->raster_mask_source, 0, sizeof(module->blend_params->raster_mask_source));
4248 module->blend_params->raster_mask_instance = 0;
4249 module->blend_params->raster_mask_id = 0;
4250 }
4251
4252 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
4254}
4255
4257{
4259 dt_develop_blend_params_t *bp = module->blend_params;
4260
4261 if(IS_NULL_PTR(bd) || !bd->masks_support || !bd->raster_inited) return;
4262
4263 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->raster_polarity), bp->raster_mask_invert);
4264
4266}
4267
4268static void _raster_polarity_callback(GtkToggleButton *togglebutton, dt_iop_module_t *self)
4269{
4270 if(dt_gui_widgets_suppressed()) return;
4271
4273
4274 bp->raster_mask_invert = gtk_toggle_button_get_active(togglebutton);
4275
4276 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
4278 dt_control_queue_redraw_widget(GTK_WIDGET(togglebutton));
4279}
4280
4281void dt_iop_gui_init_raster(GtkBox *blendw, dt_iop_module_t *module)
4282{
4284
4285 bd->raster_box = blendw;
4286
4287 /* create and add raster support if module supports it (it's coupled to masks at the moment) */
4288 if(bd->masks_support)
4289 {
4290 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4291
4295 dt_bauhaus_widget_set_label(bd->raster_combo, N_("raster mask"));
4296 dt_bauhaus_combobox_add(bd->raster_combo, _("no mask used"));
4297 g_signal_connect(G_OBJECT(bd->raster_combo), "value-changed",
4298 G_CALLBACK(_raster_value_changed_callback), module);
4300 gtk_box_pack_start(GTK_BOX(hbox), bd->raster_combo, TRUE, TRUE, 0);
4301
4303 dt_gui_add_class(bd->raster_polarity, "dt_ignore_fg_state");
4304 gtk_widget_set_tooltip_text(bd->raster_polarity, _("toggle polarity of raster mask"));
4305 g_signal_connect(G_OBJECT(bd->raster_polarity), "toggled", G_CALLBACK(_raster_polarity_callback), module);
4306 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->raster_polarity), FALSE);
4307 gtk_box_pack_start(GTK_BOX(hbox), bd->raster_polarity, FALSE, FALSE, 0);
4308
4309 gtk_box_pack_start(GTK_BOX(bd->raster_box), GTK_WIDGET(hbox), FALSE, FALSE, 0);
4310
4311 bd->raster_inited = 1;
4312 }
4313}
4314
4316{
4317 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->blend_data)) return;
4319
4322
4324 if(bd->timeout_handle)
4325 g_source_remove(bd->timeout_handle);
4326
4330
4331 dt_free(module->gui->blend_data);
4332 module->gui->blend_data = NULL;
4333}
4334
4335
4337{
4338 for(const dt_develop_name_value_t *bm = dt_develop_blend_mode_names; *bm->name; bm++)
4339 {
4340 if(bm->value == mode)
4341 {
4342 dt_bauhaus_combobox_add_full(combobox, g_dpgettext2(NULL, "blendmode", bm->name), DT_BAUHAUS_COMBOBOX_ALIGN_RIGHT, GUINT_TO_POINTER(bm->value), NULL, TRUE);
4343
4344 return TRUE;
4345 }
4346 }
4347
4348 return FALSE;
4349}
4350
4351static GtkWidget *_combobox_new_from_list(dt_iop_module_t *module, const gchar *label,
4352 const dt_develop_name_value_t *list, uint32_t *field, const gchar *tooltip)
4353{
4358
4359 if(field)
4361 dt_bauhaus_widget_set_label(combo, label);
4362 gtk_widget_set_tooltip_text(combo, tooltip);
4363 for(; *list->name; list++)
4365 GUINT_TO_POINTER(list->value), NULL, TRUE);
4366
4367 return combo;
4368}
4369
4370static void _notebook_append_full_width_page(GtkWidget *notebook, GtkWidget *page, const gchar *label)
4371{
4372 GtkWidget *tab = gtk_label_new(label);
4373 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), page, tab);
4374 gtk_container_child_set(GTK_CONTAINER(notebook), page, "tab-expand", TRUE, "tab-fill", TRUE, NULL);
4375}
4376
4377static GtkWidget *_blendop_create_enable_toggle(dt_iop_module_t *module, const unsigned int mask_bit)
4378{
4379 const gboolean is_disable_toggle = mask_bit == DEVELOP_MASK_RASTER
4380 || mask_bit == DEVELOP_MASK_SHAPE
4381 || mask_bit == DEVELOP_MASK_PARAMETRIC;
4382 GtkWidget *toggle = gtk_check_button_new_with_label(is_disable_toggle ? _("Disable") : _("Enable"));
4383 g_object_set_data(G_OBJECT(toggle), "mask-bit", GUINT_TO_POINTER(mask_bit));
4384 g_signal_connect(G_OBJECT(toggle), "toggled", G_CALLBACK(_blendop_masks_mode_changed), module);
4385 return toggle;
4386}
4387
4389{
4390 if(IS_NULL_PTR(module) || !module->gui->blend_data) return;
4391
4393 if(!GTK_IS_BUTTON(bd->top_enable)) return;
4394
4395 gchar *clean_name = delete_underscore(module->name());
4396 const char *multi_name = module->multi_name[0] ? module->multi_name : "0";
4397 gchar *multi_name_dup = ((g_strcmp0(multi_name, "0") == 0) || (g_strcmp0(multi_name, "") == 0)) ? g_strdup("") : g_strdup_printf(" (%s)", multi_name);
4398 GtkWidget *child = gtk_bin_get_child(GTK_BIN(bd->top_enable));
4399 gchar *label = g_markup_printf_escaped(_("Enable in <b>%s%s</b>"), clean_name, multi_name_dup);
4400 if(GTK_IS_LABEL(child))
4401 {
4402 gtk_label_set_markup(GTK_LABEL(child), label);
4403 gtk_label_set_line_wrap(GTK_LABEL(child), TRUE);
4404 gtk_label_set_xalign(GTK_LABEL(child), 0.0f);
4405 }
4406 dt_free(label);
4407 dt_free(clean_name);
4408 dt_free(multi_name_dup);
4409}
4410
4412 gchar *help_url, GtkWidget **content)
4413{
4414 GtkWidget *page = gtk_event_box_new();
4415 if(help_url) dt_gui_add_help_link(page, help_url);
4416 *content = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4417 gtk_container_add(GTK_CONTAINER(page), *content);
4419 return page;
4420}
4421
4423 gchar *help_url,
4424 dt_iop_module_t *module, const unsigned int mask_bit,
4425 GtkWidget **toggle, GtkWidget **content)
4426{
4427 GtkWidget *page = gtk_event_box_new();
4428 if(help_url) dt_gui_add_help_link(page, help_url);
4429 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4430 gtk_container_add(GTK_CONTAINER(page), vbox);
4431
4432 GtkWidget *header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4433 *toggle = _blendop_create_enable_toggle(module, mask_bit);
4434 gtk_box_pack_start(GTK_BOX(header), *toggle, FALSE, FALSE, 0);
4435 gtk_box_pack_start(GTK_BOX(vbox), header, FALSE, FALSE, 0);
4436
4437 *content = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4438 gtk_box_pack_start(GTK_BOX(vbox), *content, TRUE, TRUE, 0);
4439
4441 return header;
4442}
4443
4444static void _blendop_toggle_button_set_active(GtkWidget *toggle, const gboolean enabled)
4445{
4446 if(GTK_IS_TOGGLE_BUTTON(toggle))
4447 {
4448 const unsigned int bit = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(toggle), "mask-bit"));
4449 const gboolean is_disable_toggle = bit == DEVELOP_MASK_RASTER
4450 || bit == DEVELOP_MASK_SHAPE
4451 || bit == DEVELOP_MASK_PARAMETRIC;
4452 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), is_disable_toggle ? !enabled : enabled);
4453 }
4454}
4455
4456static void _blendop_sync_toggle_state(GtkWidget *toggle, const gboolean available,
4457 const gboolean enabled, GtkWidget *content)
4458{
4460 if(GTK_IS_WIDGET(toggle))
4461 gtk_widget_set_sensitive(toggle, available);
4462 if(GTK_IS_WIDGET(content))
4463 gtk_widget_set_sensitive(content, available && enabled);
4464}
4465
4467{
4469
4470 if(!(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) || IS_NULL_PTR(bd)) return;
4471
4473
4474 // update color space from parameters
4476 switch(default_csp)
4477 {
4480 break;
4484 switch(module->blend_params->blend_cst)
4485 {
4489 bd->csp = module->blend_params->blend_cst;
4490 break;
4491 default:
4492 bd->csp = default_csp;
4493 break;
4494 }
4495 break;
4497 default:
4499 break;
4500 }
4501
4502 // (un)set the mask indicator
4504
4505 if(!bd->blending_box)
4506 {
4508 return;
4509 }
4510
4511 // initialization of blending modes
4512 if(bd->csp != bd->blend_modes_csp)
4513 {
4515
4516 if(bd->csp == DEVELOP_BLEND_CS_LAB
4518 || bd->csp == DEVELOP_BLEND_CS_RAW )
4519 {
4536
4537 if(bd->csp == DEVELOP_BLEND_CS_LAB)
4538 {
4548 }
4549 else if(bd->csp == DEVELOP_BLEND_CS_RGB_DISPLAY)
4550 {
4561 }
4562 }
4563 else if(bd->csp == DEVELOP_BLEND_CS_RGB_SCENE)
4564 {
4579 }
4580 bd->blend_modes_csp = bd->csp;
4581 }
4582
4583 dt_develop_blend_mode_t blend_mode = module->blend_params->blend_mode & DEVELOP_BLEND_MODE_MASK;
4584 // module->blend_params->mask_mode
4586 {
4587 // add deprecated blend mode
4588 if(!_add_blendmode_combo(bd->blend_modes_combo, blend_mode))
4589 {
4590 // should never happen: unknown blend mode
4591 dt_control_log("unknown blend mode '%d' in module '%s'", blend_mode, module->op);
4592 module->blend_params->blend_mode = DEVELOP_BLEND_NORMAL2;
4593 blend_mode = DEVELOP_BLEND_NORMAL2;
4594 }
4595
4597 }
4598
4599 gboolean blend_mode_reversed = (module->blend_params->blend_mode & DEVELOP_BLEND_REVERSE) == DEVELOP_BLEND_REVERSE;
4601
4603 const gboolean show_param = (bd->blend_modes_csp == DEVELOP_BLEND_CS_RGB_SCENE) &&
4605 if(!show_param)
4606 {
4607 module->blend_params->blend_parameter = 0.0f;
4609 }
4610 gtk_widget_set_visible(bd->blend_mode_parameter_slider, show_param);
4611
4624
4625 /* reset all alternative display modes for blendif */
4626 memset(bd->altmode, 0, sizeof(bd->altmode));
4627
4628 // force the visibility of output channels if they contain some setting
4631
4635
4636 /* sync page states from mask mode */
4637 uint32_t mask_mode = module->blend_params->mask_mode;
4638 const gboolean top_enabled = (mask_mode & DEVELOP_MASK_ENABLED) != 0;
4639 const gboolean masks_enabled = (mask_mode & DEVELOP_MASK_SHAPE) != 0;
4640 const gboolean raster_enabled = (mask_mode & DEVELOP_MASK_RASTER) != 0;
4641 const gboolean blendif_enabled = (mask_mode & DEVELOP_MASK_PARAMETRIC) != 0;
4642 const gboolean bottom_enabled = top_enabled && ((bd->masks_inited && masks_enabled)
4643 || (bd->raster_inited && raster_enabled)
4644 || (bd->blendif_inited && blendif_enabled));
4645
4648
4649 if(GTK_IS_WIDGET(bd->blending_box))
4650 {
4651 gtk_widget_set_sensitive(bd->blending_box, top_enabled);
4652 // Note: the showmask toggle is outside the blending box.
4653 gtk_widget_set_sensitive(bd->showmask, top_enabled);
4654 }
4655
4659
4660 // The Contours page has no enable/disable toggle of its own (unlike the three pages above),
4661 // so unlike them it never gets its own explicit gtk_widget_set_sensitive() call and relies
4662 // entirely on inherited state propagated from bd->blending_box. Give it the same explicit
4663 // sync so its tab content doesn't get stuck rendering with a stale insensitive (dimmed) style.
4664 // It refines whatever mask raster/drawn/parametric produces, so it has nothing to act on
4665 // unless at least one of those three is actually enabled.
4666 if(GTK_IS_WIDGET(bd->contours_content))
4667 gtk_widget_set_sensitive(bd->contours_content, bottom_enabled);
4668
4669 if(bd->blendif_inited && !IS_NULL_PTR(bd->channel))
4670 {
4671 /* The parametric page may have made the whole content insensitive before
4672 * `_blendop_blendif_update_tab()` restored the channel-local state.
4673 * Reapply the final contract after the page toggle is synchronized:
4674 * boost factor is editable only for an enabled parametric mask and a
4675 * channel whose descriptor explicitly supports boosting. */
4676 gtk_widget_set_sensitive(
4678 blendif_enabled && bd->channel[bd->tab].boost_factor_enabled);
4679 }
4680
4681 if(bd->blendif_inited && blendif_enabled)
4682 {
4683 gtk_widget_hide(GTK_WIDGET(bd->masks_invert_combo));
4684 gtk_widget_show(GTK_WIDGET(bd->masks_combine_combo));
4685 }
4686 else
4687 {
4688 gtk_widget_show(GTK_WIDGET(bd->masks_invert_combo));
4689 gtk_widget_hide(GTK_WIDGET(bd->masks_combine_combo));
4690 }
4691
4692 /*
4693 * if this iop is operating in raw space, it has only 1 channel per pixel,
4694 * thus there is no alpha channel where we would normally store mask
4695 * that would get displayed if following button have been pressed.
4696 */
4697 if(module->blend_colorspace(module, NULL, NULL) == IOP_CS_RAW)
4698 {
4699 module->request_mask_display = DT_DEV_PIXELPIPE_DISPLAY_NONE;
4702 gtk_widget_hide(GTK_WIDGET(bd->showmask));
4703
4704 // disable also guided filters on RAW based color space
4705 gtk_widget_set_sensitive(bd->masks_feathering_guide_combo, FALSE);
4706 gtk_widget_hide(bd->masks_feathering_guide_combo);
4707 gtk_widget_set_sensitive(bd->feathering_radius_slider, FALSE);
4708 gtk_widget_hide(bd->feathering_radius_slider);
4709 gtk_widget_set_sensitive(bd->brightness_slider, FALSE);
4710 gtk_widget_hide(bd->brightness_slider);
4711 gtk_widget_set_sensitive(bd->contrast_slider, FALSE);
4712 gtk_widget_hide(bd->contrast_slider);
4713 gtk_widget_set_sensitive(bd->details_slider, FALSE);
4714 gtk_widget_hide(bd->details_slider);
4715 }
4716 else
4717 {
4718 gtk_widget_show(GTK_WIDGET(bd->showmask));
4719 gtk_widget_set_sensitive(bd->masks_feathering_guide_combo, TRUE);
4720 gtk_widget_show(bd->masks_feathering_guide_combo);
4721 gtk_widget_set_sensitive(bd->feathering_radius_slider, TRUE);
4722 gtk_widget_show(bd->feathering_radius_slider);
4723 gtk_widget_set_sensitive(bd->brightness_slider, TRUE);
4724 gtk_widget_show(bd->brightness_slider);
4725 gtk_widget_set_sensitive(bd->contrast_slider, TRUE);
4726 gtk_widget_show(bd->contrast_slider);
4727 /* show details slider only if it was used in an old edit (non-zero) */
4728 const gboolean show_details = (module->blend_params->details != 0.0f);
4729 gtk_widget_set_visible(bd->details_slider, show_details);
4730 }
4731
4732 if(!bottom_enabled)
4733 {
4734 module->request_mask_display = DT_DEV_PIXELPIPE_DISPLAY_NONE;
4737 }
4738
4739 if(bd->masks_inited && !masks_enabled)
4740 {
4743 }
4744
4745 if(bd->masks_support && !masks_enabled)
4746 {
4748 }
4749
4750 if(bd->blendif_inited && !blendif_enabled)
4751 {
4753 }
4754
4756
4757 // Re-evaluate header state after notebook toggle synchronization.
4759
4761}
4762
4764{
4765 // FIXME : there are more than 3 functions getting clever about how to setup module->request_mask_display depending on user input.
4766 // These should all use an uniform setter function.
4767 //
4768 // The lack of setter implies we don't guaranty that only 1 module can request mask display at a time.
4769 // The consequence is pipeline needs to check if module->request_mask_display AND module == dev->gui_module,
4770 // but the global pipe->mask_display is set from *_blend_process() at runtime, so it's a pipe property
4771 // that changes over the pipe lifecycle.
4772 //
4773 // This is a self-feeding loop of madness because it ties the pipeline to GUI states
4774 // (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),
4775 // and because the pipeline is executed recursively from the end, but pipe->mask_display is set in the middle,
4776 // when it reaches the process() method of the module capturing mask preview, so you don't have this info when
4777 // planning for pipeline execution.
4778 // And you need to plan for mask preview ahead in pipe because mask preview needs to work
4779 // without using the pixelpipe cache, at least between the module requiring mask preview and gamma.c, which will actually render
4780 // the preview at the far end of the pipe.
4781 // So the not-so-clever workaround inherited from darktable was to flush all cache lines when requesting mask preview,
4782 // which flushed lines that could be reused later and were only temporarily not needed.
4783
4784 if(dt_gui_widgets_suppressed()) return;
4785 if(IS_NULL_PTR(module)) return;
4786
4787 const int has_mask_display = module->request_mask_display & (DT_DEV_PIXELPIPE_DISPLAY_MASK | DT_DEV_PIXELPIPE_DISPLAY_CHANNEL);
4788
4789 if((module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && module->gui->blend_data)
4790 {
4792 if(bd->showmask)
4794 module->request_mask_display = DT_DEV_PIXELPIPE_DISPLAY_NONE;
4797
4798 // (re)set the header mask indicator too
4799 if(bd->masks_support && bd->masks_edit)
4800 {
4801 // unselect all tools
4804
4806 }
4807
4810 if(bd->timeout_handle)
4811 {
4812 // purge any remaining timeout handlers
4813 g_source_remove(bd->timeout_handle);
4814 bd->timeout_handle = 0;
4815 }
4817
4818 // reprocess main center image if needed
4819 if (has_mask_display)
4821 }
4822}
4823
4825{
4826 // reached from dt_iop_load_default_params() -- the module-defaults path, which runs
4827 // headless too; every other entry in this file exists only under a live GUI
4828 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->gui)) return;
4829 dt_iop_gui_blend_data_t *bd = module->gui->blend_data;
4830 if(IS_NULL_PTR(bd) || !bd->blendif_support || !bd->blendif_inited) return;
4832}
4833
4835{
4836 if(IS_NULL_PTR(module) || !module->gui->blend_data) return;
4837
4839 if(IS_NULL_PTR(bd->blending_box)) return;
4841 if(bd->timeout_handle)
4842 {
4843 g_source_remove(bd->timeout_handle);
4844 bd->timeout_handle = 0;
4845 }
4848
4849 if(bd->masks_ic_inverse) g_object_unref(bd->masks_ic_inverse);
4850 if(bd->masks_ic_union) g_object_unref(bd->masks_ic_union);
4851 if(bd->masks_ic_intersection) g_object_unref(bd->masks_ic_intersection);
4852 if(bd->masks_ic_difference) g_object_unref(bd->masks_ic_difference);
4853 if(bd->masks_ic_exclusion) g_object_unref(bd->masks_ic_exclusion);
4854 if(bd->group_shapes_store) g_object_unref(bd->group_shapes_store);
4855 if(bd->all_shapes_store) g_object_unref(bd->all_shapes_store);
4856
4857 gtk_widget_destroy(bd->blending_box);
4858
4859 bd->blending_box = NULL;
4860 bd->blending_notebook = NULL;
4861 bd->top_enable = NULL;
4862 bd->masks_enable = NULL;
4863 bd->raster_enable = NULL;
4864 bd->blendif_enable = NULL;
4865 bd->blending_box = NULL;
4866 bd->masks_content = NULL;
4867 bd->raster_content = NULL;
4868 bd->blendif_content = NULL;
4869 bd->contours_content = NULL;
4870 bd->blendif_box = NULL;
4871 bd->masks_box = NULL;
4872 bd->raster_box = NULL;
4873 bd->colorpicker = NULL;
4874 bd->colorpicker_set_values = NULL;
4875 memset(bd->filter, 0, sizeof(bd->filter));
4876 bd->showmask = NULL;
4877 bd->masks_combine_combo = NULL;
4878 bd->blend_modes_combo = NULL;
4879 bd->blend_modes_blend_order = NULL;
4880 bd->blend_mode_parameter_slider = NULL;
4881 bd->masks_invert_combo = NULL;
4882 bd->opacity_slider = NULL;
4884 bd->feathering_radius_slider = NULL;
4885 bd->blur_radius_slider = NULL;
4886 bd->contrast_slider = NULL;
4887 bd->brightness_slider = NULL;
4888 bd->channel_boost_factor_slider = NULL;
4889 bd->details_slider = NULL;
4890 bd->masks_combo = NULL;
4891 memset(bd->masks_shapes, 0, sizeof(bd->masks_shapes));
4892 bd->masks_edit = NULL;
4893 bd->group_shapes_label = NULL;
4894 bd->masks_polarity = NULL;
4895 bd->wire_shape_toggle = NULL;
4896 bd->masks_treeview = NULL;
4897 bd->masks_group_treeview = NULL;
4898 bd->group_shapes_store = NULL;
4899 bd->group_shapes_col = NULL;
4900 bd->group_unlink_col = NULL;
4901 bd->group_delete_col = NULL;
4902 bd->all_shapes_store = NULL;
4903 bd->group_shapes_sw = NULL;
4904 bd->all_shapes_col = NULL;
4905 bd->all_shapes_delete_col = NULL;
4906 bd->all_shapes_sw = NULL;
4907 bd->lists_stack = NULL;
4908 bd->all_shapes_buttons = NULL;
4909 bd->lists_box = NULL;
4910 bd->masks_ic_inverse = NULL;
4911 bd->masks_ic_union = NULL;
4912 bd->masks_ic_intersection = NULL;
4913 bd->masks_ic_difference = NULL;
4914 bd->masks_ic_exclusion = NULL;
4915 bd->raster_combo = NULL;
4916 bd->raster_polarity = NULL;
4918 memset(bd->picker_set_values_box, 0, sizeof(bd->picker_set_values_box));
4920 bd->channel_tabs = NULL;
4921 bd->blendif_inited = 0;
4922 bd->masks_inited = 0;
4923 bd->raster_inited = 0;
4926 module->gui->fusion_slider = NULL;
4927}
4928
4930{
4931 if(IS_NULL_PTR(container) || !GTK_IS_WIDGET(container)) return;
4932 if(!(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)) return;
4933 if(IS_NULL_PTR(module->gui) || IS_NULL_PTR(module->gui->blend_data)) return;
4934
4936 if(bd->blending_box) return;
4937
4939
4940 /* NOTE: we attach 2 modules to the parent container:
4941 1. the on/off global switch,
4942 2. the box containing all other options (recursively nested).
4943 This is just a trick so we can hide/insensitize the whole content
4944 in one shot, on main disabling, except for the main switch.
4945 */
4946
4947 /* Global ON/OFF switch + show/hide toggle */
4948 GtkWidget *display_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
4949 dt_gui_add_help_link(display_box, dt_get_help_url("masks_blending"));
4950 gtk_widget_set_name(display_box, "blending-main-switch");
4951
4954 gtk_box_pack_start(GTK_BOX(display_box), bd->top_enable, FALSE, FALSE, 0);
4955
4956 bd->showmask = dt_iop_togglebutton_new_no_register(module, "blend`tools", N_("display mask and/or color channel"), NULL,
4958 FALSE, 0, 0, dtgtk_cairo_paint_showmask, display_box);
4959 gtk_widget_set_tooltip_text(bd->showmask, _("display mask and/or color channel. ctrl+click to display mask, "
4960 "shift+click to display channel. hover over parametric mask slider to "
4961 "select channel for display"));
4962
4963 gtk_box_pack_start(GTK_BOX(container), display_box, FALSE, FALSE, 0);
4964 gtk_widget_show_all(display_box);
4965
4966 /* Main widget container */
4967 bd->blending_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
4968 gtk_box_pack_start(GTK_BOX(container), bd->blending_box, TRUE, TRUE, 0);
4969 gtk_widget_set_name(bd->blending_box, "blending-box");
4970
4971 /* Blend mode */
4976 dt_bauhaus_widget_set_label(bd->blend_modes_combo, N_("Blend mode"));
4977 g_signal_connect(G_OBJECT(bd->blend_modes_combo), "value-changed",
4978 G_CALLBACK(_blendop_blend_mode_callback), bd);
4979 dt_gui_add_help_link(GTK_WIDGET(bd->blend_modes_combo), dt_get_help_url("masks_blending_op"));
4980 gtk_box_pack_start(GTK_BOX(bd->blending_box), bd->blend_modes_combo, TRUE, TRUE, 0);
4981
4982 /* Compositing combobox: choose compositing order */
4986 GINT_TO_POINTER(0), NULL, TRUE);
4988 GINT_TO_POINTER(1), NULL, TRUE);
4991 gtk_widget_set_tooltip_text(bd->blend_modes_blend_order, _("choose compositing order: which layer is placed on top"));
4992 g_signal_connect(G_OBJECT(bd->blend_modes_blend_order), "value-changed",
4993 G_CALLBACK(_blendop_compositing_changed), bd);
4994 gtk_box_pack_start(GTK_BOX(bd->blending_box), bd->blend_modes_blend_order, TRUE, TRUE, 0);
4995
4996 /* Optional fulcrum parameter for blend modes supporting it */
5005 gtk_widget_set_tooltip_text(bd->blend_mode_parameter_slider, _("adjust the fulcrum used by some blending"
5006 " operations"));
5007 g_object_set_data(G_OBJECT(bd->blend_mode_parameter_slider), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5008 gtk_widget_set_visible(bd->blend_mode_parameter_slider, FALSE);
5009 gtk_box_pack_start(GTK_BOX(bd->blending_box), bd->blend_mode_parameter_slider, TRUE, TRUE, 0);
5010
5011 /* mask combine control: how to combine drawn/parametric masks */
5012 bd->masks_combine_combo = _combobox_new_from_list(module, _("Combine drawn & parametric"), dt_develop_combine_masks_names, NULL,
5013 _("how to combine drawn and parametric masks"));
5014 g_signal_connect(G_OBJECT(bd->masks_combine_combo), "value-changed",
5015 G_CALLBACK(_blendop_masks_combine_callback), bd);
5016 dt_gui_add_help_link(GTK_WIDGET(bd->masks_combine_combo), dt_get_help_url("masks_combined"));
5017 gtk_box_pack_start(GTK_BOX(bd->blending_box), GTK_WIDGET(bd->masks_combine_combo), TRUE, TRUE, 0);
5018
5019 /* mask invert control: single checkbox to invert masks */
5020 bd->masks_invert_combo = gtk_check_button_new_with_label(_("Invert mask"));
5021 gtk_widget_set_tooltip_text(bd->masks_invert_combo, _("invert the resulting mask (apply mask in inverted mode)"));
5022 g_signal_connect(G_OBJECT(bd->masks_invert_combo), "toggled",
5023 G_CALLBACK(_blendop_masks_invert_toggled), bd);
5024 gtk_box_pack_start(GTK_BOX(bd->blending_box), GTK_WIDGET(bd->masks_invert_combo), TRUE, TRUE, 0);
5025
5026 /* Global mask output opacity */
5032 dt_bauhaus_widget_set_label(bd->opacity_slider, N_("opacity"));
5034 module->gui->fusion_slider = bd->opacity_slider;
5035 gtk_widget_set_tooltip_text(bd->opacity_slider, _("set the opacity of the blending"));
5036 g_object_set_data(G_OBJECT(bd->opacity_slider), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5037 gtk_box_pack_start(GTK_BOX(bd->blending_box), bd->opacity_slider, TRUE, TRUE, 0);
5038
5043 dt_bauhaus_widget_set_label(bd->details_slider, N_("details threshold"));
5045 gtk_widget_set_tooltip_text(bd->details_slider, _("adjust the threshold for the details mask, "
5046 "\npositive values selects areas with strong details, "
5047 "\nnegative values select flat areas"));
5048 g_signal_connect(G_OBJECT(bd->details_slider), "value-changed", G_CALLBACK(_blendop_blendif_details_callback), bd);
5049 gtk_widget_hide(bd->details_slider);
5050
5056 dt_bauhaus_widget_set_label(bd->blur_radius_slider, N_("blurring radius"));
5058 gtk_widget_set_tooltip_text(bd->blur_radius_slider, _("radius for gaussian blur of blend mask"));
5059 g_object_set_data(G_OBJECT(bd->blur_radius_slider), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5060
5063 _("choose to guide mask by input or output image and"
5064 "\nchoose to apply feathering before or after mask blur"));
5065 g_object_set_data(G_OBJECT(bd->masks_feathering_guide_combo), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5066
5072 dt_bauhaus_widget_set_label(bd->feathering_radius_slider, N_("feathering radius"));
5074 gtk_widget_set_tooltip_text(bd->feathering_radius_slider, _("spatial radius of feathering"));
5075 g_object_set_data(G_OBJECT(bd->feathering_radius_slider), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5076
5082 dt_bauhaus_widget_set_label(bd->brightness_slider, N_("Feathering mask opacity"));
5084 gtk_widget_set_tooltip_text(bd->brightness_slider, _("shifts and tilts the tone curve of the blend mask to adjust its "
5085 "brightness without affecting fully transparent/fully opaque "
5086 "regions"));
5087 g_object_set_data(G_OBJECT(bd->brightness_slider), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5088
5094 dt_bauhaus_widget_set_label(bd->contrast_slider, N_("Feathering mask contrast"));
5096 gtk_widget_set_tooltip_text(bd->contrast_slider, _("gives the tone curve of the blend mask an s-like shape to "
5097 "adjust its contrast"));
5098 g_object_set_data(G_OBJECT(bd->contrast_slider), "dt-blendop-header-update", GINT_TO_POINTER(TRUE));
5099
5100 /* separator between global blending options and the masking sub-modes notebook */
5101 GtkWidget *blend_separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
5102 gtk_widget_set_name(blend_separator, "blending-separator");
5103 gtk_box_pack_start(GTK_BOX(bd->blending_box), blend_separator, FALSE, FALSE, 0);
5104
5105 GtkWidget *presets_button = dtgtk_button_new(dtgtk_cairo_paint_presets, 0, NULL);
5106 gtk_widget_set_tooltip_text(presets_button, _("blending options"));
5107 if(bd->blendif_support)
5108 g_signal_connect(G_OBJECT(presets_button), "button-press-event", G_CALLBACK(_blendif_options_callback), module);
5109 else
5110 gtk_widget_set_sensitive(GTK_WIDGET(presets_button), FALSE);
5111
5112 /* Blending submodes notebook */
5113 bd->blending_notebook = gtk_notebook_new();
5114 gtk_notebook_set_scrollable(GTK_NOTEBOOK(bd->blending_notebook), TRUE);
5115 gtk_notebook_set_action_widget(GTK_NOTEBOOK(bd->blending_notebook), presets_button, GTK_PACK_END);
5116 gtk_widget_show(presets_button);
5117 gtk_box_pack_start(GTK_BOX(bd->blending_box), bd->blending_notebook, TRUE, TRUE, 0);
5118
5119 /* Raster page */
5120 _blendop_create_toggle_page(bd->blending_notebook, _("Raster"), dt_get_help_url("masks_raster"),
5122 dt_iop_gui_init_raster(GTK_BOX(bd->raster_content), module);
5123
5124 /* Drawn page */
5125 _blendop_create_toggle_page(bd->blending_notebook, _("Drawn"), dt_get_help_url("masks_drawn"),
5126 module, DEVELOP_MASK_SHAPE, &bd->masks_enable, &bd->masks_content);
5127 dt_iop_gui_init_masks(GTK_BOX(bd->masks_content), module);
5128
5129 /* Parametric page */
5130 GtkWidget *blendif_header = _blendop_create_toggle_page(bd->blending_notebook, _("Parametric"),
5131 dt_get_help_url("masks_parametric"),
5133 &bd->blendif_content);
5134 dt_iop_gui_init_blendif(GTK_BOX(bd->blendif_content), module, blendif_header);
5135
5136 /* Contours page */
5137 _blendop_create_notebook_page(bd->blending_notebook, _("Contours"), dt_get_help_url("masks_refinement"), &bd->contours_content);
5138 dt_iop_gui_init_contours(GTK_BOX(bd->contours_content), module);
5139
5140 /* Init all GUI states from module params */
5141 const unsigned int mask_mode = module->blend_params->mask_mode;
5146
5147 gtk_widget_show_all(GTK_WIDGET(bd->blending_box));
5148 // show_all overrides the visibility the collapsible section set at init; re-apply config state.
5149 if(bd->masks_inited)
5150 {
5153 }
5154 gtk_widget_set_sensitive(bd->blending_box, (mask_mode & DEVELOP_MASK_ENABLED) != 0);
5155
5156 g_signal_connect(G_OBJECT(bd->blending_notebook), "switch_page", G_CALLBACK(_blendop_blending_notebook_switch), bd);
5157 const int page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(bd->blending_notebook));
5158 if(page_count > 0)
5159 {
5160 // Restore the last-used tab after the notebook has been fully realized.
5162 page = CLAMP(page, 0, page_count - 1);
5163 gtk_notebook_set_current_page(GTK_NOTEBOOK(bd->blending_notebook), page);
5164 }
5165
5167}
5168
5170{
5171 if(!(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) || module->gui->blend_data) return;
5172
5173 module->gui->blend_data = g_malloc0(sizeof(dt_iop_gui_blend_data_t));
5175
5176 bd->module = module;
5180
5181 const dt_iop_colorspace_type_t cst = module->blend_colorspace(module, NULL, NULL);
5183 bd->masks_support = !(module->flags() & IOP_FLAGS_NO_MASKS);
5184
5185 dt_pthread_mutex_init(&bd->lock, NULL);
5188
5189 if(bd->masks_support)
5190 {
5192 G_CALLBACK(_blendop_masks_handler_callback), module);
5193 }
5194}
5195
5196// clang-format off
5197// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
5198// vim: shiftwidth=2 expandtab tabstop=2 cindent
5199// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
5200// 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:1997
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3280
gboolean dt_bauhaus_combobox_set_from_value(GtkWidget *widget, int value)
Definition bauhaus.c:2119
void dt_bauhaus_widget_set_field(GtkWidget *widget, gpointer field, dt_introspection_type_t field_type)
Definition bauhaus.c:1562
void dt_bauhaus_disable_module_list(GtkWidget *widget)
Definition bauhaus.c:3740
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:3746
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:1846
gpointer dt_bauhaus_combobox_get_data(GtkWidget *widget)
Definition bauhaus.c:1987
void dt_bauhaus_combobox_add_populate_fct(GtkWidget *widget, void(*fct)(GtkWidget *w, void *module))
Definition bauhaus.c:1810
void dt_bauhaus_disable_accels(GtkWidget *widget)
Definition bauhaus.c:3734
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
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:1632
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1698
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1824
@ 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:2362
const dt_develop_name_value_t dt_develop_blend_mode_names[]
Definition blend.c:2298
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:2369
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:3923
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:5169
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:3700
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:4281
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:4377
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 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_options_callback(GtkButton *button, GdkEventButton *event, dt_iop_module_t *module)
Definition blend_gui.c:2961
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:3794
_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:3527
static void _blendop_sync_toggle_state(GtkWidget *toggle, const gboolean available, const gboolean enabled, GtkWidget *content)
Definition blend_gui.c:4456
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:3156
static gboolean _blendop_blendif_enter(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *module)
Definition blend_gui.c:3204
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:4351
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:4422
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:3785
void dt_iop_gui_init_blending_body(GtkWidget *container, dt_iop_module_t *module)
Definition blend_gui.c:4929
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:4411
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:3807
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:3059
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:4268
const char * slider_tooltip[]
Definition blend_gui.c:3431
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:3128
static gboolean _blendop_blendif_leave_delayed(gpointer data)
Definition blend_gui.c:3271
#define COLORSTOPS(gradient)
Definition blend_gui.c:3355
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:3317
const dt_iop_gui_blendif_channel_t rgb_channels[]
Definition blend_gui.c:3378
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:3334
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:4213
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:3786
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:3358
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:4466
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:3405
static gboolean _add_blendmode_combo(GtkWidget *combobox, dt_develop_blend_mode_t mode)
Definition blend_gui.c:4336
void dt_iop_gui_blending_lose_focus(dt_iop_module_t *module)
Definition blend_gui.c:4763
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:3791
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:3011
static void _raster_combo_populate(GtkWidget *w, void *m)
Definition blend_gui.c:4175
void dt_iop_gui_cleanup_blending_body(dt_iop_module_t *module)
Definition blend_gui.c:4834
static GtkWidget * _blendop_masks_create_scroll_grid(void)
Definition blend_gui.c:3830
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:4388
void dt_iop_gui_update_raster(dt_iop_module_t *module)
Definition blend_gui.c:4256
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:4370
void dt_iop_gui_cleanup_blending(dt_iop_module_t *module)
Definition blend_gui.c:4315
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:4824
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:3688
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:4444
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:3438
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:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
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:969
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:981
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
#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:1355
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1645
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:310
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:1669
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
#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:1785
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:1808
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:851
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:1196
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:909
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:81
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:42
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:386
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_MASK_CHANGED
Definition signal.h:315
@ DT_SIGNAL_DEVELOP_MASKS_GUI_CHANGED
Definition signal.h:323
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
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:4170
dt_iop_module_t *int id
Definition blend_gui.c:4172
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