[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 = ⌖ + + 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 = ⌖ + + 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: