Ver Fonte

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

Viktor Chlumský há 8 anos atrás
pai
commit
0e68504f44
17 ficheiros alterados com 493 adições e 160 exclusões
  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
 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

+ 2 - 1
Msdfgen.vcxproj

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

+ 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-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)**
@@ -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
@@ -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,
 ```
@@ -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.

+ 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 "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);
 }
@@ -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 - 0
core/Contour.h

@@ -22,6 +22,8 @@ public:
     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;
 
 };
 

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

+ 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) {
+    if (p1 == p0 || p1 == p2)
+        p1 = 0.5*(p0+p2);
     p[0] = p0;
     p[1] = p1;
     p[2] = p2;
@@ -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 {
@@ -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
@@ -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;

+ 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
 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 {
 
-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) {
     // 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) {
+    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();
 #ifdef MSDFGEN_USE_OPENMP
     #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) {
-    while (*pathDef == ' ') {
+    while (*pathDef == ' ')
         ++pathDef;
-    }
-    if (*pathDef == ',') {
+    if (*pathDef == ',')
         ++pathDef;
-    }
 }
 
 static bool buildFromPath(Shape &shape, const char *pathDef) {
@@ -74,48 +72,38 @@ static bool buildFromPath(Shape &shape, const char *pathDef) {
 
         while (true) {
             switch (nodeType) {
-                case 'M':
-                case 'm':
+                case 'M': case 'm':
                     REQUIRE(contourStart);
                     REQUIRE(readCoord(node, pathDef));
-                    if (nodeType == 'm') {
+                    if (nodeType == 'm')
                         node += prevNode;
-                    }
                     startPoint = node;
                     --nodeType; // to 'L' or 'l'
                     break;
-                case 'Z':
-                case 'z':
+                case 'Z': case 'z':
                     if (prevNode != startPoint)
                         contour.addEdge(new LinearSegment(prevNode, startPoint));
                     prevNode = startPoint;
                     goto NEXT_CONTOUR;
-                case 'L':
-                case 'l':
+                case 'L': case 'l':
                     REQUIRE(readCoord(node, pathDef));
-                    if (nodeType == 'l') {
+                    if (nodeType == 'l')
                         node += prevNode;
-                    }
                     contour.addEdge(new LinearSegment(prevNode, node));
                     break;
-                case 'H':
-                case 'h':
+                case 'H': case 'h':
                     REQUIRE(readDouble(node.x, pathDef));
-                    if (nodeType == 'h') {
+                    if (nodeType == 'h')
                         node.x += prevNode.x;
-                    }
                     contour.addEdge(new LinearSegment(prevNode, node));
                     break;
-                case 'V':
-                case 'v':
+                case 'V': case 'v':
                     REQUIRE(readDouble(node.y, pathDef));
-                    if (nodeType == 'v') {
+                    if (nodeType == 'v')
                         node.y += prevNode.y;
-                    }
                     contour.addEdge(new LinearSegment(prevNode, node));
                     break;
-                case 'Q':
-                case 'q':
+                case 'Q': case 'q':
                     REQUIRE(readCoord(controlPoint[0], pathDef));
                     consumeOptionalComma(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));
                     break;
                 // TODO T, t
-                case 'C':
-                case 'c':
+                case 'C': case 'c':
                     REQUIRE(readCoord(controlPoint[0], pathDef));
                     consumeOptionalComma(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));
                     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));
                     consumeOptionalComma(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"
     "  -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"
         "\tSets the output file name. The default value is \"output.png\".\n"
     "  -printmetrics\n"
@@ -320,12 +324,12 @@ static const char *helpText =
         "\tRenders an image preview without flattening the color channels.\n"
     "  -translate <x> <y>\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"
-        "\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"
         "\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";
 
 int main(int argc, const char * const *argv) {
@@ -346,6 +350,7 @@ int main(int argc, const char * const *argv) {
         MULTI,
         METRICS
     } mode = MULTI;
+    bool legacyMode = false;
     Format format = AUTO;
     const char *input = NULL;
     const char *output = "output.png";
@@ -375,7 +380,11 @@ int main(int argc, const char * const *argv) {
     bool yFlip = false;
     bool printMetrics = false;
     bool skipColoring = false;
-    bool reverseOrder = false;
+    enum {
+        KEEP,
+        REVERSE,
+        GUESS
+    } orientation = GUESS;
     unsigned long long coloringSeed = 0;
 
     int argPos = 1;
@@ -433,6 +442,11 @@ int main(int argc, const char * const *argv) {
             argPos += 1;
             continue;
         }
+        ARG_CASE("-legacy", 0) {
+            legacyMode = true;
+            argPos += 1;
+            continue;
+        }
         ARG_CASE("-format", 1) {
             if (!strcmp(argv[argPos+1], "auto")) format = AUTO;
             else if (!strcmp(argv[argPos+1], "png")) SETFORMAT(PNG, "png");
@@ -566,8 +580,18 @@ int main(int argc, const char * const *argv) {
             argPos += 1;
             continue;
         }
+        ARG_CASE("-keeporder", 0) {
+            orientation = KEEP;
+            argPos += 1;
+            continue;
+        }
         ARG_CASE("-reverseorder", 0) {
-            reverseOrder = true;
+            orientation = REVERSE;
+            argPos += 1;
+            continue;
+        }
+        ARG_CASE("-guessorder", 0) {
+            orientation = GUESS;
             argPos += 1;
             continue;
         }
@@ -653,7 +677,7 @@ int main(int argc, const char * const *argv) {
     } bounds = {
         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);
 
     // Auto-frame
@@ -719,12 +743,18 @@ int main(int argc, const char * const *argv) {
     switch (mode) {
         case SINGLE: {
             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;
         }
         case PSEUDO: {
             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;
         }
         case MULTI: {
@@ -733,14 +763,30 @@ int main(int argc, const char * const *argv) {
             if (edgeAssignment)
                 parseColoring(shape, edgeAssignment);
             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;
         }
         default:
             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(msdf);
     }

+ 2 - 2
msdfgen-ext.h

@@ -2,9 +2,9 @@
 #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.
  *

+ 8 - 3
msdfgen.h

@@ -2,9 +2,9 @@
 #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
  * has been developed by Viktor Chlumsky in 2014 for his master's thesis,
@@ -24,7 +24,7 @@
 #include "core/save-bmp.h"
 #include "core/shape-description.h"
 
-#define MSDFGEN_VERSION "1.3"
+#define MSDFGEN_VERSION "1.4"
 
 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)
 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);
+
 }