Skip to content

Commit

Permalink
V1.4: Overlapping contours, cubic distance fix, guessorder
Browse files Browse the repository at this point in the history
  • Loading branch information
Chlumsky committed Feb 9, 2017
1 parent 0215653 commit 0e68504
Show file tree
Hide file tree
Showing 17 changed files with 493 additions and 160 deletions.
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Debug/
Release/
*.exe
*.user
*.sdf
*.pdb
*.ipdb
*.iobj
*.suo
*.VC.opendb
output.png
render.png
6 changes: 0 additions & 6 deletions Msdfgen.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Msdfgen", "Msdfgen.vcxproj"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
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.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.Build.0 = Release|Win32
EndGlobalSection
Expand Down
3 changes: 2 additions & 1 deletion Msdfgen.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<TargetName>msdfgen</TargetName>
<OutDir>$(SolutionDir)\</OutDir>
<OutDir>$(SolutionDir)\bin\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
Expand Down Expand Up @@ -112,6 +112,7 @@
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Console</SubSystem>
<AdditionalLibraryDirectories>lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<GenerateDebugInformation>No</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ The following comparison demonstrates the improvement in image quality.
![demo-sdf16](https://cloud.githubusercontent.com/assets/18639794/14770360/20c51156-0a70-11e6-8f03-ed7632d07997.png)
![demo-sdf32](https://cloud.githubusercontent.com/assets/18639794/14770361/251a4406-0a70-11e6-95a7-e30e235ac729.png)

## 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

The project can be used either as a library or as a console program. is divided into two parts, **[core](core)**
Expand Down Expand Up @@ -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.
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.
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
Expand Down Expand Up @@ -163,7 +174,7 @@ The text shape description has the following syntax.
- 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.
- 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,
```
Expand All @@ -173,4 +184,4 @@ would represent a square with magenta and yellow edges,
```
{ 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.
File renamed without changes.
File renamed without changes.
File renamed without changes.
32 changes: 32 additions & 0 deletions core/Contour.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@

#include "Contour.h"

#include "arithmetics.hpp"

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) {
edges.push_back(edge);
}
Expand All @@ -23,4 +29,30 @@ void Contour::bounds(double &l, double &b, double &r, double &t) const {
(*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);
}

}
2 changes: 2 additions & 0 deletions core/Contour.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class Contour {
EdgeHolder & addEdge();
/// Computes the bounding box of the contour.
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;

};

Expand Down
6 changes: 6 additions & 0 deletions core/arithmetics.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ inline T clamp(T n, T a, T 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.
template <typename T>
inline int nonZeroSign(T n) {
Expand Down
67 changes: 16 additions & 51 deletions core/edge-segments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
if (p1 == p0 || p1 == p2)
p1 = 0.5*(p0+p2);
p[0] = p0;
p[1] = p1;
p[2] = p2;
Expand Down Expand Up @@ -84,7 +86,12 @@ Vector2 QuadraticSegment::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 &param) const {
Expand Down Expand Up @@ -146,13 +153,15 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double &param) const
Vector2 br = p[2]-p[1]-ab;
Vector2 as = (p[3]-p[2])-(p[2]-p[1])-br;

double minDistance = nonZeroSign(crossProduct(ab, qa))*qa.length(); // distance from A
param = -dotProduct(qa, ab)/dotProduct(ab, ab);
Vector2 epDir = direction(0);
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)) {
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
Expand All @@ -179,55 +188,11 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double &param) const
if (param >= 0 && param <= 1)
return SignedDistance(minDistance, 0);
if (param < .5)
return SignedDistance(minDistance, fabs(dotProduct(ab.normalize(), qa.normalize())));
return SignedDistance(minDistance, fabs(dotProduct(direction(0).normalize(), qa.normalize())));
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 &param) 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) {
if (p.x < l) l = p.x;
if (p.y < b) b = p.y;
Expand Down
3 changes: 0 additions & 3 deletions core/equation-solver.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,4 @@ int solveQuadratic(double x[2], double a, double b, double c);
// ax^3 + bx^2 + cx + d = 0
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);

}
Loading

0 comments on commit 0e68504

Please sign in to comment.