Browse Source

V1.4: Overlapping contours, cubic distance fix, guessorder

Viktor Chlumský 8 years ago
parent
commit
0e68504f44
17 changed files with 493 additions and 160 deletions
  1. 12 0
      .gitignore
  2. 0 6
      Msdfgen.sln
  3. 2 1
      Msdfgen.vcxproj
  4. 14 3
      README.md
  5. 0 0
      bin/example.bat
  6. 0 0
      bin/freetype6.dll
  7. 0 0
      bin/zlib1.dll
  8. 32 0
      core/Contour.cpp
  9. 2 0
      core/Contour.h
  10. 6 0
      core/arithmetics.hpp
  11. 16 51
      core/edge-segments.cpp
  12. 0 3
      core/equation-solver.h
  13. 326 50
      core/msdfgen.cpp
  14. 15 29
      ext/import-svg.cpp
  15. 58 12
      main.cpp
  16. 2 2
      msdfgen-ext.h
  17. 8 3
      msdfgen.h

+ 12 - 0
.gitignore

@@ -0,0 +1,12 @@
+Debug/
+Release/
+*.exe
+*.user
+*.sdf
+*.pdb
+*.ipdb
+*.iobj
+*.suo
+*.VC.opendb
+output.png
+render.png

+ 0 - 6
Msdfgen.sln

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

+ 2 - 1
Msdfgen.vcxproj

@@ -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'">

+ 14 - 3
README.md

@@ -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-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)
 ![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
 ## 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.

+ 0 - 0
example.bat → bin/example.bat


+ 0 - 0
lib/freetype6.dll → bin/freetype6.dll


+ 0 - 0
lib/zlib1.dll → bin/zlib1.dll


+ 32 - 0
core/Contour.cpp

@@ -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);
+}
+
 }
 }

+ 2 - 0
core/Contour.h

@@ -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;
 
 
 };
 };
 
 

+ 6 - 0
core/arithmetics.hpp

@@ -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) {

+ 16 - 51
core/edge-segments.cpp

@@ -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 &param) const {
 SignedDistance LinearSegment::signedDistance(Point2 origin, double &param) const {
@@ -146,13 +153,15 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double &param) 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
-    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)) {
         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 &param) 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 &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) {
 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;

+ 0 - 3
core/equation-solver.h

@@ -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);
-
 }
 }

+ 326 - 50
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) {
-    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(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);
-        }
-    }
-}
+struct MultiDistance {
+    double r, g, b;
+    double med;
+};
 
 
 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

+ 15 - 29
ext/import-svg.cpp

@@ -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':
-                    controlPoint[0] = node * 2 - controlPoint[1];
+                case 'S': case 's':
+                    controlPoint[0] = node+node-controlPoint[1];
                     REQUIRE(readCoord(controlPoint[1], pathDef));
                     REQUIRE(readCoord(controlPoint[1], pathDef));
                     consumeOptionalComma(pathDef);
                     consumeOptionalComma(pathDef);
                     REQUIRE(readCoord(node, pathDef));
                     REQUIRE(readCoord(node, pathDef));

+ 58 - 12
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 - 2
msdfgen-ext.h

@@ -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.
  *
  *

+ 8 - 3
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);
+
 }
 }