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