1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2009 Blender Foundation.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup edinterface
22  *
23  * Eyedropper (Color Band).
24  *
25  * Operates by either:
26  * - Dragging a straight line, sampling pixels formed by the line to extract a gradient.
27  * - Clicking on points, adding each color to the end of the color-band.
28  *
29  * Defines:
30  * - #UI_OT_eyedropper_colorramp
31  * - #UI_OT_eyedropper_colorramp_point
32  */
33 
34 #include "MEM_guardedalloc.h"
35 
36 #include "DNA_screen_types.h"
37 
38 #include "BLI_bitmap_draw_2d.h"
39 #include "BLI_math_vector.h"
40 
41 #include "BKE_colorband.h"
42 #include "BKE_context.h"
43 
44 #include "RNA_access.h"
45 
46 #include "UI_interface.h"
47 
48 #include "WM_api.h"
49 #include "WM_types.h"
50 
51 #include "interface_intern.h"
52 
53 #include "interface_eyedropper_intern.h"
54 
55 typedef struct Colorband_RNAUpdateCb {
56   PointerRNA ptr;
57   PropertyRNA *prop;
58 } Colorband_RNAUpdateCb;
59 
60 typedef struct EyedropperColorband {
61   int last_x, last_y;
62   /* Alpha is currently fixed at 1.0, may support in future. */
63   float (*color_buffer)[4];
64   int color_buffer_alloc;
65   int color_buffer_len;
66   bool sample_start;
67   ColorBand init_color_band;
68   ColorBand *color_band;
69   PointerRNA ptr;
70   PropertyRNA *prop;
71   bool is_undo;
72   bool is_set;
73 } EyedropperColorband;
74 
75 /* For user-data only. */
76 struct EyedropperColorband_Context {
77   bContext *context;
78   EyedropperColorband *eye;
79 };
80 
eyedropper_colorband_init(bContext * C,wmOperator * op)81 static bool eyedropper_colorband_init(bContext *C, wmOperator *op)
82 {
83   ColorBand *band = NULL;
84 
85   uiBut *but = UI_context_active_but_get(C);
86 
87   PointerRNA rna_update_ptr = PointerRNA_NULL;
88   PropertyRNA *rna_update_prop = NULL;
89   bool is_undo = true;
90 
91   if (but == NULL) {
92     /* pass */
93   }
94   else {
95     if (but->type == UI_BTYPE_COLORBAND) {
96       /* When invoked with a hotkey, we can find the band in 'but->poin'. */
97       band = (ColorBand *)but->poin;
98     }
99     else {
100       /* When invoked from a button it's in custom_data field. */
101       band = (ColorBand *)but->custom_data;
102     }
103 
104     if (band) {
105       rna_update_ptr = ((Colorband_RNAUpdateCb *)but->func_argN)->ptr;
106       rna_update_prop = ((Colorband_RNAUpdateCb *)but->func_argN)->prop;
107       is_undo = UI_but_flag_is_set(but, UI_BUT_UNDO);
108     }
109   }
110 
111   if (!band) {
112     const PointerRNA ptr = CTX_data_pointer_get_type(C, "color_ramp", &RNA_ColorRamp);
113     if (ptr.data != NULL) {
114       band = ptr.data;
115 
116       /* Set this to a sub-member of the property to trigger an update. */
117       extern PropertyRNA rna_ColorRamp_color_mode;
118       rna_update_ptr = ptr;
119       rna_update_prop = &rna_ColorRamp_color_mode;
120       is_undo = RNA_struct_undo_check(ptr.type);
121     }
122   }
123 
124   if (!band) {
125     return false;
126   }
127 
128   EyedropperColorband *eye = MEM_callocN(sizeof(EyedropperColorband), __func__);
129   eye->color_buffer_alloc = 16;
130   eye->color_buffer = MEM_mallocN(sizeof(*eye->color_buffer) * eye->color_buffer_alloc, __func__);
131   eye->color_buffer_len = 0;
132   eye->color_band = band;
133   eye->init_color_band = *eye->color_band;
134   eye->ptr = rna_update_ptr;
135   eye->prop = rna_update_prop;
136   eye->is_undo = is_undo;
137 
138   op->customdata = eye;
139 
140   return true;
141 }
142 
eyedropper_colorband_sample_point(bContext * C,EyedropperColorband * eye,int mx,int my)143 static void eyedropper_colorband_sample_point(bContext *C,
144                                               EyedropperColorband *eye,
145                                               int mx,
146                                               int my)
147 {
148   if (eye->last_x != mx || eye->last_y != my) {
149     float col[4];
150     col[3] = 1.0f; /* TODO: sample alpha */
151     eyedropper_color_sample_fl(C, mx, my, col);
152     if (eye->color_buffer_len + 1 == eye->color_buffer_alloc) {
153       eye->color_buffer_alloc *= 2;
154       eye->color_buffer = MEM_reallocN(eye->color_buffer,
155                                        sizeof(*eye->color_buffer) * eye->color_buffer_alloc);
156     }
157     copy_v4_v4(eye->color_buffer[eye->color_buffer_len], col);
158     eye->color_buffer_len += 1;
159     eye->last_x = mx;
160     eye->last_y = my;
161     eye->is_set = true;
162   }
163 }
164 
eyedropper_colorband_sample_callback(int mx,int my,void * userdata)165 static bool eyedropper_colorband_sample_callback(int mx, int my, void *userdata)
166 {
167   struct EyedropperColorband_Context *data = userdata;
168   bContext *C = data->context;
169   EyedropperColorband *eye = data->eye;
170   eyedropper_colorband_sample_point(C, eye, mx, my);
171   return true;
172 }
173 
eyedropper_colorband_sample_segment(bContext * C,EyedropperColorband * eye,int mx,int my)174 static void eyedropper_colorband_sample_segment(bContext *C,
175                                                 EyedropperColorband *eye,
176                                                 int mx,
177                                                 int my)
178 {
179   /* Since the mouse tends to move rather rapidly we use #BLI_bitmap_draw_2d_line_v2v2i
180    * to interpolate between the reported coordinates */
181   struct EyedropperColorband_Context userdata = {C, eye};
182   const int p1[2] = {eye->last_x, eye->last_y};
183   const int p2[2] = {mx, my};
184   BLI_bitmap_draw_2d_line_v2v2i(p1, p2, eyedropper_colorband_sample_callback, &userdata);
185 }
186 
eyedropper_colorband_exit(bContext * C,wmOperator * op)187 static void eyedropper_colorband_exit(bContext *C, wmOperator *op)
188 {
189   WM_cursor_modal_restore(CTX_wm_window(C));
190 
191   if (op->customdata) {
192     EyedropperColorband *eye = op->customdata;
193     MEM_freeN(eye->color_buffer);
194     MEM_freeN(eye);
195     op->customdata = NULL;
196   }
197 }
198 
eyedropper_colorband_apply(bContext * C,wmOperator * op)199 static void eyedropper_colorband_apply(bContext *C, wmOperator *op)
200 {
201   EyedropperColorband *eye = op->customdata;
202   /* Always filter, avoids noise in resulting color-band. */
203   const bool filter_samples = true;
204   BKE_colorband_init_from_table_rgba(
205       eye->color_band, eye->color_buffer, eye->color_buffer_len, filter_samples);
206   eye->is_set = true;
207   if (eye->prop) {
208     RNA_property_update(C, &eye->ptr, eye->prop);
209   }
210 }
211 
eyedropper_colorband_cancel(bContext * C,wmOperator * op)212 static void eyedropper_colorband_cancel(bContext *C, wmOperator *op)
213 {
214   EyedropperColorband *eye = op->customdata;
215   if (eye->is_set) {
216     *eye->color_band = eye->init_color_band;
217     if (eye->prop) {
218       RNA_property_update(C, &eye->ptr, eye->prop);
219     }
220   }
221   eyedropper_colorband_exit(C, op);
222 }
223 
224 /* main modal status check */
eyedropper_colorband_modal(bContext * C,wmOperator * op,const wmEvent * event)225 static int eyedropper_colorband_modal(bContext *C, wmOperator *op, const wmEvent *event)
226 {
227   EyedropperColorband *eye = op->customdata;
228   /* handle modal keymap */
229   if (event->type == EVT_MODAL_MAP) {
230     switch (event->val) {
231       case EYE_MODAL_CANCEL:
232         eyedropper_colorband_cancel(C, op);
233         return OPERATOR_CANCELLED;
234       case EYE_MODAL_SAMPLE_CONFIRM: {
235         const bool is_undo = eye->is_undo;
236         eyedropper_colorband_sample_segment(C, eye, event->x, event->y);
237         eyedropper_colorband_apply(C, op);
238         eyedropper_colorband_exit(C, op);
239         /* Could support finished & undo-skip. */
240         return is_undo ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
241       }
242       case EYE_MODAL_SAMPLE_BEGIN:
243         /* enable accum and make first sample */
244         eye->sample_start = true;
245         eyedropper_colorband_sample_point(C, eye, event->x, event->y);
246         eyedropper_colorband_apply(C, op);
247         eye->last_x = event->x;
248         eye->last_y = event->y;
249         break;
250       case EYE_MODAL_SAMPLE_RESET:
251         break;
252     }
253   }
254   else if (event->type == MOUSEMOVE) {
255     if (eye->sample_start) {
256       eyedropper_colorband_sample_segment(C, eye, event->x, event->y);
257       eyedropper_colorband_apply(C, op);
258     }
259   }
260   return OPERATOR_RUNNING_MODAL;
261 }
262 
eyedropper_colorband_point_modal(bContext * C,wmOperator * op,const wmEvent * event)263 static int eyedropper_colorband_point_modal(bContext *C, wmOperator *op, const wmEvent *event)
264 {
265   EyedropperColorband *eye = op->customdata;
266   /* handle modal keymap */
267   if (event->type == EVT_MODAL_MAP) {
268     switch (event->val) {
269       case EYE_MODAL_POINT_CANCEL:
270         eyedropper_colorband_cancel(C, op);
271         return OPERATOR_CANCELLED;
272       case EYE_MODAL_POINT_CONFIRM:
273         eyedropper_colorband_apply(C, op);
274         eyedropper_colorband_exit(C, op);
275         return OPERATOR_FINISHED;
276       case EYE_MODAL_POINT_REMOVE_LAST:
277         if (eye->color_buffer_len > 0) {
278           eye->color_buffer_len -= 1;
279           eyedropper_colorband_apply(C, op);
280         }
281         break;
282       case EYE_MODAL_POINT_SAMPLE:
283         eyedropper_colorband_sample_point(C, eye, event->x, event->y);
284         eyedropper_colorband_apply(C, op);
285         if (eye->color_buffer_len == MAXCOLORBAND) {
286           eyedropper_colorband_exit(C, op);
287           return OPERATOR_FINISHED;
288         }
289         break;
290       case EYE_MODAL_SAMPLE_RESET:
291         *eye->color_band = eye->init_color_band;
292         if (eye->prop) {
293           RNA_property_update(C, &eye->ptr, eye->prop);
294         }
295         eye->color_buffer_len = 0;
296         break;
297     }
298   }
299   return OPERATOR_RUNNING_MODAL;
300 }
301 
302 /* Modal Operator init */
eyedropper_colorband_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))303 static int eyedropper_colorband_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
304 {
305   /* init */
306   if (eyedropper_colorband_init(C, op)) {
307     wmWindow *win = CTX_wm_window(C);
308     /* Workaround for de-activating the button clearing the cursor, see T76794 */
309     UI_context_active_but_clear(C, win, CTX_wm_region(C));
310     WM_cursor_modal_set(win, WM_CURSOR_EYEDROPPER);
311 
312     /* add temp handler */
313     WM_event_add_modal_handler(C, op);
314 
315     return OPERATOR_RUNNING_MODAL;
316   }
317   return OPERATOR_CANCELLED;
318 }
319 
320 /* Repeat operator */
eyedropper_colorband_exec(bContext * C,wmOperator * op)321 static int eyedropper_colorband_exec(bContext *C, wmOperator *op)
322 {
323   /* init */
324   if (eyedropper_colorband_init(C, op)) {
325 
326     /* do something */
327 
328     /* cleanup */
329     eyedropper_colorband_exit(C, op);
330 
331     return OPERATOR_FINISHED;
332   }
333   return OPERATOR_CANCELLED;
334 }
335 
eyedropper_colorband_poll(bContext * C)336 static bool eyedropper_colorband_poll(bContext *C)
337 {
338   uiBut *but = UI_context_active_but_get(C);
339   if (but && but->type == UI_BTYPE_COLORBAND) {
340     return true;
341   }
342   const PointerRNA ptr = CTX_data_pointer_get_type(C, "color_ramp", &RNA_ColorRamp);
343   if (ptr.data != NULL) {
344     return true;
345   }
346   return false;
347 }
348 
UI_OT_eyedropper_colorramp(wmOperatorType * ot)349 void UI_OT_eyedropper_colorramp(wmOperatorType *ot)
350 {
351   /* identifiers */
352   ot->name = "Eyedropper colorband";
353   ot->idname = "UI_OT_eyedropper_colorramp";
354   ot->description = "Sample a color band";
355 
356   /* api callbacks */
357   ot->invoke = eyedropper_colorband_invoke;
358   ot->modal = eyedropper_colorband_modal;
359   ot->cancel = eyedropper_colorband_cancel;
360   ot->exec = eyedropper_colorband_exec;
361   ot->poll = eyedropper_colorband_poll;
362 
363   /* flags */
364   ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL;
365 
366   /* properties */
367 }
368 
UI_OT_eyedropper_colorramp_point(wmOperatorType * ot)369 void UI_OT_eyedropper_colorramp_point(wmOperatorType *ot)
370 {
371   /* identifiers */
372   ot->name = "Eyedropper colorband (points)";
373   ot->idname = "UI_OT_eyedropper_colorramp_point";
374   ot->description = "Point-sample a color band";
375 
376   /* api callbacks */
377   ot->invoke = eyedropper_colorband_invoke;
378   ot->modal = eyedropper_colorband_point_modal;
379   ot->cancel = eyedropper_colorband_cancel;
380   ot->exec = eyedropper_colorband_exec;
381   ot->poll = eyedropper_colorband_poll;
382 
383   /* flags */
384   ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING | OPTYPE_INTERNAL;
385 
386   /* properties */
387 }
388