[sdf] Add subdivision and bounding box optimization.
* src/sdf/ftsdf.c (sdf_generate_bounding_box): New function, which is an optimized version of `sdf_generate`. (sdf_generate_subdivision): New function.
This commit is contained in:
parent
986d3108ac
commit
1010f2c39c
@ -1,3 +1,11 @@
|
||||
2020-08-19 Anuj Verma <anujv@iitbhilai.ac.in>
|
||||
|
||||
[sdf] Add subdivision and bounding box optimization.
|
||||
|
||||
* src/sdf/ftsdf.c (sdf_generate_bounding_box): New function, which
|
||||
is an optimized version of `sdf_generate`.
|
||||
(sdf_generate_subdivision): New function.
|
||||
|
||||
2020-08-19 Anuj Verma <anujv@iitbhilai.ac.in>
|
||||
|
||||
[sdf] Add function to generate SDF.
|
||||
|
303
src/sdf/ftsdf.c
303
src/sdf/ftsdf.c
@ -3049,4 +3049,307 @@
|
||||
|
||||
#endif /* 0 */
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
*
|
||||
* @Function:
|
||||
* sdf_generate_bounding_box
|
||||
*
|
||||
* @Description:
|
||||
* This function does basically the same thing as `sdf_generate` above
|
||||
* but more efficiently.
|
||||
*
|
||||
* Instead of checking all pixels against all edges, we loop over all
|
||||
* edges and only check pixels around the control box of the edge; the
|
||||
* control box is increased by the spread in all directions. Anything
|
||||
* outside of the control box that exceeds `spread` doesn't need to be
|
||||
* computed.
|
||||
*
|
||||
* Lastly, to determine the sign of unchecked pixels, we do a single
|
||||
* pass of all rows starting with a '+' sign and flipping when we come
|
||||
* across a '-' sign and continue. This also eliminates the possibility
|
||||
* of overflow because we only check the proximity of the curve.
|
||||
* Therefore we can use squared distanced safely.
|
||||
*
|
||||
* @Input:
|
||||
* internal_params ::
|
||||
* Internal parameters and properties required by the rasterizer.
|
||||
* See @SDF_Params for more.
|
||||
*
|
||||
* shape ::
|
||||
* A complete shape which is used to generate SDF.
|
||||
*
|
||||
* spread ::
|
||||
* Maximum distances to be allowed in the output bitmap.
|
||||
*
|
||||
* @Output:
|
||||
* bitmap ::
|
||||
* The output bitmap which will contain the SDF information.
|
||||
*
|
||||
* @Return:
|
||||
* FreeType error, 0 means success.
|
||||
*
|
||||
*/
|
||||
static FT_Error
|
||||
sdf_generate_bounding_box( const SDF_Params internal_params,
|
||||
const SDF_Shape* shape,
|
||||
FT_UInt spread,
|
||||
const FT_Bitmap* bitmap )
|
||||
{
|
||||
FT_Error error = FT_Err_Ok;
|
||||
FT_Memory memory = NULL;
|
||||
|
||||
FT_Int width, rows, i, j;
|
||||
FT_Int sp_sq; /* max value to check */
|
||||
|
||||
SDF_Contour* contours; /* list of all contours */
|
||||
FT_Short* buffer; /* the bitmap buffer */
|
||||
|
||||
/* This buffer has the same size in indices as the */
|
||||
/* bitmap buffer. When we check a pixel position for */
|
||||
/* a shortest distance we keep it in this buffer. */
|
||||
/* This way we can find out which pixel is set, */
|
||||
/* and also determine the signs properly. */
|
||||
SDF_Signed_Distance* dists = NULL;
|
||||
|
||||
|
||||
if ( !shape || !bitmap )
|
||||
{
|
||||
error = FT_THROW( Invalid_Argument );
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
if ( spread < MIN_SPREAD || spread > MAX_SPREAD )
|
||||
{
|
||||
error = FT_THROW( Invalid_Argument );
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
memory = shape->memory;
|
||||
if ( !memory )
|
||||
{
|
||||
error = FT_THROW( Invalid_Argument );
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
contours = shape->contours;
|
||||
width = (FT_Int)bitmap->width;
|
||||
rows = (FT_Int)bitmap->rows;
|
||||
buffer = (FT_Short*)bitmap->buffer;
|
||||
|
||||
if ( SDF_ALLOC( dists, width * rows * sizeof ( *dists ) ) )
|
||||
goto Exit;
|
||||
|
||||
FT_MEM_ZERO( dists, width * rows * sizeof ( *dists ) );
|
||||
|
||||
if ( USE_SQUARED_DISTANCES )
|
||||
sp_sq = FT_INT_16D16( spread * spread );
|
||||
else
|
||||
sp_sq = FT_INT_16D16( spread );
|
||||
|
||||
if ( width == 0 || rows == 0 )
|
||||
{
|
||||
FT_TRACE0(( "sdf_generate:"
|
||||
" Cannot render glyph with width/height == 0\n" ));
|
||||
FT_TRACE0(( " "
|
||||
" (width, height provided [%d, %d])", width, rows ));
|
||||
|
||||
error = FT_THROW( Cannot_Render_Glyph );
|
||||
goto Exit;
|
||||
}
|
||||
|
||||
/* loop over all contours */
|
||||
while ( contours )
|
||||
{
|
||||
SDF_Edge* edges = contours->edges;
|
||||
|
||||
|
||||
/* loop over all edges */
|
||||
while ( edges )
|
||||
{
|
||||
FT_CBox cbox;
|
||||
FT_Int x, y;
|
||||
|
||||
|
||||
/* get the control box and increase it by `spread' */
|
||||
cbox = get_control_box( *edges );
|
||||
|
||||
cbox.xMin = ( cbox.xMin - 63 ) / 64 - ( FT_Pos )spread;
|
||||
cbox.xMax = ( cbox.xMax + 63 ) / 64 + ( FT_Pos )spread;
|
||||
cbox.yMin = ( cbox.yMin - 63 ) / 64 - ( FT_Pos )spread;
|
||||
cbox.yMax = ( cbox.yMax + 63 ) / 64 + ( FT_Pos )spread;
|
||||
|
||||
/* now loop over the pixels in the control box. */
|
||||
for ( y = cbox.yMin; y < cbox.yMax; y++ )
|
||||
{
|
||||
for ( x = cbox.xMin; x < cbox.xMax; x++ )
|
||||
{
|
||||
FT_26D6_Vec grid_point = zero_vector;
|
||||
SDF_Signed_Distance dist = max_sdf;
|
||||
FT_UInt index = 0;
|
||||
|
||||
|
||||
if ( x < 0 || x >= width )
|
||||
continue;
|
||||
if ( y < 0 || y >= rows )
|
||||
continue;
|
||||
|
||||
grid_point.x = FT_INT_26D6( x );
|
||||
grid_point.y = FT_INT_26D6( y );
|
||||
|
||||
/* This `grid_point` is at the corner, but we */
|
||||
/* use the center of the pixel. */
|
||||
grid_point.x += FT_INT_26D6( 1 ) / 2;
|
||||
grid_point.y += FT_INT_26D6( 1 ) / 2;
|
||||
|
||||
FT_CALL( sdf_edge_get_min_distance( edges,
|
||||
grid_point,
|
||||
&dist ) );
|
||||
|
||||
if ( internal_params.orientation == FT_ORIENTATION_FILL_LEFT )
|
||||
dist.sign = -dist.sign;
|
||||
|
||||
/* ignore if the distance is greater than spread; */
|
||||
/* otherwise it creates artifacts due to the wrong sign */
|
||||
if ( dist.distance > sp_sq )
|
||||
continue;
|
||||
|
||||
/* square_root the values and fit in a 6.10 fixed-point */
|
||||
if ( USE_SQUARED_DISTANCES )
|
||||
dist.distance = square_root( dist.distance );
|
||||
|
||||
if ( internal_params.flip_y )
|
||||
index = y * width + x;
|
||||
else
|
||||
index = ( rows - y - 1 ) * width + x;
|
||||
|
||||
/* check whether the pixel is set or not */
|
||||
if ( dists[index].sign == 0 )
|
||||
dists[index] = dist;
|
||||
else if ( dists[index].distance > dist.distance )
|
||||
dists[index] = dist;
|
||||
else if ( FT_ABS( dists[index].distance - dist.distance )
|
||||
< CORNER_CHECK_EPSILON )
|
||||
dists[index] = resolve_corner( dists[index], dist );
|
||||
}
|
||||
}
|
||||
|
||||
edges = edges->next;
|
||||
}
|
||||
|
||||
contours = contours->next;
|
||||
}
|
||||
|
||||
/* final pass */
|
||||
for ( j = 0; j < rows; j++ )
|
||||
{
|
||||
/* We assume the starting pixel of each row is outside. */
|
||||
FT_Char current_sign = -1;
|
||||
FT_UInt index;
|
||||
|
||||
|
||||
if ( internal_params.overload_sign != 0 )
|
||||
current_sign = internal_params.overload_sign < 0 ? -1 : 1;
|
||||
|
||||
for ( i = 0; i < width; i++ )
|
||||
{
|
||||
index = j * width + i;
|
||||
|
||||
/* if the pixel is not set */
|
||||
/* its shortest distance is more than `spread` */
|
||||
if ( dists[index].sign == 0 )
|
||||
dists[index].distance = FT_INT_16D16( spread );
|
||||
else
|
||||
current_sign = dists[index].sign;
|
||||
|
||||
/* clamp the values */
|
||||
if ( dists[index].distance > (FT_Int)FT_INT_16D16( spread ) )
|
||||
dists[index].distance = FT_INT_16D16( spread );
|
||||
|
||||
/* convert from 16.16 to 6.10 */
|
||||
dists[index].distance /= 64;
|
||||
|
||||
if ( internal_params.flip_sign )
|
||||
buffer[index] = (FT_Short)dists[index].distance * -current_sign;
|
||||
else
|
||||
buffer[index] = (FT_Short)dists[index].distance * current_sign;
|
||||
}
|
||||
}
|
||||
|
||||
Exit:
|
||||
SDF_FREE( dists );
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
*
|
||||
* @Function:
|
||||
* sdf_generate_subdivision
|
||||
*
|
||||
* @Description:
|
||||
* Subdivide the shape into a number of straight lines, then use the
|
||||
* above `sdf_generate_bounding_box` function to generate the SDF.
|
||||
*
|
||||
* Note: After calling this function `shape` no longer has the original
|
||||
* edges, it only contains lines.
|
||||
*
|
||||
* @Input:
|
||||
* internal_params ::
|
||||
* Internal parameters and properties required by the rasterizer.
|
||||
* See @SDF_Params for more.
|
||||
*
|
||||
* shape ::
|
||||
* A complete shape which is used to generate SDF.
|
||||
*
|
||||
* spread ::
|
||||
* Maximum distances to be allowed inthe output bitmap.
|
||||
*
|
||||
* @Output:
|
||||
* bitmap ::
|
||||
* The output bitmap which will contain the SDF information.
|
||||
*
|
||||
* @Return:
|
||||
* FreeType error, 0 means success.
|
||||
*
|
||||
*/
|
||||
static FT_Error
|
||||
sdf_generate_subdivision( const SDF_Params internal_params,
|
||||
SDF_Shape* shape,
|
||||
FT_UInt spread,
|
||||
const FT_Bitmap* bitmap )
|
||||
{
|
||||
/*
|
||||
* Thanks to Alexei for providing the idea of this optimization.
|
||||
*
|
||||
* We take advantage of two facts.
|
||||
*
|
||||
* (1) Computing the shortest distance from a point to a line segment is
|
||||
* very fast.
|
||||
* (2) We don't have to compute the shortest distance for the entire
|
||||
* two-dimensional grid.
|
||||
*
|
||||
* Both ideas lead to the following optimization.
|
||||
*
|
||||
* (1) Split the outlines into a number of line segments.
|
||||
*
|
||||
* (2) For each line segment, only process its neighborhood.
|
||||
*
|
||||
* (3) Compute the closest distance to the line only for neighborhood
|
||||
* grid points.
|
||||
*
|
||||
* This greatly reduces the number of grid points to check.
|
||||
*/
|
||||
|
||||
FT_Error error = FT_Err_Ok;
|
||||
|
||||
|
||||
FT_CALL( split_sdf_shape( shape ) );
|
||||
FT_CALL( sdf_generate_bounding_box( internal_params,
|
||||
shape, spread, bitmap ) );
|
||||
|
||||
Exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
/* END */
|
||||
|
Loading…
Reference in New Issue
Block a user