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