[sdf] Added bounding box optimization.

This commit is contained in:
Anuj Verma 2020-07-05 17:03:30 +05:30 committed by anujverma
parent 1d5d8ff288
commit 1e3c41d19e
2 changed files with 299 additions and 1 deletions

View File

@ -1,3 +1,15 @@
2020-07-05 Anuj Verma <anujv@iitbhilai.ac.in>
[sdf] Added bounding box optimization.
* src/sdf/ftsdfrend.c (sdf_generate_bounding_box): The
function generate SDF just like the `sdf_generate'
function, but uses boundinb box to check pixels
efficiently.
* src/sdf/ftsdfrend.c (get_control_box): Added function
to get control box of a `SDF_Edge'.
2020-07-04 Anuj Verma <anujv@iitbhilai.ac.in>
* src/sdf/ftsdfrend.c (sdf_shape_dump): Use `%ld' to

View File

@ -100,6 +100,8 @@
typedef FT_Fixed FT_16D16; /* 16.16 fixed point representation */
typedef FT_Fixed FT_26D6; /* 26.6 fixed point representation */
typedef FT_BBox FT_CBox; /* control box of a curve */
/**************************************************************************
*
* structures and enums
@ -561,6 +563,97 @@
return error;
}
/**************************************************************************
*
* utility functions
*
*/
/* The function returns the control box of a edge. */
/* The control box is a rectangle in which all the */
/* control points can fit tightly. */
static FT_CBox
get_control_box( SDF_Edge edge )
{
FT_CBox cbox;
FT_Bool is_set = 0;
switch (edge.edge_type) {
case SDF_EDGE_CUBIC:
{
cbox.xMin = edge.control_b.x;
cbox.xMax = edge.control_b.x;
cbox.yMin = edge.control_b.y;
cbox.yMax = edge.control_b.y;
is_set = 1;
}
case SDF_EDGE_CONIC:
{
if ( is_set )
{
cbox.xMin = edge.control_a.x < cbox.xMin ?
edge.control_a.x : cbox.xMin;
cbox.xMax = edge.control_a.x > cbox.xMax ?
edge.control_a.x : cbox.xMax;
cbox.yMin = edge.control_a.y < cbox.yMin ?
edge.control_a.y : cbox.yMin;
cbox.yMax = edge.control_a.y > cbox.yMax ?
edge.control_a.y : cbox.yMax;
}
else
{
cbox.xMin = edge.control_a.x;
cbox.xMax = edge.control_a.x;
cbox.yMin = edge.control_a.y;
cbox.yMax = edge.control_a.y;
is_set = 1;
}
}
case SDF_EDGE_LINE:
{
if ( is_set )
{
cbox.xMin = edge.start_pos.x < cbox.xMin ?
edge.start_pos.x : cbox.xMin;
cbox.xMax = edge.start_pos.x > cbox.xMax ?
edge.start_pos.x : cbox.xMax;
cbox.yMin = edge.start_pos.y < cbox.yMin ?
edge.start_pos.y : cbox.yMin;
cbox.yMax = edge.start_pos.y > cbox.yMax ?
edge.start_pos.y : cbox.yMax;
}
else
{
cbox.xMin = edge.start_pos.x;
cbox.xMax = edge.start_pos.x;
cbox.yMin = edge.start_pos.y;
cbox.yMax = edge.start_pos.y;
}
cbox.xMin = edge.end_pos.x < cbox.xMin ?
edge.end_pos.x : cbox.xMin;
cbox.xMax = edge.end_pos.x > cbox.xMax ?
edge.end_pos.x : cbox.xMax;
cbox.yMin = edge.end_pos.y < cbox.yMin ?
edge.end_pos.y : cbox.yMin;
cbox.yMax = edge.end_pos.y > cbox.yMax ?
edge.end_pos.y : cbox.yMax;
break;
}
default:
break;
}
return cbox;
}
/**************************************************************************
*
* for debugging
@ -2107,6 +2200,199 @@
return error;
}
/**************************************************************************
*
* @Function:
* sdf_generate_bounding_box
*
* @Description:
* This function does basically the same thing as the above
* `sdf_generate' but more efficiently.
* Instead of checking all the pixels against all the edges, we loop
* through all the edges and only check the pixels around the control
* box of the edge, the control box is increased by the spread in all
* all the directions. Anything outside the control box will naturally
* be more than the `spread' and shouldn't be computed.
* Lastly to determine the sign of unchecked pixels we do a single pass
* of all the rows starting with a + sign and flipping when we come
* across a - sign and continue.
* This also eliminate the chance of overflow because we only check the
* proximity of the curve. Therefore we can use squared distanced
* safely.
*
* @Input:
* [TODO]
*
* @Return:
* [TODO]
*/
static FT_Error
sdf_generate_bounding_box( const SDF_Shape* shape,
FT_UInt spread,
const FT_Bitmap* bitmap )
{
FT_Error error = FT_Err_Ok;
FT_Memory memory;
FT_UInt width, rows, i, j;
FT_ListRec contours; /* list of all contours */
FT_UInt sp_sq; /* max value to check */
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 */
/* shortest distance we keep the sign in this buffer */
/* This way we check find out which pixel is set. */
FT_Char* signs;
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;
contours = shape->contours;
width = bitmap->width;
rows = bitmap->rows;
buffer = (FT_Short*)bitmap->buffer;
if ( FT_ALLOC_MULT( signs, width, rows ) )
goto Exit;
FT_MEM_ZERO( signs, width * rows );
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] sdf_generate:\n"
" Cannot render glyph with width/height == 0\n"
" (width, height provided [%d, %d])", width, rows ));
error = FT_THROW( Cannot_Render_Glyph );
goto Exit;
}
/* loop through all the contours */
while ( contours.head ) {
SDF_Contour* current_contour = (SDF_Contour*)contours.head->data;
FT_ListRec edges = current_contour->edges;
/* loop through all the edges */
while ( edges.head )
{
SDF_Edge* current_edge = (SDF_Edge*)edges.head->data;
FT_CBox cbox;
FT_Int x, y;
/* get the control box and increase by `spread' */
cbox = get_control_box( *current_edge );
cbox.xMin = ROUND_F26DOT6( cbox.xMin ) / 64 - ( FT_Pos )spread;
cbox.xMax = ROUND_F26DOT6( cbox.xMax ) / 64 + ( FT_Pos )spread;
cbox.yMin = ROUND_F26DOT6( cbox.yMin ) / 64 - ( FT_Pos )spread;
cbox.yMax = ROUND_F26DOT6( cbox.yMax ) / 64 + ( FT_Pos )spread;
/* now loop 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;
FT_Short value;
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;
index = ( rows - y - 1 ) * width + x;
FT_CALL( sdf_edge_get_min_distance( current_edge,
grid_point,
&dist ) );
/* clamp the values to spread */
if ( dist.distance > sp_sq )
dist.distance = sp_sq;
/* square_root the values and fit in a 6.10 fixed point */
if ( USE_SQUARED_DISTANCES )
dist.distance = square_root( dist.distance );
dist.distance /= 64;
/* [IMPORTANT]: Do corner checking. Unfortunately */
/* this will require more memory usage to keep the */
/* direction of each pixel. */
/* check weather the pixel is set or not */
if ( signs[index] == 0 )
{
buffer[index] = dist.distance;
signs[index] = dist.sign;
}
else if ( dist.distance < buffer[index] )
{
buffer[index] = dist.distance;
signs[index] = dist.sign;
}
}
}
edges.head = edges.head->next;
}
contours.head = contours.head->next;
}
/* final pass */
for ( j = 0; j < rows; j++ )
{
/* We assume the starting pixel of each row */
/* will be outside. */
FT_Char current_sign = -1;
FT_UInt index;
for ( i = 0; i < width; i++ )
{
index = j * width + i;
if ( signs[index] == 0 )
buffer[index] = (spread * 1024);
else
current_sign = signs[index];
buffer[index] *= current_sign;
}
}
Exit:
FT_FREE( signs );
return error;
}
/**************************************************************************
*
* interface functions
@ -2225,7 +2511,7 @@
FT_CALL( sdf_outline_decompose( outline, shape ) );
FT_CALL( sdf_generate( shape, sdf_params->spread,
FT_CALL( sdf_generate_bounding_box( shape, sdf_params->spread,
sdf_params->root.target ) );
Exit: