1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 
20 #include <gdk-pixbuf/gdk-pixbuf.h>
21 #include <gegl.h>
22 
23 #include "libgimpmath/gimpmath.h"
24 
25 #include "paint-types.h"
26 
27 #include "core/gimpboundary.h"
28 #include "core/gimpdrawable.h"
29 #include "core/gimperror.h"
30 #include "core/gimpcoords.h"
31 
32 #include "vectors/gimpstroke.h"
33 #include "vectors/gimpvectors.h"
34 
35 #include "gimppaintcore.h"
36 #include "gimppaintcore-stroke.h"
37 #include "gimppaintoptions.h"
38 
39 #include "gimp-intl.h"
40 
41 
42 static void gimp_paint_core_stroke_emulate_dynamics (GimpCoords *coords,
43                                                      gint        length);
44 
45 
46 static const GimpCoords default_coords = GIMP_COORDS_DEFAULT_VALUES;
47 
48 
49 gboolean
gimp_paint_core_stroke(GimpPaintCore * core,GimpDrawable * drawable,GimpPaintOptions * paint_options,GimpCoords * strokes,gint n_strokes,gboolean push_undo,GError ** error)50 gimp_paint_core_stroke (GimpPaintCore     *core,
51                         GimpDrawable      *drawable,
52                         GimpPaintOptions  *paint_options,
53                         GimpCoords        *strokes,
54                         gint               n_strokes,
55                         gboolean           push_undo,
56                         GError           **error)
57 {
58   g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), FALSE);
59   g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
60   g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE);
61   g_return_val_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options), FALSE);
62   g_return_val_if_fail (strokes != NULL, FALSE);
63   g_return_val_if_fail (n_strokes > 0, FALSE);
64   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
65 
66   if (gimp_paint_core_start (core, drawable, paint_options, &strokes[0],
67                              error))
68     {
69       gint i;
70 
71       core->last_coords = strokes[0];
72 
73       gimp_paint_core_paint (core, drawable, paint_options,
74                              GIMP_PAINT_STATE_INIT, 0);
75 
76       gimp_paint_core_paint (core, drawable, paint_options,
77                              GIMP_PAINT_STATE_MOTION, 0);
78 
79       for (i = 1; i < n_strokes; i++)
80         {
81           gimp_paint_core_interpolate (core, drawable, paint_options,
82                                        &strokes[i], 0);
83         }
84 
85       gimp_paint_core_paint (core, drawable, paint_options,
86                              GIMP_PAINT_STATE_FINISH, 0);
87 
88       gimp_paint_core_finish (core, drawable, push_undo);
89 
90       gimp_paint_core_cleanup (core);
91 
92       return TRUE;
93     }
94 
95   return FALSE;
96 }
97 
98 gboolean
gimp_paint_core_stroke_boundary(GimpPaintCore * core,GimpDrawable * drawable,GimpPaintOptions * paint_options,gboolean emulate_dynamics,const GimpBoundSeg * bound_segs,gint n_bound_segs,gint offset_x,gint offset_y,gboolean push_undo,GError ** error)99 gimp_paint_core_stroke_boundary (GimpPaintCore      *core,
100                                  GimpDrawable       *drawable,
101                                  GimpPaintOptions   *paint_options,
102                                  gboolean            emulate_dynamics,
103                                  const GimpBoundSeg *bound_segs,
104                                  gint                n_bound_segs,
105                                  gint                offset_x,
106                                  gint                offset_y,
107                                  gboolean            push_undo,
108                                  GError            **error)
109 {
110   GimpBoundSeg *stroke_segs;
111   gint          n_stroke_segs;
112   gint          off_x;
113   gint          off_y;
114   GimpCoords   *coords;
115   gboolean      initialized = FALSE;
116   gint          n_coords;
117   gint          seg;
118   gint          s;
119 
120   g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), FALSE);
121   g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
122   g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE);
123   g_return_val_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options), FALSE);
124   g_return_val_if_fail (bound_segs != NULL && n_bound_segs > 0, FALSE);
125   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
126 
127   stroke_segs = gimp_boundary_sort (bound_segs, n_bound_segs,
128                                     &n_stroke_segs);
129 
130   if (n_stroke_segs == 0)
131     return TRUE;
132 
133   gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
134 
135   off_x -= offset_x;
136   off_y -= offset_y;
137 
138   coords = g_new0 (GimpCoords, n_bound_segs + 4);
139 
140   seg      = 0;
141   n_coords = 0;
142 
143   /* we offset all coordinates by 0.5 to align the brush with the path */
144 
145   coords[n_coords]   = default_coords;
146   coords[n_coords].x = (gdouble) (stroke_segs[0].x1 - off_x + 0.5);
147   coords[n_coords].y = (gdouble) (stroke_segs[0].y1 - off_y + 0.5);
148 
149   n_coords++;
150 
151   for (s = 0; s < n_stroke_segs; s++)
152     {
153       while (stroke_segs[seg].x1 != -1 ||
154              stroke_segs[seg].x2 != -1 ||
155              stroke_segs[seg].y1 != -1 ||
156              stroke_segs[seg].y2 != -1)
157         {
158           coords[n_coords]   = default_coords;
159           coords[n_coords].x = (gdouble) (stroke_segs[seg].x1 - off_x + 0.5);
160           coords[n_coords].y = (gdouble) (stroke_segs[seg].y1 - off_y + 0.5);
161 
162           n_coords++;
163           seg++;
164         }
165 
166       /* Close the stroke points up */
167       coords[n_coords] = coords[0];
168 
169       n_coords++;
170 
171       if (emulate_dynamics)
172         gimp_paint_core_stroke_emulate_dynamics (coords, n_coords);
173 
174       if (initialized ||
175           gimp_paint_core_start (core, drawable, paint_options, &coords[0],
176                                  error))
177         {
178           gint i;
179 
180           initialized = TRUE;
181 
182           core->cur_coords  = coords[0];
183           core->last_coords = coords[0];
184 
185           gimp_paint_core_paint (core, drawable, paint_options,
186                                  GIMP_PAINT_STATE_INIT, 0);
187 
188           gimp_paint_core_paint (core, drawable, paint_options,
189                                  GIMP_PAINT_STATE_MOTION, 0);
190 
191           for (i = 1; i < n_coords; i++)
192             {
193               gimp_paint_core_interpolate (core, drawable, paint_options,
194                                            &coords[i], 0);
195             }
196 
197           gimp_paint_core_paint (core, drawable, paint_options,
198                                  GIMP_PAINT_STATE_FINISH, 0);
199         }
200       else
201         {
202           break;
203         }
204 
205       n_coords = 0;
206       seg++;
207 
208       coords[n_coords]   = default_coords;
209       coords[n_coords].x = (gdouble) (stroke_segs[seg].x1 - off_x + 0.5);
210       coords[n_coords].y = (gdouble) (stroke_segs[seg].y1 - off_y + 0.5);
211 
212       n_coords++;
213     }
214 
215   if (initialized)
216     {
217       gimp_paint_core_finish (core, drawable, push_undo);
218 
219       gimp_paint_core_cleanup (core);
220     }
221 
222   g_free (coords);
223   g_free (stroke_segs);
224 
225   return initialized;
226 }
227 
228 gboolean
gimp_paint_core_stroke_vectors(GimpPaintCore * core,GimpDrawable * drawable,GimpPaintOptions * paint_options,gboolean emulate_dynamics,GimpVectors * vectors,gboolean push_undo,GError ** error)229 gimp_paint_core_stroke_vectors (GimpPaintCore     *core,
230                                 GimpDrawable      *drawable,
231                                 GimpPaintOptions  *paint_options,
232                                 gboolean           emulate_dynamics,
233                                 GimpVectors       *vectors,
234                                 gboolean           push_undo,
235                                 GError           **error)
236 {
237   GList    *stroke;
238   gboolean  initialized = FALSE;
239   gboolean  due_to_lack_of_points = FALSE;
240   gint      off_x, off_y;
241   gint      vectors_off_x, vectors_off_y;
242 
243   g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), FALSE);
244   g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
245   g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE);
246   g_return_val_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options), FALSE);
247   g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE);
248   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
249 
250   gimp_item_get_offset (GIMP_ITEM (vectors),  &vectors_off_x, &vectors_off_y);
251   gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
252 
253   off_x -= vectors_off_x;
254   off_y -= vectors_off_y;
255 
256   for (stroke = vectors->strokes->head;
257        stroke;
258        stroke = stroke->next)
259     {
260       GArray   *coords;
261       gboolean  closed;
262 
263       coords = gimp_stroke_interpolate (GIMP_STROKE (stroke->data),
264                                         1.0, &closed);
265 
266       if (coords && coords->len)
267         {
268           gint i;
269 
270           for (i = 0; i < coords->len; i++)
271             {
272               g_array_index (coords, GimpCoords, i).x -= off_x;
273               g_array_index (coords, GimpCoords, i).y -= off_y;
274             }
275 
276           if (emulate_dynamics)
277             gimp_paint_core_stroke_emulate_dynamics ((GimpCoords *) coords->data,
278                                                      coords->len);
279 
280           if (initialized ||
281               gimp_paint_core_start (core, drawable, paint_options,
282                                      &g_array_index (coords, GimpCoords, 0),
283                                      error))
284             {
285               initialized = TRUE;
286 
287               core->cur_coords  = g_array_index (coords, GimpCoords, 0);
288               core->last_coords = g_array_index (coords, GimpCoords, 0);
289 
290               gimp_paint_core_paint (core, drawable, paint_options,
291                                      GIMP_PAINT_STATE_INIT, 0);
292 
293               gimp_paint_core_paint (core, drawable, paint_options,
294                                      GIMP_PAINT_STATE_MOTION, 0);
295 
296               for (i = 1; i < coords->len; i++)
297                 {
298                   gimp_paint_core_interpolate (core, drawable, paint_options,
299                                                &g_array_index (coords, GimpCoords, i),
300                                                0);
301                 }
302 
303               gimp_paint_core_paint (core, drawable, paint_options,
304                                      GIMP_PAINT_STATE_FINISH, 0);
305             }
306           else
307             {
308               if (coords)
309                 g_array_free (coords, TRUE);
310 
311               break;
312             }
313         }
314       else
315         {
316           due_to_lack_of_points = TRUE;
317         }
318 
319       if (coords)
320         g_array_free (coords, TRUE);
321     }
322 
323   if (initialized)
324     {
325       gimp_paint_core_finish (core, drawable, push_undo);
326 
327       gimp_paint_core_cleanup (core);
328     }
329 
330   if (! initialized && due_to_lack_of_points && *error == NULL)
331     {
332       g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
333                            _("Not enough points to stroke"));
334     }
335 
336   return initialized;
337 }
338 
339 static void
gimp_paint_core_stroke_emulate_dynamics(GimpCoords * coords,gint length)340 gimp_paint_core_stroke_emulate_dynamics (GimpCoords *coords,
341                                          gint        length)
342 {
343   const gint ramp_length = length / 3;
344 
345   /* Calculate and create pressure ramp parameters */
346   if (ramp_length > 0)
347     {
348       gdouble slope = 1.0 / (gdouble) (ramp_length);
349       gint    i;
350 
351       /* Calculate pressure start ramp */
352       for (i = 0; i < ramp_length; i++)
353         {
354           coords[i].pressure =  i * slope;
355         }
356 
357       /* Calculate pressure end ramp */
358       for (i = length - ramp_length; i < length; i++)
359         {
360           coords[i].pressure = 1.0 - (i - (length - ramp_length)) * slope;
361         }
362     }
363 
364   /* Calculate and create velocity ramp parameters */
365   if (length > 0)
366     {
367       gdouble slope = 1.0 / length;
368       gint    i;
369 
370       /* Calculate velocity end ramp */
371       for (i = 0; i < length; i++)
372         {
373           coords[i].velocity = i * slope;
374         }
375     }
376 
377   if (length > 1)
378     {
379       gint i;
380       /* Fill in direction */
381       for (i = 1; i < length; i++)
382         {
383           coords[i].direction = gimp_coords_direction (&coords[i-1], &coords[i]);
384         }
385 
386       coords[0].direction = coords[1].direction;
387     }
388 }
389