1 /*
2 This file is part of darktable,
3 Copyright (C) 2018-2021 darktable developers.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "gui/color_picker_proxy.h"
20 #include "bauhaus/bauhaus.h"
21 #include "libs/lib.h"
22 #include "control/control.h"
23 #include "gui/gtk.h"
24 #include "develop/blend.h"
25
26 typedef struct dt_iop_color_picker_t
27 {
28 dt_iop_module_t *module;
29 dt_iop_color_picker_kind_t kind;
30 /** requested colorspace for the color picker, valid options are:
31 * iop_cs_NONE: module colorspace
32 * iop_cs_LCh: for Lab modules
33 * iop_cs_HSL: for RGB modules
34 */
35 dt_iop_colorspace_type_t picker_cst;
36 /** used to avoid recursion when a parameter is modified in the apply() */
37 GtkWidget *colorpick;
38 float pick_pos[2]; // last picker positions (max 9 picker per module)
39 float pick_box[4]; // last picker areas (max 9 picker per module)
40 } dt_iop_color_picker_t;
41
_iop_record_point_area(dt_iop_color_picker_t * self)42 static gboolean _iop_record_point_area(dt_iop_color_picker_t *self)
43 {
44 gboolean selection_changed = FALSE;
45
46 if(self && self->module)
47 {
48 for(int k = 0; k < 2; k++)
49 {
50 if(self->pick_pos[k] != self->module->color_picker_point[k])
51 {
52 self->pick_pos[k] = self->module->color_picker_point[k];
53 selection_changed = TRUE;
54 }
55 }
56 for(int k = 0; k < 4; k++)
57 {
58 if (self->pick_box[k] != self->module->color_picker_box[k])
59 {
60 self->pick_box[k] = self->module->color_picker_box[k];
61 selection_changed = TRUE;
62 }
63 }
64 }
65
66 return selection_changed;
67 }
68
_iop_get_point(dt_iop_color_picker_t * self,float * pos)69 static void _iop_get_point(dt_iop_color_picker_t *self, float *pos)
70 {
71 pos[0] = pos[1] = 0.5f;
72
73 if(!isnan(self->pick_pos[0]) && !isnan(self->pick_pos[1]))
74 {
75 pos[0] = self->pick_pos[0];
76 pos[1] = self->pick_pos[1];
77 }
78 }
79
_iop_get_area(dt_iop_color_picker_t * self,float * box)80 static void _iop_get_area(dt_iop_color_picker_t *self, float *box)
81 {
82 if(!isnan(self->pick_box[0]) && !isnan(self->pick_box[1]))
83 {
84 for(int k = 0; k < 4; k++) box[k] = self->pick_box[k];
85 }
86 else
87 {
88 const float size = 0.99f;
89
90 box[0] = box[1] = 1.0f - size;
91 box[2] = box[3] = size;
92 }
93 }
94
_iop_color_picker_apply(dt_iop_module_t * module,dt_dev_pixelpipe_iop_t * piece)95 static void _iop_color_picker_apply(dt_iop_module_t *module, dt_dev_pixelpipe_iop_t *piece)
96 {
97 if(_iop_record_point_area(module->picker))
98 {
99 if(!module->blend_data || !blend_color_picker_apply(module, module->picker->colorpick, piece))
100 {
101 if(module->color_picker_apply)
102 module->color_picker_apply(module, module->picker->colorpick, piece);
103 }
104 }
105 }
106
_iop_color_picker_reset(dt_iop_color_picker_t * picker)107 static void _iop_color_picker_reset(dt_iop_color_picker_t *picker)
108 {
109 if(picker)
110 {
111 ++darktable.gui->reset;
112
113 if(DTGTK_IS_TOGGLEBUTTON(picker->colorpick))
114 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(picker->colorpick), FALSE);
115 else
116 dt_bauhaus_widget_set_quad_active(picker->colorpick, FALSE);
117
118 --darktable.gui->reset;
119 }
120 }
121
dt_iop_color_picker_reset(dt_iop_module_t * module,gboolean keep)122 void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
123 {
124 if(module && module->picker)
125 {
126 if(!keep || (strcmp(gtk_widget_get_name(module->picker->colorpick), "keep-active") != 0))
127 {
128 _iop_color_picker_reset(module->picker);
129 module->picker = NULL;
130 module->request_color_pick = DT_REQUEST_COLORPICK_OFF;
131 }
132 }
133 }
134
_iop_init_picker(dt_iop_color_picker_t * picker,dt_iop_module_t * module,dt_iop_color_picker_kind_t kind,GtkWidget * button)135 static void _iop_init_picker(dt_iop_color_picker_t *picker, dt_iop_module_t *module,
136 dt_iop_color_picker_kind_t kind, GtkWidget *button)
137 {
138 picker->module = module;
139 picker->kind = kind;
140 picker->picker_cst = module ? module->default_colorspace(module, NULL, NULL) : iop_cs_NONE;
141 picker->colorpick = button;
142
143 for(int j = 0; j<2; j++) picker->pick_pos[j] = NAN;
144 for(int j = 0; j < 4; j++) picker->pick_box[j] = NAN;
145
146 _iop_color_picker_reset(picker);
147 }
148
_iop_color_picker_callback_button_press(GtkWidget * button,GdkEventButton * e,dt_iop_color_picker_t * self)149 static gboolean _iop_color_picker_callback_button_press(GtkWidget *button, GdkEventButton *e, dt_iop_color_picker_t *self)
150 {
151 dt_iop_module_t *module = self->module ? self->module : dt_iop_get_colorout_module();
152
153 if(!module || darktable.gui->reset) return FALSE;
154
155 // set module active if not yet the case
156 if(module->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->off), TRUE);
157
158 const GdkModifierType state = e != NULL ? e->state : dt_key_modifier_state();
159 const gboolean ctrl_key_pressed = dt_modifier_is(state, GDK_CONTROL_MASK);
160 dt_iop_color_picker_kind_t kind = self->kind;
161
162 _iop_color_picker_reset(module->picker);
163
164 if (module->picker != self || (ctrl_key_pressed && kind == DT_COLOR_PICKER_POINT_AREA))
165 {
166 module->picker = self;
167
168 ++darktable.gui->reset;
169
170 if(DTGTK_IS_TOGGLEBUTTON(self->colorpick))
171 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->colorpick), TRUE);
172 else
173 dt_bauhaus_widget_set_quad_active(self->colorpick, TRUE);
174
175 --darktable.gui->reset;
176
177 module->request_color_pick = DT_REQUEST_COLORPICK_MODULE;
178
179 if(kind == DT_COLOR_PICKER_POINT_AREA)
180 {
181 kind = ctrl_key_pressed ? DT_COLOR_PICKER_AREA : DT_COLOR_PICKER_POINT;
182 }
183 if(kind == DT_COLOR_PICKER_AREA)
184 {
185 float box[4];
186 _iop_get_area(self, box);
187 dt_lib_colorpicker_set_box_area(darktable.lib, box);
188 self->pick_pos[0] = NAN; // trigger difference on first apply
189 }
190 else
191 {
192 float pos[2];
193 _iop_get_point(self, pos);
194 dt_lib_colorpicker_set_point(darktable.lib, pos[0], pos[1]);
195 self->pick_box[0] = NAN; // trigger difference on first apply
196 }
197
198 module->dev->preview_status = DT_DEV_PIXELPIPE_DIRTY;
199 dt_iop_request_focus(module);
200 }
201 else
202 {
203 module->picker = NULL;
204 module->request_color_pick = DT_REQUEST_COLORPICK_OFF;
205 }
206
207 dt_control_queue_redraw();
208
209 return TRUE;
210 }
211
_iop_color_picker_callback(GtkWidget * button,dt_iop_color_picker_t * self)212 static void _iop_color_picker_callback(GtkWidget *button, dt_iop_color_picker_t *self)
213 {
214 _iop_color_picker_callback_button_press(button, NULL, self);
215 }
216
dt_iop_color_picker_set_cst(dt_iop_module_t * module,const dt_iop_colorspace_type_t picker_cst)217 void dt_iop_color_picker_set_cst(dt_iop_module_t *module, const dt_iop_colorspace_type_t picker_cst)
218 {
219 if(module->picker && module->picker->picker_cst != picker_cst)
220 {
221 module->picker->picker_cst = picker_cst;
222 module->picker->pick_pos[0] = NAN; // trigger difference on next apply
223 }
224 }
225
dt_iop_color_picker_get_active_cst(dt_iop_module_t * module)226 dt_iop_colorspace_type_t dt_iop_color_picker_get_active_cst(dt_iop_module_t *module)
227 {
228 if(module->picker)
229 return module->picker->picker_cst;
230 else
231 return iop_cs_NONE;
232 }
233
_iop_color_picker_signal_callback(gpointer instance,dt_iop_module_t * module,dt_dev_pixelpipe_iop_t * piece,gpointer user_data)234 static void _iop_color_picker_signal_callback(gpointer instance, dt_iop_module_t *module, dt_dev_pixelpipe_iop_t *piece,
235 gpointer user_data)
236 {
237 dt_develop_t *dev = module->dev;
238
239 // Invalidate the cache to ensure it will be fully recomputed.
240 // modules between colorin & colorout may need the work_profile
241 // to work properly. This will force colorin to be run and it
242 // will set the work_profile if needed.
243
244 if(!dev) return;
245
246 dev->preview_pipe->changed |= DT_DEV_PIPE_REMOVE;
247 dev->preview_pipe->cache_obsolete = 1;
248
249 _iop_color_picker_apply(module, piece);
250 }
251
dt_iop_color_picker_init(void)252 void dt_iop_color_picker_init(void)
253 {
254 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_CONTROL_PICKERDATA_READY,
255 G_CALLBACK(_iop_color_picker_signal_callback), NULL);
256 }
257
dt_iop_color_picker_cleanup(void)258 void dt_iop_color_picker_cleanup(void)
259 {
260 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(_iop_color_picker_signal_callback), NULL);
261 }
262
_color_picker_new(dt_iop_module_t * module,dt_iop_color_picker_kind_t kind,GtkWidget * w,const gboolean init_cst,const dt_iop_colorspace_type_t cst)263 static GtkWidget *_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w,
264 const gboolean init_cst, const dt_iop_colorspace_type_t cst)
265 {
266 dt_iop_color_picker_t *color_picker = (dt_iop_color_picker_t *)g_malloc(sizeof(dt_iop_color_picker_t));
267
268 if(w == NULL || GTK_IS_BOX(w))
269 {
270 GtkWidget *button = dtgtk_togglebutton_new(dtgtk_cairo_paint_colorpicker, CPF_STYLE_FLAT | CPF_BG_TRANSPARENT, NULL);
271 _iop_init_picker(color_picker, module, kind, button);
272 if(init_cst)
273 color_picker->picker_cst = cst;
274 g_signal_connect_data(G_OBJECT(button), "button-press-event",
275 G_CALLBACK(_iop_color_picker_callback_button_press), color_picker, (GClosureNotify)g_free, 0);
276 if (w) gtk_box_pack_start(GTK_BOX(w), button, FALSE, FALSE, 0);
277
278 return button;
279 }
280 else
281 {
282 dt_bauhaus_widget_set_quad_paint(w, dtgtk_cairo_paint_colorpicker, CPF_STYLE_FLAT, NULL);
283 dt_bauhaus_widget_set_quad_toggle(w, TRUE);
284 _iop_init_picker(color_picker, module, kind, w);
285 if(init_cst)
286 color_picker->picker_cst = cst;
287 g_signal_connect_data(G_OBJECT(w), "quad-pressed",
288 G_CALLBACK(_iop_color_picker_callback), color_picker, (GClosureNotify)g_free, 0);
289
290 return w;
291 }
292 }
293
dt_color_picker_new(dt_iop_module_t * module,dt_iop_color_picker_kind_t kind,GtkWidget * w)294 GtkWidget *dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
295 {
296 return _color_picker_new(module, kind, w, FALSE, iop_cs_NONE);
297 }
298
dt_color_picker_new_with_cst(dt_iop_module_t * module,dt_iop_color_picker_kind_t kind,GtkWidget * w,const dt_iop_colorspace_type_t cst)299 GtkWidget *dt_color_picker_new_with_cst(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w,
300 const dt_iop_colorspace_type_t cst)
301 {
302 return _color_picker_new(module, kind, w, TRUE, cst);
303 }
304
305 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
306 // vim: shiftwidth=2 expandtab tabstop=2 cindent
307 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
308