[base, smooth] Implement direct LCD filtering.

Applying an LCD filter to spans rather than the entire image improves
the performance of ClearType-like rendering by about 40% at 32 ppem
and much more at larger sizes.

* src/smooth/ftsmooth.c (ft_smooth_raster_lcd, ft_smooth_lcd_spans,
ft_smooth_raster_lcdv, ft_smooth_lcdv_spans, TOrigin): Implement it.
* include/freetype/internal/ftobjs.h (FT_LibraryRec): Remove
`lcd_filter_func`.
* src/base/ftlcdfil.c (ft_lcd_filter_fir): Removed.
(ft_lcd_padding): Use padding sufficient for any 5-tap filter.
(FT_Library_SetLcdFilterWeights, FT_Library_SetLcdFilter): Updated.
* docs/CHANGES: Updated.
diff --git a/docs/CHANGES b/docs/CHANGES
index c936114..5aa71e5 100644
--- a/docs/CHANGES
+++ b/docs/CHANGES
@@ -5,6 +5,11 @@
   - Several  changes  related  to  LCD  filtering  are implemented  to
     achieve better performance and encourage sound practices.
 
+    . Instead of  blanket LCD filtering over the entire bitmap,  it is
+      now applied only to non-zero spans using direct rendering.  This
+      speeds up the ClearType-like rendering by more than 40% at sizes
+      above 32 ppem.
+
     . Setting the filter weights with FT_Face_Properties  is no longer
       supported.  The default and light filters  are optimized to work
       with any face.
diff --git a/include/freetype/internal/ftobjs.h b/include/freetype/internal/ftobjs.h
index 059ac3b..0419296 100644
--- a/include/freetype/internal/ftobjs.h
+++ b/include/freetype/internal/ftobjs.h
@@ -879,10 +879,6 @@
    *   lcd_weights ::
    *     The LCD filter weights for ClearType-style subpixel rendering.
    *
-   *   lcd_filter_func ::
-   *     The LCD filtering callback function for for ClearType-style subpixel
-   *     rendering.
-   *
    *   lcd_geometry ::
    *     This array specifies LCD subpixel geometry and controls Harmony LCD
    *     rendering technique, alternative to ClearType.
@@ -916,7 +912,6 @@
 
 #ifdef FT_CONFIG_OPTION_SUBPIXEL_RENDERING
     FT_LcdFiveTapFilter      lcd_weights;      /* filter weights, if any */
-    FT_Bitmap_LcdFilterFunc  lcd_filter_func;  /* filtering callback     */
 #else
     FT_Vector                lcd_geometry[3];  /* RGB subpixel positions */
 #endif
diff --git a/src/base/ftlcdfil.c b/src/base/ftlcdfil.c
index 8ab2e7c..51c6fd4 100644
--- a/src/base/ftlcdfil.c
+++ b/src/base/ftlcdfil.c
@@ -25,142 +25,24 @@
 
 #ifdef FT_CONFIG_OPTION_SUBPIXEL_RENDERING
 
-#define FT_SHIFTCLAMP( x )  ( x >>= 8, (FT_Byte)( x > 255 ? 255 : x ) )
-
-
-  /* add padding according to filter weights */
+  /* add padding sufficient for a 5-tap filter, */
+  /* which is 2/3 of a pixel                    */
   FT_BASE_DEF( void )
   ft_lcd_padding( FT_BBox*        cbox,
                   FT_GlyphSlot    slot,
                   FT_Render_Mode  mode )
   {
-    FT_Byte*                 lcd_weights;
-    FT_Bitmap_LcdFilterFunc  lcd_filter_func;
+    FT_UNUSED( slot );
 
-
-    lcd_weights     = slot->library->lcd_weights;
-    lcd_filter_func = slot->library->lcd_filter_func;
-
-    if ( lcd_filter_func == ft_lcd_filter_fir )
+    if ( mode == FT_RENDER_MODE_LCD )
     {
-      if ( mode == FT_RENDER_MODE_LCD )
-      {
-        cbox->xMin -= lcd_weights[0] ? 43 :
-                      lcd_weights[1] ? 22 : 0;
-        cbox->xMax += lcd_weights[4] ? 43 :
-                      lcd_weights[3] ? 22 : 0;
-      }
-      else if ( mode == FT_RENDER_MODE_LCD_V )
-      {
-        cbox->yMin -= lcd_weights[0] ? 43 :
-                      lcd_weights[1] ? 22 : 0;
-        cbox->yMax += lcd_weights[4] ? 43 :
-                      lcd_weights[3] ? 22 : 0;
-      }
+      cbox->xMin -= 43;
+      cbox->xMax += 43;
     }
-  }
-
-
-  /* FIR filter used by the default and light filters */
-  FT_BASE_DEF( void )
-  ft_lcd_filter_fir( FT_Bitmap*           bitmap,
-                     FT_LcdFiveTapFilter  weights )
-  {
-    FT_UInt   width  = (FT_UInt)bitmap->width;
-    FT_UInt   height = (FT_UInt)bitmap->rows;
-    FT_Int    pitch  = bitmap->pitch;
-    FT_Byte*  origin = bitmap->buffer;
-    FT_Byte   mode   = bitmap->pixel_mode;
-
-
-    /* take care of bitmap flow */
-    if ( pitch > 0 && height > 0 )
-      origin += pitch * (FT_Int)( height - 1 );
-
-    /* horizontal in-place FIR filter */
-    if ( mode == FT_PIXEL_MODE_LCD && width >= 2 )
+    else if ( mode == FT_RENDER_MODE_LCD_V )
     {
-      FT_Byte*  line = origin;
-
-
-      /* `fir' must be at least 32 bit wide, since the sum of */
-      /* the values in `weights' can exceed 0xFF              */
-
-      for ( ; height > 0; height--, line -= pitch )
-      {
-        FT_UInt  fir[5];
-        FT_UInt  val, xx;
-
-
-        val    = line[0];
-        fir[2] = weights[2] * val;
-        fir[3] = weights[3] * val;
-        fir[4] = weights[4] * val;
-
-        val    = line[1];
-        fir[1] = fir[2] + weights[1] * val;
-        fir[2] = fir[3] + weights[2] * val;
-        fir[3] = fir[4] + weights[3] * val;
-        fir[4] =          weights[4] * val;
-
-        for ( xx = 2; xx < width; xx++ )
-        {
-          val    = line[xx];
-          fir[0] = fir[1] + weights[0] * val;
-          fir[1] = fir[2] + weights[1] * val;
-          fir[2] = fir[3] + weights[2] * val;
-          fir[3] = fir[4] + weights[3] * val;
-          fir[4] =          weights[4] * val;
-
-          line[xx - 2] = FT_SHIFTCLAMP( fir[0] );
-        }
-
-        line[xx - 2] = FT_SHIFTCLAMP( fir[1] );
-        line[xx - 1] = FT_SHIFTCLAMP( fir[2] );
-      }
-    }
-
-    /* vertical in-place FIR filter */
-    else if ( mode == FT_PIXEL_MODE_LCD_V && height >= 2 )
-    {
-      FT_Byte*  column = origin;
-
-
-      for ( ; width > 0; width--, column++ )
-      {
-        FT_Byte*  col = column;
-        FT_UInt   fir[5];
-        FT_UInt   val, yy;
-
-
-        val    = col[0];
-        fir[2] = weights[2] * val;
-        fir[3] = weights[3] * val;
-        fir[4] = weights[4] * val;
-        col   -= pitch;
-
-        val    = col[0];
-        fir[1] = fir[2] + weights[1] * val;
-        fir[2] = fir[3] + weights[2] * val;
-        fir[3] = fir[4] + weights[3] * val;
-        fir[4] =          weights[4] * val;
-        col   -= pitch;
-
-        for ( yy = 2; yy < height; yy++, col -= pitch )
-        {
-          val    = col[0];
-          fir[0] = fir[1] + weights[0] * val;
-          fir[1] = fir[2] + weights[1] * val;
-          fir[2] = fir[3] + weights[2] * val;
-          fir[3] = fir[4] + weights[3] * val;
-          fir[4] =          weights[4] * val;
-
-          col[pitch * 2]  = FT_SHIFTCLAMP( fir[0] );
-        }
-
-        col[pitch * 2]  = FT_SHIFTCLAMP( fir[1] );
-        col[pitch]      = FT_SHIFTCLAMP( fir[2] );
-      }
+      cbox->yMin -= 43;
+      cbox->yMax += 43;
     }
   }
 
@@ -178,7 +60,6 @@
       return FT_THROW( Invalid_Argument );
 
     ft_memcpy( library->lcd_weights, weights, FT_LCD_FILTER_FIVE_TAPS );
-    library->lcd_filter_func = ft_lcd_filter_fir;
 
     return FT_Err_Ok;
   }
@@ -202,21 +83,21 @@
     switch ( filter )
     {
     case FT_LCD_FILTER_NONE:
-      library->lcd_filter_func = NULL;
+      ft_memset( library->lcd_weights,
+                 0,
+                 FT_LCD_FILTER_FIVE_TAPS );
       break;
 
     case FT_LCD_FILTER_DEFAULT:
       ft_memcpy( library->lcd_weights,
                  default_weights,
                  FT_LCD_FILTER_FIVE_TAPS );
-      library->lcd_filter_func = ft_lcd_filter_fir;
       break;
 
     case FT_LCD_FILTER_LIGHT:
       ft_memcpy( library->lcd_weights,
                  light_weights,
                  FT_LCD_FILTER_FIVE_TAPS );
-      library->lcd_filter_func = ft_lcd_filter_fir;
       break;
 
     default:
diff --git a/src/smooth/ftsmooth.c b/src/smooth/ftsmooth.c
index 98a3103..5a7a852 100644
--- a/src/smooth/ftsmooth.c
+++ b/src/smooth/ftsmooth.c
@@ -80,6 +80,7 @@
   {
     unsigned char*  origin;  /* pixmap origin at the bottom-left */
     int             pitch;   /* pitch to go down one row */
+    unsigned char   wght[5]; /* filtering weights */
 
   } TOrigin;
 
@@ -274,6 +275,32 @@
   }
 
 
+  /* This function applies a horizontal filter in direct rendering mode */
+  static void
+  ft_smooth_lcd_spans( int             y,
+                       int             count,
+                       const FT_Span*  spans,
+                       void*           target_ )   /* TOrigin* */
+  {
+    TOrigin*  target = (TOrigin*)target_;
+
+    unsigned char*  dst_line = target->origin - y * target->pitch - 2;
+    unsigned char*  dst;
+    unsigned short  w;
+
+
+    for ( ; count--; spans++ )
+      for ( dst = dst_line + spans->x, w = spans->len; w--; dst++ )
+      {
+        dst[0] += ( spans->coverage * target->wght[0] + 85 ) >> 8;
+        dst[1] += ( spans->coverage * target->wght[1] + 85 ) >> 8;
+        dst[2] += ( spans->coverage * target->wght[2] + 85 ) >> 8;
+        dst[3] += ( spans->coverage * target->wght[3] + 85 ) >> 8;
+        dst[4] += ( spans->coverage * target->wght[4] + 85 ) >> 8;
+      }
+  }
+
+
   static FT_Error
   ft_smooth_raster_lcd( FT_Renderer  render,
                         FT_Outline*  outline,
@@ -285,11 +312,47 @@
     FT_Vector*  vec;
 
     FT_Raster_Params  params;
+    TOrigin           target;
 
 
-    params.target = bitmap;
-    params.source = outline;
-    params.flags  = FT_RASTER_FLAG_AA;
+    if ( render->root.library->lcd_weights[2] )
+    {
+      /* Reject outlines that are too wide for 16-bit FT_Span.       */
+      /* Other limits are applied upstream with the same error code. */
+      if ( bitmap->width > 0x7FFF )
+        return FT_THROW( Raster_Overflow );
+
+      /* Set up direct rendering for instant filtering. */
+      params.source     = outline;
+      params.flags      = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT;
+      params.gray_spans = ft_smooth_lcd_spans;
+      params.user       = &target;
+
+      params.clip_box.xMin = 0;
+      params.clip_box.yMin = 0;
+      params.clip_box.xMax = bitmap->width;
+      params.clip_box.yMax = bitmap->rows;
+
+      if ( bitmap->pitch < 0 )
+        target.origin = bitmap->buffer;
+      else
+        target.origin = bitmap->buffer
+                        + ( bitmap->rows - 1 ) * (unsigned int)bitmap->pitch;
+
+      target.pitch = bitmap->pitch;
+
+      target.wght[0] = render->root.library->lcd_weights[0];
+      target.wght[1] = render->root.library->lcd_weights[1];
+      target.wght[2] = render->root.library->lcd_weights[2];
+      target.wght[3] = render->root.library->lcd_weights[3];
+      target.wght[4] = render->root.library->lcd_weights[4];
+    }
+    else
+    {
+      params.target = bitmap;
+      params.source = outline;
+      params.flags  = FT_RASTER_FLAG_AA;
+    }
 
     /* implode outline */
     for ( vec = points; vec < points_end; vec++ )
@@ -306,6 +369,32 @@
   }
 
 
+  /* This function applies a vertical filter in direct rendering mode */
+  static void
+  ft_smooth_lcdv_spans( int             y,
+                        int             count,
+                        const FT_Span*  spans,
+                        void*           target_ )   /* TOrigin* */
+  {
+    TOrigin*  target = (TOrigin*)target_;
+
+    int             pitch    = target->pitch;
+    unsigned char*  dst_line = target->origin - ( y + 2 ) * pitch;
+    unsigned char*  dst;
+    unsigned short  w;
+
+
+    for ( ; count--; spans++ )
+      for ( dst = dst_line + spans->x, w = spans->len; w--; dst++ )
+      {
+        dst[        0] += ( spans->coverage * target->wght[0] + 85 ) >> 8;
+        dst[    pitch] += ( spans->coverage * target->wght[1] + 85 ) >> 8;
+        dst[2 * pitch] += ( spans->coverage * target->wght[2] + 85 ) >> 8;
+        dst[3 * pitch] += ( spans->coverage * target->wght[3] + 85 ) >> 8;
+        dst[4 * pitch] += ( spans->coverage * target->wght[4] + 85 ) >> 8;
+      }
+  }
+
   static FT_Error
   ft_smooth_raster_lcdv( FT_Renderer  render,
                          FT_Outline*  outline,
@@ -317,11 +406,42 @@
     FT_Vector*  vec;
 
     FT_Raster_Params  params;
+    TOrigin           target;
 
 
-    params.target = bitmap;
-    params.source = outline;
-    params.flags  = FT_RASTER_FLAG_AA;
+    if ( render->root.library->lcd_weights[2] )
+    {
+      /* Set up direct rendering for instant filtering. */
+      params.source     = outline;
+      params.flags      = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT;
+      params.gray_spans = ft_smooth_lcdv_spans;
+      params.user       = &target;
+
+      params.clip_box.xMin = 0;
+      params.clip_box.yMin = 0;
+      params.clip_box.xMax = bitmap->width;
+      params.clip_box.yMax = bitmap->rows;
+
+      if ( bitmap->pitch < 0 )
+        target.origin = bitmap->buffer;
+      else
+        target.origin = bitmap->buffer
+                        + ( bitmap->rows - 1 ) * (unsigned int)bitmap->pitch;
+
+      target.pitch = bitmap->pitch;
+
+      target.wght[0] = render->root.library->lcd_weights[0];
+      target.wght[1] = render->root.library->lcd_weights[1];
+      target.wght[2] = render->root.library->lcd_weights[2];
+      target.wght[3] = render->root.library->lcd_weights[3];
+      target.wght[4] = render->root.library->lcd_weights[4];
+    }
+    else
+    {
+      params.target = bitmap;
+      params.source = outline;
+      params.flags  = FT_RASTER_FLAG_AA;
+    }
 
     /* implode outline */
     for ( vec = points; vec < points_end; vec++ )
@@ -521,24 +641,6 @@
         error = ft_smooth_raster_lcd ( render, outline, bitmap );
       else if ( mode == FT_RENDER_MODE_LCD_V )
         error = ft_smooth_raster_lcdv( render, outline, bitmap );
-
-#ifdef FT_CONFIG_OPTION_SUBPIXEL_RENDERING
-
-      /* finally apply filtering */
-      {
-        FT_Byte*                 lcd_weights;
-        FT_Bitmap_LcdFilterFunc  lcd_filter_func;
-
-
-        lcd_weights     = slot->library->lcd_weights;
-        lcd_filter_func = slot->library->lcd_filter_func;
-
-        if ( lcd_filter_func )
-          lcd_filter_func( bitmap, lcd_weights );
-      }
-
-#endif /* FT_CONFIG_OPTION_SUBPIXEL_RENDERING */
-
     }
 
   Exit: