[bsdf] Added function to approximate edge distances.

* src/sdf/ftbsdf.c (compute_edge_distance): Added function to approximate edges 
  given only the array of alpha values representing pixel coverage. This function uses
  the Gustavson's algorithm for approximating.

* src/sdf/ftbsdf.c (bsdf_approximate_edge): This function loops through the entire
  bitmap and for edge pixel (found using `bsdf_is_edge') compute approximate edge
  distances (using `compute_edge_distance').
This commit is contained in:
Anuj Verma 2020-08-20 09:25:15 +05:30
parent 44e9d12f0f
commit e883c6dc50
1 changed files with 245 additions and 0 deletions

View File

@ -234,5 +234,250 @@
#undef CHECK_NEIGHBOR
/**************************************************************************
*
* @Function:
* compute_edge_distance
*
* @Description:
* Approximate the outline and compute the distance from `current'
* to the approximated outline.
*
* @Input:
* current ::
* Array of distances. This parameter is an array of Euclidean
* distances. The `current' must point to the position for which
* the distance is to be caculated. We treat this array as a 2D
* array mapped to a 1D array.
*
* x ::
* The x coordinate of the `current' parameter in the array.
*
* y ::
* The y coordinate of the `current' parameter in the array.
*
* w ::
* The width of the distances array.
*
* r ::
* Number of rows in the distances array.
*
* @Return:
* FT_16D16_Vec ::
* A vector pointing to the approximate edge distance.
*
* @Note:
* This is a computationally expensive function. Try to reduce the
* number of calls to this function. Moreover this must only be used
* for edge pixel positions.
*
*/
static FT_16D16_Vec
compute_edge_distance( ED* current,
FT_Int x,
FT_Int y,
FT_Int w,
FT_Int r )
{
/* This is the function which is based on the paper presented */
/* by Stefan Gustavson and Robin Strand which is used to app- */
/* roximate edge distance from anti-aliased bitmaps. */
/* */
/* The algorithm is as follows: */
/* */
/* * In anti-aliased images, the pixel's alpha value is the */
/* coverage of the pixel by the outline. For example if the */
/* alpha value is 0.5f then we can assume the the outline */
/* passes through the center of the pixel. */
/* */
/* * So, we can use that alpha value to approximate the real */
/* distance of the pixel to edge pretty accurately. A real */
/* simple approximation is ( 0.5f - alpha ), assuming that */
/* the outline is parallel to the x or y axis. But in this */
/* algorithm we use a different approximation which is qui- */
/* te accurate even for non axis aligned edges. */
/* */
/* * The only remaining piece of information that we cannot */
/* approximate directly from the alpha is the direction of */
/* the edge. This is where we use the Sobel's operator to */
/* compute the gradient of the pixel. The gradient give us */
/* a pretty good approximation of the edge direction. */
/* We use a 3x3 kernel filter to compute the gradient. */
/* */
/* * After the above two steps we have both the direction and */
/* the distance to the edge which is used to generate the */
/* Signed Distance Field. */
/* */
/* References: */
/* * Anti-Aliased Euclidean Distance Transform: */
/* http://weber.itn.liu.se/~stegu/aadist/edtaa_preprint.pdf */
/* * Sobel Operator: */
/* https://en.wikipedia.org/wiki/Sobel_operator */
/* */
FT_16D16_Vec g = { 0, 0 };
FT_16D16 dist, current_alpha;
FT_16D16 a1, temp;
FT_16D16 gx, gy;
FT_16D16 alphas[9];
/* Since our spread cannot be 0, this condition */
/* can never be true. */
if ( x <= 0 || x >= w - 1 ||
y <= 0 || y >= r - 1 )
return g;
/* initialize the alphas */
alphas[0] = 256 * (FT_16D16)current[-w - 1].alpha;
alphas[1] = 256 * (FT_16D16)current[ -w ].alpha;
alphas[2] = 256 * (FT_16D16)current[-w + 1].alpha;
alphas[3] = 256 * (FT_16D16)current[ -1 ].alpha;
alphas[4] = 256 * (FT_16D16)current[ 0 ].alpha;
alphas[5] = 256 * (FT_16D16)current[ 1 ].alpha;
alphas[6] = 256 * (FT_16D16)current[ w - 1].alpha;
alphas[7] = 256 * (FT_16D16)current[ w ].alpha;
alphas[8] = 256 * (FT_16D16)current[ w + 1].alpha;
current_alpha = alphas[4];
/* Compute the gradient using the Sobel operator. */
/* In this case we use the following 3x3 filters: */
/* */
/* For x: | -1 0 -1 | */
/* | -root(2) 0 root(2) | */
/* | -1 0 1 | */
/* */
/* For y: | -1 -root(2) -1 | */
/* | 0 0 0 | */
/* | 1 root(2) 1 | */
/* */
/* [Note]: 92681 is nothing but root(2) in 16.16 */
g.x = -alphas[0] -
FT_MulFix( alphas[3], 92681 ) -
alphas[6] +
alphas[2] +
FT_MulFix( alphas[5], 92681 ) +
alphas[8];
g.y = -alphas[0] -
FT_MulFix( alphas[1], 92681 ) -
alphas[2] +
alphas[6] +
FT_MulFix( alphas[7], 92681 ) +
alphas[8];
FT_Vector_NormLen( &g );
/* The gradient gives us the direction of the */
/* edge for the current pixel. Once we have the */
/* approximate direction of the edge, we can */
/* approximate the edge distance much better. */
if ( g.x == 0 || g.y == 0 )
dist = ONE / 2 - alphas[4];
else
{
gx = g.x;
gy = g.y;
gx = FT_ABS( gx );
gy = FT_ABS( gy );
if ( gx < gy )
{
temp = gx;
gx = gy;
gy = temp;
}
a1 = FT_DivFix( gy, gx ) / 2;
if ( current_alpha < a1 )
dist = (( gx + gy ) / 2) -
square_root( 2 * FT_MulFix( gx,
FT_MulFix( gy, current_alpha ) ) );
else if ( current_alpha < ( ONE - a1 ) )
dist = FT_MulFix( ONE / 2 - current_alpha, gx );
else
dist = -(( gx + gy ) / 2) +
square_root( 2 * FT_MulFix( gx,
FT_MulFix( gy, ONE - current_alpha ) ) );
}
g.x = FT_MulFix( g.x, dist );
g.y = FT_MulFix( g.y, dist );
return g;
}
/**************************************************************************
*
* @Function:
* bsdf_approximate_edge
*
* @Description:
* This is a handy function which loops through all the pixels, and
* calls `compute_edge_distance' function only for edge pixels. This
* maked the process a lot faster since `compute_edge_distance' uses
* some functions such as `FT_Vector_NormLen' which are quite slow.
*
* @Input:
* worker ::
* Contains the distance map as well as all the relevant parameters
* required by the function.
*
* @Return:
* FT_Error ::
* FreeType error, 0 means success.
*
* @Note:
* The function dosen't have any actual output, it do computation on
* the `distance_map' parameter of the `worker' and put the data in
* that distance map itself.
*
*/
static FT_Error
bsdf_approximate_edge( BSDF_Worker* worker )
{
FT_Error error = FT_Err_Ok;
FT_Int i, j;
FT_Int index;
ED* ed;
if ( !worker || !worker->distance_map )
{
error = FT_THROW( Invalid_Argument );
goto Exit;
}
ed = worker->distance_map;
for ( j = 0; j < worker->rows; j++ )
{
for ( i = 0; i < worker->width; i++ )
{
index = j * worker->width + i;
if ( bsdf_is_edge( worker->distance_map + index,
i, j, worker->width, worker->rows ) )
{
/* for edge pixels approximate the edge distance */
ed[index].near = compute_edge_distance( ed + index, i, j,
worker->width, worker->rows );
ed[index].dist = VECTOR_LENGTH_16D16( ed[index].near );
}
else
{
/* for non edge pixels assign far away distances */
ed[index].dist = 400 * ONE;
ed[index].near.x = 200 * ONE;
ed[index].near.y = 200 * ONE;
}
}
}
Exit:
return error;
}
/* END */