mirror of
https://gitlab.freedesktop.org/freetype/freetype.git
synced 2026-05-01 15:07:57 +02:00
[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. Small rounding differences are expected. * 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): lcd_filter_func gone. * 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.
This commit is contained in:
parent
e8f0969dcf
commit
7cc8f37b9a
4 changed files with 143 additions and 160 deletions
|
|
@ -5,6 +5,11 @@ CHANGES BETWEEN 2.14.1 and 2.14.2 (2026-Mmm-DD)
|
|||
- 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.
|
||||
|
|
|
|||
|
|
@ -879,10 +879,6 @@ FT_BEGIN_HEADER
|
|||
* 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 @@ FT_BEGIN_HEADER
|
|||
|
||||
#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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue