1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimpsymmetry.c
5 * Copyright (C) 2015 Jehan <jehan@gimp.org>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include <string.h>
24
25 #include <gegl.h>
26 #include <gdk-pixbuf/gdk-pixbuf.h>
27
28 #include "libgimpbase/gimpbase.h"
29 #include "libgimpconfig/gimpconfig.h"
30 #include "libgimpmath/gimpmath.h"
31
32 #include "core-types.h"
33
34 #include "gegl/gimp-gegl-nodes.h"
35
36 #include "gimpdrawable.h"
37 #include "gimpimage.h"
38 #include "gimpimage-symmetry.h"
39 #include "gimpitem.h"
40 #include "gimpsymmetry.h"
41
42 #include "gimp-intl.h"
43
44
45 enum
46 {
47 STROKES_UPDATED,
48 GUI_PARAM_CHANGED,
49 ACTIVE_CHANGED,
50 LAST_SIGNAL
51 };
52
53 enum
54 {
55 PROP_0,
56 PROP_IMAGE,
57 PROP_ACTIVE,
58 PROP_VERSION,
59 };
60
61
62 /* Local function prototypes */
63
64 static void gimp_symmetry_finalize (GObject *object);
65 static void gimp_symmetry_set_property (GObject *object,
66 guint property_id,
67 const GValue *value,
68 GParamSpec *pspec);
69 static void gimp_symmetry_get_property (GObject *object,
70 guint property_id,
71 GValue *value,
72 GParamSpec *pspec);
73
74 static void gimp_symmetry_real_update_strokes (GimpSymmetry *sym,
75 GimpDrawable *drawable,
76 GimpCoords *origin);
77 static void gimp_symmetry_real_get_transform (GimpSymmetry *sym,
78 gint stroke,
79 gdouble *angle,
80 gboolean *reflect);
81 static gboolean gimp_symmetry_real_update_version (GimpSymmetry *sym);
82
83
84 G_DEFINE_TYPE_WITH_CODE (GimpSymmetry, gimp_symmetry, GIMP_TYPE_OBJECT,
85 G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, NULL))
86
87 #define parent_class gimp_symmetry_parent_class
88
89 static guint gimp_symmetry_signals[LAST_SIGNAL] = { 0 };
90
91
92 static void
gimp_symmetry_class_init(GimpSymmetryClass * klass)93 gimp_symmetry_class_init (GimpSymmetryClass *klass)
94 {
95 GObjectClass *object_class = G_OBJECT_CLASS (klass);
96
97 /* This signal should likely be emitted at the end of
98 * update_strokes() if stroke coordinates were changed.
99 */
100 gimp_symmetry_signals[STROKES_UPDATED] =
101 g_signal_new ("strokes-updated",
102 G_TYPE_FROM_CLASS (klass),
103 G_SIGNAL_RUN_FIRST,
104 0,
105 NULL, NULL,
106 NULL,
107 G_TYPE_NONE,
108 1, GIMP_TYPE_IMAGE);
109
110 /* This signal should be emitted when you request a change in the
111 * settings UI. For instance adding some settings (therefore having
112 * a dynamic UI), or changing scale min/max extremes, etc.
113 */
114 gimp_symmetry_signals[GUI_PARAM_CHANGED] =
115 g_signal_new ("gui-param-changed",
116 G_TYPE_FROM_CLASS (klass),
117 G_SIGNAL_RUN_FIRST,
118 0,
119 NULL, NULL,
120 NULL,
121 G_TYPE_NONE,
122 1, GIMP_TYPE_IMAGE);
123
124 gimp_symmetry_signals[ACTIVE_CHANGED] =
125 g_signal_new ("active-changed",
126 G_TYPE_FROM_CLASS (klass),
127 G_SIGNAL_RUN_FIRST,
128 G_STRUCT_OFFSET (GimpSymmetryClass, active_changed),
129 NULL, NULL,
130 NULL,
131 G_TYPE_NONE, 0);
132
133 object_class->finalize = gimp_symmetry_finalize;
134 object_class->set_property = gimp_symmetry_set_property;
135 object_class->get_property = gimp_symmetry_get_property;
136
137 klass->label = _("None");
138 klass->update_strokes = gimp_symmetry_real_update_strokes;
139 klass->get_transform = gimp_symmetry_real_get_transform;
140 klass->active_changed = NULL;
141 klass->update_version = gimp_symmetry_real_update_version;
142
143 g_object_class_install_property (object_class, PROP_IMAGE,
144 g_param_spec_object ("image",
145 NULL, NULL,
146 GIMP_TYPE_IMAGE,
147 GIMP_PARAM_READWRITE |
148 G_PARAM_CONSTRUCT_ONLY));
149 GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ACTIVE,
150 "active",
151 _("Active"),
152 _("Activate symmetry painting"),
153 FALSE,
154 GIMP_PARAM_STATIC_STRINGS);
155
156 GIMP_CONFIG_PROP_INT (object_class, PROP_VERSION,
157 "version",
158 "Symmetry version",
159 "Version of the symmetry object",
160 -1, G_MAXINT, 0,
161 GIMP_PARAM_STATIC_STRINGS);
162 }
163
164 static void
gimp_symmetry_init(GimpSymmetry * sym)165 gimp_symmetry_init (GimpSymmetry *sym)
166 {
167 }
168
169 static void
gimp_symmetry_finalize(GObject * object)170 gimp_symmetry_finalize (GObject *object)
171 {
172 GimpSymmetry *sym = GIMP_SYMMETRY (object);
173
174 gimp_symmetry_clear_origin (sym);
175
176 G_OBJECT_CLASS (parent_class)->finalize (object);
177 }
178
179 static void
gimp_symmetry_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)180 gimp_symmetry_set_property (GObject *object,
181 guint property_id,
182 const GValue *value,
183 GParamSpec *pspec)
184 {
185 GimpSymmetry *sym = GIMP_SYMMETRY (object);
186
187 switch (property_id)
188 {
189 case PROP_IMAGE:
190 sym->image = g_value_get_object (value);
191 break;
192 case PROP_ACTIVE:
193 sym->active = g_value_get_boolean (value);
194 g_signal_emit (sym, gimp_symmetry_signals[ACTIVE_CHANGED], 0,
195 sym->active);
196 break;
197 case PROP_VERSION:
198 sym->version = g_value_get_int (value);
199 break;
200 default:
201 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
202 break;
203 }
204 }
205
206 static void
gimp_symmetry_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)207 gimp_symmetry_get_property (GObject *object,
208 guint property_id,
209 GValue *value,
210 GParamSpec *pspec)
211 {
212 GimpSymmetry *sym = GIMP_SYMMETRY (object);
213
214 switch (property_id)
215 {
216 case PROP_IMAGE:
217 g_value_set_object (value, sym->image);
218 break;
219 case PROP_ACTIVE:
220 g_value_set_boolean (value, sym->active);
221 break;
222 case PROP_VERSION:
223 g_value_set_int (value, sym->version);
224 break;
225 default:
226 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
227 break;
228 }
229 }
230
231 static void
gimp_symmetry_real_update_strokes(GimpSymmetry * sym,GimpDrawable * drawable,GimpCoords * origin)232 gimp_symmetry_real_update_strokes (GimpSymmetry *sym,
233 GimpDrawable *drawable,
234 GimpCoords *origin)
235 {
236 /* The basic symmetry just uses the origin as is. */
237 sym->strokes = g_list_prepend (sym->strokes,
238 g_memdup (origin, sizeof (GimpCoords)));
239 }
240
241 static void
gimp_symmetry_real_get_transform(GimpSymmetry * sym,gint stroke,gdouble * angle,gboolean * reflect)242 gimp_symmetry_real_get_transform (GimpSymmetry *sym,
243 gint stroke,
244 gdouble *angle,
245 gboolean *reflect)
246 {
247 /* The basic symmetry does nothing, since no transformation of the
248 * brush painting happen. */
249 }
250
251 static gboolean
gimp_symmetry_real_update_version(GimpSymmetry * symmetry)252 gimp_symmetry_real_update_version (GimpSymmetry *symmetry)
253 {
254 /* Currently all symmetries are at version 0. So all this check has to
255 * do is verify that we are at version 0.
256 * If one of the child symmetry bumps its version, it will have to
257 * override the update_version() virtual function and do any necessary
258 * update there (for instance new properties, modified properties, or
259 * whatnot).
260 */
261 gint version;
262
263 g_object_get (symmetry,
264 "version", &version,
265 NULL);
266
267 return (version == 0);
268 }
269
270 /***** Public Functions *****/
271
272 /**
273 * gimp_symmetry_set_stateful:
274 * @sym: the #GimpSymmetry
275 * @stateful: whether the symmetry should be stateful or stateless.
276 *
277 * By default, symmetry is made stateless, which means in particular
278 * that the size of points can change from one stroke to the next, and
279 * in particular you cannot map the coordinates from a stroke to the
280 * next. I.e. stroke N at time T+1 is not necessarily the continuation
281 * of stroke N at time T.
282 * To obtain corresponding strokes, stateful tools, such as MyPaint
283 * brushes or the ink tool, need to run this function. They should reset
284 * to stateless behavior once finished painting.
285 *
286 * One of the first consequence of being stateful is that the number of
287 * strokes cannot be changed, so more strokes than possible on canvas
288 * may be computed, and oppositely it will be possible to end up in
289 * cases with missing strokes (e.g. a tiling, theoretically infinite,
290 * won't be for the ink tool if one draws too far out of canvas).
291 **/
292 void
gimp_symmetry_set_stateful(GimpSymmetry * symmetry,gboolean stateful)293 gimp_symmetry_set_stateful (GimpSymmetry *symmetry,
294 gboolean stateful)
295 {
296 symmetry->stateful = stateful;
297 }
298
299 /**
300 * gimp_symmetry_set_origin:
301 * @sym: the #GimpSymmetry
302 * @drawable: the #GimpDrawable where painting will happen
303 * @origin: new base coordinates.
304 *
305 * Set the symmetry to new origin coordinates and drawable.
306 **/
307 void
gimp_symmetry_set_origin(GimpSymmetry * sym,GimpDrawable * drawable,GimpCoords * origin)308 gimp_symmetry_set_origin (GimpSymmetry *sym,
309 GimpDrawable *drawable,
310 GimpCoords *origin)
311 {
312 g_return_if_fail (GIMP_IS_SYMMETRY (sym));
313 g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
314 g_return_if_fail (gimp_item_get_image (GIMP_ITEM (drawable)) == sym->image);
315
316 if (drawable != sym->drawable)
317 {
318 if (sym->drawable)
319 g_object_unref (sym->drawable);
320 sym->drawable = g_object_ref (drawable);
321 }
322
323 if (origin != sym->origin)
324 {
325 g_free (sym->origin);
326 sym->origin = g_memdup (origin, sizeof (GimpCoords));
327 }
328
329 g_list_free_full (sym->strokes, g_free);
330 sym->strokes = NULL;
331
332 GIMP_SYMMETRY_GET_CLASS (sym)->update_strokes (sym, drawable, origin);
333 }
334
335 /**
336 * gimp_symmetry_clear_origin:
337 * @sym: the #GimpSymmetry
338 *
339 * Clear the symmetry's origin coordinates and drawable.
340 **/
341 void
gimp_symmetry_clear_origin(GimpSymmetry * sym)342 gimp_symmetry_clear_origin (GimpSymmetry *sym)
343 {
344 g_return_if_fail (GIMP_IS_SYMMETRY (sym));
345
346 g_clear_object (&sym->drawable);
347
348 g_clear_pointer (&sym->origin, g_free);
349
350 g_list_free_full (sym->strokes, g_free);
351 sym->strokes = NULL;
352 }
353
354 /**
355 * gimp_symmetry_get_origin:
356 * @sym: the #GimpSymmetry
357 *
358 * Returns: the origin stroke coordinates.
359 * The returned value is owned by the #GimpSymmetry and must not be freed.
360 **/
361 GimpCoords *
gimp_symmetry_get_origin(GimpSymmetry * sym)362 gimp_symmetry_get_origin (GimpSymmetry *sym)
363 {
364 g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
365
366 return sym->origin;
367 }
368
369 /**
370 * gimp_symmetry_get_size:
371 * @sym: the #GimpSymmetry
372 *
373 * Returns: the total number of strokes.
374 **/
375 gint
gimp_symmetry_get_size(GimpSymmetry * sym)376 gimp_symmetry_get_size (GimpSymmetry *sym)
377 {
378 g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), 0);
379
380 return g_list_length (sym->strokes);
381 }
382
383 /**
384 * gimp_symmetry_get_coords:
385 * @sym: the #GimpSymmetry
386 * @stroke: the stroke number
387 *
388 * Returns: the coordinates of the stroke number @stroke.
389 * The returned value is owned by the #GimpSymmetry and must not be freed.
390 **/
391 GimpCoords *
gimp_symmetry_get_coords(GimpSymmetry * sym,gint stroke)392 gimp_symmetry_get_coords (GimpSymmetry *sym,
393 gint stroke)
394 {
395 g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
396
397 return g_list_nth_data (sym->strokes, stroke);
398 }
399
400 /**
401 * gimp_symmetry_get_transform:
402 * @sym: the #GimpSymmetry
403 * @stroke: the stroke number
404 * @angle: output pointer to the transformation rotation angle,
405 * in degrees (ccw)
406 * @reflect: output pointer to the transformation reflection flag
407 *
408 * Returns: the transformation to apply to the paint content for stroke
409 * number @stroke. The transformation is comprised of rotation, possibly
410 * followed by horizontal reflection, around the stroke coordinates.
411 **/
412 void
gimp_symmetry_get_transform(GimpSymmetry * sym,gint stroke,gdouble * angle,gboolean * reflect)413 gimp_symmetry_get_transform (GimpSymmetry *sym,
414 gint stroke,
415 gdouble *angle,
416 gboolean *reflect)
417 {
418 g_return_if_fail (GIMP_IS_SYMMETRY (sym));
419 g_return_if_fail (angle != NULL);
420 g_return_if_fail (reflect != NULL);
421
422 *angle = 0.0;
423 *reflect = FALSE;
424
425 GIMP_SYMMETRY_GET_CLASS (sym)->get_transform (sym,
426 stroke,
427 angle,
428 reflect);
429 }
430
431 /**
432 * gimp_symmetry_get_matrix:
433 * @sym: the #GimpSymmetry
434 * @stroke: the stroke number
435 * @matrix: output pointer to the transformation matrix
436 *
437 * Returns: the transformation matrix to apply to the paint content for stroke
438 * number @stroke.
439 **/
440 void
gimp_symmetry_get_matrix(GimpSymmetry * sym,gint stroke,GimpMatrix3 * matrix)441 gimp_symmetry_get_matrix (GimpSymmetry *sym,
442 gint stroke,
443 GimpMatrix3 *matrix)
444 {
445 gdouble angle;
446 gboolean reflect;
447
448 g_return_if_fail (GIMP_IS_SYMMETRY (sym));
449 g_return_if_fail (matrix != NULL);
450
451 gimp_symmetry_get_transform (sym, stroke, &angle, &reflect);
452
453 gimp_matrix3_identity (matrix);
454 gimp_matrix3_rotate (matrix, -gimp_deg_to_rad (angle));
455 if (reflect)
456 gimp_matrix3_scale (matrix, -1.0, 1.0);
457 }
458
459 /**
460 * gimp_symmetry_get_operation:
461 * @sym: the #GimpSymmetry
462 * @stroke: the stroke number
463 *
464 * Returns: the transformation operation to apply to the paint content for
465 * stroke number @stroke, or NULL for the identity transformation.
466 *
467 * The returned #GeglNode should be freed by the caller.
468 **/
469 GeglNode *
gimp_symmetry_get_operation(GimpSymmetry * sym,gint stroke)470 gimp_symmetry_get_operation (GimpSymmetry *sym,
471 gint stroke)
472 {
473 GimpMatrix3 matrix;
474
475 g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
476
477 gimp_symmetry_get_matrix (sym, stroke, &matrix);
478
479 if (gimp_matrix3_is_identity (&matrix))
480 return NULL;
481
482 return gimp_gegl_create_transform_node (&matrix);
483 }
484
485 /*
486 * gimp_symmetry_parasite_name:
487 * @type: the #GimpSymmetry's #GType
488 *
489 * Returns: a newly allocated string.
490 */
491 gchar *
gimp_symmetry_parasite_name(GType type)492 gimp_symmetry_parasite_name (GType type)
493 {
494 return g_strconcat ("gimp-image-symmetry:", g_type_name (type), NULL);
495 }
496
497 GimpParasite *
gimp_symmetry_to_parasite(const GimpSymmetry * sym)498 gimp_symmetry_to_parasite (const GimpSymmetry *sym)
499 {
500 GimpParasite *parasite;
501 gchar *parasite_name;
502 gchar *str;
503
504 g_return_val_if_fail (GIMP_IS_SYMMETRY (sym), NULL);
505
506 str = gimp_config_serialize_to_string (GIMP_CONFIG (sym), NULL);
507 g_return_val_if_fail (str != NULL, NULL);
508
509 parasite_name = gimp_symmetry_parasite_name (G_TYPE_FROM_INSTANCE (sym));
510 parasite = gimp_parasite_new (parasite_name,
511 GIMP_PARASITE_PERSISTENT,
512 strlen (str) + 1, str);
513 g_free (parasite_name);
514 g_free (str);
515
516 return parasite;
517 }
518
519 GimpSymmetry *
gimp_symmetry_from_parasite(const GimpParasite * parasite,GimpImage * image,GType type)520 gimp_symmetry_from_parasite (const GimpParasite *parasite,
521 GimpImage *image,
522 GType type)
523 {
524 GimpSymmetry *symmetry;
525 gchar *parasite_name;
526 const gchar *str;
527 GError *error = NULL;
528
529 parasite_name = gimp_symmetry_parasite_name (type);
530
531 g_return_val_if_fail (parasite != NULL, NULL);
532 g_return_val_if_fail (strcmp (gimp_parasite_name (parasite),
533 parasite_name) == 0,
534 NULL);
535
536 str = gimp_parasite_data (parasite);
537
538 if (! str)
539 {
540 g_warning ("Empty symmetry parasite \"%s\"", parasite_name);
541
542 return NULL;
543 }
544
545 symmetry = gimp_image_symmetry_new (image, type);
546
547 g_object_set (symmetry,
548 "version", -1,
549 NULL);
550
551 if (! gimp_config_deserialize_string (GIMP_CONFIG (symmetry),
552 str,
553 gimp_parasite_data_size (parasite),
554 NULL,
555 &error))
556 {
557 g_printerr ("Failed to deserialize symmetry parasite: %s\n"
558 "\t- parasite name: %s\n\t- parasite data: %s\n",
559 error->message, parasite_name, str);
560 g_error_free (error);
561
562 g_object_unref (symmetry);
563 symmetry = NULL;
564 }
565 g_free (parasite_name);
566
567 if (symmetry)
568 {
569 gint version;
570
571 g_object_get (symmetry,
572 "version", &version,
573 NULL);
574 if (version == -1)
575 {
576 /* If version has not been updated, let's assume this parasite was
577 * not representing symmetry settings.
578 */
579 g_object_unref (symmetry);
580 symmetry = NULL;
581 }
582 else if (GIMP_SYMMETRY_GET_CLASS (symmetry)->update_version (symmetry) &&
583 ! GIMP_SYMMETRY_GET_CLASS (symmetry)->update_version (symmetry))
584 {
585 g_object_unref (symmetry);
586 symmetry = NULL;
587 }
588 }
589
590 return symmetry;
591 }
592