| This patch fixes bug #84145 in GIMP 1.3.7
|
| It is much better than the previously supplied patch for 1.2.3,
| especially in the handling of 15-degree constrained lines. Now
| you're sure to get a legal end-point pixel if you point somewhere
| within it (not always the case with more naive algorithms).
|
| As a special feature, the ending point of a line constrained to
| one of the angles 15 or 30 (or the similar slopes in the other
| octants) will *not always be a pixel center*. This makes sure that
| the pixels plotted by a short 15-degree line will be a subset of
| the ones plotted by a longer 15-degree line - which wouldn't be the
| case if the end point was always rounded to a pixel center before
| gimp_paint_core_interpolate was turned loose on it.
|
| The non-centered ending points are now previewed as being
| non-centered (which can be seen if the zoom level is high enough).
|
| Furthermore the patch fixes a problem with the preview line that
| Sven discovered with in the context of the previous patch. In my
| opinion the reason for that problem is that gimpdrawtool.c uses
| gdisplay_transform_coords_f() followed by RINT() to compute pixel
| coordinates for gtk. However, the original coordinate system - and
| hence the one after gdisplay_transform_coords_f() - is one where the
| integers corrspond to the boundaries between pixels, not the pixel
| centers themselves. Therefore, to find the pixel that covers a
| particular floating-point coordinate, one must use round-towards-
| minus-infinity, not round-to-nearest.
|
| Because of this, the patch changed RINT() to floor() in all pixel
| calculations in gimpdrawtool.c [which is the only place where
| gdisplay_transform_coords_f() is called].
|
|   Henning Makholm <henning@makholm.net>
|   This patch is explicitly placed in the public domain

diff -ur gimp-1.3.7.prev/app/paint/gimppaintcore.c gimp-1.3.7/app/paint/gimppaintcore.c
--- gimp-1.3.7.prev/app/paint/gimppaintcore.c	Wed Jun 12 17:46:35 2002
+++ gimp-1.3.7/app/paint/gimppaintcore.c	Fri Jun 14 03:45:00 2002
@@ -492,47 +492,64 @@
 }
 
 /**
+ * gimp_paint_core_constrain_helper:
+ * @dx: the (fixed) delta-x
+ * @dy: a suggested delta-y
+ * 
+ * Returns an adjusted dy' near dy such that the slope (dx,dy') is a
+ * multiple of 15 degrees.
+ **/
+static gdouble
+gimp_paint_core_constrain_helper (gdouble dx,
+                                  gdouble dy)
+{
+  static gdouble slope[4] = { 0, 0.26795, 0.57735, 1 };
+  static gdouble divider[3] = { 0.13165, 0.41421, 0.76732 };
+  gint i;
+
+  if (dy < 0)
+    return - gimp_paint_core_constrain_helper (dx,-dy);
+  dx = fabs (dx);
+  for (i = 0; i < 3; i ++)
+    if (dy < dx * divider[i])
+      break;
+  dy = dx * slope[i];
+  return dy;
+}
+
+/**
  * gimp_paint_core_constrain:
  * @core: the #GimpPaintCore.
- * 
- * Restricts the (core->last_coords, core->curr_coords) vector to 15
- * degree steps, possibly changing core->curr_coords
+ *
+ * Restricts the (core->last_coords, core->cur_coords) vector to 15
+ * degree steps, possibly changing core->cur_coords.
  **/
 void
 gimp_paint_core_constrain (GimpPaintCore *core)
 {
-  static const gint tangens2[6] = {  34, 106, 196, 334, 618, 1944    };
-  static const gint cosinus[7]  = { 256, 247, 222, 181, 128,   66, 0 };
-
-  gint dx, dy, i, radius, frac;
+  gdouble dx, dy;
 
   g_return_if_fail (GIMP_IS_PAINT_CORE (core));
 
   dx = core->cur_coords.x - core->last_coords.x;
   dy = core->cur_coords.y - core->last_coords.y;
 
-  if (dy)
-    {
-      radius = sqrt (SQR (dx) + SQR (dy));
-      frac   = abs ((dx << 8) / dy);
-
-      for (i = 0; i < 6; i++)
-        {
-          if (frac < tangens2[i])
-            break;
-        }
-
-      dx = (dx > 0 ?
-            (cosinus[6-i] * radius) >> 8 :
-            - ((cosinus[6-i] * radius) >> 8));
-
-      dy = (dy > 0 ?
-            (cosinus[i] * radius)   >> 8 :
-            - ((cosinus[i] * radius)   >> 8));
-    }
-
-  core->cur_coords.x = core->last_coords.x + dx;
-  core->cur_coords.y = core->last_coords.y + dy;
+  /*  This algorithm changes only one of dx and dy, and does not try
+   *  to constrain the resulting dx and dy to integers. This gives
+   *  at least two benefits:
+   *    1. gimp_paint_core_constrain is idempotent, even if followed by
+   *       a rounding operation.
+   *    2. For any two lines with the same starting-point and ideal
+   *       15-degree direction, the points plotted by
+   *       gimp_paint_core_interpolate for the shorter line will always
+   *       be a superset of those plotted for the longer line.
+   */
+  if (fabs(dx) > fabs(dy))
+    core->cur_coords.y = core->last_coords.y +
+                         gimp_paint_core_constrain_helper (dx,dy);
+  else
+    core->cur_coords.x = core->last_coords.x +
+                         gimp_paint_core_constrain_helper (dy,dx);
 }
 
 /**
diff -ur gimp-1.3.7.prev/app/tools/gimpdrawtool.c gimp-1.3.7/app/tools/gimpdrawtool.c
--- gimp-1.3.7.prev/app/tools/gimpdrawtool.c	Fri May 10 15:15:45 2002
+++ gimp-1.3.7/app/tools/gimpdrawtool.c	Fri Jun 14 04:03:41 2002
@@ -342,8 +342,8 @@
 
   gdk_draw_line (draw_tool->win,
                  draw_tool->gc,
-                 RINT (tx1), RINT (ty1),
-                 RINT (tx2), RINT (ty2));
+                 floor (tx1), floor (ty1),
+                 floor (tx2), floor (ty2));
 }
 
 void
@@ -380,8 +380,8 @@
   gdk_draw_rectangle (draw_tool->win,
                       draw_tool->gc,
                       filled,
-                      RINT (tx1), RINT (ty1),
-                      RINT (tx2 - tx1), RINT (ty2 - ty1));
+                      floor (tx1), floor (ty1),
+                      floor (tx2 - tx1), floor (ty2 - ty1));
 }
 
 void
@@ -420,8 +420,8 @@
   gdk_draw_arc (draw_tool->win,
                 draw_tool->gc,
                 filled,
-                RINT (tx1), RINT (ty1),
-                RINT (tx2 - tx1), RINT (ty2 - ty1),
+                floor (tx1), floor (ty1),
+                floor (tx2 - tx1), floor (ty2 - ty1),
                 angle1, angle2);
 }
 
@@ -461,7 +461,7 @@
   gdk_draw_rectangle (draw_tool->win,
                       draw_tool->gc,
                       filled,
-                      RINT (tx), RINT (ty),
+                      floor (tx), floor (ty),
                       width, height);
 }
 
@@ -507,7 +507,7 @@
   gdk_draw_arc (draw_tool->win,
                 draw_tool->gc,
                 filled,
-                RINT (tx), RINT (ty),
+                floor (tx), floor (ty),
                 radius_x, radius_y,
                 angle1, angle2);
 }
@@ -540,12 +540,12 @@
 
   gdk_draw_line (draw_tool->win,
                  draw_tool->gc,
-		 RINT (tx), RINT (ty) - (height >> 1),
-		 RINT (tx), RINT (ty) + (height >> 1));
+		 floor (tx), floor (ty) - (height >> 1),
+		 floor (tx), floor (ty) + (height >> 1));
   gdk_draw_line (draw_tool->win,
                  draw_tool->gc,
-		 RINT (tx) - (width >> 1), RINT (ty),
-		 RINT (tx) + (width >> 1), RINT (ty));
+		 floor (tx) - (width >> 1), floor (ty),
+		 floor (tx) + (width >> 1), floor (ty));
 }
 
 void
diff -ur gimp-1.3.7.prev/app/tools/gimppainttool.c gimp-1.3.7/app/tools/gimppainttool.c
--- gimp-1.3.7.prev/app/tools/gimppainttool.c	Fri May 10 15:15:46 2002
+++ gimp-1.3.7/app/tools/gimppainttool.c	Fri Jun 14 03:58:14 2002
@@ -235,6 +235,31 @@
   GIMP_TOOL_CLASS (parent_class)->control (tool, action, gdisp);
 }
 
+/**
+ * gimp_paint_tool_round_line:
+ * @core:  the #GimpPaintCore
+ * @state: the modifier state
+ *
+ * Adjusts core->last_coords and core_cur_coords in preparation to
+ * drawing a straight line. The rounding of the slope to 15 degree
+ * steps if ctrl is pressed happens, as does rounding the start and
+ * end coordinates (which may be fractional in high zoom modes) to
+ * the center of pixels.
+ **/
+static void
+gimp_paint_tool_round_line (GimpPaintCore   *core,
+                            GdkModifierType  state)
+{
+  core->last_coords.x = floor (core->last_coords.x) + 0.5;
+  core->last_coords.y = floor (core->last_coords.y) + 0.5;
+  core->cur_coords.x  = floor (core->cur_coords.x ) + 0.5;
+  core->cur_coords.y  = floor (core->cur_coords.y ) + 0.5;
+  
+  /* Restrict to multiples of 15 degrees if ctrl is pressed */
+  if (state & GDK_CONTROL_MASK)
+    gimp_paint_core_constrain (core);
+}
+
 static void
 gimp_paint_tool_button_press (GimpTool        *tool,
                               GimpCoords      *coords,
@@ -317,12 +342,7 @@
 
       core->start_coords = core->last_coords;
 
-      if (state & GDK_CONTROL_MASK)
-	{
-          /* Restrict to multiples of 15 degrees if ctrl is pressed */
-
-          gimp_paint_core_constrain (core);
-	}
+      gimp_paint_tool_round_line (core, state);
     }
 
   gimp_tool_control_activate (tool->control);
@@ -544,12 +564,7 @@
             core->cur_coords.y -= off_y;
           }
 
-	  if (state & GDK_CONTROL_MASK)
-	    {
-              /*  Restrict to multiples of 15 degrees if ctrl is pressed  */
-
-              gimp_paint_core_constrain (core);
-	    }
+          gimp_paint_tool_round_line (core, state);
 
 	  dx = core->cur_coords.x - core->last_coords.x;
 	  dy = core->cur_coords.y - core->last_coords.y;
@@ -639,8 +654,8 @@
       /*  Draw start target  */
       gimp_draw_tool_draw_handle (draw_tool,
                                   GIMP_HANDLE_CROSS,
-                                  floor (core->last_coords.x) + 0.5,
-                                  floor (core->last_coords.y) + 0.5,
+                                  core->last_coords.x,
+                                  core->last_coords.y,
                                   TARGET_SIZE,
                                   TARGET_SIZE,
                                   GTK_ANCHOR_CENTER,
@@ -649,8 +664,8 @@
       /*  Draw end target  */
       gimp_draw_tool_draw_handle (draw_tool,
                                   GIMP_HANDLE_CROSS,
-                                  floor (core->cur_coords.x) + 0.5,
-                                  floor (core->cur_coords.y) + 0.5,
+                                  core->cur_coords.x,
+                                  core->cur_coords.y,
                                   TARGET_SIZE,
                                   TARGET_SIZE,
                                   GTK_ANCHOR_CENTER,
@@ -658,10 +673,10 @@
 
       /*  Draw the line between the start and end coords  */
       gimp_draw_tool_draw_line (draw_tool,
-                                floor (core->last_coords.x) + 0.5,
-                                floor (core->last_coords.y) + 0.5,
-                                floor (core->cur_coords.x) + 0.5,
-                                floor (core->cur_coords.y) + 0.5,
+                                core->last_coords.x,
+                                core->last_coords.y,
+                                core->cur_coords.x,
+                                core->cur_coords.y,
                                 TRUE);
     }
 }
