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