2020-12-24 08:29:05 +01:00
|
|
|
/****************************************************************************
|
|
|
|
*
|
|
|
|
* ftbsdf.c
|
|
|
|
*
|
|
|
|
* Signed Distance Field support for bitmap fonts (body only).
|
|
|
|
*
|
|
|
|
* Copyright (C) 2020 by
|
|
|
|
* David Turner, Robert Wilhelm, and Werner Lemberg.
|
|
|
|
*
|
|
|
|
* Written by Anuj Verma.
|
|
|
|
*
|
|
|
|
* This file is part of the FreeType project, and may only be used,
|
|
|
|
* modified, and distributed under the terms of the FreeType project
|
|
|
|
* license, LICENSE.TXT. By continuing to use, modify, or distribute
|
|
|
|
* this file you indicate that you have read the license and
|
|
|
|
* understand and accept it fully.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2020-08-16 13:39:22 +02:00
|
|
|
|
2020-08-20 05:47:38 +02:00
|
|
|
#include <freetype/internal/ftobjs.h>
|
|
|
|
#include <freetype/internal/ftdebug.h>
|
|
|
|
#include <freetype/internal/ftmemory.h>
|
|
|
|
#include <freetype/fttrigon.h>
|
|
|
|
|
|
|
|
#include "ftsdf.h"
|
|
|
|
#include "ftsdferrs.h"
|
|
|
|
#include "ftsdfcommon.h"
|
|
|
|
|
|
|
|
|
2020-08-21 12:59:23 +02:00
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* A brief technical overview of how the BSDF rasterizer works
|
|
|
|
* -----------------------------------------------------------
|
|
|
|
*
|
|
|
|
* [Notes]:
|
|
|
|
* * SDF stands for Signed Distance Field everywhere.
|
|
|
|
*
|
|
|
|
* * BSDF stands for Bitmap to Signed Distance Field rasterizer.
|
|
|
|
*
|
|
|
|
* * This renderer converts rasterized bitmaps to SDF. There is another
|
|
|
|
* renderer called 'sdf', which generates SDF directly from outlines;
|
|
|
|
* see file `ftsdf.c` for more.
|
|
|
|
*
|
|
|
|
* * The idea of generating SDF from bitmaps is taken from two research
|
|
|
|
* papers, where one is dependent on the other:
|
|
|
|
*
|
|
|
|
* - Per-Erik Danielsson: Euclidean Distance Mapping
|
|
|
|
* http://webstaff.itn.liu.se/~stegu/JFA/Danielsson.pdf
|
|
|
|
*
|
|
|
|
* From this paper we use the eight-point sequential Euclidean
|
|
|
|
* distance mapping (8SED). This is the heart of the process used
|
|
|
|
* in this rasterizer.
|
|
|
|
*
|
|
|
|
* - Stefan Gustavson, Robin Strand: Anti-aliased Euclidean distance transform.
|
|
|
|
* http://weber.itn.liu.se/~stegu/aadist/edtaa_preprint.pdf
|
|
|
|
*
|
|
|
|
* The original 8SED algorithm discards the pixels' alpha values,
|
|
|
|
* which can contain information about the actual outline of the
|
|
|
|
* glyph. This paper takes advantage of those alpha values and
|
|
|
|
* approximates outline pretty accurately.
|
|
|
|
*
|
|
|
|
* * This rasterizer also works for monochrome bitmaps. However, the
|
|
|
|
* result is not as accurate since we don't have any way to
|
|
|
|
* approximate outlines from binary bitmaps.
|
|
|
|
*
|
|
|
|
* ========================================================================
|
|
|
|
*
|
|
|
|
* Generating SDF from bitmap is done in several steps.
|
|
|
|
*
|
|
|
|
* (1) The only information we have is the bitmap itself. It can
|
|
|
|
* be monochrome or anti-aliased. If it is anti-aliased, pixel values
|
|
|
|
* are nothing but coverage values. These coverage values can be used
|
|
|
|
* to extract information about the outline of the image. For
|
|
|
|
* example, if the pixel's alpha value is 0.5, then we can safely
|
|
|
|
* assume that the outline passes through the center of the pixel.
|
|
|
|
*
|
|
|
|
* (2) Find edge pixels in the bitmap (see `bsdf_is_edge` for more). For
|
|
|
|
* all edge pixels we use the Anti-aliased Euclidean distance
|
|
|
|
* transform algorithm and compute approximate edge distances (see
|
|
|
|
* `compute_edge_distance` and/or the second paper for more).
|
|
|
|
*
|
|
|
|
* (3) Now that we have computed approximate distances for edge pixels we
|
|
|
|
* use the 8SED algorithm to basically sweep the entire bitmap and
|
|
|
|
* compute distances for the rest of the pixels. (Since the algorithm
|
|
|
|
* is pretty convoluted it is only explained briefly in a comment to
|
|
|
|
* function `edt8`. To see the actual algorithm refer to the first
|
|
|
|
* paper.)
|
|
|
|
*
|
|
|
|
* (4) Finally, compute the sign for each pixel. This is done in function
|
|
|
|
* `finalize_sdf`. The basic idea is that if a pixel's original
|
|
|
|
* alpha/coverage value is greater than 0.5 then it is 'inside' (and
|
|
|
|
* 'outside' otherwise).
|
|
|
|
*
|
|
|
|
* Pseudo Code:
|
|
|
|
*
|
|
|
|
* ```
|
|
|
|
* b = source bitmap;
|
|
|
|
* t = target bitmap;
|
|
|
|
* dm = list of distances; // dimension equal to b
|
|
|
|
*
|
|
|
|
* foreach grid_point (x, y) in b:
|
|
|
|
* {
|
|
|
|
* if (is_edge(x, y)):
|
|
|
|
* dm = approximate_edge_distance(b, x, y);
|
|
|
|
*
|
|
|
|
* // do the 8SED on the distances
|
|
|
|
* edt8(dm);
|
|
|
|
*
|
|
|
|
* // determine the signs
|
|
|
|
* determine_signs(dm):
|
|
|
|
*
|
|
|
|
* // copy SDF data to the target bitmap
|
|
|
|
* copy(dm to t);
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2020-08-20 05:47:38 +02:00
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* useful macros
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define ONE 65536 /* 1 in 16.16 */
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* structs
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* @Struct:
|
|
|
|
* BSDF_TRaster
|
|
|
|
*
|
|
|
|
* @Description:
|
|
|
|
* This struct is used in place of @FT_Raster and is stored within the
|
|
|
|
* internal FreeType renderer struct. While rasterizing this is passed
|
|
|
|
* to the @FT_Raster_RenderFunc function, which then can be used however
|
|
|
|
* we want.
|
|
|
|
*
|
|
|
|
* @Fields:
|
|
|
|
* memory ::
|
|
|
|
* Used internally to allocate intermediate memory while raterizing.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
typedef struct BSDF_TRaster_
|
|
|
|
{
|
|
|
|
FT_Memory memory;
|
|
|
|
|
|
|
|
} BSDF_TRaster;
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* @Struct:
|
|
|
|
* ED
|
|
|
|
*
|
|
|
|
* @Description:
|
|
|
|
* Euclidean distance. It gets used for Euclidean distance transforms;
|
|
|
|
* it can also be interpreted as an edge distance.
|
|
|
|
*
|
|
|
|
* @Fields:
|
|
|
|
* dist ::
|
|
|
|
* Vector length of the `near` parameter. Can be squared or absolute
|
|
|
|
* depending on the `USE_SQUARED_DISTANCES` macro defined in file
|
|
|
|
* `ftsdfcommon.h`.
|
|
|
|
*
|
|
|
|
* near ::
|
|
|
|
* Vector to the nearest edge. Can also be interpreted as shortest
|
|
|
|
* distance of a point.
|
|
|
|
*
|
|
|
|
* alpha ::
|
|
|
|
* Alpha value of the original bitmap from which we generate SDF.
|
|
|
|
* Needed for computing the gradient and determining the proper sign
|
|
|
|
* of a pixel.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
typedef struct ED_
|
|
|
|
{
|
|
|
|
FT_16D16 dist;
|
|
|
|
FT_16D16_Vec near;
|
|
|
|
FT_Byte alpha;
|
|
|
|
|
|
|
|
} ED;
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* @Struct:
|
|
|
|
* BSDF_Worker
|
|
|
|
*
|
|
|
|
* @Description:
|
|
|
|
* A convenience struct that is passed to functions while generating
|
|
|
|
* SDF; most of those functions require the same parameters.
|
|
|
|
*
|
|
|
|
* @Fields:
|
|
|
|
* distance_map ::
|
|
|
|
* A one-dimensional array that gets interpreted as two-dimensional
|
|
|
|
* one. It contains the Euclidean distances of all points of the
|
|
|
|
* bitmap.
|
|
|
|
*
|
|
|
|
* width ::
|
|
|
|
* Width of the above `distance_map`.
|
|
|
|
*
|
|
|
|
* rows ::
|
|
|
|
* Number of rows in the above `distance_map`.
|
|
|
|
*
|
|
|
|
* params ::
|
|
|
|
* Internal parameters and properties required by the rasterizer. See
|
|
|
|
* file `ftsdf.h` for more.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
typedef struct BSDF_Worker_
|
|
|
|
{
|
|
|
|
ED* distance_map;
|
|
|
|
|
|
|
|
FT_Int width;
|
|
|
|
FT_Int rows;
|
|
|
|
|
|
|
|
SDF_Raster_Params params;
|
|
|
|
|
|
|
|
} BSDF_Worker;
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* initializer
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
static const ED zero_ed = { 0, { 0, 0 }, 0 };
|
|
|
|
|
2020-08-16 13:39:22 +02:00
|
|
|
|
2020-08-20 05:50:26 +02:00
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* rasterizer functions
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* @Function:
|
|
|
|
* bsdf_is_edge
|
|
|
|
*
|
|
|
|
* @Description:
|
|
|
|
* Check whether a pixel is an edge pixel, i.e., whether it is
|
|
|
|
* surrounded by a completely black pixel (zero alpha), and the current
|
|
|
|
* pixel is not a completely black pixel.
|
|
|
|
*
|
|
|
|
* @Input:
|
|
|
|
* dm ::
|
|
|
|
* Array of distances. The parameter must point to the current
|
|
|
|
* pixel, i.e., the pixel that is to be checked for being an edge.
|
|
|
|
*
|
|
|
|
* x ::
|
|
|
|
* The x position of the current pixel.
|
|
|
|
*
|
|
|
|
* y ::
|
|
|
|
* The y position of the current pixel.
|
|
|
|
*
|
|
|
|
* w ::
|
|
|
|
* Width of the bitmap.
|
|
|
|
*
|
|
|
|
* r ::
|
|
|
|
* Number of rows in the bitmap.
|
|
|
|
*
|
|
|
|
* @Return:
|
|
|
|
* 1~if the current pixel is an edge pixel, 0~otherwise.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef CHECK_NEIGHBOR
|
|
|
|
#undef CHECK_NEIGHBOR
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define CHECK_NEIGHBOR( x_offset, y_offset ) \
|
|
|
|
if ( x + x_offset >= 0 && x + x_offset < w && \
|
|
|
|
y + y_offset >= 0 && y + y_offset < r ) \
|
|
|
|
{ \
|
|
|
|
num_neighbors++; \
|
|
|
|
\
|
|
|
|
to_check = dm + y_offset * w + x_offset; \
|
|
|
|
if ( to_check->alpha == 0 ) \
|
|
|
|
{ \
|
|
|
|
is_edge = 1; \
|
|
|
|
goto Done; \
|
|
|
|
} \
|
|
|
|
}
|
|
|
|
|
|
|
|
static FT_Bool
|
|
|
|
bsdf_is_edge( ED* dm, /* distance map */
|
|
|
|
FT_Int x, /* x index of point to check */
|
|
|
|
FT_Int y, /* y index of point to check */
|
|
|
|
FT_Int w, /* width */
|
|
|
|
FT_Int r ) /* rows */
|
|
|
|
{
|
2020-08-20 05:55:15 +02:00
|
|
|
FT_Bool is_edge = 0;
|
2020-08-20 05:50:26 +02:00
|
|
|
ED* to_check = NULL;
|
|
|
|
FT_Int num_neighbors = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if ( dm->alpha == 0 )
|
|
|
|
goto Done;
|
|
|
|
|
|
|
|
if ( dm->alpha > 0 && dm->alpha < 255 )
|
|
|
|
{
|
|
|
|
is_edge = 1;
|
|
|
|
goto Done;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* up */
|
|
|
|
CHECK_NEIGHBOR( 0, -1 );
|
|
|
|
|
|
|
|
/* down */
|
|
|
|
CHECK_NEIGHBOR( 0, 1 );
|
|
|
|
|
|
|
|
/* left */
|
|
|
|
CHECK_NEIGHBOR( -1, 0 );
|
|
|
|
|
|
|
|
/* right */
|
|
|
|
CHECK_NEIGHBOR( 1, 0 );
|
|
|
|
|
|
|
|
/* up left */
|
|
|
|
CHECK_NEIGHBOR( -1, -1 );
|
|
|
|
|
|
|
|
/* up right */
|
|
|
|
CHECK_NEIGHBOR( 1, -1 );
|
|
|
|
|
|
|
|
/* down left */
|
|
|
|
CHECK_NEIGHBOR( -1, 1 );
|
|
|
|
|
|
|
|
/* down right */
|
|
|
|
CHECK_NEIGHBOR( 1, 1 );
|
|
|
|
|
|
|
|
if ( num_neighbors != 8 )
|
|
|
|
is_edge = 1;
|
|
|
|
|
|
|
|
Done:
|
|
|
|
return is_edge;
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef CHECK_NEIGHBOR
|
|
|
|
|
|
|
|
|
2020-08-20 05:55:15 +02:00
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* @Function:
|
|
|
|
* compute_edge_distance
|
|
|
|
*
|
|
|
|
* @Description:
|
|
|
|
* Approximate the outline and compute the distance from `current`
|
|
|
|
* to the approximated outline.
|
|
|
|
*
|
|
|
|
* @Input:
|
|
|
|
* current ::
|
|
|
|
* Array of Euclidean distances. `current` must point to the position
|
|
|
|
* for which the distance is to be caculated. We treat this array as
|
|
|
|
* a two-dimensional array mapped to a one-dimensional 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:
|
|
|
|
* 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 function, based on the paper presented by Stefan Gustavson and
|
|
|
|
* Robin Strand, gets used to approximate edge distances from
|
|
|
|
* anti-aliased bitmaps.
|
|
|
|
*
|
|
|
|
* The algorithm is as follows.
|
|
|
|
*
|
|
|
|
* (1) 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 we can assume that the outline passes through the center of
|
|
|
|
* the pixel.
|
|
|
|
*
|
|
|
|
* (2) For this reason we can use that alpha value to approximate the real
|
|
|
|
* distance of the pixel to edge pretty accurately. A simple
|
|
|
|
* approximation is `(0.5f - alpha)`, assuming that the outline is
|
|
|
|
* parallel to the x or y~axis. However, in this algorithm we use a
|
|
|
|
* different approximation which is quite accurate even for
|
|
|
|
* non-axis-aligned edges.
|
|
|
|
*
|
|
|
|
* (3) The only remaining piece of information that we cannot
|
2020-08-21 06:19:32 +02:00
|
|
|
* approximate directly from the alpha is the direction of the edge.
|
2020-08-20 05:55:15 +02:00
|
|
|
* This is where we use 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.
|
|
|
|
*
|
|
|
|
* (4) 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 root(2) in 16.16 format. */
|
|
|
|
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:
|
|
|
|
* Loops over all the pixels and call `compute_edge_distance` only for
|
|
|
|
* edge pixels. This maked the process a lot faster since
|
|
|
|
* `compute_edge_distance` uses functions such as `FT_Vector_NormLen',
|
|
|
|
* which are quite slow.
|
|
|
|
*
|
|
|
|
* @InOut:
|
|
|
|
* worker ::
|
|
|
|
* Contains the distance map as well as all the relevant parameters
|
|
|
|
* required by the function.
|
|
|
|
*
|
|
|
|
* @Return:
|
|
|
|
* FreeType error, 0 means success.
|
|
|
|
*
|
|
|
|
* @Note:
|
|
|
|
* The function directly manipulates `worker->distance_map`.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
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 ) )
|
|
|
|
{
|
|
|
|
/* approximate the edge distance for edge pixels */
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-08-21 06:09:23 +02:00
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* @Function:
|
|
|
|
* bsdf_init_distance_map
|
|
|
|
*
|
|
|
|
* @Description:
|
|
|
|
* Initialize the distance map according to the '8-point sequential
|
|
|
|
* Euclidean distance mapping' (8SED) algorithm. Basically it copies
|
|
|
|
* the `source` bitmap alpha values to the `distance_map->alpha`
|
|
|
|
* parameter of `worker`.
|
|
|
|
*
|
|
|
|
* @Input:
|
|
|
|
* source ::
|
|
|
|
* Source bitmap to copy the data from.
|
|
|
|
*
|
|
|
|
* @Output:
|
|
|
|
* worker ::
|
|
|
|
* Target distance map to copy the data to.
|
|
|
|
*
|
|
|
|
* @Return:
|
|
|
|
* FreeType error, 0 means success.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static FT_Error
|
|
|
|
bsdf_init_distance_map( const FT_Bitmap* source,
|
|
|
|
BSDF_Worker* worker )
|
|
|
|
{
|
|
|
|
FT_Error error = FT_Err_Ok;
|
|
|
|
|
|
|
|
FT_Int x_diff, y_diff;
|
|
|
|
FT_Int t_i, t_j, s_i, s_j;
|
|
|
|
FT_Byte* s;
|
|
|
|
ED* t;
|
|
|
|
|
|
|
|
|
|
|
|
/* again check the parameters (probably unnecessary) */
|
|
|
|
if ( !source || !worker )
|
|
|
|
{
|
|
|
|
error = FT_THROW( Invalid_Argument );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Because of the way we convert a bitmap to SDF, */
|
|
|
|
/* i.e., aligning the source to the center of the */
|
|
|
|
/* target, the target's width and rows must be */
|
|
|
|
/* checked before copying. */
|
|
|
|
if ( worker->width < (FT_Int)source->width ||
|
|
|
|
worker->rows < (FT_Int)source->rows )
|
|
|
|
{
|
|
|
|
error = FT_THROW( Invalid_Argument );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check pixel mode */
|
|
|
|
if ( source->pixel_mode == FT_PIXEL_MODE_NONE )
|
|
|
|
{
|
|
|
|
FT_ERROR(( "bsdf_copy_source_to_target:"
|
|
|
|
" Invalid pixel mode of source bitmap" ));
|
|
|
|
error = FT_THROW( Invalid_Argument );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef FT_DEBUG_LEVEL_TRACE
|
|
|
|
if ( source->pixel_mode == FT_PIXEL_MODE_MONO )
|
|
|
|
{
|
|
|
|
FT_TRACE0(( "bsdf_copy_source_to_target:"
|
|
|
|
" The `bsdf' renderer can convert monochrome\n" ));
|
|
|
|
FT_TRACE0(( " "
|
|
|
|
" bitmaps to SDF but the results are not perfect\n" ));
|
|
|
|
FT_TRACE0(( " "
|
|
|
|
" because there is no way to approximate actual\n" ));
|
|
|
|
FT_TRACE0(( " "
|
|
|
|
" outlines from monochrome bitmaps. Consider\n" ));
|
|
|
|
FT_TRACE0(( " "
|
|
|
|
" using an anti-aliased bitmap instead.\n" ));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Calculate the width and row differences */
|
|
|
|
/* between target and source. */
|
|
|
|
x_diff = worker->width - source->width;
|
|
|
|
y_diff = worker->rows - source->rows;
|
|
|
|
|
|
|
|
x_diff /= 2;
|
|
|
|
y_diff /= 2;
|
|
|
|
|
|
|
|
t = (ED*)worker->distance_map;
|
|
|
|
s = source->buffer;
|
|
|
|
|
|
|
|
/* For now we only support pixel mode `FT_PIXEL_MODE_MONO` */
|
|
|
|
/* and `FT_PIXEL_MODE_GRAY`. More will be added later. */
|
|
|
|
/* */
|
|
|
|
/* [NOTE]: We can also use @FT_Bitmap_Convert to convert */
|
|
|
|
/* bitmap to 8bpp. To avoid extra allocation and */
|
|
|
|
/* since the target bitmap can be 16bpp we manually */
|
|
|
|
/* convert the source bitmap to the desired bpp. */
|
|
|
|
|
|
|
|
switch ( source->pixel_mode )
|
|
|
|
{
|
|
|
|
case FT_PIXEL_MODE_MONO:
|
|
|
|
{
|
|
|
|
FT_Int t_width = worker->width;
|
|
|
|
FT_Int t_rows = worker->rows;
|
|
|
|
FT_Int s_width = source->width;
|
|
|
|
FT_Int s_rows = source->rows;
|
|
|
|
|
|
|
|
|
|
|
|
for ( t_j = 0; t_j < t_rows; t_j++ )
|
|
|
|
{
|
|
|
|
for ( t_i = 0; t_i < t_width; t_i++ )
|
|
|
|
{
|
|
|
|
FT_Int t_index = t_j * t_width + t_i;
|
|
|
|
FT_Int s_index;
|
|
|
|
FT_Int div, mod;
|
|
|
|
FT_Byte pixel, byte;
|
|
|
|
|
|
|
|
|
|
|
|
t[t_index] = zero_ed;
|
|
|
|
|
|
|
|
s_i = t_i - x_diff;
|
|
|
|
s_j = t_j - y_diff;
|
|
|
|
|
|
|
|
/* Assign 0 to padding similar to */
|
|
|
|
/* the source bitmap. */
|
|
|
|
if ( s_i < 0 || s_i >= s_width ||
|
|
|
|
s_j < 0 || s_j >= s_rows )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ( worker->params.flip_y )
|
|
|
|
s_index = ( s_rows - s_j - 1 ) * source->pitch;
|
|
|
|
else
|
|
|
|
s_index = s_j * source->pitch;
|
|
|
|
|
|
|
|
div = s_index + s_i / 8;
|
|
|
|
mod = 7 - s_i % 8;
|
|
|
|
|
|
|
|
pixel = s[div];
|
|
|
|
byte = 1 << mod;
|
|
|
|
|
|
|
|
t[t_index].alpha = pixel & byte ? 255 : 0;
|
|
|
|
|
|
|
|
pixel = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FT_PIXEL_MODE_GRAY:
|
|
|
|
{
|
|
|
|
FT_Int t_width = worker->width;
|
|
|
|
FT_Int t_rows = worker->rows;
|
|
|
|
FT_Int s_width = source->width;
|
|
|
|
FT_Int s_rows = source->rows;
|
|
|
|
|
|
|
|
|
|
|
|
/* loop over all pixels and assign pixel values from source */
|
|
|
|
for ( t_j = 0; t_j < t_rows; t_j++ )
|
|
|
|
{
|
|
|
|
for ( t_i = 0; t_i < t_width; t_i++ )
|
|
|
|
{
|
|
|
|
FT_Int t_index = t_j * t_width + t_i;
|
|
|
|
FT_Int s_index;
|
|
|
|
|
|
|
|
|
|
|
|
t[t_index] = zero_ed;
|
|
|
|
|
|
|
|
s_i = t_i - x_diff;
|
|
|
|
s_j = t_j - y_diff;
|
|
|
|
|
|
|
|
/* Assign 0 to padding similar to */
|
|
|
|
/* the source bitmap. */
|
|
|
|
if ( s_i < 0 || s_i >= s_width ||
|
|
|
|
s_j < 0 || s_j >= s_rows )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ( worker->params.flip_y )
|
|
|
|
s_index = ( s_rows - s_j - 1 ) * s_width + s_i;
|
|
|
|
else
|
|
|
|
s_index = s_j * s_width + s_i;
|
|
|
|
|
|
|
|
/* simply copy the alpha values */
|
|
|
|
t[t_index].alpha = s[s_index];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
FT_ERROR(( "bsdf_copy_source_to_target:"
|
|
|
|
" unsopported pixel mode of source bitmap\n" ));
|
|
|
|
|
|
|
|
error = FT_THROW( Unimplemented_Feature );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
Exit:
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-08-21 06:19:32 +02:00
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* @Function:
|
|
|
|
* compare_neighbor
|
|
|
|
*
|
|
|
|
* @Description:
|
|
|
|
* Compare neighbor pixel (which is defined by the offset) and update
|
|
|
|
* `current` distance if the new distance is shorter than the original.
|
|
|
|
*
|
|
|
|
* @Input:
|
|
|
|
* x_offset ::
|
|
|
|
* X offset of the neighbor to be checked. The offset is relative to
|
|
|
|
* the `current`.
|
|
|
|
*
|
|
|
|
* y_offset ::
|
|
|
|
* Y offset of the neighbor to be checked. The offset is relative to
|
|
|
|
* the `current`.
|
|
|
|
*
|
|
|
|
* width ::
|
|
|
|
* Width of the `current` array.
|
|
|
|
*
|
|
|
|
* @InOut:
|
|
|
|
* current ::
|
|
|
|
* Pointer into array of distances. This parameter must point to the
|
|
|
|
* position whose neighbor is to be checked. The array is treated as
|
|
|
|
* a two-dimensional array.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
compare_neighbor( ED* current,
|
|
|
|
FT_Int x_offset,
|
|
|
|
FT_Int y_offset,
|
|
|
|
FT_Int width )
|
|
|
|
{
|
|
|
|
ED* to_check;
|
|
|
|
FT_16D16 dist;
|
|
|
|
FT_16D16_Vec dist_vec;
|
|
|
|
|
|
|
|
|
|
|
|
to_check = current + ( y_offset * width ) + x_offset;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* While checking for the nearest point we first approximate the
|
|
|
|
* distance of `current` by adding the deviation (which is sqrt(2) at
|
|
|
|
* most). Only if the new value is less than the current value we
|
|
|
|
* calculate the actual distances using `FT_Vector_Length`. This last
|
|
|
|
* step can be omitted by using squared distances.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Approximate the distance. We subtract 1 to avoid precision errors,
|
|
|
|
* which could happen because the two directions can be opposite.
|
|
|
|
*/
|
|
|
|
dist = to_check->dist - ONE;
|
|
|
|
|
|
|
|
if ( dist < current->dist )
|
|
|
|
{
|
|
|
|
dist_vec = to_check->near;
|
|
|
|
|
|
|
|
dist_vec.x += x_offset * ONE;
|
|
|
|
dist_vec.y += y_offset * ONE;
|
|
|
|
dist = VECTOR_LENGTH_16D16( dist_vec );
|
|
|
|
|
|
|
|
if ( dist < current->dist )
|
|
|
|
{
|
|
|
|
current->dist = dist;
|
|
|
|
current->near = dist_vec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* @Function:
|
|
|
|
* first_pass
|
|
|
|
*
|
|
|
|
* @Description:
|
|
|
|
* First pass of the 8SED algorithm. Loop over the bitmap from top to
|
|
|
|
* bottom and scan each row left to right, updating the distances in
|
|
|
|
* `worker->distance_map`.
|
|
|
|
*
|
|
|
|
* @InOut:
|
|
|
|
* worker::
|
|
|
|
* Contains all the relevant parameters.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
first_pass( BSDF_Worker* worker )
|
|
|
|
{
|
|
|
|
FT_Int i, j; /* iterators */
|
|
|
|
FT_Int w, r; /* width, rows */
|
|
|
|
ED* dm; /* distance map */
|
|
|
|
|
|
|
|
|
|
|
|
dm = worker->distance_map;
|
|
|
|
w = worker->width;
|
|
|
|
r = worker->rows;
|
|
|
|
|
|
|
|
/* Start scanning from top to bottom and sweep each */
|
|
|
|
/* row back and forth comparing the distances of the */
|
|
|
|
/* neighborhood. Leave the first row as it has no top */
|
|
|
|
/* neighbor; it will be covered in the second scan of */
|
|
|
|
/* the image (from bottom to top). */
|
|
|
|
for ( j = 1; j < r; j++ )
|
|
|
|
{
|
|
|
|
FT_Int index;
|
|
|
|
ED* current;
|
|
|
|
|
|
|
|
|
|
|
|
/* Forward pass of rows (left -> right). Leave the first */
|
|
|
|
/* column, which gets covered in the backward pass. */
|
|
|
|
for ( i = 1; i < w; i++ )
|
|
|
|
{
|
|
|
|
index = j * w + i;
|
|
|
|
current = dm + index;
|
|
|
|
|
|
|
|
/* left-up */
|
|
|
|
compare_neighbor( current, -1, -1, w );
|
|
|
|
/* up */
|
|
|
|
compare_neighbor( current, 0, -1, w );
|
|
|
|
/* up-right */
|
|
|
|
compare_neighbor( current, 1, -1, w );
|
|
|
|
/* left */
|
|
|
|
compare_neighbor( current, -1, 0, w );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Backward pass of rows (right -> left). Leave the last */
|
|
|
|
/* column, which was already covered in the forward pass. */
|
|
|
|
for ( i = w - 2; i >= 0; i-- )
|
|
|
|
{
|
|
|
|
index = j * w + i;
|
|
|
|
current = dm + index;
|
|
|
|
|
|
|
|
/* right */
|
|
|
|
compare_neighbor( current, 1, 0, w );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* @Function:
|
|
|
|
* second_pass
|
|
|
|
*
|
|
|
|
* @Description:
|
|
|
|
* Second pass of the 8SED algorithm. Loop over the bitmap from bottom
|
|
|
|
* to top and scan each row left to right, updating the distances in
|
|
|
|
* `worker->distance_map`.
|
|
|
|
*
|
|
|
|
* @InOut:
|
|
|
|
* worker::
|
|
|
|
* Contains all the relevant parameters.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
second_pass( BSDF_Worker* worker )
|
|
|
|
{
|
|
|
|
FT_Int i, j; /* iterators */
|
|
|
|
FT_Int w, r; /* width, rows */
|
|
|
|
ED* dm; /* distance map */
|
|
|
|
|
|
|
|
|
|
|
|
dm = worker->distance_map;
|
|
|
|
w = worker->width;
|
|
|
|
r = worker->rows;
|
|
|
|
|
|
|
|
/* Start scanning from bottom to top and sweep each */
|
|
|
|
/* row back and forth comparing the distances of the */
|
|
|
|
/* neighborhood. Leave the last row as it has no down */
|
|
|
|
/* neighbor; it is already covered in the first scan */
|
|
|
|
/* of the image (from top to bottom). */
|
|
|
|
for ( j = r - 2; j >= 0; j-- )
|
|
|
|
{
|
|
|
|
FT_Int index;
|
|
|
|
ED* current;
|
|
|
|
|
|
|
|
|
|
|
|
/* Forward pass of rows (left -> right). Leave the first */
|
|
|
|
/* column, which gets covered in the backward pass. */
|
|
|
|
for ( i = 1; i < w; i++ )
|
|
|
|
{
|
|
|
|
index = j * w + i;
|
|
|
|
current = dm + index;
|
|
|
|
|
|
|
|
/* left-up */
|
|
|
|
compare_neighbor( current, -1, 1, w );
|
|
|
|
/* up */
|
|
|
|
compare_neighbor( current, 0, 1, w );
|
|
|
|
/* up-right */
|
|
|
|
compare_neighbor( current, 1, 1, w );
|
|
|
|
/* left */
|
|
|
|
compare_neighbor( current, -1, 0, w );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Backward pass of rows (right -> left). Leave the last */
|
|
|
|
/* column, which was already covered in the forward pass. */
|
|
|
|
for ( i = w - 2; i >= 0; i-- )
|
|
|
|
{
|
|
|
|
index = j * w + i;
|
|
|
|
current = dm + index;
|
|
|
|
|
|
|
|
/* right */
|
|
|
|
compare_neighbor( current, 1, 0, w );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* @Function:
|
|
|
|
* edt8
|
|
|
|
*
|
|
|
|
* @Description:
|
|
|
|
* Compute the distance map of the a bitmap. Execute both first and
|
|
|
|
* second pass of the 8SED algorithm.
|
|
|
|
*
|
|
|
|
* @InOut:
|
|
|
|
* worker::
|
|
|
|
* Contains all the relevant parameters.
|
|
|
|
*
|
|
|
|
* @Return:
|
|
|
|
* FreeType error, 0 means success.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static FT_Error
|
|
|
|
edt8( BSDF_Worker* worker )
|
|
|
|
{
|
|
|
|
FT_Error error = FT_Err_Ok;
|
|
|
|
|
|
|
|
|
|
|
|
if ( !worker || !worker->distance_map )
|
|
|
|
{
|
|
|
|
error = FT_THROW( Invalid_Argument );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* first scan of the image */
|
|
|
|
first_pass( worker );
|
|
|
|
|
|
|
|
/* second scan of the image */
|
|
|
|
second_pass( worker );
|
|
|
|
|
|
|
|
Exit:
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2020-08-21 06:21:33 +02:00
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* @Function:
|
|
|
|
* finalize_sdf
|
|
|
|
*
|
|
|
|
* @Description:
|
|
|
|
* Copy the SDF data from `worker->distance_map` to the `target` bitmap.
|
|
|
|
* Also transform the data to output format, (which is 6.10 fixed-point
|
|
|
|
* format at the moment).
|
|
|
|
*
|
|
|
|
* @Input:
|
|
|
|
* worker ::
|
|
|
|
* Contains source distance map and other SDF data.
|
|
|
|
*
|
|
|
|
* @Output:
|
|
|
|
* target ::
|
|
|
|
* Target bitmap to which the SDF data is copied to.
|
|
|
|
*
|
|
|
|
* @Return:
|
|
|
|
* FreeType error, 0 means success.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static FT_Error
|
|
|
|
finalize_sdf( BSDF_Worker* worker,
|
|
|
|
const FT_Bitmap* target )
|
|
|
|
{
|
|
|
|
FT_Error error = FT_Err_Ok;
|
|
|
|
|
|
|
|
FT_Int w, r;
|
|
|
|
FT_Int i, j;
|
|
|
|
FT_6D10* t_buffer;
|
|
|
|
FT_16D16 spread;
|
|
|
|
|
|
|
|
|
|
|
|
if ( !worker || !target )
|
|
|
|
{
|
|
|
|
error = FT_THROW( Invalid_Argument );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
w = target->width;
|
|
|
|
r = target->rows;
|
|
|
|
t_buffer = (FT_6D10*)target->buffer;
|
|
|
|
|
|
|
|
if ( w != worker->width ||
|
|
|
|
r != worker->rows )
|
|
|
|
{
|
|
|
|
error = FT_THROW( Invalid_Argument );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if USE_SQUARED_DISTANCES
|
|
|
|
spread = FT_INT_16D16( worker->params.spread *
|
|
|
|
worker->params.spread );
|
|
|
|
#else
|
|
|
|
spread = FT_INT_16D16( worker->params.spread );
|
|
|
|
#endif
|
|
|
|
|
|
|
|
for ( j = 0; j < r; j++ )
|
|
|
|
{
|
|
|
|
for ( i = 0; i < w; i++ )
|
|
|
|
{
|
|
|
|
FT_Int index;
|
|
|
|
FT_16D16 dist;
|
|
|
|
FT_6D10 final_dist;
|
|
|
|
FT_Char sign;
|
|
|
|
|
|
|
|
|
|
|
|
index = j * w + i;
|
|
|
|
dist = worker->distance_map[index].dist;
|
|
|
|
|
|
|
|
if ( dist < 0 || dist > spread )
|
|
|
|
dist = spread;
|
|
|
|
|
|
|
|
#if USE_SQUARED_DISTANCES
|
|
|
|
dist = square_root( dist );
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* convert from 16.16 to 6.10 */
|
|
|
|
dist /= 64;
|
|
|
|
final_dist = (FT_6D10)(dist & 0x0000FFFF);
|
|
|
|
|
|
|
|
/* We assume that if the pixel is inside a contour */
|
|
|
|
/* its coverage value must be > 127. */
|
|
|
|
sign = worker->distance_map[index].alpha < 127 ? -1 : 1;
|
|
|
|
|
|
|
|
/* flip the sign according to the property */
|
|
|
|
if ( worker->params.flip_sign )
|
|
|
|
sign = -sign;
|
|
|
|
|
|
|
|
t_buffer[index] = final_dist * sign;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Exit:
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2020-08-21 06:25:46 +02:00
|
|
|
|
|
|
|
/**************************************************************************
|
|
|
|
*
|
|
|
|
* interface functions
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* called when adding a new module through @FT_Add_Module */
|
|
|
|
static FT_Error
|
|
|
|
bsdf_raster_new( FT_Memory memory,
|
|
|
|
FT_Raster* araster )
|
|
|
|
{
|
|
|
|
FT_Error error = FT_Err_Ok;
|
|
|
|
BSDF_TRaster* raster = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
*araster = 0;
|
|
|
|
if ( !FT_ALLOC( raster, sizeof ( BSDF_TRaster ) ) )
|
|
|
|
{
|
|
|
|
raster->memory = memory;
|
|
|
|
*araster = (FT_Raster)raster;
|
|
|
|
}
|
|
|
|
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* unused */
|
|
|
|
static void
|
|
|
|
bsdf_raster_reset( FT_Raster raster,
|
|
|
|
unsigned char* pool_base,
|
|
|
|
unsigned long pool_size )
|
|
|
|
{
|
|
|
|
FT_UNUSED( raster );
|
|
|
|
FT_UNUSED( pool_base );
|
|
|
|
FT_UNUSED( pool_size );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* unused */
|
|
|
|
static FT_Error
|
|
|
|
bsdf_raster_set_mode( FT_Raster raster,
|
|
|
|
unsigned long mode,
|
|
|
|
void* args )
|
|
|
|
{
|
|
|
|
FT_UNUSED( raster );
|
|
|
|
FT_UNUSED( mode );
|
|
|
|
FT_UNUSED( args );
|
|
|
|
|
|
|
|
return FT_Err_Ok;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* called while rendering through @FT_Render_Glyph */
|
|
|
|
static FT_Error
|
|
|
|
bsdf_raster_render( FT_Raster raster,
|
|
|
|
const FT_Raster_Params* params )
|
|
|
|
{
|
|
|
|
FT_Error error = FT_Err_Ok;
|
|
|
|
FT_Memory memory = NULL;
|
|
|
|
|
|
|
|
const FT_Bitmap* source = NULL;
|
|
|
|
const FT_Bitmap* target = NULL;
|
|
|
|
|
|
|
|
BSDF_TRaster* bsdf_raster = (BSDF_TRaster*)raster;
|
|
|
|
BSDF_Worker worker;
|
|
|
|
|
|
|
|
const SDF_Raster_Params* sdf_params = (const SDF_Raster_Params*)params;
|
|
|
|
|
|
|
|
|
|
|
|
worker.distance_map = NULL;
|
|
|
|
|
|
|
|
/* check for valid parameters */
|
|
|
|
if ( !raster || !params )
|
|
|
|
{
|
|
|
|
error = FT_THROW( Invalid_Argument );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check whether the flag is set */
|
|
|
|
if ( sdf_params->root.flags != FT_RASTER_FLAG_SDF )
|
|
|
|
{
|
|
|
|
error = FT_THROW( Raster_Corrupted );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
source = sdf_params->root.source;
|
|
|
|
target = sdf_params->root.target;
|
|
|
|
|
|
|
|
/* check source and target bitmap */
|
|
|
|
if ( !source || !target )
|
|
|
|
{
|
|
|
|
error = FT_THROW( Invalid_Argument );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
memory = bsdf_raster->memory;
|
|
|
|
if ( !memory )
|
|
|
|
{
|
|
|
|
FT_TRACE0(( "bsdf_raster_render: Raster not set up properly,\n" ));
|
|
|
|
FT_TRACE0(( " unable to find memory handle.\n" ));
|
|
|
|
|
|
|
|
error = FT_THROW( Invalid_Handle );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check whether spread is set properly */
|
|
|
|
if ( sdf_params->spread > MAX_SPREAD ||
|
|
|
|
sdf_params->spread < MIN_SPREAD )
|
|
|
|
{
|
|
|
|
FT_TRACE0(( "bsdf_raster_render:"
|
|
|
|
" The `spread' field of `SDF_Raster_Params'\n" ));
|
|
|
|
FT_TRACE0(( " "
|
|
|
|
" is invalid; the value of this field must be\n" ));
|
|
|
|
FT_TRACE0(( " "
|
|
|
|
" within [%d, %d].\n",
|
|
|
|
MIN_SPREAD, MAX_SPREAD ));
|
|
|
|
FT_TRACE0(( " "
|
|
|
|
" Also, you must pass `SDF_Raster_Params'\n" ));
|
|
|
|
FT_TRACE0(( " "
|
|
|
|
" instead of the default `FT_Raster_Params'\n" ));
|
|
|
|
FT_TRACE0(( " "
|
|
|
|
" while calling this function and set the fields\n" ));
|
|
|
|
FT_TRACE0(( " "
|
|
|
|
" accordingly.\n" ));
|
|
|
|
|
|
|
|
error = FT_THROW( Invalid_Argument );
|
|
|
|
goto Exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set up the worker */
|
|
|
|
|
|
|
|
/* allocate the distance map */
|
|
|
|
if ( FT_QALLOC_MULT( worker.distance_map, target->rows,
|
|
|
|
target->width * sizeof ( *worker.distance_map ) ) )
|
|
|
|
goto Exit;
|
|
|
|
|
|
|
|
worker.width = target->width;
|
|
|
|
worker.rows = target->rows;
|
|
|
|
worker.params = *sdf_params;
|
|
|
|
|
|
|
|
FT_CALL( bsdf_init_distance_map( source, &worker ) );
|
|
|
|
FT_CALL( bsdf_approximate_edge( &worker ) );
|
|
|
|
FT_CALL( edt8( &worker ) );
|
|
|
|
FT_CALL( finalize_sdf( &worker, target ) );
|
|
|
|
|
|
|
|
FT_TRACE0(( "bsdf_raster_render: Total memory used = %ld\n",
|
|
|
|
worker.width * worker.rows *
|
|
|
|
sizeof ( *worker.distance_map ) ));
|
|
|
|
|
|
|
|
Exit:
|
|
|
|
if ( worker.distance_map )
|
|
|
|
FT_FREE( worker.distance_map );
|
|
|
|
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* called while deleting `FT_Library` only if the module is added */
|
|
|
|
static void
|
|
|
|
bsdf_raster_done( FT_Raster raster )
|
|
|
|
{
|
|
|
|
FT_Memory memory = (FT_Memory)((BSDF_TRaster*)raster)->memory;
|
|
|
|
|
|
|
|
|
|
|
|
FT_FREE( raster );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FT_DEFINE_RASTER_FUNCS(
|
|
|
|
ft_bitmap_sdf_raster,
|
|
|
|
|
|
|
|
FT_GLYPH_FORMAT_BITMAP,
|
|
|
|
|
|
|
|
(FT_Raster_New_Func) bsdf_raster_new, /* raster_new */
|
|
|
|
(FT_Raster_Reset_Func) bsdf_raster_reset, /* raster_reset */
|
|
|
|
(FT_Raster_Set_Mode_Func)bsdf_raster_set_mode, /* raster_set_mode */
|
|
|
|
(FT_Raster_Render_Func) bsdf_raster_render, /* raster_render */
|
|
|
|
(FT_Raster_Done_Func) bsdf_raster_done /* raster_done */
|
|
|
|
)
|
|
|
|
|
2020-12-24 08:29:05 +01:00
|
|
|
|
2020-08-16 13:39:22 +02:00
|
|
|
/* END */
|