mirror of https://github.com/Chlumsky/msdfgen.git
V1.4: Overlapping contours, cubic distance fix, guessorder
This commit is contained in:
parent
0215653a8a
commit
0e68504f44
|
|
@ -0,0 +1,12 @@
|
||||||
|
Debug/
|
||||||
|
Release/
|
||||||
|
*.exe
|
||||||
|
*.user
|
||||||
|
*.sdf
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.iobj
|
||||||
|
*.suo
|
||||||
|
*.VC.opendb
|
||||||
|
output.png
|
||||||
|
render.png
|
||||||
|
|
@ -7,18 +7,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Msdfgen", "Msdfgen.vcxproj"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|x64 = Debug|x64
|
|
||||||
Debug|x86 = Debug|x86
|
Debug|x86 = Debug|x86
|
||||||
Release|x64 = Release|x64
|
|
||||||
Release|x86 = Release|x86
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x64.ActiveCfg = Debug|x64
|
|
||||||
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x64.Build.0 = Debug|x64
|
|
||||||
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x86.ActiveCfg = Debug|Win32
|
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x86.ActiveCfg = Debug|Win32
|
||||||
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x86.Build.0 = Debug|Win32
|
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x86.Build.0 = Debug|Win32
|
||||||
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x64.ActiveCfg = Release|x64
|
|
||||||
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x64.Build.0 = Release|x64
|
|
||||||
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x86.ActiveCfg = Release|Win32
|
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x86.ActiveCfg = Release|Win32
|
||||||
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x86.Build.0 = Release|Win32
|
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x86.Build.0 = Release|Win32
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
<TargetName>msdfgen</TargetName>
|
<TargetName>msdfgen</TargetName>
|
||||||
<OutDir>$(SolutionDir)\</OutDir>
|
<OutDir>$(SolutionDir)\bin\</OutDir>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
|
|
@ -112,6 +112,7 @@
|
||||||
<OptimizeReferences>true</OptimizeReferences>
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
<SubSystem>Console</SubSystem>
|
<SubSystem>Console</SubSystem>
|
||||||
<AdditionalLibraryDirectories>lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
<AdditionalLibraryDirectories>lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||||
|
<GenerateDebugInformation>No</GenerateDebugInformation>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
|
|
|
||||||
17
README.md
17
README.md
|
|
@ -14,6 +14,17 @@ The following comparison demonstrates the improvement in image quality.
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
## New in version 1.4
|
||||||
|
- The procedure of how contours are combined together has been reworked, and now supports overlapping contours,
|
||||||
|
which are often present in fonts with auto-generated accented glyphs. Since this is a major change to the core algorithm,
|
||||||
|
the original versions of all functions in [msdfgen.h](msdfgen.h) have been preserved with `_legacy` suffix,
|
||||||
|
and can be enabled in the command line tool with **-legacy** switch.
|
||||||
|
- A major bug has been fixed in the evaluation of signed distance of cubic curves, in which at least one of the control points
|
||||||
|
lies at the endpoint. If you use an older version, you should update now.
|
||||||
|
- In the standalone program, the orientation of the input is now being automatically detected by sampling the signed distance
|
||||||
|
at an arbitrary point outside the shape's bounding box, and the output adjusted accordingly. This can be disabled
|
||||||
|
by new option **-keeporder** or the pre-existing **-reverseorder**.
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
The project can be used either as a library or as a console program. is divided into two parts, **[core](core)**
|
The project can be used either as a library or as a console program. is divided into two parts, **[core](core)**
|
||||||
|
|
@ -86,7 +97,7 @@ in order to generate a distance field. Please note that all classes and function
|
||||||
|
|
||||||
- Acquire a `Shape` object. You can either load it via `loadGlyph` or `loadSvgShape`, or construct it manually.
|
- Acquire a `Shape` object. You can either load it via `loadGlyph` or `loadSvgShape`, or construct it manually.
|
||||||
It consists of closed contours, which in turn consist of edges. An edge is represented by a `LinearEdge`, `QuadraticEdge`,
|
It consists of closed contours, which in turn consist of edges. An edge is represented by a `LinearEdge`, `QuadraticEdge`,
|
||||||
or `CubicEdge`. You can construct them from two endpoints and 0 to 2 Bézier control points.
|
or `CubicEdge`. You can construct them from two endpoints and 0 to 2 Bézier control points.
|
||||||
- Normalize the shape using its `normalize` method and assign colors to edges if you need a multi-channel SDF.
|
- Normalize the shape using its `normalize` method and assign colors to edges if you need a multi-channel SDF.
|
||||||
This can be performed automatically using the `edgeColoringSimple` heuristic, or manually by setting each edge's
|
This can be performed automatically using the `edgeColoringSimple` heuristic, or manually by setting each edge's
|
||||||
`color` member. Keep in mind that at least two color channels must be turned on in each edge, and iff two edges meet
|
`color` member. Keep in mind that at least two color channels must be turned on in each edge, and iff two edges meet
|
||||||
|
|
@ -163,7 +174,7 @@ The text shape description has the following syntax.
|
||||||
- Points in a contour are separated with semicolons.
|
- Points in a contour are separated with semicolons.
|
||||||
- The last point of each contour must be equal to the first, or the symbol `#` can be used, which represents the first point.
|
- The last point of each contour must be equal to the first, or the symbol `#` can be used, which represents the first point.
|
||||||
- There can be an edge segment specification between any two points, also separated by semicolons.
|
- There can be an edge segment specification between any two points, also separated by semicolons.
|
||||||
This can include the edge's color (`c`, `m`, `y` or `w`) and/or one or two Bézier curve control points inside parentheses.
|
This can include the edge's color (`c`, `m`, `y` or `w`) and/or one or two Bézier curve control points inside parentheses.
|
||||||
|
|
||||||
For example,
|
For example,
|
||||||
```
|
```
|
||||||
|
|
@ -173,4 +184,4 @@ would represent a square with magenta and yellow edges,
|
||||||
```
|
```
|
||||||
{ 0, 1; (+1.6, -0.8; -1.6, -0.8); # }
|
{ 0, 1; (+1.6, -0.8; -1.6, -0.8); # }
|
||||||
```
|
```
|
||||||
is a teardrop shape formed by a single cubic Bézier curve.
|
is a teardrop shape formed by a single cubic Bézier curve.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
|
|
||||||
#include "Contour.h"
|
#include "Contour.h"
|
||||||
|
|
||||||
|
#include "arithmetics.hpp"
|
||||||
|
|
||||||
namespace msdfgen {
|
namespace msdfgen {
|
||||||
|
|
||||||
|
static double shoelace(const Point2 &a, const Point2 &b) {
|
||||||
|
return (b.x-a.x)*(a.y+b.y);
|
||||||
|
}
|
||||||
|
|
||||||
void Contour::addEdge(const EdgeHolder &edge) {
|
void Contour::addEdge(const EdgeHolder &edge) {
|
||||||
edges.push_back(edge);
|
edges.push_back(edge);
|
||||||
}
|
}
|
||||||
|
|
@ -23,4 +29,30 @@ void Contour::bounds(double &l, double &b, double &r, double &t) const {
|
||||||
(*edge)->bounds(l, b, r, t);
|
(*edge)->bounds(l, b, r, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Contour::winding() const {
|
||||||
|
if (edges.empty())
|
||||||
|
return 0;
|
||||||
|
double total = 0;
|
||||||
|
if (edges.size() == 1) {
|
||||||
|
Point2 a = edges[0]->point(0), b = edges[0]->point(1/3.), c = edges[0]->point(2/3.);
|
||||||
|
total += shoelace(a, b);
|
||||||
|
total += shoelace(b, c);
|
||||||
|
total += shoelace(c, a);
|
||||||
|
} else if (edges.size() == 2) {
|
||||||
|
Point2 a = edges[0]->point(0), b = edges[0]->point(.5), c = edges[1]->point(0), d = edges[1]->point(.5);
|
||||||
|
total += shoelace(a, b);
|
||||||
|
total += shoelace(b, c);
|
||||||
|
total += shoelace(c, d);
|
||||||
|
total += shoelace(d, a);
|
||||||
|
} else {
|
||||||
|
Point2 prev = edges[edges.size()-1]->point(0);
|
||||||
|
for (std::vector<EdgeHolder>::const_iterator edge = edges.begin(); edge != edges.end(); ++edge) {
|
||||||
|
Point2 cur = (*edge)->point(0);
|
||||||
|
total += shoelace(prev, cur);
|
||||||
|
prev = cur;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sign(total);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ public:
|
||||||
EdgeHolder & addEdge();
|
EdgeHolder & addEdge();
|
||||||
/// Computes the bounding box of the contour.
|
/// Computes the bounding box of the contour.
|
||||||
void bounds(double &l, double &b, double &r, double &t) const;
|
void bounds(double &l, double &b, double &r, double &t) const;
|
||||||
|
/// Computes the winding of the contour. Returns 1 if positive, -1 if negative.
|
||||||
|
int winding() const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,12 @@ inline T clamp(T n, T a, T b) {
|
||||||
return n >= a && n <= b ? n : n < a ? a : b;
|
return n >= a && n <= b ? n : n < a ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns 1 for positive values, -1 for negative values, and 0 for zero.
|
||||||
|
template <typename T>
|
||||||
|
inline int sign(T n) {
|
||||||
|
return (T(0) < n)-(n < T(0));
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns 1 for non-negative values and -1 for negative values.
|
/// Returns 1 for non-negative values and -1 for negative values.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline int nonZeroSign(T n) {
|
inline int nonZeroSign(T n) {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ LinearSegment::LinearSegment(Point2 p0, Point2 p1, EdgeColor edgeColor) : EdgeSe
|
||||||
}
|
}
|
||||||
|
|
||||||
QuadraticSegment::QuadraticSegment(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) : EdgeSegment(edgeColor) {
|
QuadraticSegment::QuadraticSegment(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) : EdgeSegment(edgeColor) {
|
||||||
|
if (p1 == p0 || p1 == p2)
|
||||||
|
p1 = 0.5*(p0+p2);
|
||||||
p[0] = p0;
|
p[0] = p0;
|
||||||
p[1] = p1;
|
p[1] = p1;
|
||||||
p[2] = p2;
|
p[2] = p2;
|
||||||
|
|
@ -84,7 +86,12 @@ Vector2 QuadraticSegment::direction(double param) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector2 CubicSegment::direction(double param) const {
|
Vector2 CubicSegment::direction(double param) const {
|
||||||
return mix(mix(p[1]-p[0], p[2]-p[1], param), mix(p[2]-p[1], p[3]-p[2], param), param);
|
Vector2 tangent = mix(mix(p[1]-p[0], p[2]-p[1], param), mix(p[2]-p[1], p[3]-p[2], param), param);
|
||||||
|
if (!tangent) {
|
||||||
|
if (param == 0) return p[2]-p[0];
|
||||||
|
if (param == 1) return p[3]-p[1];
|
||||||
|
}
|
||||||
|
return tangent;
|
||||||
}
|
}
|
||||||
|
|
||||||
SignedDistance LinearSegment::signedDistance(Point2 origin, double ¶m) const {
|
SignedDistance LinearSegment::signedDistance(Point2 origin, double ¶m) const {
|
||||||
|
|
@ -146,13 +153,15 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const
|
||||||
Vector2 br = p[2]-p[1]-ab;
|
Vector2 br = p[2]-p[1]-ab;
|
||||||
Vector2 as = (p[3]-p[2])-(p[2]-p[1])-br;
|
Vector2 as = (p[3]-p[2])-(p[2]-p[1])-br;
|
||||||
|
|
||||||
double minDistance = nonZeroSign(crossProduct(ab, qa))*qa.length(); // distance from A
|
Vector2 epDir = direction(0);
|
||||||
param = -dotProduct(qa, ab)/dotProduct(ab, ab);
|
double minDistance = nonZeroSign(crossProduct(epDir, qa))*qa.length(); // distance from A
|
||||||
|
param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir);
|
||||||
{
|
{
|
||||||
double distance = nonZeroSign(crossProduct(p[3]-p[2], p[3]-origin))*(p[3]-origin).length(); // distance from B
|
epDir = direction(1);
|
||||||
|
double distance = nonZeroSign(crossProduct(epDir, p[3]-origin))*(p[3]-origin).length(); // distance from B
|
||||||
if (fabs(distance) < fabs(minDistance)) {
|
if (fabs(distance) < fabs(minDistance)) {
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
param = dotProduct(origin-p[2], p[3]-p[2])/dotProduct(p[3]-p[2], p[3]-p[2]);
|
param = dotProduct(origin+epDir-p[3], epDir)/dotProduct(epDir, epDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Iterative minimum distance search
|
// Iterative minimum distance search
|
||||||
|
|
@ -179,55 +188,11 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const
|
||||||
if (param >= 0 && param <= 1)
|
if (param >= 0 && param <= 1)
|
||||||
return SignedDistance(minDistance, 0);
|
return SignedDistance(minDistance, 0);
|
||||||
if (param < .5)
|
if (param < .5)
|
||||||
return SignedDistance(minDistance, fabs(dotProduct(ab.normalize(), qa.normalize())));
|
return SignedDistance(minDistance, fabs(dotProduct(direction(0).normalize(), qa.normalize())));
|
||||||
else
|
else
|
||||||
return SignedDistance(minDistance, fabs(dotProduct((p[3]-p[2]).normalize(), (p[3]-origin).normalize())));
|
return SignedDistance(minDistance, fabs(dotProduct(direction(1).normalize(), (p[3]-origin).normalize())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Original method by solving a fifth order polynomial
|
|
||||||
/*SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const {
|
|
||||||
Vector2 qa = p[0]-origin;
|
|
||||||
Vector2 ab = p[1]-p[0];
|
|
||||||
Vector2 br = p[2]-p[1]-ab;
|
|
||||||
Vector2 as = (p[3]-p[2])-(p[2]-p[1])-br;
|
|
||||||
double a = dotProduct(as, as);
|
|
||||||
double b = 5*dotProduct(br, as);
|
|
||||||
double c = 4*dotProduct(ab, as)+6*dotProduct(br, br);
|
|
||||||
double d = 9*dotProduct(ab, br)+dotProduct(qa, as);
|
|
||||||
double e = 3*dotProduct(ab, ab)+2*dotProduct(qa, br);
|
|
||||||
double f = dotProduct(qa, ab);
|
|
||||||
double t[5];
|
|
||||||
int solutions = solveQuintic(t, a, b, c, d, e, f);
|
|
||||||
|
|
||||||
double minDistance = nonZeroSign(crossProduct(ab, qa))*qa.length(); // distance from A
|
|
||||||
param = -dotProduct(qa, ab)/dotProduct(ab, ab);
|
|
||||||
{
|
|
||||||
double distance = nonZeroSign(crossProduct(p[3]-p[2], p[3]-origin))*(p[3]-origin).length(); // distance from B
|
|
||||||
if (fabs(distance) < fabs(minDistance)) {
|
|
||||||
minDistance = distance;
|
|
||||||
param = dotProduct(origin-p[2], p[3]-p[2])/dotProduct(p[3]-p[2], p[3]-p[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < solutions; ++i) {
|
|
||||||
if (t[i] > 0 && t[i] < 1) {
|
|
||||||
Point2 endpoint = p[0]+3*t[i]*ab+3*t[i]*t[i]*br+t[i]*t[i]*t[i]*as;
|
|
||||||
Vector2 dirVec = t[i]*t[i]*as+2*t[i]*br+ab;
|
|
||||||
double distance = nonZeroSign(crossProduct(dirVec, endpoint-origin))*(endpoint-origin).length();
|
|
||||||
if (fabs(distance) <= fabs(minDistance)) {
|
|
||||||
minDistance = distance;
|
|
||||||
param = t[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (param >= 0 && param <= 1)
|
|
||||||
return SignedDistance(minDistance, 0);
|
|
||||||
if (param < .5)
|
|
||||||
return SignedDistance(minDistance, fabs(dotProduct(ab.normalize(), qa.normalize())));
|
|
||||||
else
|
|
||||||
return SignedDistance(minDistance, fabs(dotProduct((p[3]-p[2]).normalize(), (p[3]-origin).normalize())));
|
|
||||||
}*/
|
|
||||||
|
|
||||||
static void pointBounds(Point2 p, double &l, double &b, double &r, double &t) {
|
static void pointBounds(Point2 p, double &l, double &b, double &r, double &t) {
|
||||||
if (p.x < l) l = p.x;
|
if (p.x < l) l = p.x;
|
||||||
if (p.y < b) b = p.y;
|
if (p.y < b) b = p.y;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,4 @@ int solveQuadratic(double x[2], double a, double b, double c);
|
||||||
// ax^3 + bx^2 + cx + d = 0
|
// ax^3 + bx^2 + cx + d = 0
|
||||||
int solveCubic(double x[3], double a, double b, double c, double d);
|
int solveCubic(double x[3], double a, double b, double c, double d);
|
||||||
|
|
||||||
// ax^5 + bx^4 + cx^3 + dx^2 + ex + f = 0
|
|
||||||
//int solveQuintic(double x[5], double a, double b, double c, double d, double e, double f);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
376
core/msdfgen.cpp
376
core/msdfgen.cpp
|
|
@ -5,56 +5,10 @@
|
||||||
|
|
||||||
namespace msdfgen {
|
namespace msdfgen {
|
||||||
|
|
||||||
void generateSDF(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
|
struct MultiDistance {
|
||||||
int w = output.width(), h = output.height();
|
double r, g, b;
|
||||||
#ifdef MSDFGEN_USE_OPENMP
|
double med;
|
||||||
#pragma omp parallel for
|
};
|
||||||
#endif
|
|
||||||
for (int y = 0; y < h; ++y) {
|
|
||||||
int row = shape.inverseYAxis ? h-y-1 : y;
|
|
||||||
for (int x = 0; x < w; ++x) {
|
|
||||||
double dummy;
|
|
||||||
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
|
||||||
SignedDistance minDistance;
|
|
||||||
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
|
||||||
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
|
||||||
SignedDistance distance = (*edge)->signedDistance(p, dummy);
|
|
||||||
if (distance < minDistance)
|
|
||||||
minDistance = distance;
|
|
||||||
}
|
|
||||||
output(x, row) = float(minDistance.distance/range+.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void generatePseudoSDF(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
|
|
||||||
int w = output.width(), h = output.height();
|
|
||||||
#ifdef MSDFGEN_USE_OPENMP
|
|
||||||
#pragma omp parallel for
|
|
||||||
#endif
|
|
||||||
for (int y = 0; y < h; ++y) {
|
|
||||||
int row = shape.inverseYAxis ? h-y-1 : y;
|
|
||||||
for (int x = 0; x < w; ++x) {
|
|
||||||
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
|
||||||
SignedDistance minDistance;
|
|
||||||
const EdgeHolder *nearEdge = NULL;
|
|
||||||
double nearParam = 0;
|
|
||||||
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
|
||||||
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
|
||||||
double param;
|
|
||||||
SignedDistance distance = (*edge)->signedDistance(p, param);
|
|
||||||
if (distance < minDistance) {
|
|
||||||
minDistance = distance;
|
|
||||||
nearEdge = &*edge;
|
|
||||||
nearParam = param;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nearEdge)
|
|
||||||
(*nearEdge)->distanceToPseudoDistance(minDistance, p, nearParam);
|
|
||||||
output(x, row) = float(minDistance.distance/range+.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool pixelClash(const FloatRGB &a, const FloatRGB &b, double threshold) {
|
static inline bool pixelClash(const FloatRGB &a, const FloatRGB &b, double threshold) {
|
||||||
// Only consider pair where both are on the inside or both are on the outside
|
// Only consider pair where both are on the inside or both are on the outside
|
||||||
|
|
@ -108,7 +62,329 @@ void msdfErrorCorrection(Bitmap<FloatRGB> &output, const Vector2 &threshold) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void generateSDF(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
|
||||||
|
int contourCount = shape.contours.size();
|
||||||
|
int w = output.width(), h = output.height();
|
||||||
|
std::vector<int> windings;
|
||||||
|
windings.reserve(contourCount);
|
||||||
|
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
||||||
|
windings.push_back(contour->winding());
|
||||||
|
|
||||||
|
#ifdef MSDFGEN_USE_OPENMP
|
||||||
|
#pragma omp parallel
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
std::vector<double> contourSD;
|
||||||
|
contourSD.resize(contourCount);
|
||||||
|
#ifdef MSDFGEN_USE_OPENMP
|
||||||
|
#pragma omp for
|
||||||
|
#endif
|
||||||
|
for (int y = 0; y < h; ++y) {
|
||||||
|
int row = shape.inverseYAxis ? h-y-1 : y;
|
||||||
|
for (int x = 0; x < w; ++x) {
|
||||||
|
double dummy;
|
||||||
|
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
||||||
|
double negDist = -SignedDistance::INFINITE.distance;
|
||||||
|
double posDist = SignedDistance::INFINITE.distance;
|
||||||
|
int winding = 0;
|
||||||
|
|
||||||
|
std::vector<Contour>::const_iterator contour = shape.contours.begin();
|
||||||
|
for (int i = 0; i < contourCount; ++i, ++contour) {
|
||||||
|
SignedDistance minDistance;
|
||||||
|
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
||||||
|
SignedDistance distance = (*edge)->signedDistance(p, dummy);
|
||||||
|
if (distance < minDistance)
|
||||||
|
minDistance = distance;
|
||||||
|
}
|
||||||
|
contourSD[i] = minDistance.distance;
|
||||||
|
if (windings[i] > 0 && minDistance.distance >= 0 && fabs(minDistance.distance) < fabs(posDist))
|
||||||
|
posDist = minDistance.distance;
|
||||||
|
if (windings[i] < 0 && minDistance.distance <= 0 && fabs(minDistance.distance) < fabs(negDist))
|
||||||
|
negDist = minDistance.distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
double sd = SignedDistance::INFINITE.distance;
|
||||||
|
if (posDist >= 0 && fabs(posDist) <= fabs(negDist)) {
|
||||||
|
sd = posDist;
|
||||||
|
winding = 1;
|
||||||
|
for (int i = 0; i < contourCount; ++i)
|
||||||
|
if (windings[i] > 0 && contourSD[i] > sd && fabs(contourSD[i]) < fabs(negDist))
|
||||||
|
sd = contourSD[i];
|
||||||
|
} else if (negDist <= 0 && fabs(negDist) <= fabs(posDist)) {
|
||||||
|
sd = negDist;
|
||||||
|
winding = -1;
|
||||||
|
for (int i = 0; i < contourCount; ++i)
|
||||||
|
if (windings[i] < 0 && contourSD[i] < sd && fabs(contourSD[i]) < fabs(posDist))
|
||||||
|
sd = contourSD[i];
|
||||||
|
}
|
||||||
|
for (int i = 0; i < contourCount; ++i)
|
||||||
|
if (windings[i] != winding && fabs(contourSD[i]) < fabs(sd))
|
||||||
|
sd = contourSD[i];
|
||||||
|
|
||||||
|
output(x, row) = float(sd/range+.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void generatePseudoSDF(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
|
||||||
|
int contourCount = shape.contours.size();
|
||||||
|
int w = output.width(), h = output.height();
|
||||||
|
std::vector<int> windings;
|
||||||
|
windings.reserve(contourCount);
|
||||||
|
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
||||||
|
windings.push_back(contour->winding());
|
||||||
|
|
||||||
|
#ifdef MSDFGEN_USE_OPENMP
|
||||||
|
#pragma omp parallel
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
std::vector<double> contourSD;
|
||||||
|
contourSD.resize(contourCount);
|
||||||
|
#ifdef MSDFGEN_USE_OPENMP
|
||||||
|
#pragma omp for
|
||||||
|
#endif
|
||||||
|
for (int y = 0; y < h; ++y) {
|
||||||
|
int row = shape.inverseYAxis ? h-y-1 : y;
|
||||||
|
for (int x = 0; x < w; ++x) {
|
||||||
|
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
||||||
|
double sd = SignedDistance::INFINITE.distance;
|
||||||
|
double negDist = -SignedDistance::INFINITE.distance;
|
||||||
|
double posDist = SignedDistance::INFINITE.distance;
|
||||||
|
int winding = 0;
|
||||||
|
|
||||||
|
std::vector<Contour>::const_iterator contour = shape.contours.begin();
|
||||||
|
for (int i = 0; i < contourCount; ++i, ++contour) {
|
||||||
|
SignedDistance minDistance;
|
||||||
|
const EdgeHolder *nearEdge = NULL;
|
||||||
|
double nearParam = 0;
|
||||||
|
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
||||||
|
double param;
|
||||||
|
SignedDistance distance = (*edge)->signedDistance(p, param);
|
||||||
|
if (distance < minDistance) {
|
||||||
|
minDistance = distance;
|
||||||
|
nearEdge = &*edge;
|
||||||
|
nearParam = param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fabs(minDistance.distance) < fabs(sd)) {
|
||||||
|
sd = minDistance.distance;
|
||||||
|
winding = -windings[i];
|
||||||
|
}
|
||||||
|
if (nearEdge)
|
||||||
|
(*nearEdge)->distanceToPseudoDistance(minDistance, p, nearParam);
|
||||||
|
contourSD[i] = minDistance.distance;
|
||||||
|
if (windings[i] > 0 && minDistance.distance >= 0 && fabs(minDistance.distance) < fabs(posDist))
|
||||||
|
posDist = minDistance.distance;
|
||||||
|
if (windings[i] < 0 && minDistance.distance <= 0 && fabs(minDistance.distance) < fabs(negDist))
|
||||||
|
negDist = minDistance.distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
double psd = SignedDistance::INFINITE.distance;
|
||||||
|
if (posDist >= 0 && fabs(posDist) <= fabs(negDist)) {
|
||||||
|
psd = posDist;
|
||||||
|
winding = 1;
|
||||||
|
for (int i = 0; i < contourCount; ++i)
|
||||||
|
if (windings[i] > 0 && contourSD[i] > psd && fabs(contourSD[i]) < fabs(negDist))
|
||||||
|
psd = contourSD[i];
|
||||||
|
} else if (negDist <= 0 && fabs(negDist) <= fabs(posDist)) {
|
||||||
|
psd = negDist;
|
||||||
|
winding = -1;
|
||||||
|
for (int i = 0; i < contourCount; ++i)
|
||||||
|
if (windings[i] < 0 && contourSD[i] < psd && fabs(contourSD[i]) < fabs(posDist))
|
||||||
|
psd = contourSD[i];
|
||||||
|
}
|
||||||
|
for (int i = 0; i < contourCount; ++i)
|
||||||
|
if (windings[i] != winding && fabs(contourSD[i]) < fabs(psd))
|
||||||
|
psd = contourSD[i];
|
||||||
|
|
||||||
|
output(x, row) = float(psd/range+.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void generateMSDF(Bitmap<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold) {
|
void generateMSDF(Bitmap<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold) {
|
||||||
|
int contourCount = shape.contours.size();
|
||||||
|
int w = output.width(), h = output.height();
|
||||||
|
std::vector<int> windings;
|
||||||
|
windings.reserve(contourCount);
|
||||||
|
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
||||||
|
windings.push_back(contour->winding());
|
||||||
|
|
||||||
|
#ifdef MSDFGEN_USE_OPENMP
|
||||||
|
#pragma omp parallel
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
std::vector<MultiDistance> contourSD;
|
||||||
|
contourSD.resize(contourCount);
|
||||||
|
#ifdef MSDFGEN_USE_OPENMP
|
||||||
|
#pragma omp for
|
||||||
|
#endif
|
||||||
|
for (int y = 0; y < h; ++y) {
|
||||||
|
int row = shape.inverseYAxis ? h-y-1 : y;
|
||||||
|
for (int x = 0; x < w; ++x) {
|
||||||
|
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
||||||
|
|
||||||
|
struct EdgePoint {
|
||||||
|
SignedDistance minDistance;
|
||||||
|
const EdgeHolder *nearEdge;
|
||||||
|
double nearParam;
|
||||||
|
} sr, sg, sb;
|
||||||
|
sr.nearEdge = sg.nearEdge = sb.nearEdge = NULL;
|
||||||
|
sr.nearParam = sg.nearParam = sb.nearParam = 0;
|
||||||
|
double d = fabs(SignedDistance::INFINITE.distance);
|
||||||
|
double negDist = -SignedDistance::INFINITE.distance;
|
||||||
|
double posDist = SignedDistance::INFINITE.distance;
|
||||||
|
int winding = 0;
|
||||||
|
|
||||||
|
std::vector<Contour>::const_iterator contour = shape.contours.begin();
|
||||||
|
for (int i = 0; i < contourCount; ++i, ++contour) {
|
||||||
|
EdgePoint r, g, b;
|
||||||
|
r.nearEdge = g.nearEdge = b.nearEdge = NULL;
|
||||||
|
r.nearParam = g.nearParam = b.nearParam = 0;
|
||||||
|
|
||||||
|
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
||||||
|
double param;
|
||||||
|
SignedDistance distance = (*edge)->signedDistance(p, param);
|
||||||
|
if ((*edge)->color&RED && distance < r.minDistance) {
|
||||||
|
r.minDistance = distance;
|
||||||
|
r.nearEdge = &*edge;
|
||||||
|
r.nearParam = param;
|
||||||
|
}
|
||||||
|
if ((*edge)->color&GREEN && distance < g.minDistance) {
|
||||||
|
g.minDistance = distance;
|
||||||
|
g.nearEdge = &*edge;
|
||||||
|
g.nearParam = param;
|
||||||
|
}
|
||||||
|
if ((*edge)->color&BLUE && distance < b.minDistance) {
|
||||||
|
b.minDistance = distance;
|
||||||
|
b.nearEdge = &*edge;
|
||||||
|
b.nearParam = param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (r.minDistance < sr.minDistance)
|
||||||
|
sr = r;
|
||||||
|
if (g.minDistance < sg.minDistance)
|
||||||
|
sg = g;
|
||||||
|
if (b.minDistance < sb.minDistance)
|
||||||
|
sb = b;
|
||||||
|
|
||||||
|
double medMinDistance = fabs(median(r.minDistance.distance, g.minDistance.distance, b.minDistance.distance));
|
||||||
|
if (medMinDistance < d) {
|
||||||
|
d = medMinDistance;
|
||||||
|
winding = -windings[i];
|
||||||
|
}
|
||||||
|
if (r.nearEdge)
|
||||||
|
(*r.nearEdge)->distanceToPseudoDistance(r.minDistance, p, r.nearParam);
|
||||||
|
if (g.nearEdge)
|
||||||
|
(*g.nearEdge)->distanceToPseudoDistance(g.minDistance, p, g.nearParam);
|
||||||
|
if (b.nearEdge)
|
||||||
|
(*b.nearEdge)->distanceToPseudoDistance(b.minDistance, p, b.nearParam);
|
||||||
|
medMinDistance = median(r.minDistance.distance, g.minDistance.distance, b.minDistance.distance);
|
||||||
|
contourSD[i].r = r.minDistance.distance;
|
||||||
|
contourSD[i].g = g.minDistance.distance;
|
||||||
|
contourSD[i].b = b.minDistance.distance;
|
||||||
|
contourSD[i].med = medMinDistance;
|
||||||
|
if (windings[i] > 0 && medMinDistance >= 0 && fabs(medMinDistance) < fabs(posDist))
|
||||||
|
posDist = medMinDistance;
|
||||||
|
if (windings[i] < 0 && medMinDistance <= 0 && fabs(medMinDistance) < fabs(negDist))
|
||||||
|
negDist = medMinDistance;
|
||||||
|
}
|
||||||
|
if (sr.nearEdge)
|
||||||
|
(*sr.nearEdge)->distanceToPseudoDistance(sr.minDistance, p, sr.nearParam);
|
||||||
|
if (sg.nearEdge)
|
||||||
|
(*sg.nearEdge)->distanceToPseudoDistance(sg.minDistance, p, sg.nearParam);
|
||||||
|
if (sb.nearEdge)
|
||||||
|
(*sb.nearEdge)->distanceToPseudoDistance(sb.minDistance, p, sb.nearParam);
|
||||||
|
|
||||||
|
MultiDistance msd;
|
||||||
|
msd.r = msd.g = msd.b = msd.med = SignedDistance::INFINITE.distance;
|
||||||
|
if (posDist >= 0 && fabs(posDist) <= fabs(negDist)) {
|
||||||
|
msd.med = SignedDistance::INFINITE.distance;
|
||||||
|
winding = 1;
|
||||||
|
for (int i = 0; i < contourCount; ++i)
|
||||||
|
if (windings[i] > 0 && contourSD[i].med > msd.med && fabs(contourSD[i].med) < fabs(negDist))
|
||||||
|
msd = contourSD[i];
|
||||||
|
} else if (negDist <= 0 && fabs(negDist) <= fabs(posDist)) {
|
||||||
|
msd.med = -SignedDistance::INFINITE.distance;
|
||||||
|
winding = -1;
|
||||||
|
for (int i = 0; i < contourCount; ++i)
|
||||||
|
if (windings[i] < 0 && contourSD[i].med < msd.med && fabs(contourSD[i].med) < fabs(posDist))
|
||||||
|
msd = contourSD[i];
|
||||||
|
}
|
||||||
|
for (int i = 0; i < contourCount; ++i)
|
||||||
|
if (windings[i] != winding && fabs(contourSD[i].med) < fabs(msd.med))
|
||||||
|
msd = contourSD[i];
|
||||||
|
if (median(sr.minDistance.distance, sg.minDistance.distance, sb.minDistance.distance) == msd.med) {
|
||||||
|
msd.r = sr.minDistance.distance;
|
||||||
|
msd.g = sg.minDistance.distance;
|
||||||
|
msd.b = sb.minDistance.distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
output(x, row).r = float(msd.r/range+.5);
|
||||||
|
output(x, row).g = float(msd.g/range+.5);
|
||||||
|
output(x, row).b = float(msd.b/range+.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edgeThreshold > 0)
|
||||||
|
msdfErrorCorrection(output, edgeThreshold/(scale*range));
|
||||||
|
}
|
||||||
|
|
||||||
|
void generateSDF_legacy(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
|
||||||
|
int w = output.width(), h = output.height();
|
||||||
|
#ifdef MSDFGEN_USE_OPENMP
|
||||||
|
#pragma omp parallel for
|
||||||
|
#endif
|
||||||
|
for (int y = 0; y < h; ++y) {
|
||||||
|
int row = shape.inverseYAxis ? h-y-1 : y;
|
||||||
|
for (int x = 0; x < w; ++x) {
|
||||||
|
double dummy;
|
||||||
|
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
||||||
|
SignedDistance minDistance;
|
||||||
|
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
||||||
|
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
||||||
|
SignedDistance distance = (*edge)->signedDistance(p, dummy);
|
||||||
|
if (distance < minDistance)
|
||||||
|
minDistance = distance;
|
||||||
|
}
|
||||||
|
output(x, row) = float(minDistance.distance/range+.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void generatePseudoSDF_legacy(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
|
||||||
|
int w = output.width(), h = output.height();
|
||||||
|
#ifdef MSDFGEN_USE_OPENMP
|
||||||
|
#pragma omp parallel for
|
||||||
|
#endif
|
||||||
|
for (int y = 0; y < h; ++y) {
|
||||||
|
int row = shape.inverseYAxis ? h-y-1 : y;
|
||||||
|
for (int x = 0; x < w; ++x) {
|
||||||
|
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
||||||
|
SignedDistance minDistance;
|
||||||
|
const EdgeHolder *nearEdge = NULL;
|
||||||
|
double nearParam = 0;
|
||||||
|
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
||||||
|
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
||||||
|
double param;
|
||||||
|
SignedDistance distance = (*edge)->signedDistance(p, param);
|
||||||
|
if (distance < minDistance) {
|
||||||
|
minDistance = distance;
|
||||||
|
nearEdge = &*edge;
|
||||||
|
nearParam = param;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nearEdge)
|
||||||
|
(*nearEdge)->distanceToPseudoDistance(minDistance, p, nearParam);
|
||||||
|
output(x, row) = float(minDistance.distance/range+.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void generateMSDF_legacy(Bitmap<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold) {
|
||||||
int w = output.width(), h = output.height();
|
int w = output.width(), h = output.height();
|
||||||
#ifdef MSDFGEN_USE_OPENMP
|
#ifdef MSDFGEN_USE_OPENMP
|
||||||
#pragma omp parallel for
|
#pragma omp parallel for
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,10 @@ static bool readDouble(double &output, const char *&pathDef) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void consumeOptionalComma(const char *&pathDef) {
|
static void consumeOptionalComma(const char *&pathDef) {
|
||||||
while (*pathDef == ' ') {
|
while (*pathDef == ' ')
|
||||||
++pathDef;
|
++pathDef;
|
||||||
}
|
if (*pathDef == ',')
|
||||||
if (*pathDef == ',') {
|
|
||||||
++pathDef;
|
++pathDef;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool buildFromPath(Shape &shape, const char *pathDef) {
|
static bool buildFromPath(Shape &shape, const char *pathDef) {
|
||||||
|
|
@ -74,48 +72,38 @@ static bool buildFromPath(Shape &shape, const char *pathDef) {
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
switch (nodeType) {
|
switch (nodeType) {
|
||||||
case 'M':
|
case 'M': case 'm':
|
||||||
case 'm':
|
|
||||||
REQUIRE(contourStart);
|
REQUIRE(contourStart);
|
||||||
REQUIRE(readCoord(node, pathDef));
|
REQUIRE(readCoord(node, pathDef));
|
||||||
if (nodeType == 'm') {
|
if (nodeType == 'm')
|
||||||
node += prevNode;
|
node += prevNode;
|
||||||
}
|
|
||||||
startPoint = node;
|
startPoint = node;
|
||||||
--nodeType; // to 'L' or 'l'
|
--nodeType; // to 'L' or 'l'
|
||||||
break;
|
break;
|
||||||
case 'Z':
|
case 'Z': case 'z':
|
||||||
case 'z':
|
|
||||||
if (prevNode != startPoint)
|
if (prevNode != startPoint)
|
||||||
contour.addEdge(new LinearSegment(prevNode, startPoint));
|
contour.addEdge(new LinearSegment(prevNode, startPoint));
|
||||||
prevNode = startPoint;
|
prevNode = startPoint;
|
||||||
goto NEXT_CONTOUR;
|
goto NEXT_CONTOUR;
|
||||||
case 'L':
|
case 'L': case 'l':
|
||||||
case 'l':
|
|
||||||
REQUIRE(readCoord(node, pathDef));
|
REQUIRE(readCoord(node, pathDef));
|
||||||
if (nodeType == 'l') {
|
if (nodeType == 'l')
|
||||||
node += prevNode;
|
node += prevNode;
|
||||||
}
|
|
||||||
contour.addEdge(new LinearSegment(prevNode, node));
|
contour.addEdge(new LinearSegment(prevNode, node));
|
||||||
break;
|
break;
|
||||||
case 'H':
|
case 'H': case 'h':
|
||||||
case 'h':
|
|
||||||
REQUIRE(readDouble(node.x, pathDef));
|
REQUIRE(readDouble(node.x, pathDef));
|
||||||
if (nodeType == 'h') {
|
if (nodeType == 'h')
|
||||||
node.x += prevNode.x;
|
node.x += prevNode.x;
|
||||||
}
|
|
||||||
contour.addEdge(new LinearSegment(prevNode, node));
|
contour.addEdge(new LinearSegment(prevNode, node));
|
||||||
break;
|
break;
|
||||||
case 'V':
|
case 'V': case 'v':
|
||||||
case 'v':
|
|
||||||
REQUIRE(readDouble(node.y, pathDef));
|
REQUIRE(readDouble(node.y, pathDef));
|
||||||
if (nodeType == 'v') {
|
if (nodeType == 'v')
|
||||||
node.y += prevNode.y;
|
node.y += prevNode.y;
|
||||||
}
|
|
||||||
contour.addEdge(new LinearSegment(prevNode, node));
|
contour.addEdge(new LinearSegment(prevNode, node));
|
||||||
break;
|
break;
|
||||||
case 'Q':
|
case 'Q': case 'q':
|
||||||
case 'q':
|
|
||||||
REQUIRE(readCoord(controlPoint[0], pathDef));
|
REQUIRE(readCoord(controlPoint[0], pathDef));
|
||||||
consumeOptionalComma(pathDef);
|
consumeOptionalComma(pathDef);
|
||||||
REQUIRE(readCoord(node, pathDef));
|
REQUIRE(readCoord(node, pathDef));
|
||||||
|
|
@ -126,8 +114,7 @@ static bool buildFromPath(Shape &shape, const char *pathDef) {
|
||||||
contour.addEdge(new QuadraticSegment(prevNode, controlPoint[0], node));
|
contour.addEdge(new QuadraticSegment(prevNode, controlPoint[0], node));
|
||||||
break;
|
break;
|
||||||
// TODO T, t
|
// TODO T, t
|
||||||
case 'C':
|
case 'C': case 'c':
|
||||||
case 'c':
|
|
||||||
REQUIRE(readCoord(controlPoint[0], pathDef));
|
REQUIRE(readCoord(controlPoint[0], pathDef));
|
||||||
consumeOptionalComma(pathDef);
|
consumeOptionalComma(pathDef);
|
||||||
REQUIRE(readCoord(controlPoint[1], pathDef));
|
REQUIRE(readCoord(controlPoint[1], pathDef));
|
||||||
|
|
@ -140,9 +127,8 @@ static bool buildFromPath(Shape &shape, const char *pathDef) {
|
||||||
}
|
}
|
||||||
contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
|
contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
|
||||||
break;
|
break;
|
||||||
case 'S':
|
case 'S': case 's':
|
||||||
case 's':
|
controlPoint[0] = node+node-controlPoint[1];
|
||||||
controlPoint[0] = node * 2 - controlPoint[1];
|
|
||||||
REQUIRE(readCoord(controlPoint[1], pathDef));
|
REQUIRE(readCoord(controlPoint[1], pathDef));
|
||||||
consumeOptionalComma(pathDef);
|
consumeOptionalComma(pathDef);
|
||||||
REQUIRE(readCoord(node, pathDef));
|
REQUIRE(readCoord(node, pathDef));
|
||||||
|
|
|
||||||
70
main.cpp
70
main.cpp
|
|
@ -1,8 +1,8 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.3 (2016-12-07) - standalone console program
|
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.4 (2017-02-09) - standalone console program
|
||||||
* --------------------------------------------------------------------------------------------
|
* --------------------------------------------------------------------------------------------
|
||||||
* A utility by Viktor Chlumsky, (c) 2014 - 2016
|
* A utility by Viktor Chlumsky, (c) 2014 - 2017
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -300,6 +300,10 @@ static const char *helpText =
|
||||||
"\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
|
"\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
|
||||||
" -help\n"
|
" -help\n"
|
||||||
"\tDisplays this help.\n"
|
"\tDisplays this help.\n"
|
||||||
|
" -keeporder\n"
|
||||||
|
"\tDisables the detection of shape orientation and keeps it as is.\n"
|
||||||
|
" -legacy\n"
|
||||||
|
"\tUses the original (legacy) distance field algorithms.\n"
|
||||||
" -o <filename>\n"
|
" -o <filename>\n"
|
||||||
"\tSets the output file name. The default value is \"output.png\".\n"
|
"\tSets the output file name. The default value is \"output.png\".\n"
|
||||||
" -printmetrics\n"
|
" -printmetrics\n"
|
||||||
|
|
@ -320,12 +324,12 @@ static const char *helpText =
|
||||||
"\tRenders an image preview without flattening the color channels.\n"
|
"\tRenders an image preview without flattening the color channels.\n"
|
||||||
" -translate <x> <y>\n"
|
" -translate <x> <y>\n"
|
||||||
"\tSets the translation of the shape in shape units.\n"
|
"\tSets the translation of the shape in shape units.\n"
|
||||||
" -yflip\n"
|
|
||||||
"\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
|
|
||||||
" -reverseorder\n"
|
" -reverseorder\n"
|
||||||
"\tReverses the order of the points in each contour.\n"
|
"\tDisables the detection of shape orientation and reverses the order of its vertices.\n"
|
||||||
" -seed <n>\n"
|
" -seed <n>\n"
|
||||||
"\tSets the random seed for edge coloring heuristic.\n"
|
"\tSets the random seed for edge coloring heuristic.\n"
|
||||||
|
" -yflip\n"
|
||||||
|
"\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
|
||||||
"\n";
|
"\n";
|
||||||
|
|
||||||
int main(int argc, const char * const *argv) {
|
int main(int argc, const char * const *argv) {
|
||||||
|
|
@ -346,6 +350,7 @@ int main(int argc, const char * const *argv) {
|
||||||
MULTI,
|
MULTI,
|
||||||
METRICS
|
METRICS
|
||||||
} mode = MULTI;
|
} mode = MULTI;
|
||||||
|
bool legacyMode = false;
|
||||||
Format format = AUTO;
|
Format format = AUTO;
|
||||||
const char *input = NULL;
|
const char *input = NULL;
|
||||||
const char *output = "output.png";
|
const char *output = "output.png";
|
||||||
|
|
@ -375,7 +380,11 @@ int main(int argc, const char * const *argv) {
|
||||||
bool yFlip = false;
|
bool yFlip = false;
|
||||||
bool printMetrics = false;
|
bool printMetrics = false;
|
||||||
bool skipColoring = false;
|
bool skipColoring = false;
|
||||||
bool reverseOrder = false;
|
enum {
|
||||||
|
KEEP,
|
||||||
|
REVERSE,
|
||||||
|
GUESS
|
||||||
|
} orientation = GUESS;
|
||||||
unsigned long long coloringSeed = 0;
|
unsigned long long coloringSeed = 0;
|
||||||
|
|
||||||
int argPos = 1;
|
int argPos = 1;
|
||||||
|
|
@ -433,6 +442,11 @@ int main(int argc, const char * const *argv) {
|
||||||
argPos += 1;
|
argPos += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
ARG_CASE("-legacy", 0) {
|
||||||
|
legacyMode = true;
|
||||||
|
argPos += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
ARG_CASE("-format", 1) {
|
ARG_CASE("-format", 1) {
|
||||||
if (!strcmp(argv[argPos+1], "auto")) format = AUTO;
|
if (!strcmp(argv[argPos+1], "auto")) format = AUTO;
|
||||||
else if (!strcmp(argv[argPos+1], "png")) SETFORMAT(PNG, "png");
|
else if (!strcmp(argv[argPos+1], "png")) SETFORMAT(PNG, "png");
|
||||||
|
|
@ -566,8 +580,18 @@ int main(int argc, const char * const *argv) {
|
||||||
argPos += 1;
|
argPos += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
ARG_CASE("-keeporder", 0) {
|
||||||
|
orientation = KEEP;
|
||||||
|
argPos += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
ARG_CASE("-reverseorder", 0) {
|
ARG_CASE("-reverseorder", 0) {
|
||||||
reverseOrder = true;
|
orientation = REVERSE;
|
||||||
|
argPos += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ARG_CASE("-guessorder", 0) {
|
||||||
|
orientation = GUESS;
|
||||||
argPos += 1;
|
argPos += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -653,7 +677,7 @@ int main(int argc, const char * const *argv) {
|
||||||
} bounds = {
|
} bounds = {
|
||||||
LARGE_VALUE, LARGE_VALUE, -LARGE_VALUE, -LARGE_VALUE
|
LARGE_VALUE, LARGE_VALUE, -LARGE_VALUE, -LARGE_VALUE
|
||||||
};
|
};
|
||||||
if (autoFrame || mode == METRICS || printMetrics)
|
if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS)
|
||||||
shape.bounds(bounds.l, bounds.b, bounds.r, bounds.t);
|
shape.bounds(bounds.l, bounds.b, bounds.r, bounds.t);
|
||||||
|
|
||||||
// Auto-frame
|
// Auto-frame
|
||||||
|
|
@ -719,12 +743,18 @@ int main(int argc, const char * const *argv) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case SINGLE: {
|
case SINGLE: {
|
||||||
sdf = Bitmap<float>(width, height);
|
sdf = Bitmap<float>(width, height);
|
||||||
generateSDF(sdf, shape, range, scale, translate);
|
if (legacyMode)
|
||||||
|
generateSDF_legacy(sdf, shape, range, scale, translate);
|
||||||
|
else
|
||||||
|
generateSDF(sdf, shape, range, scale, translate);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PSEUDO: {
|
case PSEUDO: {
|
||||||
sdf = Bitmap<float>(width, height);
|
sdf = Bitmap<float>(width, height);
|
||||||
generatePseudoSDF(sdf, shape, range, scale, translate);
|
if (legacyMode)
|
||||||
|
generatePseudoSDF_legacy(sdf, shape, range, scale, translate);
|
||||||
|
else
|
||||||
|
generatePseudoSDF(sdf, shape, range, scale, translate);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MULTI: {
|
case MULTI: {
|
||||||
|
|
@ -733,14 +763,30 @@ int main(int argc, const char * const *argv) {
|
||||||
if (edgeAssignment)
|
if (edgeAssignment)
|
||||||
parseColoring(shape, edgeAssignment);
|
parseColoring(shape, edgeAssignment);
|
||||||
msdf = Bitmap<FloatRGB>(width, height);
|
msdf = Bitmap<FloatRGB>(width, height);
|
||||||
generateMSDF(msdf, shape, range, scale, translate, edgeThreshold);
|
if (legacyMode)
|
||||||
|
generateMSDF_legacy(msdf, shape, range, scale, translate, edgeThreshold);
|
||||||
|
else
|
||||||
|
generateMSDF(msdf, shape, range, scale, translate, edgeThreshold);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reverseOrder) {
|
if (orientation == GUESS) {
|
||||||
|
// Get sign of signed distance outside bounds
|
||||||
|
Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
|
||||||
|
double dummy;
|
||||||
|
SignedDistance minDistance;
|
||||||
|
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
||||||
|
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
||||||
|
SignedDistance distance = (*edge)->signedDistance(p, dummy);
|
||||||
|
if (distance < minDistance)
|
||||||
|
minDistance = distance;
|
||||||
|
}
|
||||||
|
orientation = minDistance.distance <= 0 ? KEEP : REVERSE;
|
||||||
|
}
|
||||||
|
if (orientation == REVERSE) {
|
||||||
invertColor(sdf);
|
invertColor(sdf);
|
||||||
invertColor(msdf);
|
invertColor(msdf);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.1 (2016-05-08) - extensions
|
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.4 (2017-02-09) - extensions
|
||||||
* ----------------------------------------------------------------------------
|
* ----------------------------------------------------------------------------
|
||||||
* A utility by Viktor Chlumsky, (c) 2014 - 2016
|
* A utility by Viktor Chlumsky, (c) 2014 - 2017
|
||||||
*
|
*
|
||||||
* The extension module provides ways to easily load input and save output using popular formats.
|
* The extension module provides ways to easily load input and save output using popular formats.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
11
msdfgen.h
11
msdfgen.h
|
|
@ -2,9 +2,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.3 (2016-12-07)
|
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.4 (2017-02-09)
|
||||||
* ---------------------------------------------------------------
|
* ---------------------------------------------------------------
|
||||||
* A utility by Viktor Chlumsky, (c) 2014 - 2016
|
* A utility by Viktor Chlumsky, (c) 2014 - 2017
|
||||||
*
|
*
|
||||||
* The technique used to generate multi-channel distance fields in this code
|
* The technique used to generate multi-channel distance fields in this code
|
||||||
* has been developed by Viktor Chlumsky in 2014 for his master's thesis,
|
* has been developed by Viktor Chlumsky in 2014 for his master's thesis,
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
#include "core/save-bmp.h"
|
#include "core/save-bmp.h"
|
||||||
#include "core/shape-description.h"
|
#include "core/shape-description.h"
|
||||||
|
|
||||||
#define MSDFGEN_VERSION "1.3"
|
#define MSDFGEN_VERSION "1.4"
|
||||||
|
|
||||||
namespace msdfgen {
|
namespace msdfgen {
|
||||||
|
|
||||||
|
|
@ -37,4 +37,9 @@ void generatePseudoSDF(Bitmap<float> &output, const Shape &shape, double range,
|
||||||
/// Generates a multi-channel signed distance field. Edge colors must be assigned first! (see edgeColoringSimple)
|
/// Generates a multi-channel signed distance field. Edge colors must be assigned first! (see edgeColoringSimple)
|
||||||
void generateMSDF(Bitmap<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.00000001);
|
void generateMSDF(Bitmap<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.00000001);
|
||||||
|
|
||||||
|
// Original simpler versions of the previous functions, which work well under normal circumstances, but cannot deal with overlapping contours.
|
||||||
|
void generateSDF_legacy(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
|
||||||
|
void generatePseudoSDF_legacy(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
|
||||||
|
void generateMSDF_legacy(Bitmap<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.00000001);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue