| This patch is an attempt to make the Gimp draw nicer straight lines with
| 1x1 brushes. See problem description in bug #69773 for a description of
| what's wrong.
|
| Patch against current anoncvs head. Works with 1.3.7 tarball too.
|
|    Henning Makholm <henning@makholm.net>
|    This patch is explicitly placed in the public domain
|
Index: app/paint/gimppaintcore.c
===================================================================
RCS file: /cvs/gnome/gimp/app/paint/gimppaintcore.c,v
retrieving revision 1.66
diff -u -r1.66 gimppaintcore.c
--- app/paint/gimppaintcore.c	8 Jun 2002 00:01:17 -0000	1.66
+++ app/paint/gimppaintcore.c	11 Jun 2002 00:35:53 -0000
@@ -327,6 +327,10 @@
                                                  &core->last_coords,
                                                  &core->cur_coords);
         }
+
+      /* Save rounded (towards 0) coords for gimp_paint_core_interpolate */
+      core->rounded_last_paint_x = core->cur_coords.x ;
+      core->rounded_last_paint_y = core->cur_coords.y ;
     }
 
   GIMP_PAINT_CORE_GET_CLASS (core)->paint (core,
@@ -407,6 +411,9 @@
   core->distance   = 0.0;
   core->pixel_dist = 0.0;
 
+  core->rounded_last_paint_x     = -32766 ;
+  core->rounded_last_paint_y     = -32766 ;
+  
   return TRUE;
 }
 
@@ -545,6 +552,8 @@
   gdouble     pixel_initial;
   gdouble     xd, yd;
   gdouble     mag;
+  gdouble     st_factor, st_offset;
+  gint        s, last_s ;
 
   g_return_if_fail (GIMP_IS_PAINT_CORE (core));
   g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
@@ -587,6 +596,87 @@
   /*   curscale = MIN (gimp_paint_tool->curpressure,  1/256); */
   /*   spacing = gimp_paint_tool->spacing * sqrt (0.5 * (lastscale + curscale)); */
 
+  /*  Set up to round brush positions to pixel centers either in the
+   *  X or in the Y direction. This is to make thin likes prettier.
+   *  The rounding is done by adjusting the 't' parameter before painting
+   *  a point, so even though we move the brush a bit, it still stays
+   *  on the mathematical line.
+   *  The rounding is set up by deriving another parameter 's' from 't':
+   *      s = t * st_factor + st_offset
+   *  such that the "nice" brush positions are the ones with *integer*
+   *  s values. (Actually the value of s will be 1/2 less than the nice
+   *  brush position's x or y coordinate - note that st_factor may
+   *  be negative!)
+   *  We only paint each rounded s value once; the last_s variable
+   *  contains the s value that we should skip if we hit it.
+   */
+
+  if( fabs(delta.x) + fabs(delta.y) < EPSILON )
+    {
+      /*  The line is so short that we'd risk a division overflow when
+       *  trying to compute the t value that goes along with the nearest
+       *  pixel center.
+       *  Bummer. Just set up the scale such that everything will get
+       *  plotted in the middle of the line.
+       */
+    dont_dare_round_to_pixel_centers:
+      st_factor = 0.25;
+      st_offset = -0.125;
+      last_s = 42; /* will not get hit */
+    }
+  else
+    {
+      gint previous_last_s ;
+      if( fabs(delta.x) > fabs(delta.y) )
+        {
+          st_factor = delta.x;
+          st_offset = core->last_coords.x - 0.5;
+          previous_last_s = core->rounded_last_paint_x;
+        }
+      else
+        {
+          st_factor = delta.y;
+          st_offset = core->last_coords.y - 0.5;
+          previous_last_s = core->rounded_last_paint_y;
+        }
+      
+      /*  We don't want too excessive extrapolations, relative to the total
+       *  length of the line (because we don't really trust the slope
+       *  if the line is short). Try to round t=0 and t=1 and check that we
+       *  don't get too far away from the interpolation interval [0;1]
+       */
+      s = (int)( st_offset + 0.5 );
+      if( (s-st_offset)/st_factor < -1 )
+        goto dont_dare_round_to_pixel_centers ;
+
+      last_s = (int)( st_factor+st_offset + 0.5 );
+      if( (last_s-st_offset)/st_factor > 2 )
+        goto dont_dare_round_to_pixel_centers ;
+
+      /* Set last_s to an unreachable s value */
+      last_s = abs(s) + abs(last_s) + 1;
+      
+      /*  Sometimes we want to avoid plotting something in stripe number s.
+       *  Namely: when the previous line already has plotted something useful
+       *  in stripe number s; "useful" meaning that it rounds to a pixel that
+       *  is at least a diagonal neighbour to what we're going to plot in
+       *  stripe number s+1.
+       *  (This rule is Right when we're painting with an aliasing 1x1
+       *  brush, and will also prevent too discontinuous joins otherwise).
+       */
+      if( previous_last_s == s )
+        {
+          gint x, y;
+          t = ((double)(s+1) - st_offset) / st_factor;
+          x = (int)( core->last_coords.x + t * delta.x );
+          y = (int)( core->last_coords.y + t * delta.y );
+          x -= core->rounded_last_paint_x;
+          y -= core->rounded_last_paint_y;
+          if( x <= 1 && x >= -1 && y <= 1 && y >= -1 )
+            last_s = s;
+        }
+    }
+  
   while (core->distance < total)
     {
       GimpBrush *current_brush;
@@ -600,6 +690,13 @@
 	{
 	  t = (core->distance - initial) / dist;
 
+          /* Do the pixel adjustment; see comment above */
+          s = (int)( t * st_factor + st_offset + 0.5 );
+          if( s == last_s )
+            continue;
+          last_s = s;
+          t = (s - st_offset) / st_factor;
+          
 	  core->cur_coords.x        = core->last_coords.x        + t * delta.x;
 	  core->cur_coords.y        = core->last_coords.y        + t * delta.y;
 	  core->cur_coords.pressure = core->last_coords.pressure + t * delta.pressure;
Index: app/paint/gimppaintcore.h
===================================================================
RCS file: /cvs/gnome/gimp/app/paint/gimppaintcore.h,v
retrieving revision 1.27
diff -u -r1.27 gimppaintcore.h
--- app/paint/gimppaintcore.h	5 May 2002 19:17:35 -0000	1.27
+++ app/paint/gimppaintcore.h	11 Jun 2002 00:35:54 -0000
@@ -113,6 +113,9 @@
   gint                last_scale_pixmap_width;
   gint                last_scale_pixmap_height;
 
+  gint                rounded_last_paint_x;
+  gint                rounded_last_paint_y;
+  
   MaskBuf            *kernel_brushes[PAINT_CORE_SUBSAMPLE + 1][PAINT_CORE_SUBSAMPLE + 1];
 
   MaskBuf            *last_brush_mask;
