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 (RGB Color)
24  *
25  * Defines:
26  * - #UI_OT_eyedropper_gpencil_color
27  */
28 
29 #include "MEM_guardedalloc.h"
30 
31 #include "BLI_listbase.h"
32 #include "BLI_string.h"
33 
34 #include "BLT_translation.h"
35 
36 #include "DNA_gpencil_types.h"
37 #include "DNA_space_types.h"
38 
39 #include "BKE_context.h"
40 #include "BKE_gpencil.h"
41 #include "BKE_lib_id.h"
42 #include "BKE_main.h"
43 #include "BKE_material.h"
44 #include "BKE_paint.h"
45 #include "BKE_report.h"
46 
47 #include "UI_interface.h"
48 
49 #include "IMB_colormanagement.h"
50 
51 #include "WM_api.h"
52 #include "WM_types.h"
53 
54 #include "RNA_access.h"
55 #include "RNA_define.h"
56 
57 #include "ED_gpencil.h"
58 #include "ED_screen.h"
59 #include "ED_undo.h"
60 
61 #include "DEG_depsgraph.h"
62 #include "DEG_depsgraph_build.h"
63 
64 #include "interface_eyedropper_intern.h"
65 #include "interface_intern.h"
66 
67 typedef struct EyedropperGPencil {
68   struct ColorManagedDisplay *display;
69   /** color under cursor RGB */
70   float color[3];
71   /** Mode */
72   int mode;
73 } EyedropperGPencil;
74 
75 /* Helper: Draw status message while the user is running the operator */
eyedropper_gpencil_status_indicators(bContext * C)76 static void eyedropper_gpencil_status_indicators(bContext *C)
77 {
78   char msg_str[UI_MAX_DRAW_STR];
79   BLI_strncpy(
80       msg_str, TIP_("LMB: Stroke - Shift: Fill - Shift+Ctrl: Stroke + Fill"), UI_MAX_DRAW_STR);
81 
82   ED_workspace_status_text(C, msg_str);
83 }
84 
85 /* Initialize. */
eyedropper_gpencil_init(bContext * C,wmOperator * op)86 static bool eyedropper_gpencil_init(bContext *C, wmOperator *op)
87 {
88   EyedropperGPencil *eye = MEM_callocN(sizeof(EyedropperGPencil), __func__);
89 
90   op->customdata = eye;
91   Scene *scene = CTX_data_scene(C);
92 
93   const char *display_device;
94   display_device = scene->display_settings.display_device;
95   eye->display = IMB_colormanagement_display_get_named(display_device);
96 
97   eye->mode = RNA_enum_get(op->ptr, "mode");
98   return true;
99 }
100 
101 /* Exit and free memory. */
eyedropper_gpencil_exit(bContext * C,wmOperator * op)102 static void eyedropper_gpencil_exit(bContext *C, wmOperator *op)
103 {
104   /* Clear status message area. */
105   ED_workspace_status_text(C, NULL);
106 
107   MEM_SAFE_FREE(op->customdata);
108 }
109 
eyedropper_add_material(bContext * C,const float col_conv[4],const bool only_stroke,const bool only_fill,const bool both)110 static void eyedropper_add_material(bContext *C,
111                                     const float col_conv[4],
112                                     const bool only_stroke,
113                                     const bool only_fill,
114                                     const bool both)
115 {
116   Main *bmain = CTX_data_main(C);
117   Object *ob = CTX_data_active_object(C);
118   Material *ma = NULL;
119 
120   bool found = false;
121 
122   /* Look for a similar material in grease pencil slots. */
123   short *totcol = BKE_object_material_len_p(ob);
124   for (short i = 0; i < *totcol; i++) {
125     ma = BKE_object_material_get(ob, i + 1);
126     if (ma == NULL) {
127       continue;
128     }
129 
130     MaterialGPencilStyle *gp_style = ma->gp_style;
131     if (gp_style != NULL) {
132       /* Check stroke color. */
133       bool found_stroke = compare_v3v3(gp_style->stroke_rgba, col_conv, 0.01f) &&
134                           (gp_style->flag & GP_MATERIAL_STROKE_SHOW);
135       /* Check fill color. */
136       bool found_fill = compare_v3v3(gp_style->fill_rgba, col_conv, 0.01f) &&
137                         (gp_style->flag & GP_MATERIAL_FILL_SHOW);
138 
139       if ((only_stroke) && (found_stroke) && ((gp_style->flag & GP_MATERIAL_FILL_SHOW) == 0)) {
140         found = true;
141       }
142       else if ((only_fill) && (found_fill) && ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) == 0)) {
143         found = true;
144       }
145       else if ((both) && (found_stroke) && (found_fill)) {
146         found = true;
147       }
148 
149       /* Found existing material. */
150       if (found) {
151         ob->actcol = i + 1;
152         WM_main_add_notifier(NC_MATERIAL | ND_SHADING_LINKS, NULL);
153         WM_main_add_notifier(NC_SPACE | ND_SPACE_VIEW3D, NULL);
154         return;
155       }
156     }
157   }
158 
159   /* If material was not found add a new material with stroke and/or fill color
160    * depending of the secondary key (LMB: Stroke, Shift: Fill, Shift+Ctrl: Stroke/Fill)
161    */
162   int idx;
163   Material *ma_new = BKE_gpencil_object_material_new(bmain, ob, "Material", &idx);
164   WM_main_add_notifier(NC_OBJECT | ND_OB_SHADING, &ob->id);
165   WM_main_add_notifier(NC_MATERIAL | ND_SHADING_LINKS, NULL);
166   DEG_relations_tag_update(bmain);
167 
168   BLI_assert(ma_new != NULL);
169 
170   MaterialGPencilStyle *gp_style_new = ma_new->gp_style;
171   BLI_assert(gp_style_new != NULL);
172 
173   /* Only create Stroke (default option). */
174   if (only_stroke) {
175     /* Stroke color. */
176     gp_style_new->flag |= GP_MATERIAL_STROKE_SHOW;
177     gp_style_new->flag &= ~GP_MATERIAL_FILL_SHOW;
178     copy_v3_v3(gp_style_new->stroke_rgba, col_conv);
179     zero_v4(gp_style_new->fill_rgba);
180   }
181   /* Fill Only. */
182   else if (only_fill) {
183     /* Fill color. */
184     gp_style_new->flag &= ~GP_MATERIAL_STROKE_SHOW;
185     gp_style_new->flag |= GP_MATERIAL_FILL_SHOW;
186     zero_v4(gp_style_new->stroke_rgba);
187     copy_v3_v3(gp_style_new->fill_rgba, col_conv);
188   }
189   /* Stroke and Fill. */
190   else if (both) {
191     gp_style_new->flag |= GP_MATERIAL_STROKE_SHOW | GP_MATERIAL_FILL_SHOW;
192     copy_v3_v3(gp_style_new->stroke_rgba, col_conv);
193     copy_v3_v3(gp_style_new->fill_rgba, col_conv);
194   }
195   /* Push undo for new created material. */
196   ED_undo_push(C, "Add Grease Pencil Material");
197 }
198 
199 /* Create a new palette color and palette if needed. */
eyedropper_add_palette_color(bContext * C,const float col_conv[4])200 static void eyedropper_add_palette_color(bContext *C, const float col_conv[4])
201 {
202   Main *bmain = CTX_data_main(C);
203   Scene *scene = CTX_data_scene(C);
204   ToolSettings *ts = scene->toolsettings;
205   GpPaint *gp_paint = ts->gp_paint;
206   GpVertexPaint *gp_vertexpaint = ts->gp_vertexpaint;
207   Paint *paint = &gp_paint->paint;
208   Paint *vertexpaint = &gp_vertexpaint->paint;
209 
210   /* Check for Palette in Draw and Vertex Paint Mode. */
211   if (paint->palette == NULL) {
212     Palette *palette = BKE_palette_add(bmain, "Grease Pencil");
213     id_us_min(&palette->id);
214 
215     BKE_paint_palette_set(paint, palette);
216 
217     if (vertexpaint->palette == NULL) {
218       BKE_paint_palette_set(vertexpaint, palette);
219     }
220   }
221   /* Check if the color exist already. */
222   Palette *palette = paint->palette;
223   LISTBASE_FOREACH (PaletteColor *, palcolor, &palette->colors) {
224     if (compare_v3v3(palcolor->rgb, col_conv, 0.01f)) {
225       return;
226     }
227   }
228 
229   /* Create Colors. */
230   PaletteColor *palcol = BKE_palette_color_add(palette);
231   if (palcol) {
232     copy_v3_v3(palcol->rgb, col_conv);
233   }
234 }
235 
236 /* Set the material or the palette color. */
eyedropper_gpencil_color_set(bContext * C,const wmEvent * event,EyedropperGPencil * eye)237 static void eyedropper_gpencil_color_set(bContext *C, const wmEvent *event, EyedropperGPencil *eye)
238 {
239 
240   const bool only_stroke = ((!event->ctrl) && (!event->shift));
241   const bool only_fill = ((!event->ctrl) && (event->shift));
242   const bool both = ((event->ctrl) && (event->shift));
243 
244   float col_conv[4];
245 
246   /* Convert from linear rgb space to display space because grease pencil colors are in display
247    *  space, and this conversion is needed to undo the conversion to linear performed by
248    *  eyedropper_color_sample_fl. */
249   if (eye->display) {
250     copy_v3_v3(col_conv, eye->color);
251     IMB_colormanagement_scene_linear_to_display_v3(col_conv, eye->display);
252   }
253   else {
254     copy_v3_v3(col_conv, eye->color);
255   }
256 
257   /* Add material or Palette color*/
258   if (eye->mode == 0) {
259     eyedropper_add_material(C, col_conv, only_stroke, only_fill, both);
260   }
261   else {
262     eyedropper_add_palette_color(C, col_conv);
263   }
264 }
265 
266 /* Sample the color below cursor. */
eyedropper_gpencil_color_sample(bContext * C,EyedropperGPencil * eye,int mx,int my)267 static void eyedropper_gpencil_color_sample(bContext *C, EyedropperGPencil *eye, int mx, int my)
268 {
269   eyedropper_color_sample_fl(C, mx, my, eye->color);
270 }
271 
272 /* Cancel operator. */
eyedropper_gpencil_cancel(bContext * C,wmOperator * op)273 static void eyedropper_gpencil_cancel(bContext *C, wmOperator *op)
274 {
275   eyedropper_gpencil_exit(C, op);
276 }
277 
278 /* Main modal status check. */
eyedropper_gpencil_modal(bContext * C,wmOperator * op,const wmEvent * event)279 static int eyedropper_gpencil_modal(bContext *C, wmOperator *op, const wmEvent *event)
280 {
281   EyedropperGPencil *eye = (EyedropperGPencil *)op->customdata;
282   /* Handle modal keymap */
283   switch (event->type) {
284     case EVT_MODAL_MAP: {
285       switch (event->val) {
286         case EYE_MODAL_SAMPLE_BEGIN: {
287           return OPERATOR_RUNNING_MODAL;
288         }
289         case EYE_MODAL_CANCEL: {
290           eyedropper_gpencil_cancel(C, op);
291           return OPERATOR_CANCELLED;
292         }
293         case EYE_MODAL_SAMPLE_CONFIRM: {
294           eyedropper_gpencil_color_sample(C, eye, event->x, event->y);
295 
296           /* Create material. */
297           eyedropper_gpencil_color_set(C, event, eye);
298           WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
299 
300           eyedropper_gpencil_exit(C, op);
301           return OPERATOR_FINISHED;
302           break;
303         }
304         default: {
305           break;
306         }
307       }
308       break;
309     }
310     case MOUSEMOVE:
311     case INBETWEEN_MOUSEMOVE: {
312       eyedropper_gpencil_color_sample(C, eye, event->x, event->y);
313       break;
314     }
315     default: {
316       break;
317     }
318   }
319 
320   return OPERATOR_RUNNING_MODAL;
321 }
322 
323 /* Modal Operator init */
eyedropper_gpencil_invoke(bContext * C,wmOperator * op,const wmEvent * UNUSED (event))324 static int eyedropper_gpencil_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
325 {
326   /* Init. */
327   if (eyedropper_gpencil_init(C, op)) {
328     /* Add modal temp handler. */
329     WM_event_add_modal_handler(C, op);
330     /* Status message. */
331     eyedropper_gpencil_status_indicators(C);
332 
333     return OPERATOR_RUNNING_MODAL;
334   }
335   return OPERATOR_PASS_THROUGH;
336 }
337 
338 /* Repeat operator */
eyedropper_gpencil_exec(bContext * C,wmOperator * op)339 static int eyedropper_gpencil_exec(bContext *C, wmOperator *op)
340 {
341   /* init */
342   if (eyedropper_gpencil_init(C, op)) {
343 
344     /* cleanup */
345     eyedropper_gpencil_exit(C, op);
346 
347     return OPERATOR_FINISHED;
348   }
349   return OPERATOR_PASS_THROUGH;
350 }
351 
eyedropper_gpencil_poll(bContext * C)352 static bool eyedropper_gpencil_poll(bContext *C)
353 {
354   /* Only valid if the current active object is grease pencil. */
355   Object *obact = CTX_data_active_object(C);
356   if ((obact == NULL) || (obact->type != OB_GPENCIL)) {
357     return false;
358   }
359 
360   /* Test we have a window below. */
361   return (CTX_wm_window(C) != NULL);
362 }
363 
UI_OT_eyedropper_gpencil_color(wmOperatorType * ot)364 void UI_OT_eyedropper_gpencil_color(wmOperatorType *ot)
365 {
366   static const EnumPropertyItem items_mode[] = {
367       {0, "MATERIAL", 0, "Material", ""},
368       {1, "PALETTE", 0, "Palette", ""},
369       {0, NULL, 0, NULL, NULL},
370   };
371 
372   /* identifiers */
373   ot->name = "Grease Pencil Eyedropper";
374   ot->idname = "UI_OT_eyedropper_gpencil_color";
375   ot->description = "Sample a color from the Blender Window and create Grease Pencil material";
376 
377   /* api callbacks */
378   ot->invoke = eyedropper_gpencil_invoke;
379   ot->modal = eyedropper_gpencil_modal;
380   ot->cancel = eyedropper_gpencil_cancel;
381   ot->exec = eyedropper_gpencil_exec;
382   ot->poll = eyedropper_gpencil_poll;
383 
384   /* flags */
385   ot->flag = OPTYPE_UNDO | OPTYPE_BLOCKING;
386 
387   /* properties */
388   ot->prop = RNA_def_enum(ot->srna, "mode", items_mode, 0, "Mode", "");
389 }
390