1 /* Watercolor color_select_module, Raph Levien <raph@acm.org>, February 1998
2  *
3  * Ported to loadable color-selector, Sven Neumann <sven@gimp.org>, May 1999
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU 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  * This program 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 General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 
21 #include <stdlib.h>
22 #include <stdio.h>
23 
24 #include <gegl.h>
25 #include <gtk/gtk.h>
26 
27 #include "libgimpcolor/gimpcolor.h"
28 #include "libgimpmath/gimpmath.h"
29 #include "libgimpmodule/gimpmodule.h"
30 #include "libgimpwidgets/gimpwidgets.h"
31 
32 #include "libgimp/libgimp-intl.h"
33 
34 
35 #define COLORSEL_TYPE_WATER            (colorsel_water_get_type ())
36 #define COLORSEL_WATER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), COLORSEL_TYPE_WATER, ColorselWater))
37 #define COLORSEL_WATER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), COLORSEL_TYPE_WATER, ColorselWaterClass))
38 #define COLORSEL_IS_WATER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), COLORSEL_TYPE_WATER))
39 #define COLORSEL_IS_WATER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), COLORSEL_TYPE_WATER))
40 
41 
42 typedef struct _ColorselWater      ColorselWater;
43 typedef struct _ColorselWaterClass ColorselWaterClass;
44 
45 struct _ColorselWater
46 {
47   GimpColorSelector   parent_instance;
48 
49   GtkWidget          *area;
50 
51   gdouble             last_x;
52   gdouble             last_y;
53 
54   gfloat              pressure_adjust;
55   guint32             motion_time;
56 
57   GimpColorConfig    *config;
58   GimpColorTransform *transform;
59 };
60 
61 struct _ColorselWaterClass
62 {
63   GimpColorSelectorClass  parent_class;
64 };
65 
66 
67 static GType      colorsel_water_get_type          (void);
68 
69 static void       colorsel_water_dispose           (GObject           *object);
70 
71 static void       colorsel_water_set_config        (GimpColorSelector *selector,
72                                                     GimpColorConfig   *config);
73 
74 static void       colorsel_water_create_transform  (ColorselWater     *water);
75 static void       colorsel_water_destroy_transform (ColorselWater     *water);
76 
77 static gboolean   select_area_expose               (GtkWidget         *widget,
78                                                     GdkEventExpose    *event,
79                                                     ColorselWater     *water);
80 static gboolean   button_press_event               (GtkWidget         *widget,
81                                                     GdkEventButton    *event,
82                                                     ColorselWater     *water);
83 static gboolean   motion_notify_event              (GtkWidget         *widget,
84                                                     GdkEventMotion    *event,
85                                                     ColorselWater     *water);
86 static gboolean   proximity_out_event              (GtkWidget         *widget,
87                                                     GdkEventProximity *event,
88                                                     ColorselWater     *water);
89 static void       pressure_adjust_update           (GtkAdjustment     *adj,
90                                                     ColorselWater     *water);
91 
92 
93 static const GimpModuleInfo colorsel_water_info =
94 {
95   GIMP_MODULE_ABI_VERSION,
96   N_("Watercolor style color selector"),
97   "Raph Levien <raph@acm.org>, Sven Neumann <sven@gimp.org>",
98   "v0.4",
99   "released under the GPL",
100   "1998-2006"
101 };
102 
103 
G_DEFINE_DYNAMIC_TYPE(ColorselWater,colorsel_water,GIMP_TYPE_COLOR_SELECTOR)104 G_DEFINE_DYNAMIC_TYPE (ColorselWater, colorsel_water,
105                        GIMP_TYPE_COLOR_SELECTOR)
106 
107 
108 G_MODULE_EXPORT const GimpModuleInfo *
109 gimp_module_query (GTypeModule *module)
110 {
111   return &colorsel_water_info;
112 }
113 
114 G_MODULE_EXPORT gboolean
gimp_module_register(GTypeModule * module)115 gimp_module_register (GTypeModule *module)
116 {
117   colorsel_water_register_type (module);
118 
119   return TRUE;
120 }
121 
122 static void
colorsel_water_class_init(ColorselWaterClass * klass)123 colorsel_water_class_init (ColorselWaterClass *klass)
124 {
125   GObjectClass           *object_class   = G_OBJECT_CLASS (klass);
126   GimpColorSelectorClass *selector_class = GIMP_COLOR_SELECTOR_CLASS (klass);
127 
128   object_class->dispose      = colorsel_water_dispose;
129 
130   selector_class->name       = _("Watercolor");
131   selector_class->help_id    = "gimp-colorselector-watercolor";
132   selector_class->icon_name  = GIMP_ICON_COLOR_SELECTOR_WATER;
133   selector_class->set_config = colorsel_water_set_config;
134 }
135 
136 static void
colorsel_water_class_finalize(ColorselWaterClass * klass)137 colorsel_water_class_finalize (ColorselWaterClass *klass)
138 {
139 }
140 
141 static void
colorsel_water_init(ColorselWater * water)142 colorsel_water_init (ColorselWater *water)
143 {
144   GtkWidget     *hbox;
145   GtkWidget     *frame;
146   GtkAdjustment *adj;
147   GtkWidget     *scale;
148 
149   colorsel_water_get_type (); /* useless function call to silence compiler */
150 
151   water->pressure_adjust = 1.0;
152 
153   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
154   gtk_box_pack_start (GTK_BOX (water), hbox, TRUE, TRUE, 0);
155 
156   frame = gtk_frame_new (NULL);
157   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
158   gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0);
159 
160   water->area = gtk_drawing_area_new ();
161   gtk_container_add (GTK_CONTAINER (frame), water->area);
162   g_signal_connect (water->area, "expose-event",
163                     G_CALLBACK (select_area_expose),
164                     water);
165 
166   /* Event signals */
167   g_signal_connect (water->area, "motion-notify-event",
168                     G_CALLBACK (motion_notify_event),
169                     water);
170   g_signal_connect (water->area, "button-press-event",
171                     G_CALLBACK (button_press_event),
172                     water);
173   g_signal_connect (water->area, "proximity-out-event",
174                     G_CALLBACK (proximity_out_event),
175                     water);
176 
177   gtk_widget_add_events (water->area,
178                          GDK_LEAVE_NOTIFY_MASK        |
179                          GDK_BUTTON_PRESS_MASK        |
180                          GDK_KEY_PRESS_MASK           |
181                          GDK_POINTER_MOTION_MASK      |
182                          GDK_POINTER_MOTION_HINT_MASK |
183                          GDK_PROXIMITY_OUT_MASK);
184 
185   /* The following call enables tracking and processing of extension
186    * events for the drawing area
187    */
188   gtk_widget_set_extension_events (water->area, GDK_EXTENSION_EVENTS_ALL);
189   gtk_widget_grab_focus (water->area);
190 
191   adj = GTK_ADJUSTMENT (gtk_adjustment_new (200.0 - water->pressure_adjust * 100.0,
192                                             0.0, 200.0, 1.0, 1.0, 0.0));
193   g_signal_connect (adj, "value-changed",
194                     G_CALLBACK (pressure_adjust_update),
195                     water);
196 
197   scale = gtk_scale_new (GTK_ORIENTATION_VERTICAL, adj);
198   gtk_scale_set_digits (GTK_SCALE (scale), 0);
199   gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);
200   gimp_help_set_help_data (scale, _("Pressure"), NULL);
201   gtk_box_pack_start (GTK_BOX (hbox), scale, FALSE, FALSE, 0);
202 
203   gtk_widget_show_all (hbox);
204 
205   gimp_widget_track_monitor (GTK_WIDGET (water),
206                              G_CALLBACK (colorsel_water_destroy_transform),
207                              NULL);
208 }
209 
210 static gdouble
calc(gdouble x,gdouble y,gdouble angle)211 calc (gdouble x,
212       gdouble y,
213       gdouble angle)
214 {
215   gdouble s = 2.0 * sin (angle * G_PI / 180.0) * 256.0;
216   gdouble c = 2.0 * cos (angle * G_PI / 180.0) * 256.0;
217 
218   return 128 + (x - 0.5) * c - (y - 0.5) * s;
219 }
220 
221 static void
colorsel_water_dispose(GObject * object)222 colorsel_water_dispose (GObject *object)
223 {
224   colorsel_water_set_config (GIMP_COLOR_SELECTOR (object), NULL);
225 
226   G_OBJECT_CLASS (colorsel_water_parent_class)->dispose (object);
227 }
228 
229 static void
colorsel_water_set_config(GimpColorSelector * selector,GimpColorConfig * config)230 colorsel_water_set_config (GimpColorSelector *selector,
231                            GimpColorConfig   *config)
232 {
233   ColorselWater *water = COLORSEL_WATER (selector);
234 
235   if (config != water->config)
236     {
237       if (water->config)
238         {
239           g_signal_handlers_disconnect_by_func (water->config,
240                                                 colorsel_water_destroy_transform,
241                                                 water);
242 
243           colorsel_water_destroy_transform (water);
244         }
245 
246       g_set_object (&water->config, config);
247 
248       if (water->config)
249         {
250           g_signal_connect_swapped (water->config, "notify",
251                                     G_CALLBACK (colorsel_water_destroy_transform),
252                                     water);
253         }
254     }
255 }
256 
257 static void
colorsel_water_create_transform(ColorselWater * water)258 colorsel_water_create_transform (ColorselWater *water)
259 {
260   if (water->config)
261     {
262       static GimpColorProfile *profile = NULL;
263 
264       const Babl *format = babl_format ("cairo-RGB24");
265 
266       if (G_UNLIKELY (! profile))
267         profile = gimp_color_profile_new_rgb_srgb ();
268 
269       water->transform = gimp_widget_get_color_transform (water->area,
270                                                           water->config,
271                                                           profile,
272                                                           format,
273                                                           format);
274     }
275 }
276 
277 static void
colorsel_water_destroy_transform(ColorselWater * water)278 colorsel_water_destroy_transform (ColorselWater *water)
279 {
280   if (water->transform)
281     {
282       g_object_unref (water->transform);
283       water->transform = NULL;
284     }
285 
286   gtk_widget_queue_draw (GTK_WIDGET (water->area));
287 }
288 
289 static gboolean
select_area_expose(GtkWidget * widget,GdkEventExpose * event,ColorselWater * water)290 select_area_expose (GtkWidget      *widget,
291                     GdkEventExpose *event,
292                     ColorselWater  *water)
293 {
294   cairo_t         *cr;
295   GtkAllocation    allocation;
296   gdouble          dx;
297   gdouble          dy;
298   cairo_surface_t *surface;
299   guchar          *dest;
300   gdouble          y;
301   gint             j;
302 
303   cr = gdk_cairo_create (event->window);
304 
305   gdk_cairo_region (cr, event->region);
306   cairo_clip (cr);
307 
308   gtk_widget_get_allocation (widget, &allocation);
309 
310   dx = 1.0 / allocation.width;
311   dy = 1.0 / allocation.height;
312 
313   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
314                                         event->area.width,
315                                         event->area.height);
316 
317   dest = cairo_image_surface_get_data (surface);
318 
319   if (! water->transform)
320     colorsel_water_create_transform (water);
321 
322   for (j = 0, y = event->area.y / allocation.height;
323        j < event->area.height;
324        j++, y += dy)
325     {
326       guchar  *d  = dest;
327 
328       gdouble  r  = calc (0, y, 0);
329       gdouble  g  = calc (0, y, 120);
330       gdouble  b  = calc (0, y, 240);
331 
332       gdouble  dr = calc (dx, y, 0)   - r;
333       gdouble  dg = calc (dx, y, 120) - g;
334       gdouble  db = calc (dx, y, 240) - b;
335 
336       gint     i;
337 
338       r += event->area.x * dr;
339       g += event->area.x * dg;
340       b += event->area.x * db;
341 
342       for (i = 0; i < event->area.width ; i++)
343         {
344           GIMP_CAIRO_RGB24_SET_PIXEL (d,
345                                       CLAMP ((gint) r, 0, 255),
346                                       CLAMP ((gint) g, 0, 255),
347                                       CLAMP ((gint) b, 0, 255));
348 
349           r += dr;
350           g += dg;
351           b += db;
352 
353           d += 4;
354         }
355 
356       if (water->transform)
357         gimp_color_transform_process_pixels (water->transform,
358                                              babl_format ("cairo-RGB24"),
359                                              dest,
360                                              babl_format ("cairo-RGB24"),
361                                              dest,
362                                              event->area.width);
363 
364       dest += cairo_image_surface_get_stride (surface);
365     }
366 
367   cairo_surface_mark_dirty (surface);
368   cairo_set_source_surface (cr, surface,
369                             event->area.x, event->area.y);
370   cairo_surface_destroy (surface);
371 
372   cairo_paint (cr);
373 
374   cairo_destroy (cr);
375 
376   return FALSE;
377 }
378 
379 static void
add_pigment(ColorselWater * water,gboolean erase,gdouble x,gdouble y,gdouble much)380 add_pigment (ColorselWater *water,
381              gboolean       erase,
382              gdouble        x,
383              gdouble        y,
384              gdouble        much)
385 {
386   GimpColorSelector *selector = GIMP_COLOR_SELECTOR (water);
387 
388   much *= (gdouble) water->pressure_adjust;
389 
390   if (erase)
391     {
392       selector->rgb.r = 1.0 - (1.0 - selector->rgb.r) * (1.0 - much);
393       selector->rgb.g = 1.0 - (1.0 - selector->rgb.g) * (1.0 - much);
394       selector->rgb.b = 1.0 - (1.0 - selector->rgb.b) * (1.0 - much);
395     }
396   else
397     {
398       gdouble r = calc (x, y, 0)   / 256.0;
399       gdouble g = calc (x, y, 120) / 256.0;
400       gdouble b = calc (x, y, 240) / 256.0;
401 
402       selector->rgb.r *= (1.0 - (1.0 - r) * much);
403       selector->rgb.g *= (1.0 - (1.0 - g) * much);
404       selector->rgb.b *= (1.0 - (1.0 - b) * much);
405     }
406 
407   gimp_rgb_clamp (&selector->rgb);
408 
409   gimp_rgb_to_hsv (&selector->rgb, &selector->hsv);
410 
411   gimp_color_selector_color_changed (selector);
412 }
413 
414 static void
draw_brush(ColorselWater * water,GtkWidget * widget,gboolean erase,gdouble x,gdouble y,gdouble pressure)415 draw_brush (ColorselWater *water,
416             GtkWidget     *widget,
417             gboolean       erase,
418             gdouble        x,
419             gdouble        y,
420             gdouble        pressure)
421 {
422   gdouble much = sqrt (SQR (x - water->last_x) + SQR (y - water->last_y));
423 
424   add_pigment (water, erase, x, y, much * pressure);
425 
426   water->last_x = x;
427   water->last_y = y;
428 }
429 
430 static gboolean
button_press_event(GtkWidget * widget,GdkEventButton * event,ColorselWater * water)431 button_press_event (GtkWidget      *widget,
432                     GdkEventButton *event,
433                     ColorselWater  *water)
434 {
435   GtkAllocation allocation;
436   gboolean      erase;
437 
438   gtk_widget_get_allocation (widget, &allocation);
439 
440   water->last_x = event->x / allocation.width;
441   water->last_y = event->y / allocation.height;
442 
443   erase = (event->button != 1);
444   /* FIXME: (event->source == GDK_SOURCE_ERASER) */
445 
446   if (event->state & GDK_SHIFT_MASK)
447     erase = !erase;
448 
449   add_pigment (water, erase, water->last_x, water->last_y, 0.05);
450 
451   water->motion_time = event->time;
452 
453   return FALSE;
454 }
455 
456 static gboolean
motion_notify_event(GtkWidget * widget,GdkEventMotion * event,ColorselWater * water)457 motion_notify_event (GtkWidget      *widget,
458                      GdkEventMotion *event,
459                      ColorselWater  *water)
460 {
461   GtkAllocation  allocation;
462   GdkTimeCoord **coords;
463   gint           nevents;
464   gint           i;
465   gboolean       erase;
466 
467   gtk_widget_get_allocation (widget, &allocation);
468 
469   if (event->state & (GDK_BUTTON1_MASK |
470                       GDK_BUTTON2_MASK |
471                       GDK_BUTTON3_MASK |
472                       GDK_BUTTON4_MASK))
473     {
474       guint32 last_motion_time = event->time;
475 
476       erase = ((event->state &
477                 (GDK_BUTTON2_MASK | GDK_BUTTON3_MASK | GDK_BUTTON4_MASK)) ||
478                FALSE);
479       /* FIXME: (event->source == GDK_SOURCE_ERASER) */
480 
481       if (event->state & GDK_SHIFT_MASK)
482         erase = !erase;
483 
484       water->motion_time = event->time;
485 
486       if (gdk_device_get_history (event->device,
487                                   event->window,
488                                   last_motion_time,
489                                   event->time,
490                                   &coords,
491                                   &nevents))
492         {
493           for (i = 0; i < nevents; i++)
494             {
495               gdouble x        = 0.0;
496               gdouble y        = 0.0;
497               gdouble pressure = 0.5;
498 
499               gdk_device_get_axis (event->device, coords[i]->axes,
500                                    GDK_AXIS_X, &x);
501               gdk_device_get_axis (event->device, coords[i]->axes,
502                                    GDK_AXIS_Y, &y);
503               gdk_device_get_axis (event->device, coords[i]->axes,
504                                    GDK_AXIS_PRESSURE, &pressure);
505 
506               draw_brush (water, widget, erase,
507                           x / allocation.width,
508                           y / allocation.height, pressure);
509             }
510 
511           gdk_device_free_history (coords, nevents);
512         }
513       else
514         {
515           gdouble pressure = 0.5;
516 
517           gdk_event_get_axis ((GdkEvent *) event, GDK_AXIS_PRESSURE, &pressure);
518 
519           draw_brush (water, widget, erase,
520                       event->x / allocation.width,
521                       event->y / allocation.height, pressure);
522         }
523     }
524 
525   /* Ask for more motion events in case the event was a hint */
526   gdk_event_request_motions (event);
527 
528   return TRUE;
529 }
530 
531 static gboolean
proximity_out_event(GtkWidget * widget,GdkEventProximity * event,ColorselWater * water)532 proximity_out_event (GtkWidget         *widget,
533                      GdkEventProximity *event,
534                      ColorselWater     *water)
535 {
536   return TRUE;
537 }
538 
539 static void
pressure_adjust_update(GtkAdjustment * adj,ColorselWater * water)540 pressure_adjust_update (GtkAdjustment *adj,
541                         ColorselWater *water)
542 {
543   water->pressure_adjust = (gtk_adjustment_get_upper (adj) -
544                             gtk_adjustment_get_value (adj)) / 100.0;
545 }
546