Compare commits

...

4 Commits

Author SHA1 Message Date
Anuj Verma ee0f8213be * include/freetype/freetype.h (FT_RENDER_MODE_SDF): Improve documentation.
* include/freetype/freetype.h (FT_RENDER_MODE_SDF): Added one more point
  to the limitations of sdf.
2021-10-16 10:30:06 +05:30
Anuj Verma 31c0a833d6 [sdf] Fixed corner checks and improved performance.
* src/sdf/ftsdf.c (sdf_generate_bounding_box): Always check for
  corner if two distance (for different curves) are very close.

* src/sdf/ftsdf.c (sdf_conic_to): Added check to figure out if
  the conic can be treated as a line (which happens if the control
  point coincide with any end-point).

  Also, replace tabs `\t` with spaces.
2021-10-16 10:02:30 +05:30
Anuj Verma 3d3be1dfd1 [sdf] Impliment deviation based splitting for bezier curves.
* src/sdf/ftsdf.c (split_sdf_cubic, split_sdf_shape): Added checks
  to figure out the deviation of bezier curves and stop splitting
  if the curve is flat enough.

* src/sdf/ftsdfcommon.h (ONE_PIXEL): Added macro for unit pixel size
  in 26.6 fixed point representation.
2021-10-15 08:55:41 +05:30
Anuj Verma 8ffa954507 * include/freetype/freetype.h (FT_RENDER_MODE_SDF): Improve documentation.
* include/freetype/freetype.h (FT_RENDER_MODE_SDF): Added more
  documentation for the render mode regarding the stability and point
  where artifacts are expected to occur.  Also added more information
  for both `sdf`, `bsdf` and when they are used.
2021-10-09 10:53:39 +05:30
3 changed files with 101 additions and 12 deletions

View File

@ -3368,6 +3368,45 @@ FT_BEGIN_HEADER
* } * }
* *
* ``` * ```
*
* FreeType has two rasterizers for generating SDF, namely:
*
* 1. `sdf` - For generating SDF directly from glyph's outline.
*
* 2. `bsdf` - For generating SDF from rasterized bitmaps.
*
* Depending on the glyph type (i.e. outline or bitmap), one of the two
* rasterizer is chosen at runtime and used for generating SDF. To
* force use `bsdf` you can simply render the glyph using any of the
* FreeType's other rendering modes (e.g. `FT_RENDER_MODE_NORMAL`) and
* then re-render using `FT_RENDER_MODE_SDF`.
*
* Now, a few notes on the stability and point of failures for the SDF
* renderers (specifically `sdf`):
*
* 1. The `sdf` rasterizer is sensitive to really small features (e.g.
* sharp turns which are less than 1 pixel) and imperfections in the
* glyph's outline. Having these in the outline can cause artifats
* in the final output.
*
* 2. The `sdf` rasterizer has limited support for handling intersecting
* contours and *cannot* handle self-intersecting contours whatsoever.
* Self intersection happens when a single connected contour intersect
* itself at some point and having these in your font will definately
* pose a problem to the rasterizer and cause artifacts.
*
* 3. Generating SDF for really small glyphs may result in undesirable
* output due to limited availability of pixel grid to store distance
* information.
*
* 4. Since the output buffer is normalized, precision at smaller spread
* will be greater than precision at larger spread values, because the
* output range of [0 .. 255] will be mapped to a smaller sdf range.
* A spread of 2 should be sufficient in most cases.
*
* Point (1) and (2) can be avoided by using `bsdf` and overall it is
* more stable than the `sdf` rasterizer.
*
*/ */
typedef enum FT_Render_Mode_ typedef enum FT_Render_Mode_
{ {

View File

@ -738,6 +738,18 @@
contour = shape->contours; contour = shape->contours;
/* If the control point coincide with any of the end point */
/* then it's a line and should be treated as one to avoid */
/* unnecessary complexity later in the algorithm. */
if ( ( contour->last_pos.x == control_1->x &&
contour->last_pos.y == control_1->y ) ||
( control_1->x == to->x &&
control_1->y == to->y ) )
{
sdf_line_to( to, user );
goto Exit;
}
FT_CALL( sdf_edge_new( memory, &edge ) ); FT_CALL( sdf_edge_new( memory, &edge ) );
edge->edge_type = SDF_EDGE_CONIC; edge->edge_type = SDF_EDGE_CONIC;
@ -1140,6 +1152,7 @@
FT_Error error = FT_Err_Ok; FT_Error error = FT_Err_Ok;
FT_26D6_Vec cpos[7]; FT_26D6_Vec cpos[7];
SDF_Edge* left,* right; SDF_Edge* left,* right;
const FT_26D6 threshold = ONE_PIXEL / 4;
if ( !memory || !out ) if ( !memory || !out )
@ -1148,12 +1161,25 @@
goto Exit; goto Exit;
} }
/* split the conic */ /* split the cubic */
cpos[0] = control_points[0]; cpos[0] = control_points[0];
cpos[1] = control_points[1]; cpos[1] = control_points[1];
cpos[2] = control_points[2]; cpos[2] = control_points[2];
cpos[3] = control_points[3]; cpos[3] = control_points[3];
/* If the segment is flat enough, we won't get any benifit by */
/* splitting it further, so we can just stop splitting. Here, */
/* we check the deviation of the bezier and stop if it is */
/* lower than a pre-defined `threhold` value. */
if ( FT_ABS( 2 * cpos[0].x - 3 * cpos[1].x + cpos[3].x ) < threshold &&
FT_ABS( 2 * cpos[0].y - 3 * cpos[1].y + cpos[3].y ) < threshold &&
FT_ABS( cpos[0].x - 3 * cpos[2].x + 2 * cpos[3].x ) < threshold &&
FT_ABS( cpos[0].y - 3 * cpos[2].y + 2 * cpos[3].y ) < threshold )
{
split_cubic( cpos );
goto Append;
}
split_cubic( cpos ); split_cubic( cpos );
/* If max number of splits is done */ /* If max number of splits is done */
@ -1250,13 +1276,31 @@
/* Subdivide the curve and add it to the list. */ /* Subdivide the curve and add it to the list. */
{ {
FT_26D6_Vec ctrls[3]; FT_26D6_Vec ctrls[3];
FT_26D6 dx, dy;
FT_UInt num_splits;
ctrls[0] = edge->start_pos; ctrls[0] = edge->start_pos;
ctrls[1] = edge->control_a; ctrls[1] = edge->control_a;
ctrls[2] = edge->end_pos; ctrls[2] = edge->end_pos;
error = split_sdf_conic( memory, ctrls, 32, &new_edges ); dx = FT_ABS( ctrls[2].x + ctrls[0].x - 2 * ctrls[1].x );
dy = FT_ABS( ctrls[2].y + ctrls[0].y - 2 * ctrls[1].y );
if ( dx < dy )
dx = dy;
/* Here we calculate the number of necessary bisections. Each */
/* bisection reduces the deviation by exactly 4-fold, hence */
/* we bisect the bezier until the deviation becomes less than */
/* 1/8th of a pixel. For more details check `ftgrays.c`. */
num_splits = 1;
while ( dx > ONE_PIXEL / 8 )
{
dx >>= 2;
num_splits <<= 1;
}
error = split_sdf_conic( memory, ctrls, num_splits, &new_edges );
} }
break; break;
@ -3284,6 +3328,7 @@
FT_26D6_Vec grid_point = zero_vector; FT_26D6_Vec grid_point = zero_vector;
SDF_Signed_Distance dist = max_sdf; SDF_Signed_Distance dist = max_sdf;
FT_UInt index = 0; FT_UInt index = 0;
FT_16D16 diff = 0;
if ( x < 0 || x >= width ) if ( x < 0 || x >= width )
@ -3311,7 +3356,7 @@
if ( dist.distance > sp_sq ) if ( dist.distance > sp_sq )
continue; continue;
/* square_root the values and fit in a 6.10 fixed-point */ /* square_root the values if required */
if ( USE_SQUARED_DISTANCES ) if ( USE_SQUARED_DISTANCES )
dist.distance = square_root( dist.distance ); dist.distance = square_root( dist.distance );
@ -3323,11 +3368,15 @@
/* check whether the pixel is set or not */ /* check whether the pixel is set or not */
if ( dists[index].sign == 0 ) if ( dists[index].sign == 0 )
dists[index] = dist; dists[index] = dist;
else
{
diff = FT_ABS( dists[index].distance - dist.distance );
if ( diff <= CORNER_CHECK_EPSILON )
dists[index] = resolve_corner( dists[index], dist );
else if ( dists[index].distance > dist.distance ) else if ( dists[index].distance > dist.distance )
dists[index] = dist; dists[index] = dist;
else if ( FT_ABS( dists[index].distance - dist.distance ) }
< CORNER_CHECK_EPSILON )
dists[index] = resolve_corner( dists[index], dist );
} }
} }

View File

@ -48,7 +48,8 @@ FT_BEGIN_HEADER
#define MIN_SPREAD 2 #define MIN_SPREAD 2
/* maximum spread supported by the renderer */ /* maximum spread supported by the renderer */
#define MAX_SPREAD 32 #define MAX_SPREAD 32
/* pixel size in 26.6 */
#define ONE_PIXEL ( 1 << 6 )
/************************************************************************** /**************************************************************************
* *