/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * gimpsymmetry-tiling.c * Copyright (C) 2015 Jehan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include "libgimpconfig/gimpconfig.h" #include "core-types.h" #include "gimp.h" #include "gimpdrawable.h" #include "gimpimage.h" #include "gimpitem.h" #include "gimpsymmetry-tiling.h" #include "gimp-intl.h" /* Using same epsilon as in GLIB. */ #define G_DOUBLE_EPSILON (1e-90) enum { PROP_0, PROP_INTERVAL_X, PROP_INTERVAL_Y, PROP_SHIFT, PROP_MAX_X, PROP_MAX_Y }; /* Local function prototypes */ static void gimp_tiling_constructed (GObject *object); static void gimp_tiling_finalize (GObject *object); static void gimp_tiling_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void gimp_tiling_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void gimp_tiling_update_strokes (GimpSymmetry *tiling, GimpDrawable *drawable, GimpCoords *origin); static void gimp_tiling_image_size_changed_cb (GimpImage *image, gint previous_origin_x, gint previous_origin_y, gint previous_width, gint previous_height, GimpSymmetry *sym); G_DEFINE_TYPE (GimpTiling, gimp_tiling, GIMP_TYPE_SYMMETRY) #define parent_class gimp_tiling_parent_class static void gimp_tiling_class_init (GimpTilingClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GimpSymmetryClass *symmetry_class = GIMP_SYMMETRY_CLASS (klass); GParamSpec *pspec; object_class->constructed = gimp_tiling_constructed; object_class->finalize = gimp_tiling_finalize; object_class->set_property = gimp_tiling_set_property; object_class->get_property = gimp_tiling_get_property; symmetry_class->label = _("Tiling"); symmetry_class->update_strokes = gimp_tiling_update_strokes; GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_INTERVAL_X, "interval-x", _("Interval X"), _("Interval on the X axis (pixels)"), 0.0, G_MAXDOUBLE, 0.0, GIMP_PARAM_STATIC_STRINGS | GIMP_SYMMETRY_PARAM_GUI); pspec = g_object_class_find_property (object_class, "interval-x"); gegl_param_spec_set_property_key (pspec, "unit", "pixel-distance"); gegl_param_spec_set_property_key (pspec, "axis", "x"); GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_INTERVAL_Y, "interval-y", _("Interval Y"), _("Interval on the Y axis (pixels)"), 0.0, G_MAXDOUBLE, 0.0, GIMP_PARAM_STATIC_STRINGS | GIMP_SYMMETRY_PARAM_GUI); pspec = g_object_class_find_property (object_class, "interval-y"); gegl_param_spec_set_property_key (pspec, "unit", "pixel-distance"); gegl_param_spec_set_property_key (pspec, "axis", "y"); GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_SHIFT, "shift", _("Shift"), _("X-shift between lines (pixels)"), 0.0, G_MAXDOUBLE, 0.0, GIMP_PARAM_STATIC_STRINGS | GIMP_SYMMETRY_PARAM_GUI); pspec = g_object_class_find_property (object_class, "shift"); gegl_param_spec_set_property_key (pspec, "unit", "pixel-distance"); gegl_param_spec_set_property_key (pspec, "axis", "x"); GIMP_CONFIG_PROP_INT (object_class, PROP_MAX_X, "max-x", _("Max strokes X"), _("Maximum number of strokes on the X axis"), 0, 100, 0, GIMP_PARAM_STATIC_STRINGS | GIMP_SYMMETRY_PARAM_GUI); GIMP_CONFIG_PROP_INT (object_class, PROP_MAX_Y, "max-y", _("Max strokes Y"), _("Maximum number of strokes on the Y axis"), 0, 100, 0, GIMP_PARAM_STATIC_STRINGS | GIMP_SYMMETRY_PARAM_GUI); } static void gimp_tiling_init (GimpTiling *tiling) { } static void gimp_tiling_constructed (GObject *object) { GimpSymmetry *sym = GIMP_SYMMETRY (object); GimpTiling *tiling = GIMP_TILING (object); g_signal_connect_object (sym->image, "size-changed-detailed", G_CALLBACK (gimp_tiling_image_size_changed_cb), sym, 0); /* Set reasonable defaults. */ tiling->interval_x = gimp_image_get_width (sym->image) / 2; tiling->interval_y = gimp_image_get_height (sym->image) / 2; } static void gimp_tiling_finalize (GObject *object) { G_OBJECT_CLASS (parent_class)->finalize (object); } static void gimp_tiling_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GimpTiling *tiling = GIMP_TILING (object); GimpSymmetry *sym = GIMP_SYMMETRY (tiling); switch (property_id) { case PROP_INTERVAL_X: if (sym->image) { gdouble new_x = g_value_get_double (value); if (new_x < gimp_image_get_width (sym->image)) { tiling->interval_x = new_x; if (tiling->interval_x <= tiling->shift + G_DOUBLE_EPSILON) { GValue val = G_VALUE_INIT; g_value_init (&val, G_TYPE_DOUBLE); g_value_set_double (&val, 0.0); g_object_set_property (G_OBJECT (object), "shift", &val); } if (sym->drawable) gimp_tiling_update_strokes (sym, sym->drawable, sym->origin); } } break; case PROP_INTERVAL_Y: { gdouble new_y = g_value_get_double (value); if (new_y < gimp_image_get_height (sym->image)) { tiling->interval_y = new_y; if (tiling->interval_y <= G_DOUBLE_EPSILON) { GValue val = G_VALUE_INIT; g_value_init (&val, G_TYPE_DOUBLE); g_value_set_double (&val, 0.0); g_object_set_property (G_OBJECT (object), "shift", &val); } if (sym->drawable) gimp_tiling_update_strokes (sym, sym->drawable, sym->origin); } } break; case PROP_SHIFT: { gdouble new_shift = g_value_get_double (value); if (new_shift == 0.0 || (tiling->interval_y != 0.0 && new_shift < tiling->interval_x)) { tiling->shift = new_shift; if (sym->drawable) gimp_tiling_update_strokes (sym, sym->drawable, sym->origin); } } break; case PROP_MAX_X: tiling->max_x = g_value_get_int (value); if (sym->drawable) gimp_tiling_update_strokes (sym, sym->drawable, sym->origin); break; case PROP_MAX_Y: tiling->max_y = g_value_get_int (value); if (sym->drawable) gimp_tiling_update_strokes (sym, sym->drawable, sym->origin); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gimp_tiling_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GimpTiling *tiling = GIMP_TILING (object); switch (property_id) { case PROP_INTERVAL_X: g_value_set_double (value, tiling->interval_x); break; case PROP_INTERVAL_Y: g_value_set_double (value, tiling->interval_y); break; case PROP_SHIFT: g_value_set_double (value, tiling->shift); break; case PROP_MAX_X: g_value_set_int (value, tiling->max_x); break; case PROP_MAX_Y: g_value_set_int (value, tiling->max_y); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gimp_tiling_update_strokes (GimpSymmetry *sym, GimpDrawable *drawable, GimpCoords *origin) { GimpTiling *tiling = GIMP_TILING (sym); GList *strokes = NULL; GimpCoords *coords; gdouble width; gdouble height; gdouble startx = origin->x; gdouble starty = origin->y; gdouble x; gdouble y; gint x_count; gint y_count; g_list_free_full (sym->strokes, g_free); sym->strokes = NULL; width = gimp_item_get_width (GIMP_ITEM (drawable)); height = gimp_item_get_height (GIMP_ITEM (drawable)); if (sym->stateful) { /* While I can compute exactly the right number of strokes to * paint on-canvas for stateless tools, stateful tools need to * always have the same number and order of strokes. For this * reason, I compute strokes to fill 2 times the width and height. * This makes the symmetry less efficient with stateful tools, but * also weird behavior may happen if you decide to paint out of * canvas and expect tiling to work in-canvas since it won't * actually be infinite (as no new strokes can be added while * painting since we are stateful). */ gint i, j; if (tiling->interval_x < 1.0) { x_count = 1; } else if (tiling->max_x == 0) { x_count = (gint) ceil (width / tiling->interval_x); startx -= tiling->interval_x * (gdouble) x_count; x_count = 2 * x_count + 1; } else { x_count = tiling->max_x; } if (tiling->interval_y < 1.0) { y_count = 1; } else if (tiling->max_y == 0) { y_count = (gint) ceil (height / tiling->interval_y); starty -= tiling->interval_y * (gdouble) y_count; y_count = 2 * y_count + 1; } else { y_count = tiling->max_y; } for (i = 0, x = startx; i < x_count; i++) { for (j = 0, y = starty; j < y_count; j++) { coords = g_memdup (origin, sizeof (GimpCoords)); coords->x = x; coords->y = y; strokes = g_list_prepend (strokes, coords); y += tiling->interval_y; } x += tiling->interval_x; } } else { if (origin->x > 0 && tiling->max_x == 0 && tiling->interval_x >= 1.0) startx = fmod (origin->x, tiling->interval_x) - tiling->interval_x; if (origin->y > 0 && tiling->max_y == 0 && tiling->interval_y >= 1.0) { starty = fmod (origin->y, tiling->interval_y) - tiling->interval_y; if (tiling->shift > 0.0) startx -= tiling->shift * floor (origin->y / tiling->interval_y + 1); } for (y_count = 0, y = starty; y < height + tiling->interval_y; y_count++, y += tiling->interval_y) { if (tiling->max_y && y_count >= tiling->max_y) break; for (x_count = 0, x = startx; x < width + tiling->interval_x; x_count++, x += tiling->interval_x) { if (tiling->max_x && x_count >= tiling->max_x) break; coords = g_memdup (origin, sizeof (GimpCoords)); coords->x = x; coords->y = y; strokes = g_list_prepend (strokes, coords); if (tiling->interval_x < 1.0) break; } if (tiling->max_x || startx + tiling->shift <= 0.0) startx = startx + tiling->shift; else startx = startx - tiling->interval_x + tiling->shift; if (tiling->interval_y < 1.0) break; } } sym->strokes = strokes; g_signal_emit_by_name (sym, "strokes-updated", sym->image); } static void gimp_tiling_image_size_changed_cb (GimpImage *image, gint previous_origin_x, gint previous_origin_y, gint previous_width, gint previous_height, GimpSymmetry *sym) { if (previous_width != gimp_image_get_width (image) || previous_height != gimp_image_get_height (image)) { g_signal_emit_by_name (sym, "gui-param-changed", sym->image); } }