[gimp/gimp-2-10] app: streamline GimpCurve



commit b5da9918f1d7b90ebb230602cbfd9c4569449a61
Author: Ell <ell_se yahoo com>
Date:   Fri Apr 19 04:24:24 2019 -0400

    app: streamline GimpCurve
    
    In GimpCurve, replace the use of a fixed-length control-point array
    with a dynamically-sized array.  Adapt GimpCurve's interface, and
    the rest of the code.
    
    In addition to simplifying the code, this fixes a bug where the
    curve object could be broken by moving the mouse too fast (yep...),
    and allows more accurate point placement, both in the GUI editor,
    and through canvas interaction in the Curves tool (see issue #814).
    
    (cherry picked from commit b6d829a1b2f71d03185f0f7ed7c6b80fc0c2dfe6)

 app/core/gimpcurve.c              | 372 +++++++++++++++++++++++---------------
 app/core/gimpcurve.h              |  16 +-
 app/operations/gimpcurvesconfig.c |  81 ++++-----
 app/operations/gimplevelsconfig.c |  17 +-
 app/tools/gimpcurvestool.c        |  39 ++--
 app/widgets/gimpcurveview.c       | 218 ++++++++++++----------
 6 files changed, 415 insertions(+), 328 deletions(-)
---
diff --git a/app/core/gimpcurve.c b/app/core/gimpcurve.c
index 247158c7ed..2a8e1f4302 100644
--- a/app/core/gimpcurve.c
+++ b/app/core/gimpcurve.c
@@ -37,6 +37,9 @@
 #include "gimp-intl.h"
 
 
+#define EPSILON 1e-6
+
+
 enum
 {
   PROP_0,
@@ -155,7 +158,9 @@ gimp_curve_class_init (GimpCurveClass *klass)
                         "n-points",
                         "Number of Points",
                         "The number of points",
-                        17, 17, 17, 0);
+                        0, G_MAXINT, 0,
+                        /* for backward compatibility */
+                        GIMP_CONFIG_PARAM_IGNORE);
 
   array_spec = g_param_spec_double ("point", NULL, NULL,
                                     -1.0, 1.0, 0.0, GIMP_PARAM_READWRITE);
@@ -237,7 +242,7 @@ gimp_curve_set_property (GObject      *object,
       break;
 
     case PROP_N_POINTS:
-      gimp_curve_set_n_points (curve, g_value_get_int (value));
+      /* ignored */
       break;
 
     case PROP_POINTS:
@@ -246,19 +251,41 @@ gimp_curve_set_property (GObject      *object,
         gint            length;
         gint            i;
 
+        curve->n_points = 0;
+        g_clear_pointer (&curve->points, g_free);
+
         if (! array)
           break;
 
-        length = gimp_value_array_length (array);
+        length = gimp_value_array_length (array) / 2;
+
+        curve->points = g_new (GimpVector2, length);
 
-        for (i = 0; i < curve->n_points && i * 2 < length; i++)
+        for (i = 0; i < length; i++)
           {
             GValue *x = gimp_value_array_index (array, i * 2);
             GValue *y = gimp_value_array_index (array, i * 2 + 1);
 
-            curve->points[i].x = g_value_get_double (x);
-            curve->points[i].y = g_value_get_double (y);
+            /* for backward compatibility */
+            if (g_value_get_double (x) < 0.0)
+              continue;
+
+            curve->points[curve->n_points].x = CLAMP (g_value_get_double (x),
+                                                      0.0, 1.0);
+            curve->points[curve->n_points].y = CLAMP (g_value_get_double (y),
+                                                      0.0, 1.0);
+
+            if (curve->n_points > 0)
+              {
+                curve->points[curve->n_points].x = MAX (
+                  curve->points[curve->n_points].x,
+                  curve->points[curve->n_points - 1].x);
+              }
+
+            curve->n_points++;
           }
+
+        g_object_notify (object, "n-points");
       }
       break;
 
@@ -281,7 +308,7 @@ gimp_curve_set_property (GObject      *object,
           {
             GValue *v = gimp_value_array_index (array, i);
 
-            curve->samples[i] = g_value_get_double (v);
+            curve->samples[i] = CLAMP (g_value_get_double (v), 0.0, 1.0);
           }
       }
       break;
@@ -484,11 +511,19 @@ gimp_curve_equal (GimpConfig *a,
   if (a_curve->curve_type != b_curve->curve_type)
     return FALSE;
 
-  if (memcmp (a_curve->points, b_curve->points,
-              sizeof (GimpVector2) * b_curve->n_points) ||
+  if (a_curve->n_points != b_curve->n_points ||
+      memcmp (a_curve->points, b_curve->points,
+              sizeof (GimpVector2) * a_curve->n_points))
+    {
+      return FALSE;
+    }
+
+  if (a_curve->n_samples != b_curve->n_samples ||
       memcmp (a_curve->samples, b_curve->samples,
-              sizeof (gdouble) * b_curve->n_samples))
-    return FALSE;
+              sizeof (gdouble) * a_curve->n_samples))
+    {
+      return FALSE;
+    }
 
   return TRUE;
 }
@@ -564,18 +599,18 @@ gimp_curve_reset (GimpCurve *curve,
 
   g_object_notify (G_OBJECT (curve), "samples");
 
+  g_free (curve->points);
+
+  curve->n_points = 2;
+  curve->points   = g_new (GimpVector2, 2);
+
   curve->points[0].x = 0.0;
   curve->points[0].y = 0.0;
 
-  for (i = 1; i < curve->n_points - 1; i++)
-    {
-      curve->points[i].x = -1.0;
-      curve->points[i].y = -1.0;
-    }
-
-  curve->points[curve->n_points - 1].x = 1.0;
-  curve->points[curve->n_points - 1].y = 1.0;
+  curve->points[1].x = 1.0;
+  curve->points[1].y = 1.0;
 
+  g_object_notify (G_OBJECT (curve), "n-points");
   g_object_notify (G_OBJECT (curve), "points");
 
   if (reset_type)
@@ -599,44 +634,46 @@ gimp_curve_set_curve_type (GimpCurve     *curve,
 
   if (curve->curve_type != curve_type)
     {
+      gimp_data_freeze (GIMP_DATA (curve));
+
       g_object_freeze_notify (G_OBJECT (curve));
 
       curve->curve_type = curve_type;
 
       if (curve_type == GIMP_CURVE_SMOOTH)
         {
-          gint n_points;
           gint i;
 
-          for (i = 0; i < curve->n_points; i++)
-            {
-              curve->points[i].x = -1;
-              curve->points[i].y = -1;
-            }
+          g_free (curve->points);
 
           /*  pick some points from the curve and make them control
            *  points
            */
-          n_points = CLAMP (9, curve->n_points / 2, curve->n_points);
+          curve->n_points = 9;
+          curve->points   = g_new (GimpVector2, 9);
 
-          for (i = 0; i < n_points; i++)
+          for (i = 0; i < curve->n_points; i++)
             {
-              gint sample = i * (curve->n_samples - 1) / (n_points - 1);
-              gint point  = i * (curve->n_points  - 1) / (n_points - 1);
+              gint sample = i * (curve->n_samples - 1) / (curve->n_points - 1);
 
-              curve->points[point].x = ((gdouble) sample /
-                                        (gdouble) (curve->n_samples - 1));
-              curve->points[point].y = curve->samples[sample];
+              curve->points[i].x = (gdouble) sample /
+                                   (gdouble) (curve->n_samples - 1);
+              curve->points[i].y = curve->samples[sample];
             }
 
+          g_object_notify (G_OBJECT (curve), "n-points");
           g_object_notify (G_OBJECT (curve), "points");
         }
+      else
+        {
+          gimp_curve_clear_points (curve);
+        }
 
       g_object_notify (G_OBJECT (curve), "curve-type");
 
       g_object_thaw_notify (G_OBJECT (curve));
 
-      gimp_data_dirty (GIMP_DATA (curve));
+      gimp_data_thaw (GIMP_DATA (curve));
     }
 }
 
@@ -648,46 +685,6 @@ gimp_curve_get_curve_type (GimpCurve *curve)
   return curve->curve_type;
 }
 
-void
-gimp_curve_set_n_points (GimpCurve *curve,
-                         gint       n_points)
-{
-  g_return_if_fail (GIMP_IS_CURVE (curve));
-  g_return_if_fail (n_points >= 2);
-  g_return_if_fail (n_points <= 1024);
-
-  if (n_points != curve->n_points)
-    {
-      gint i;
-
-      g_object_freeze_notify (G_OBJECT (curve));
-
-      curve->n_points = n_points;
-      g_object_notify (G_OBJECT (curve), "n-points");
-
-      curve->points = g_renew (GimpVector2, curve->points, curve->n_points);
-
-      curve->points[0].x = 0.0;
-      curve->points[0].y = 0.0;
-
-      for (i = 1; i < curve->n_points - 1; i++)
-        {
-          curve->points[i].x = -1.0;
-          curve->points[i].y = -1.0;
-        }
-
-      curve->points[curve->n_points - 1].x = 1.0;
-      curve->points[curve->n_points - 1].y = 1.0;
-
-      g_object_notify (G_OBJECT (curve), "points");
-
-      if (curve->curve_type == GIMP_CURVE_SMOOTH)
-        curve->identity = TRUE;
-
-      g_object_thaw_notify (G_OBJECT (curve));
-    }
-}
-
 gint
 gimp_curve_get_n_points (GimpCurve *curve)
 {
@@ -736,66 +733,152 @@ gimp_curve_get_n_samples (GimpCurve *curve)
 }
 
 gint
-gimp_curve_get_closest_point (GimpCurve *curve,
-                              gdouble    x)
+gimp_curve_get_point_at (GimpCurve *curve,
+                         gdouble    x)
 {
-  gint    closest_point = 0;
-  gdouble distance      = G_MAXDOUBLE;
+  gint    closest_point = -1;
+  gdouble distance      = EPSILON;
   gint    i;
 
-  g_return_val_if_fail (GIMP_IS_CURVE (curve), 0);
+  g_return_val_if_fail (GIMP_IS_CURVE (curve), -1);
 
   for (i = 0; i < curve->n_points; i++)
     {
-      if (curve->points[i].x >= 0.0 &&
-          fabs (x - curve->points[i].x) < distance)
+      gdouble point_distance;
+
+      point_distance = fabs (x - curve->points[i].x);
+
+      if (point_distance <= distance)
         {
-          distance = fabs (x - curve->points[i].x);
           closest_point = i;
+          distance      = point_distance;
         }
     }
 
-  if (distance > (1.0 / (curve->n_points * 2.0)))
-    closest_point = ROUND (x * (gdouble) (curve->n_points - 1));
+  return closest_point;
+}
+
+gint
+gimp_curve_get_closest_point (GimpCurve *curve,
+                              gdouble    x,
+                              gdouble    y,
+                              gdouble    max_distance)
+{
+  gint    closest_point = -1;
+  gdouble distance2     = G_MAXDOUBLE;
+  gint    i;
+
+  g_return_val_if_fail (GIMP_IS_CURVE (curve), -1);
+
+  if (max_distance >= 0.0)
+    distance2 = SQR (max_distance);
+
+  for (i = curve->n_points - 1; i >= 0; i--)
+    {
+      gdouble point_distance2;
+
+      point_distance2 = SQR (x - curve->points[i].x) +
+                        SQR (y - curve->points[i].y);
+
+      if (point_distance2 <= distance2)
+        {
+          closest_point = i;
+          distance2     = point_distance2;
+        }
+    }
 
   return closest_point;
 }
 
-void
-gimp_curve_set_point (GimpCurve *curve,
-                      gint       point,
+gint
+gimp_curve_add_point (GimpCurve *curve,
                       gdouble    x,
                       gdouble    y)
 {
+  GimpVector2 *points;
+  gint         point;
+
+  g_return_val_if_fail (GIMP_IS_CURVE (curve), -1);
+
+  if (curve->curve_type == GIMP_CURVE_FREE)
+    return -1;
+
+  x = CLAMP (x, 0.0, 1.0);
+  y = CLAMP (y, 0.0, 1.0);
+
+  for (point = 0; point < curve->n_points; point++)
+    {
+      if (curve->points[point].x > x)
+        break;
+    }
+
+  points = g_new (GimpVector2, curve->n_points + 1);
+
+  memcpy (points,         curve->points,
+          point * sizeof (GimpVector2));
+  memcpy (points + point + 1, curve->points + point,
+          (curve->n_points - point) * sizeof (GimpVector2));
+
+  points[point].x = x;
+  points[point].y = y;
+
+  g_free (curve->points);
+
+  curve->n_points++;
+  curve->points = points;
+
+  g_object_notify (G_OBJECT (curve), "n-points");
+  g_object_notify (G_OBJECT (curve), "points");
+
+  gimp_data_dirty (GIMP_DATA (curve));
+
+  return point;
+}
+
+void
+gimp_curve_delete_point (GimpCurve *curve,
+                         gint       point)
+{
+  GimpVector2 *points;
+
   g_return_if_fail (GIMP_IS_CURVE (curve));
   g_return_if_fail (point >= 0 && point < curve->n_points);
-  g_return_if_fail (x == -1.0 || (x >= 0 && x <= 1.0));
-  g_return_if_fail (y == -1.0 || (y >= 0 && y <= 1.0));
 
-  if (curve->curve_type == GIMP_CURVE_FREE)
-    return;
+  points = g_new (GimpVector2, curve->n_points - 1);
 
-  curve->points[point].x = x;
-  curve->points[point].y = y;
+  memcpy (points,         curve->points,
+          point * sizeof (GimpVector2));
+  memcpy (points + point, curve->points + point + 1,
+          (curve->n_points - point - 1) * sizeof (GimpVector2));
 
+  g_free (curve->points);
+
+  curve->n_points--;
+  curve->points = points;
+
+  g_object_notify (G_OBJECT (curve), "n-points");
   g_object_notify (G_OBJECT (curve), "points");
 
   gimp_data_dirty (GIMP_DATA (curve));
 }
 
 void
-gimp_curve_move_point (GimpCurve *curve,
-                       gint       point,
-                       gdouble    y)
+gimp_curve_set_point (GimpCurve *curve,
+                      gint       point,
+                      gdouble    x,
+                      gdouble    y)
 {
   g_return_if_fail (GIMP_IS_CURVE (curve));
   g_return_if_fail (point >= 0 && point < curve->n_points);
-  g_return_if_fail (y >= 0 && y <= 1.0);
 
-  if (curve->curve_type == GIMP_CURVE_FREE)
-    return;
+  curve->points[point].x = CLAMP (x, 0.0, 1.0);
+  curve->points[point].y = CLAMP (y, 0.0, 1.0);
 
-  curve->points[point].y = y;
+  if (point > 0)
+    curve->points[point].x = MAX (x, curve->points[point - 1].x);
+
+  if (point < curve->n_points - 1)
+    curve->points[point].x = MIN (x, curve->points[point + 1].x);
 
   g_object_notify (G_OBJECT (curve), "points");
 
@@ -803,27 +886,14 @@ gimp_curve_move_point (GimpCurve *curve,
 }
 
 void
-gimp_curve_delete_point (GimpCurve *curve,
-                         gint       point)
+gimp_curve_move_point (GimpCurve *curve,
+                       gint       point,
+                       gdouble    y)
 {
   g_return_if_fail (GIMP_IS_CURVE (curve));
   g_return_if_fail (point >= 0 && point < curve->n_points);
 
-  if (point == 0)
-    {
-      curve->points[0].x = 0.0;
-      curve->points[0].y = 0.0;
-    }
-  else if (point == curve->n_points - 1)
-    {
-      curve->points[curve->n_points - 1].x = 1.0;
-      curve->points[curve->n_points - 1].y = 1.0;
-    }
-  else
-    {
-      curve->points[point].x = -1.0;
-      curve->points[point].y = -1.0;
-    }
+  curve->points[point].y = CLAMP (y, 0.0, 1.0);
 
   g_object_notify (G_OBJECT (curve), "points");
 
@@ -839,16 +909,25 @@ gimp_curve_get_point (GimpCurve *curve,
   g_return_if_fail (GIMP_IS_CURVE (curve));
   g_return_if_fail (point >= 0 && point < curve->n_points);
 
-  if (curve->curve_type == GIMP_CURVE_FREE)
+  if (x) *x = curve->points[point].x;
+  if (y) *y = curve->points[point].y;
+}
+
+void
+gimp_curve_clear_points (GimpCurve *curve)
+{
+  g_return_if_fail (GIMP_IS_CURVE (curve));
+
+  if (curve->points)
     {
-      if (x) *x = -1.0;
-      if (y) *y = -1.0;
+      curve->n_points = 0;
+      g_clear_pointer (&curve->points, g_free);
 
-      return;
-    }
+      g_object_notify (G_OBJECT (curve), "n-points");
+      g_object_notify (G_OBJECT (curve), "points");
 
-  if (x) *x = curve->points[point].x;
-  if (y) *y = curve->points[point].y;
+      gimp_data_dirty (GIMP_DATA (curve));
+    }
 }
 
 void
@@ -909,59 +988,49 @@ gimp_curve_get_uchar (GimpCurve *curve,
 static void
 gimp_curve_calculate (GimpCurve *curve)
 {
-  gint *points;
-  gint  i;
-  gint  num_pts;
-  gint  p1, p2, p3, p4;
+  gint i;
+  gint p1, p2, p3, p4;
 
   if (gimp_data_is_frozen (GIMP_DATA (curve)))
     return;
 
-  points = g_newa (gint, curve->n_points);
-
   switch (curve->curve_type)
     {
     case GIMP_CURVE_SMOOTH:
-      /*  cycle through the curves  */
-      num_pts = 0;
-      for (i = 0; i < curve->n_points; i++)
-        if (curve->points[i].x >= 0.0)
-          points[num_pts++] = i;
-
       /*  Initialize boundary curve points */
-      if (num_pts != 0)
+      if (curve->n_points > 0)
         {
           GimpVector2 point;
           gint        boundary;
 
-          point    = curve->points[points[0]];
+          point    = curve->points[0];
           boundary = ROUND (point.x * (gdouble) (curve->n_samples - 1));
 
           for (i = 0; i < boundary; i++)
             curve->samples[i] = point.y;
 
-          point    = curve->points[points[num_pts - 1]];
+          point    = curve->points[curve->n_points - 1];
           boundary = ROUND (point.x * (gdouble) (curve->n_samples - 1));
 
           for (i = boundary; i < curve->n_samples; i++)
             curve->samples[i] = point.y;
         }
 
-      for (i = 0; i < num_pts - 1; i++)
+      for (i = 0; i < curve->n_points - 1; i++)
         {
-          p1 = points[MAX (i - 1, 0)];
-          p2 = points[i];
-          p3 = points[i + 1];
-          p4 = points[MIN (i + 2, num_pts - 1)];
+          p1 = MAX (i - 1, 0);
+          p2 = i;
+          p3 = i + 1;
+          p4 = MIN (i + 2, curve->n_points - 1);
 
           gimp_curve_plot (curve, p1, p2, p3, p4);
         }
 
       /* ensure that the control points are used exactly */
-      for (i = 0; i < num_pts; i++)
+      for (i = 0; i < curve->n_points; i++)
         {
-          gdouble x = curve->points[points[i]].x;
-          gdouble y = curve->points[points[i]].y;
+          gdouble x = curve->points[i].x;
+          gdouble y = curve->points[i].y;
 
           curve->samples[ROUND (x * (gdouble) (curve->n_samples - 1))] = y;
         }
@@ -1013,7 +1082,16 @@ gimp_curve_plot (GimpCurve *curve,
   dx = x3 - x0;
   dy = y3 - y0;
 
-  g_return_if_fail (dx > 0);
+  if (dx <= EPSILON)
+    {
+      gint index;
+
+      index = ROUND (x0 * (gdouble) (curve->n_samples - 1));
+
+      curve->samples[index] = y3;
+
+      return;
+    }
 
   if (p1 == p2 && p3 == p4)
     {
diff --git a/app/core/gimpcurve.h b/app/core/gimpcurve.h
index 77f553a074..b41794c168 100644
--- a/app/core/gimpcurve.h
+++ b/app/core/gimpcurve.h
@@ -65,17 +65,24 @@ void            gimp_curve_set_curve_type    (GimpCurve     *curve,
                                               GimpCurveType  curve_type);
 GimpCurveType   gimp_curve_get_curve_type    (GimpCurve     *curve);
 
-void            gimp_curve_set_n_points      (GimpCurve     *curve,
-                                              gint           n_points);
 gint            gimp_curve_get_n_points      (GimpCurve     *curve);
 
 void            gimp_curve_set_n_samples     (GimpCurve     *curve,
                                               gint           n_samples);
 gint            gimp_curve_get_n_samples     (GimpCurve     *curve);
 
-gint            gimp_curve_get_closest_point (GimpCurve     *curve,
+gint            gimp_curve_get_point_at      (GimpCurve     *curve,
                                               gdouble        x);
+gint            gimp_curve_get_closest_point (GimpCurve     *curve,
+                                              gdouble        x,
+                                              gdouble        y,
+                                              gdouble        max_distance);
 
+gint            gimp_curve_add_point         (GimpCurve     *curve,
+                                              gdouble        x,
+                                              gdouble        y);
+void            gimp_curve_delete_point      (GimpCurve     *curve,
+                                              gint           point);
 void            gimp_curve_set_point         (GimpCurve     *curve,
                                               gint           point,
                                               gdouble        x,
@@ -83,12 +90,11 @@ void            gimp_curve_set_point         (GimpCurve     *curve,
 void            gimp_curve_move_point        (GimpCurve     *curve,
                                               gint           point,
                                               gdouble        y);
-void            gimp_curve_delete_point      (GimpCurve     *curve,
-                                              gint           point);
 void            gimp_curve_get_point         (GimpCurve     *curve,
                                               gint           point,
                                               gdouble       *x,
                                               gdouble       *y);
+void            gimp_curve_clear_points      (GimpCurve     *curve);
 
 void            gimp_curve_set_curve         (GimpCurve     *curve,
                                               gdouble        x,
diff --git a/app/operations/gimpcurvesconfig.c b/app/operations/gimpcurvesconfig.c
index 4855ac8135..6f5a2ef8b5 100644
--- a/app/operations/gimpcurvesconfig.c
+++ b/app/operations/gimpcurvesconfig.c
@@ -402,13 +402,10 @@ gimp_curves_config_new_spline (gint32         channel,
   gimp_data_freeze (GIMP_DATA (curve));
 
   gimp_curve_set_curve_type (curve, GIMP_CURVE_SMOOTH);
-  gimp_curve_set_n_points (curve, n_points);
-
-  /*  unset the last point  */
-  gimp_curve_set_point (curve, curve->n_points - 1, -1.0, -1.0);
+  gimp_curve_clear_points (curve);
 
   for (i = 0; i < n_points; i++)
-    gimp_curve_set_point (curve, i,
+    gimp_curve_add_point (curve,
                           (gdouble) points[i * 2],
                           (gdouble) points[i * 2 + 1]);
 
@@ -598,18 +595,18 @@ gimp_curves_config_load_cruft (GimpCurvesConfig  *config,
       gimp_data_freeze (GIMP_DATA (curve));
 
       gimp_curve_set_curve_type (curve, GIMP_CURVE_SMOOTH);
-      gimp_curve_set_n_points (curve, GIMP_CURVE_N_CRUFT_POINTS);
-
-      gimp_curve_reset (curve, FALSE);
+      gimp_curve_clear_points (curve);
 
       for (j = 0; j < GIMP_CURVE_N_CRUFT_POINTS; j++)
         {
-          if (index[i][j] < 0 || value[i][j] < 0)
-            gimp_curve_set_point (curve, j, -1.0, -1.0);
-          else
-            gimp_curve_set_point (curve, j,
-                                  (gdouble) index[i][j] / 255.0,
-                                  (gdouble) value[i][j] / 255.0);
+          gdouble x;
+          gdouble y;
+
+          x = (gdouble) index[i][j] / 255.0;
+          y = (gdouble) value[i][j] / 255.0;
+
+          if (x >= 0.0)
+            gimp_curve_add_point (curve, x, y);
         }
 
       gimp_data_thaw (GIMP_DATA (curve));
@@ -643,50 +640,34 @@ gimp_curves_config_save_cruft (GimpCurvesConfig  *config,
       GimpCurve *curve = config->curve[i];
       gint       j;
 
-      if (curve->curve_type == GIMP_CURVE_FREE)
+      if (curve->curve_type == GIMP_CURVE_SMOOTH)
         {
-          gint n_points;
-
-          for (j = 0; j < curve->n_points; j++)
-            {
-              curve->points[j].x = -1;
-              curve->points[j].y = -1;
-            }
-
-          /* pick some points from the curve and make them control
-           * points
-           */
-          n_points = CLAMP (9, curve->n_points / 2, curve->n_points);
-
-          for (j = 0; j < n_points; j++)
-            {
-              gint sample = j * (curve->n_samples - 1) / (n_points - 1);
-              gint point  = j * (curve->n_points  - 1) / (n_points - 1);
+          g_object_ref (curve);
+        }
+      else
+        {
+          curve = GIMP_CURVE (gimp_data_duplicate (GIMP_DATA (curve)));
 
-              curve->points[point].x = ((gdouble) sample /
-                                        (gdouble) (curve->n_samples - 1));
-              curve->points[point].y = curve->samples[sample];
-            }
+          gimp_curve_set_curve_type (curve, GIMP_CURVE_SMOOTH);
         }
 
-      for (j = 0; j < curve->n_points; j++)
+      for (j = 0; j < GIMP_CURVE_N_CRUFT_POINTS; j++)
         {
-          /* don't use gimp_curve_get_point() because that doesn't
-           * work when the curve type is GIMP_CURVE_FREE
-           */
-          gdouble x = curve->points[j].x;
-          gdouble y = curve->points[j].y;
+          gint x = -1;
+          gint y = -1;
 
-          if (x < 0.0 || y < 0.0)
+          if (j < gimp_curve_get_n_points (curve))
             {
-              g_string_append_printf (string, "%d %d ", -1, -1);
-            }
-          else
-            {
-              g_string_append_printf (string, "%d %d ",
-                                      (gint) (x * 255.999),
-                                      (gint) (y * 255.999));
+              gdouble point_x;
+              gdouble point_y;
+
+              gimp_curve_get_point (curve, j, &point_x, &point_y);
+
+              x = floor (point_x * 255.999);
+              y = floor (point_y * 255.999);
             }
+
+          g_string_append_printf (string, "%d %d ", x, y);
         }
 
       g_string_append_printf (string, "\n");
diff --git a/app/operations/gimplevelsconfig.c b/app/operations/gimplevelsconfig.c
index 27325a3083..5ea17e9f51 100644
--- a/app/operations/gimplevelsconfig.c
+++ b/app/operations/gimplevelsconfig.c
@@ -696,17 +696,14 @@ gimp_levels_config_to_curves_config (GimpLevelsConfig *config)
        channel++)
     {
       GimpCurve  *curve    = curves->curve[channel];
-      const gint  n_points = gimp_curve_get_n_points (curve);
       static const gint n  = 8;
-      gint        point    = -1;
       gdouble     gamma    = config->gamma[channel];
       gdouble     delta_in;
       gdouble     delta_out;
       gdouble     x, y;
 
       /* clear the points set by default */
-      gimp_curve_set_point (curve, 0, -1, -1);
-      gimp_curve_set_point (curve, n_points - 1, -1, -1);
+      gimp_curve_clear_points (curve);
 
       delta_in  = config->high_input[channel] - config->low_input[channel];
       delta_out = config->high_output[channel] - config->low_output[channel];
@@ -714,8 +711,7 @@ gimp_levels_config_to_curves_config (GimpLevelsConfig *config)
       x = config->low_input[channel];
       y = config->low_output[channel];
 
-      point = CLAMP (n_points * x, point + 1, n_points - 1 - n);
-      gimp_curve_set_point (curve, point, x, y);
+      gimp_curve_add_point (curve, x, y);
 
       if (delta_out != 0 && gamma != 1.0)
         {
@@ -755,8 +751,7 @@ gimp_levels_config_to_curves_config (GimpLevelsConfig *config)
                   x = config->low_input[channel] + dx;
                   y = config->low_output[channel] + delta_out *
                       gimp_operation_levels_map_input (config, channel, x);
-                  point = CLAMP (n_points * x, point + 1, n_points - 1 - n + i);
-                  gimp_curve_set_point (curve, point, x, y);
+                  gimp_curve_add_point (curve, x, y);
                 }
             }
           else
@@ -792,8 +787,7 @@ gimp_levels_config_to_curves_config (GimpLevelsConfig *config)
                   y = config->low_output[channel] + dy;
                   x = config->low_input[channel] + delta_in *
                       gimp_operation_levels_map_input (config_inv, channel, y);
-                  point = CLAMP (n_points * x, point + 1, n_points - 1 - n + i);
-                  gimp_curve_set_point (curve, point, x, y);
+                  gimp_curve_add_point (curve, x, y);
                 }
 
               g_object_unref (config_inv);
@@ -803,8 +797,7 @@ gimp_levels_config_to_curves_config (GimpLevelsConfig *config)
       x = config->high_input[channel];
       y = config->high_output[channel];
 
-      point = CLAMP (n_points * x, point + 1, n_points - 1);
-      gimp_curve_set_point (curve, point, x, y);
+      gimp_curve_add_point (curve, x, y);
     }
 
   return curves;
diff --git a/app/tools/gimpcurvestool.c b/app/tools/gimpcurvestool.c
index 6e8b9b2961..d4c7ac4fb7 100644
--- a/app/tools/gimpcurvestool.c
+++ b/app/tools/gimpcurvestool.c
@@ -242,15 +242,18 @@ gimp_curves_tool_button_release (GimpTool              *tool,
     {
       GimpCurve *curve = config->curve[config->channel];
       gdouble    value = c_tool->picked_color[config->channel];
-      gint       closest;
+      gint       point;
 
-      closest = gimp_curve_get_closest_point (curve, value);
+      point = gimp_curve_get_point_at (curve, value);
 
-      gimp_curve_view_set_selected (GIMP_CURVE_VIEW (c_tool->graph),
-                                    closest);
+      if (point < 0)
+        {
+          point = gimp_curve_add_point (
+            curve,
+            value, gimp_curve_map_value (curve, value));
+        }
 
-      gimp_curve_set_point (curve, closest,
-                            value, gimp_curve_map_value (curve, value));
+      gimp_curve_view_set_selected (GIMP_CURVE_VIEW (c_tool->graph), point);
     }
   else if (state & gimp_get_toggle_behavior_mask ())
     {
@@ -262,17 +265,25 @@ gimp_curves_tool_button_release (GimpTool              *tool,
         {
           GimpCurve *curve = config->curve[channel];
           gdouble    value = c_tool->picked_color[channel];
-          gint       closest;
 
           if (value != -1)
             {
-              closest = gimp_curve_get_closest_point (curve, value);
-
-              gimp_curve_view_set_selected (GIMP_CURVE_VIEW (c_tool->graph),
-                                            closest);
-
-              gimp_curve_set_point (curve, closest,
-                                    value, gimp_curve_map_value (curve, value));
+              gint point;
+
+              point = gimp_curve_get_point_at (curve, value);
+
+              if (point < 0)
+                {
+                  point = gimp_curve_add_point (
+                    curve,
+                    value, gimp_curve_map_value (curve, value));
+                }
+
+              if (channel == config->channel)
+                {
+                  gimp_curve_view_set_selected (GIMP_CURVE_VIEW (c_tool->graph),
+                                                point);
+                }
             }
         }
     }
diff --git a/app/widgets/gimpcurveview.c b/app/widgets/gimpcurveview.c
index 2ba38e876c..5bfeb1e1c8 100644
--- a/app/widgets/gimpcurveview.c
+++ b/app/widgets/gimpcurveview.c
@@ -37,6 +37,10 @@
 
 #include "gimpclipboard.h"
 #include "gimpcurveview.h"
+#include "gimpwidgets-utils.h"
+
+
+#define POINT_MAX_DISTANCE 16.0
 
 
 enum
@@ -67,40 +71,46 @@ typedef struct
 } BGCurve;
 
 
-static void       gimp_curve_view_finalize        (GObject          *object);
-static void       gimp_curve_view_dispose         (GObject          *object);
-static void       gimp_curve_view_set_property    (GObject          *object,
-                                                   guint             property_id,
-                                                   const GValue     *value,
-                                                   GParamSpec       *pspec);
-static void       gimp_curve_view_get_property    (GObject          *object,
-                                                   guint             property_id,
-                                                   GValue           *value,
-                                                   GParamSpec       *pspec);
-
-static void       gimp_curve_view_style_set       (GtkWidget        *widget,
-                                                   GtkStyle         *prev_style);
-static gboolean   gimp_curve_view_expose          (GtkWidget        *widget,
-                                                   GdkEventExpose   *event);
-static gboolean   gimp_curve_view_button_press    (GtkWidget        *widget,
-                                                   GdkEventButton   *bevent);
-static gboolean   gimp_curve_view_button_release  (GtkWidget        *widget,
-                                                   GdkEventButton   *bevent);
-static gboolean   gimp_curve_view_motion_notify   (GtkWidget        *widget,
-                                                   GdkEventMotion   *bevent);
-static gboolean   gimp_curve_view_leave_notify    (GtkWidget        *widget,
-                                                   GdkEventCrossing *cevent);
-static gboolean   gimp_curve_view_key_press       (GtkWidget        *widget,
-                                                   GdkEventKey      *kevent);
-
-static void       gimp_curve_view_cut_clipboard   (GimpCurveView    *view);
-static void       gimp_curve_view_copy_clipboard  (GimpCurveView    *view);
-static void       gimp_curve_view_paste_clipboard (GimpCurveView    *view);
-
-static void       gimp_curve_view_set_cursor      (GimpCurveView    *view,
-                                                   gdouble           x,
-                                                   gdouble           y);
-static void       gimp_curve_view_unset_cursor    (GimpCurveView *view);
+static void       gimp_curve_view_finalize              (GObject          *object);
+static void       gimp_curve_view_dispose               (GObject          *object);
+static void       gimp_curve_view_set_property          (GObject          *object,
+                                                         guint             property_id,
+                                                         const GValue     *value,
+                                                         GParamSpec       *pspec);
+static void       gimp_curve_view_get_property          (GObject          *object,
+                                                         guint             property_id,
+                                                         GValue           *value,
+                                                         GParamSpec       *pspec);
+
+static void       gimp_curve_view_style_set             (GtkWidget        *widget,
+                                                         GtkStyle         *prev_style);
+static gboolean   gimp_curve_view_expose                (GtkWidget        *widget,
+                                                         GdkEventExpose   *event);
+static gboolean   gimp_curve_view_button_press          (GtkWidget        *widget,
+                                                         GdkEventButton   *bevent);
+static gboolean   gimp_curve_view_button_release        (GtkWidget        *widget,
+                                                         GdkEventButton   *bevent);
+static gboolean   gimp_curve_view_motion_notify         (GtkWidget        *widget,
+                                                         GdkEventMotion   *bevent);
+static gboolean   gimp_curve_view_leave_notify          (GtkWidget        *widget,
+                                                         GdkEventCrossing *cevent);
+static gboolean   gimp_curve_view_key_press             (GtkWidget        *widget,
+                                                         GdkEventKey      *kevent);
+
+static void       gimp_curve_view_cut_clipboard         (GimpCurveView    *view);
+static void       gimp_curve_view_copy_clipboard        (GimpCurveView    *view);
+static void       gimp_curve_view_paste_clipboard       (GimpCurveView    *view);
+
+static void       gimp_curve_view_curve_dirty           (GimpCurve        *curve,
+                                                         GimpCurveView    *view);
+static void       gimp_curve_view_curve_notify_n_points (GimpCurve        *curve,
+                                                         GParamSpec       *pspec,
+                                                         GimpCurveView    *view);
+
+static void       gimp_curve_view_set_cursor            (GimpCurveView    *view,
+                                                         gdouble           x,
+                                                         gdouble           y);
+static void       gimp_curve_view_unset_cursor          (GimpCurveView *view);
 
 
 G_DEFINE_TYPE (GimpCurveView, gimp_curve_view,
@@ -211,7 +221,7 @@ static void
 gimp_curve_view_init (GimpCurveView *view)
 {
   view->curve       = NULL;
-  view->selected    = 0;
+  view->selected    = -1;
   view->offset_x    = 0.0;
   view->offset_y    = 0.0;
   view->last_x      = 0.0;
@@ -419,9 +429,6 @@ gimp_curve_view_draw_point (GimpCurveView *view,
 
   gimp_curve_get_point (view->curve, i, &x, &y);
 
-  if (x < 0.0)
-    return;
-
   y = 1.0 - y;
 
 #define RADIUS 3
@@ -785,10 +792,9 @@ gimp_curve_view_button_press (GtkWidget      *widget,
   gint           width, height;
   gdouble        x;
   gdouble        y;
+  gint           point;
   gdouble        point_x;
   gdouble        point_y;
-  gint           closest_point;
-  gint           i;
 
   if (! curve || bevent->button != 1)
     return TRUE;
@@ -805,8 +811,6 @@ gimp_curve_view_button_press (GtkWidget      *widget,
   x = CLAMP (x, 0.0, 1.0);
   y = CLAMP (y, 0.0, 1.0);
 
-  closest_point = gimp_curve_get_closest_point (curve, x);
-
   view->grabbed = TRUE;
 
   view->orig_curve = GIMP_CURVE (gimp_data_duplicate (GIMP_DATA (curve)));
@@ -816,47 +820,34 @@ gimp_curve_view_button_press (GtkWidget      *widget,
   switch (gimp_curve_get_curve_type (curve))
     {
     case GIMP_CURVE_SMOOTH:
-      /*  determine the leftmost and rightmost points  */
-      view->leftmost = -1.0;
-      for (i = closest_point - 1; i >= 0; i--)
-        {
-          gimp_curve_get_point (curve, i, &point_x, NULL);
-
-          if (point_x >= 0.0)
-            {
-              view->leftmost = point_x;
-              break;
-            }
-        }
+      point = gimp_curve_get_closest_point (curve, x, 1.0 - y,
+                                            POINT_MAX_DISTANCE /
+                                            MAX (width, height));
 
-      view->rightmost = 2.0;
-      for (i = closest_point + 1; i < curve->n_points; i++)
+      if (point < 0)
         {
-          gimp_curve_get_point (curve, i, &point_x, NULL);
+          if (bevent->state & gimp_get_constrain_behavior_mask ())
+            y = 1.0 - gimp_curve_map_value (view->orig_curve, x);
 
-          if (point_x >= 0.0)
-            {
-              view->rightmost = point_x;
-              break;
-            }
+          point = gimp_curve_add_point (curve, x, 1.0 - y);
         }
 
-      gimp_curve_view_set_selected (view, closest_point);
-
-      gimp_curve_get_point (curve, view->selected, &point_x, &point_y);
+      if (point > 0)
+        gimp_curve_get_point (curve, point - 1, &view->leftmost, NULL);
+      else
+        view->leftmost = -1.0;
 
-      if (point_x >= 0.0)
-        {
-          view->offset_x = point_x         - x;
-          view->offset_y = (1.0 - point_y) - y;
-        }
+      if (point < gimp_curve_get_n_points (curve) - 1)
+        gimp_curve_get_point (curve, point + 1, &view->rightmost, NULL);
       else
-        {
-          if (bevent->state & gimp_get_constrain_behavior_mask ())
-            y = 1.0 - gimp_curve_map_value (view->orig_curve, x);
+        view->rightmost = 2.0;
 
-          gimp_curve_set_point (curve, view->selected, x, 1.0 - y);
-        }
+      gimp_curve_view_set_selected (view, point);
+
+      gimp_curve_get_point (curve, point, &point_x, &point_y);
+
+      view->offset_x = point_x         - x;
+      view->offset_y = (1.0 - point_y) - y;
       break;
 
     case GIMP_CURVE_FREE:
@@ -908,7 +899,7 @@ gimp_curve_view_motion_notify (GtkWidget      *widget,
   gdouble         y;
   gdouble         point_x;
   gdouble         point_y;
-  gint            closest_point;
+  gint            point;
 
   if (! curve)
     return TRUE;
@@ -928,17 +919,19 @@ gimp_curve_view_motion_notify (GtkWidget      *widget,
   x = CLAMP (x, 0.0, 1.0);
   y = CLAMP (y, 0.0, 1.0);
 
-  closest_point = gimp_curve_get_closest_point (curve, x);
-
   switch (gimp_curve_get_curve_type (curve))
     {
     case GIMP_CURVE_SMOOTH:
       if (! view->grabbed) /*  If no point is grabbed...  */
         {
-          gimp_curve_get_point (curve, closest_point, &point_x, &point_y);
+          point = gimp_curve_get_closest_point (curve, x, 1.0 - y,
+                                                POINT_MAX_DISTANCE /
+                                                MAX (width, height));
 
-          if (point_x >= 0.0)
+          if (point >= 0)
             {
+              gimp_curve_get_point (curve, point, &point_x, &point_y);
+
               new_cursor = GDK_FLEUR;
 
               x = point_x;
@@ -961,20 +954,27 @@ gimp_curve_view_motion_notify (GtkWidget      *widget,
 
           gimp_data_freeze (GIMP_DATA (curve));
 
-          gimp_curve_set_point (curve, view->selected, -1.0, -1.0);
-
           if (x > view->leftmost && x < view->rightmost)
             {
-              gint n_points = gimp_curve_get_n_points (curve);
-
-              closest_point = ROUND (x * (gdouble) (n_points - 1));
-
-              gimp_curve_get_point (curve, closest_point, &point_x, NULL);
-
-              if (point_x < 0.0)
-                gimp_curve_view_set_selected (view, closest_point);
+              if (view->selected < 0)
+                {
+                  gimp_curve_view_set_selected (
+                    view,
+                    gimp_curve_add_point (curve, x, 1.0 - y));
+                }
+              else
+                {
+                  gimp_curve_set_point (curve, view->selected, x, 1.0 - y);
+                }
+            }
+          else
+            {
+              if (view->selected >= 0)
+                {
+                  gimp_curve_delete_point (curve, view->selected);
 
-              gimp_curve_set_point (curve, view->selected, x, 1.0 - y);
+                  gimp_curve_view_set_selected (view, -1);
+                }
             }
 
           gimp_data_thaw (GIMP_DATA (curve));
@@ -1067,8 +1067,10 @@ gimp_curve_view_key_press (GtkWidget   *widget,
   GimpCurve     *curve   = view->curve;
   gboolean       handled = FALSE;
 
-  if (! view->grabbed && curve &&
-      gimp_curve_get_curve_type (curve) == GIMP_CURVE_SMOOTH)
+  if (! view->grabbed                                        &&
+      curve                                                  &&
+      gimp_curve_get_curve_type (curve) == GIMP_CURVE_SMOOTH &&
+      view->selected                    >= 0)
     {
       gint    i = view->selected;
       gdouble x, y;
@@ -1205,6 +1207,21 @@ gimp_curve_view_paste_clipboard (GimpCurveView *view)
     }
 }
 
+static void
+gimp_curve_view_curve_dirty (GimpCurve     *curve,
+                             GimpCurveView *view)
+{
+  gtk_widget_queue_draw (GTK_WIDGET (view));
+}
+
+static void
+gimp_curve_view_curve_notify_n_points (GimpCurve     *curve,
+                                       GParamSpec    *pspec,
+                                       GimpCurveView *view)
+{
+  gimp_curve_view_set_selected (view, -1);
+}
+
 
 /*  public functions  */
 
@@ -1214,13 +1231,6 @@ gimp_curve_view_new (void)
   return g_object_new (GIMP_TYPE_CURVE_VIEW, NULL);
 }
 
-static void
-gimp_curve_view_curve_dirty (GimpCurve     *curve,
-                             GimpCurveView *view)
-{
-  gtk_widget_queue_draw (GTK_WIDGET (view));
-}
-
 void
 gimp_curve_view_set_curve (GimpCurveView *view,
                            GimpCurve     *curve,
@@ -1237,6 +1247,9 @@ gimp_curve_view_set_curve (GimpCurveView *view,
       g_signal_handlers_disconnect_by_func (view->curve,
                                             gimp_curve_view_curve_dirty,
                                             view);
+      g_signal_handlers_disconnect_by_func (view->curve,
+                                            gimp_curve_view_curve_notify_n_points,
+                                            view);
       g_object_unref (view->curve);
     }
 
@@ -1248,6 +1261,9 @@ gimp_curve_view_set_curve (GimpCurveView *view,
       g_signal_connect (view->curve, "dirty",
                         G_CALLBACK (gimp_curve_view_curve_dirty),
                         view);
+      g_signal_connect (view->curve, "notify::n-points",
+                        G_CALLBACK (gimp_curve_view_curve_notify_n_points),
+                        view);
     }
 
   if (view->curve_color)
@@ -1258,6 +1274,8 @@ gimp_curve_view_set_curve (GimpCurveView *view,
   else
     view->curve_color = NULL;
 
+  gimp_curve_view_set_selected (view, -1);
+
   gtk_widget_queue_draw (GTK_WIDGET (view));
 }
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]