فهرست منبع

Error correction algorithm expanded

Chlumsky 4 سال پیش
والد
کامیت
dc36f7140e
8فایلهای تغییر یافته به همراه418 افزوده شده و 134 حذف شده
  1. 198 41
      core/MSDFErrorCorrection.cpp
  2. 16 5
      core/MSDFErrorCorrection.h
  3. 36 12
      core/generator-config.h
  4. 36 22
      core/msdf-error-correction.cpp
  5. 8 8
      core/msdf-error-correction.h
  6. 14 14
      core/msdfgen.cpp
  7. 104 26
      main.cpp
  8. 6 6
      msdfgen.h

+ 198 - 41
core/MSDFErrorCorrection.cpp

@@ -5,19 +5,120 @@
 #include "arithmetics.hpp"
 #include "arithmetics.hpp"
 #include "equation-solver.h"
 #include "equation-solver.h"
 #include "EdgeColor.h"
 #include "EdgeColor.h"
+#include "bitmap-interpolation.hpp"
+#include "edge-selectors.h"
+#include "contour-combiners.h"
+#include "ShapeDistanceFinder.h"
+#include "generator-config.h"
 
 
 namespace msdfgen {
 namespace msdfgen {
 
 
 #define ARTIFACT_T_EPSILON .01
 #define ARTIFACT_T_EPSILON .01
 #define PROTECTION_RADIUS_TOLERANCE 1.001
 #define PROTECTION_RADIUS_TOLERANCE 1.001
 
 
+#define CLASSIFIER_FLAG_CANDIDATE 0x01
+#define CLASSIFIER_FLAG_ARTIFACT 0x02
+
+const double ErrorCorrectionConfig::defaultMinDeviationRatio = 1.11111111111111111;
+const double ErrorCorrectionConfig::defaultMinImproveRatio = 1.11111111111111111;
+
+/// The base artifact classifier recognizes artifacts based on the contents of the SDF alone.
+class BaseArtifactClassifier {
+public:
+    inline BaseArtifactClassifier(double span, bool protectedFlag) : span(span), protectedFlag(protectedFlag) { }
+    /// Evaluates if the median value xm interpolated at xt in the range between am at at and bm at bt indicates an artifact.
+    inline int rangeTest(double at, double bt, double xt, float am, float bm, float xm) const {
+        // For protected texels, only consider inversion artifacts (interpolated median has different sign than boundaries). For the rest, it is sufficient that the interpolated median is outside its boundaries.
+        if ((am > .5f && bm > .5f && xm < .5f) || (am < .5f && bm < .5f && xm > .5f) || (!protectedFlag && median(am, bm, xm) != xm)) {
+            double axSpan = (xt-at)*span, bxSpan = (bt-xt)*span;
+            // Check if the interpolated median's value is in the expected range based on its distance (span) from boundaries a, b.
+            if (!(xm >= am-axSpan && xm <= am+axSpan && xm >= bm-bxSpan && xm <= bm+bxSpan))
+                return CLASSIFIER_FLAG_CANDIDATE|CLASSIFIER_FLAG_ARTIFACT;
+            return CLASSIFIER_FLAG_CANDIDATE;
+        }
+        return 0;
+    }
+    /// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact.
+    inline bool evaluate(double t, float m, int flags) const {
+        return (flags&2) != 0;
+    }
+private:
+    double span;
+    bool protectedFlag;
+};
+
+/// The shape distance checker evaluates the exact shape distance to find additional artifacts at a significant performance cost.
+template <template <typename> class ContourCombiner, int N>
+class ShapeDistanceChecker {
+public:
+    class ArtifactClassifier : public BaseArtifactClassifier {
+    public:
+        inline ArtifactClassifier(ShapeDistanceChecker *parent, const Vector2 &direction, double span) : BaseArtifactClassifier(span, parent->protectedFlag), parent(parent), direction(direction) { }
+        /// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact.
+        inline bool evaluate(double t, float m, int flags) const {
+            if (flags&CLASSIFIER_FLAG_CANDIDATE) {
+                // Skip expensive distance evaluation if the point has already been classified as an artifact by the base classifier.
+                if (flags&CLASSIFIER_FLAG_ARTIFACT)
+                    return true;
+                Vector2 tVector = t*direction;
+                float oldMSD[N], newMSD[3];
+                // Compute the color that would be currently interpolated at the artifact candidate's position.
+                Point2 sdfCoord = parent->sdfCoord+tVector;
+                interpolate(oldMSD, parent->sdf, sdfCoord);
+                // Compute the color that would be interpolated at the artifact candidate's position if error correction was applied on the current texel.
+                double aWeight = (1-fabs(tVector.x))*(1-fabs(tVector.y));
+                float aPSD = median(parent->msd[0], parent->msd[1], parent->msd[2]);
+                newMSD[0] = float(oldMSD[0]+aWeight*(aPSD-parent->msd[0]));
+                newMSD[1] = float(oldMSD[1]+aWeight*(aPSD-parent->msd[1]));
+                newMSD[2] = float(oldMSD[2]+aWeight*(aPSD-parent->msd[2]));
+                // Compute the evaluated distance (interpolated median) before and after error correction, as well as the exact shape distance.
+                float oldPSD = median(oldMSD[0], oldMSD[1], oldMSD[2]);
+                float newPSD = median(newMSD[0], newMSD[1], newMSD[2]);
+                float refPSD = float(parent->invRange*parent->distanceFinder.distance(parent->shapeCoord+tVector*parent->texelSize)+.5);
+                // Compare the differences of the exact distance and the before and after distances.
+                return parent->minImproveRatio*fabsf(newPSD-refPSD) < double(fabsf(oldPSD-refPSD));
+            }
+            return false;
+        }
+    private:
+        ShapeDistanceChecker *parent;
+        Vector2 direction;
+    };
+    Point2 shapeCoord, sdfCoord;
+    const float *msd;
+    bool protectedFlag;
+    inline ShapeDistanceChecker(const BitmapConstRef<float, N> &sdf, const Shape &shape, const Projection &projection, double invRange, double minImproveRatio) : distanceFinder(shape), sdf(sdf), invRange(invRange), minImproveRatio(minImproveRatio) {
+        texelSize = projection.unprojectVector(Vector2(1));
+    }
+    inline ArtifactClassifier classifier(const Vector2 &direction, double span) {
+        return ArtifactClassifier(this, direction, span);
+    }
+private:
+    ShapeDistanceFinder<ContourCombiner<PseudoDistanceSelector> > distanceFinder;
+    BitmapConstRef<float, N> sdf;
+    double invRange;
+    Vector2 texelSize;
+    double minImproveRatio;
+};
+
 MSDFErrorCorrection::MSDFErrorCorrection() { }
 MSDFErrorCorrection::MSDFErrorCorrection() { }
 
 
-MSDFErrorCorrection::MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil) : stencil(stencil) {
+MSDFErrorCorrection::MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const Projection &projection, double range) : stencil(stencil), projection(projection) {
+    invRange = 1/range;
+    minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio;
+    minImproveRatio = ErrorCorrectionConfig::defaultMinImproveRatio;
     memset(stencil.pixels, 0, sizeof(byte)*stencil.width*stencil.height);
     memset(stencil.pixels, 0, sizeof(byte)*stencil.width*stencil.height);
 }
 }
 
 
-void MSDFErrorCorrection::protectCorners(const Shape &shape, const Projection &projection) {
+void MSDFErrorCorrection::setMinDeviationRatio(double minDeviationRatio) {
+    this->minDeviationRatio = minDeviationRatio;
+}
+
+void MSDFErrorCorrection::setMinImproveRatio(double minImproveRatio) {
+    this->minImproveRatio = minImproveRatio;
+}
+
+void MSDFErrorCorrection::protectCorners(const Shape &shape) {
     for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
     for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
         if (!contour->edges.empty()) {
         if (!contour->edges.empty()) {
             const EdgeSegment *prevEdge = contour->edges.back();
             const EdgeSegment *prevEdge = contour->edges.back();
@@ -87,10 +188,10 @@ static void protectExtremeChannels(byte *stencil, const float *msd, float m, int
 }
 }
 
 
 template <int N>
 template <int N>
-void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf, const Projection &projection, double range) {
+void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf) {
     float radius;
     float radius;
     // Horizontal texel pairs
     // Horizontal texel pairs
-    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(1/range, 0)).length());
+    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(invRange, 0)).length());
     for (int y = 0; y < sdf.height; ++y) {
     for (int y = 0; y < sdf.height; ++y) {
         const float *left = sdf(0, y);
         const float *left = sdf(0, y);
         const float *right = sdf(1, y);
         const float *right = sdf(1, y);
@@ -106,7 +207,7 @@ void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf, cons
         }
         }
     }
     }
     // Vertical texel pairs
     // Vertical texel pairs
-    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(0, 1/range)).length());
+    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(0, invRange)).length());
     for (int y = 0; y < sdf.height-1; ++y) {
     for (int y = 0; y < sdf.height-1; ++y) {
         const float *bottom = sdf(0, y);
         const float *bottom = sdf(0, y);
         const float *top = sdf(0, y+1);
         const float *top = sdf(0, y+1);
@@ -122,7 +223,7 @@ void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf, cons
         }
         }
     }
     }
     // Diagonal texel pairs
     // Diagonal texel pairs
-    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(1/range)).length());
+    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(invRange)).length());
     for (int y = 0; y < sdf.height-1; ++y) {
     for (int y = 0; y < sdf.height-1; ++y) {
         const float *lb = sdf(0, y);
         const float *lb = sdf(0, y);
         const float *rb = sdf(1, y);
         const float *rb = sdf(1, y);
@@ -182,15 +283,21 @@ static bool isArtifact(bool isProtected, double axSpan, double bxSpan, float am,
 }
 }
 
 
 /// Checks if a linear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values.
 /// Checks if a linear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values.
-static bool hasLinearArtifactInner(double span, bool isProtected, float am, float bm, const float *a, const float *b, float dA, float dB) {
+template <class ArtifactClassifier>
+static bool hasLinearArtifactInner(const ArtifactClassifier &artifactClassifier, float am, float bm, const float *a, const float *b, float dA, float dB) {
     // Find interpolation ratio t (0 < t < 1) where two color channels are equal (mix(dA, dB, t) == 0).
     // Find interpolation ratio t (0 < t < 1) where two color channels are equal (mix(dA, dB, t) == 0).
     double t = (double) dA/(dA-dB);
     double t = (double) dA/(dA-dB);
-    // Interpolate median at t and determine if it deviates too much from medians of a, b.
-    return t > ARTIFACT_T_EPSILON && t < 1-ARTIFACT_T_EPSILON && isArtifact(isProtected, t*span, (1-t)*span, am, bm, interpolatedMedian(a, b, t));
+    if (t > ARTIFACT_T_EPSILON && t < 1-ARTIFACT_T_EPSILON) {
+        // Interpolate median at t and let the classifier decide if its value indicates an artifact.
+        float xm = interpolatedMedian(a, b, t);
+        return artifactClassifier.evaluate(t, xm, artifactClassifier.rangeTest(0, 1, t, am, bm, xm));
+    }
+    return false;
 }
 }
 
 
 /// Checks if a bilinear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values.
 /// Checks if a bilinear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values.
-static bool hasDiagonalArtifactInner(double span, bool isProtected, float am, float dm, const float *a, const float *l, const float *q, float dA, float dBC, float dD, double tEx0, double tEx1) {
+template <class ArtifactClassifier>
+static bool hasDiagonalArtifactInner(const ArtifactClassifier &artifactClassifier, float am, float dm, const float *a, const float *l, const float *q, float dA, float dBC, float dD, double tEx0, double tEx1) {
     // Find interpolation ratios t (0 < t[i] < 1) where two color channels are equal.
     // Find interpolation ratios t (0 < t[i] < 1) where two color channels are equal.
     double t[2];
     double t[2];
     int solutions = solveQuadratic(t, dD-dBC+dA, dBC-dA-dA, dA);
     int solutions = solveQuadratic(t, dD-dBC+dA, dBC-dA-dA, dA);
@@ -200,8 +307,7 @@ static bool hasDiagonalArtifactInner(double span, bool isProtected, float am, fl
             // Interpolate median xm at t.
             // Interpolate median xm at t.
             float xm = interpolatedMedian(a, l, q, t[i]);
             float xm = interpolatedMedian(a, l, q, t[i]);
             // Determine if xm deviates too much from medians of a, d.
             // Determine if xm deviates too much from medians of a, d.
-            if (isArtifact(isProtected, t[i]*span, (1-t[i])*span, am, dm, xm))
-                return true;
+            int rangeFlags = artifactClassifier.rangeTest(0, 1, t[i], am, dm, xm);
             // Additionally, check xm against the interpolated medians at the local extremes tEx0, tEx1.
             // Additionally, check xm against the interpolated medians at the local extremes tEx0, tEx1.
             double tEnd[2];
             double tEnd[2];
             float em[2];
             float em[2];
@@ -211,8 +317,7 @@ static bool hasDiagonalArtifactInner(double span, bool isProtected, float am, fl
                 em[0] = am, em[1] = dm;
                 em[0] = am, em[1] = dm;
                 tEnd[tEx0 > t[i]] = tEx0;
                 tEnd[tEx0 > t[i]] = tEx0;
                 em[tEx0 > t[i]] = interpolatedMedian(a, l, q, tEx0);
                 em[tEx0 > t[i]] = interpolatedMedian(a, l, q, tEx0);
-                if (isArtifact(isProtected, (t[i]-tEnd[0])*span, (tEnd[1]-t[i])*span, em[0], em[1], xm))
-                    return true;
+                rangeFlags |= artifactClassifier.rangeTest(tEnd[0], tEnd[1], t[i], am, dm, xm);
             }
             }
             // tEx1
             // tEx1
             if (tEx1 > 0 && tEx1 < 1) {
             if (tEx1 > 0 && tEx1 < 1) {
@@ -220,30 +325,33 @@ static bool hasDiagonalArtifactInner(double span, bool isProtected, float am, fl
                 em[0] = am, em[1] = dm;
                 em[0] = am, em[1] = dm;
                 tEnd[tEx1 > t[i]] = tEx1;
                 tEnd[tEx1 > t[i]] = tEx1;
                 em[tEx1 > t[i]] = interpolatedMedian(a, l, q, tEx1);
                 em[tEx1 > t[i]] = interpolatedMedian(a, l, q, tEx1);
-                if (isArtifact(isProtected, (t[i]-tEnd[0])*span, (tEnd[1]-t[i])*span, em[0], em[1], xm))
-                    return true;
+                rangeFlags |= artifactClassifier.rangeTest(tEnd[0], tEnd[1], t[i], am, dm, xm);
             }
             }
+            if (artifactClassifier.evaluate(t[i], xm, rangeFlags))
+                return true;
         }
         }
     }
     }
     return false;
     return false;
 }
 }
 
 
 /// Checks if a linear interpolation artifact will occur inbetween two horizontally or vertically adjacent texels a, b.
 /// Checks if a linear interpolation artifact will occur inbetween two horizontally or vertically adjacent texels a, b.
-static bool hasLinearArtifact(double span, bool isProtected, float am, const float *a, const float *b) {
+template <class ArtifactClassifier>
+static bool hasLinearArtifact(const ArtifactClassifier &artifactClassifier, float am, const float *a, const float *b) {
     float bm = median(b[0], b[1], b[2]);
     float bm = median(b[0], b[1], b[2]);
     return (
     return (
         // Out of the pair, only report artifacts for the texel further from the edge to minimize side effects.
         // Out of the pair, only report artifacts for the texel further from the edge to minimize side effects.
         fabsf(am-.5f) > fabsf(bm-.5f) && (
         fabsf(am-.5f) > fabsf(bm-.5f) && (
             // Check points where each pair of color channels meets.
             // Check points where each pair of color channels meets.
-            hasLinearArtifactInner(span, isProtected, am, bm, a, b, a[1]-a[0], b[1]-b[0]) ||
-            hasLinearArtifactInner(span, isProtected, am, bm, a, b, a[2]-a[1], b[2]-b[1]) ||
-            hasLinearArtifactInner(span, isProtected, am, bm, a, b, a[0]-a[2], b[0]-b[2])
+            hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[1]-a[0], b[1]-b[0]) ||
+            hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[2]-a[1], b[2]-b[1]) ||
+            hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[0]-a[2], b[0]-b[2])
         )
         )
     );
     );
 }
 }
 
 
 /// Checks if a bilinear interpolation artifact will occur inbetween two diagonally adjacent texels a, d (with b, c forming the other diagonal).
 /// Checks if a bilinear interpolation artifact will occur inbetween two diagonally adjacent texels a, d (with b, c forming the other diagonal).
-static bool hasDiagonalArtifact(double span, bool isProtected, float am, const float *a, const float *b, const float *c, const float *d) {
+template <class ArtifactClassifier>
+static bool hasDiagonalArtifact(const ArtifactClassifier &artifactClassifier, float am, const float *a, const float *b, const float *c, const float *d) {
     float dm = median(d[0], d[1], d[2]);
     float dm = median(d[0], d[1], d[2]);
     // Out of the pair, only report artifacts for the texel further from the edge to minimize side effects.
     // Out of the pair, only report artifacts for the texel further from the edge to minimize side effects.
     if (fabsf(am-.5f) > fabsf(dm-.5f)) {
     if (fabsf(am-.5f) > fabsf(dm-.5f)) {
@@ -272,42 +380,87 @@ static bool hasDiagonalArtifact(double span, bool isProtected, float am, const f
         };
         };
         // Check points where each pair of color channels meets.
         // Check points where each pair of color channels meets.
         return (
         return (
-            hasDiagonalArtifactInner(span, isProtected, am, dm, a, l, q, a[1]-a[0], b[1]-b[0]+c[1]-c[0], d[1]-d[0], tEx[0], tEx[1]) ||
-            hasDiagonalArtifactInner(span, isProtected, am, dm, a, l, q, a[2]-a[1], b[2]-b[1]+c[2]-c[1], d[2]-d[1], tEx[1], tEx[2]) ||
-            hasDiagonalArtifactInner(span, isProtected, am, dm, a, l, q, a[0]-a[2], b[0]-b[2]+c[0]-c[2], d[0]-d[2], tEx[2], tEx[0])
+            hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[1]-a[0], b[1]-b[0]+c[1]-c[0], d[1]-d[0], tEx[0], tEx[1]) ||
+            hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[2]-a[1], b[2]-b[1]+c[2]-c[1], d[2]-d[1], tEx[1], tEx[2]) ||
+            hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[0]-a[2], b[0]-b[2]+c[0]-c[2], d[0]-d[2], tEx[2], tEx[0])
         );
         );
     }
     }
     return false;
     return false;
 }
 }
 
 
 template <int N>
 template <int N>
-void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf, const Projection &projection, double range, double threshold) {
+void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf) {
     // Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.
     // Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.
-    double hSpan = threshold*projection.unprojectVector(Vector2(1/range, 0)).length();
-    double vSpan = threshold*projection.unprojectVector(Vector2(0, 1/range)).length();
-    double dSpan = threshold*projection.unprojectVector(Vector2(1/range)).length();
+    double hSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange, 0)).length();
+    double vSpan = minDeviationRatio*projection.unprojectVector(Vector2(0, invRange)).length();
+    double dSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange)).length();
     // Inspect all texels.
     // Inspect all texels.
     for (int y = 0; y < sdf.height; ++y) {
     for (int y = 0; y < sdf.height; ++y) {
         for (int x = 0; x < sdf.width; ++x) {
         for (int x = 0; x < sdf.width; ++x) {
             const float *c = sdf(x, y);
             const float *c = sdf(x, y);
             float cm = median(c[0], c[1], c[2]);
             float cm = median(c[0], c[1], c[2]);
-            bool isProtected = (*stencil(x, y)&PROTECTED) != 0;
+            bool protectedFlag = (*stencil(x, y)&PROTECTED) != 0;
             const float *l = NULL, *b = NULL, *r = NULL, *t = NULL;
             const float *l = NULL, *b = NULL, *r = NULL, *t = NULL;
             // Mark current texel c with the error flag if an artifact occurs when it's interpolated with any of its 8 neighbors.
             // Mark current texel c with the error flag if an artifact occurs when it's interpolated with any of its 8 neighbors.
             *stencil(x, y) |= (byte) (ERROR*(
             *stencil(x, y) |= (byte) (ERROR*(
-                (x > 0 && ((l = sdf(x-1, y)), hasLinearArtifact(hSpan, isProtected, cm, c, l))) ||
-                (y > 0 && ((b = sdf(x, y-1)), hasLinearArtifact(vSpan, isProtected, cm, c, b))) ||
-                (x < sdf.width-1 && ((r = sdf(x+1, y)), hasLinearArtifact(hSpan, isProtected, cm, c, r))) ||
-                (y < sdf.height-1 && ((t = sdf(x, y+1)), hasLinearArtifact(vSpan, isProtected, cm, c, t))) ||
-                (x > 0 && y > 0 && hasDiagonalArtifact(dSpan, isProtected, cm, c, l, b, sdf(x-1, y-1))) ||
-                (x < sdf.width-1 && y > 0 && hasDiagonalArtifact(dSpan, isProtected, cm, c, r, b, sdf(x+1, y-1))) ||
-                (x > 0 && y < sdf.height-1 && hasDiagonalArtifact(dSpan, isProtected, cm, c, l, t, sdf(x-1, y+1))) ||
-                (x < sdf.width-1 && y < sdf.height-1 && hasDiagonalArtifact(dSpan, isProtected, cm, c, r, t, sdf(x+1, y+1)))
+                (x > 0 && ((l = sdf(x-1, y)), hasLinearArtifact(BaseArtifactClassifier(hSpan, protectedFlag), cm, c, l))) ||
+                (y > 0 && ((b = sdf(x, y-1)), hasLinearArtifact(BaseArtifactClassifier(vSpan, protectedFlag), cm, c, b))) ||
+                (x < sdf.width-1 && ((r = sdf(x+1, y)), hasLinearArtifact(BaseArtifactClassifier(hSpan, protectedFlag), cm, c, r))) ||
+                (y < sdf.height-1 && ((t = sdf(x, y+1)), hasLinearArtifact(BaseArtifactClassifier(vSpan, protectedFlag), cm, c, t))) ||
+                (x > 0 && y > 0 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, l, b, sdf(x-1, y-1))) ||
+                (x < sdf.width-1 && y > 0 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, r, b, sdf(x+1, y-1))) ||
+                (x > 0 && y < sdf.height-1 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, l, t, sdf(x-1, y+1))) ||
+                (x < sdf.width-1 && y < sdf.height-1 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, r, t, sdf(x+1, y+1)))
             ));
             ));
         }
         }
     }
     }
 }
 }
 
 
+template <template <typename> class ContourCombiner, int N>
+void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf, const Shape &shape) {
+    // Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.
+    double hSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange, 0)).length();
+    double vSpan = minDeviationRatio*projection.unprojectVector(Vector2(0, invRange)).length();
+    double dSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange)).length();
+#ifdef MSDFGEN_USE_OPENMP
+    #pragma omp parallel
+#endif
+    {
+        ShapeDistanceChecker<ContourCombiner, N> shapeDistanceChecker(sdf, shape, projection, invRange, minImproveRatio);
+        bool rightToLeft = false;
+        // Inspect all texels.
+#ifdef MSDFGEN_USE_OPENMP
+        #pragma omp for
+#endif
+        for (int y = 0; y < sdf.height; ++y) {
+            int row = shape.inverseYAxis ? sdf.height-y-1 : y;
+            for (int col = 0; col < sdf.width; ++col) {
+                int x = rightToLeft ? sdf.width-col-1 : col;
+                if ((*stencil(x, row)&ERROR))
+                    continue;
+                const float *c = sdf(x, row);
+                shapeDistanceChecker.shapeCoord = projection.unproject(Point2(x+.5, y+.5));
+                shapeDistanceChecker.sdfCoord = Point2(x+.5, row+.5);
+                shapeDistanceChecker.msd = c;
+                shapeDistanceChecker.protectedFlag = (*stencil(x, row)&PROTECTED) != 0;
+                float cm = median(c[0], c[1], c[2]);
+                const float *l = NULL, *b = NULL, *r = NULL, *t = NULL;
+                // Mark current texel c with the error flag if an artifact occurs when it's interpolated with any of its 8 neighbors.
+                *stencil(x, row) |= (byte) (ERROR*(
+                    (x > 0 && ((l = sdf(x-1, row)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(-1, 0), hSpan), cm, c, l))) ||
+                    (row > 0 && ((b = sdf(x, row-1)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(0, -1), vSpan), cm, c, b))) ||
+                    (x < sdf.width-1 && ((r = sdf(x+1, row)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(+1, 0), hSpan), cm, c, r))) ||
+                    (row < sdf.height-1 && ((t = sdf(x, row+1)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(0, +1), vSpan), cm, c, t))) ||
+                    (x > 0 && row > 0 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(-1, -1), dSpan), cm, c, l, b, sdf(x-1, row-1))) ||
+                    (x < sdf.width-1 && row > 0 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(+1, -1), dSpan), cm, c, r, b, sdf(x+1, row-1))) ||
+                    (x > 0 && row < sdf.height-1 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(-1, +1), dSpan), cm, c, l, t, sdf(x-1, row+1))) ||
+                    (x < sdf.width-1 && row < sdf.height-1 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(+1, +1), dSpan), cm, c, r, t, sdf(x+1, row+1)))
+                ));
+            }
+        }
+    }
+}
+
 template <int N>
 template <int N>
 void MSDFErrorCorrection::apply(const BitmapRef<float, N> &sdf) const {
 void MSDFErrorCorrection::apply(const BitmapRef<float, N> &sdf) const {
     int texelCount = sdf.width*sdf.height;
     int texelCount = sdf.width*sdf.height;
@@ -328,10 +481,14 @@ BitmapConstRef<byte, 1> MSDFErrorCorrection::getStencil() const {
     return stencil;
     return stencil;
 }
 }
 
 
-template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 3> &sdf, const Projection &projection, double range);
-template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 4> &sdf, const Projection &projection, double range);
-template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 3> &sdf, const Projection &projection, double range, double threshold);
-template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 4> &sdf, const Projection &projection, double range, double threshold);
+template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 3> &sdf);
+template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 4> &sdf);
+template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 3> &sdf);
+template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 4> &sdf);
+template void MSDFErrorCorrection::findErrors<SimpleContourCombiner>(const BitmapConstRef<float, 3> &sdf, const Shape &shape);
+template void MSDFErrorCorrection::findErrors<SimpleContourCombiner>(const BitmapConstRef<float, 4> &sdf, const Shape &shape);
+template void MSDFErrorCorrection::findErrors<OverlappingContourCombiner>(const BitmapConstRef<float, 3> &sdf, const Shape &shape);
+template void MSDFErrorCorrection::findErrors<OverlappingContourCombiner>(const BitmapConstRef<float, 4> &sdf, const Shape &shape);
 template void MSDFErrorCorrection::apply(const BitmapRef<float, 3> &sdf) const;
 template void MSDFErrorCorrection::apply(const BitmapRef<float, 3> &sdf) const;
 template void MSDFErrorCorrection::apply(const BitmapRef<float, 4> &sdf) const;
 template void MSDFErrorCorrection::apply(const BitmapRef<float, 4> &sdf) const;
 
 

+ 16 - 5
core/MSDFErrorCorrection.h

@@ -20,17 +20,24 @@ public:
     };
     };
 
 
     MSDFErrorCorrection();
     MSDFErrorCorrection();
-    explicit MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil);
+    explicit MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const Projection &projection, double range);
+    /// Sets the minimum ratio between the actual and maximum expected distance delta to be considered an error.
+    void setMinDeviationRatio(double minDeviationRatio);
+    /// Sets the minimum ratio between the pre-correction distance error and the post-correction distance error.
+    void setMinImproveRatio(double minImproveRatio);
     /// Flags all texels that are interpolated at corners as protected.
     /// Flags all texels that are interpolated at corners as protected.
-    void protectCorners(const Shape &shape, const Projection &projection);
+    void protectCorners(const Shape &shape);
     /// Flags all texels that contribute to edges as protected.
     /// Flags all texels that contribute to edges as protected.
     template <int N>
     template <int N>
-    void protectEdges(const BitmapConstRef<float, N> &sdf, const Projection &projection, double range);
+    void protectEdges(const BitmapConstRef<float, N> &sdf);
     /// Flags all texels as protected.
     /// Flags all texels as protected.
     void protectAll();
     void protectAll();
-    /// Flags texels that are expected to cause interpolation artifacts.
+    /// Flags texels that are expected to cause interpolation artifacts based on analysis of the SDF only.
     template <int N>
     template <int N>
-    void findErrors(const BitmapConstRef<float, N> &sdf, const Projection &projection, double range, double threshold);
+    void findErrors(const BitmapConstRef<float, N> &sdf);
+    /// Flags texels that are expected to cause interpolation artifacts based on analysis of the SDF and comparison with the exact shape distance.
+    template <template <typename> class ContourCombiner, int N>
+    void findErrors(const BitmapConstRef<float, N> &sdf, const Shape &shape);
     /// Modifies the MSDF so that all texels with the error flag are converted to single-channel.
     /// Modifies the MSDF so that all texels with the error flag are converted to single-channel.
     template <int N>
     template <int N>
     void apply(const BitmapRef<float, N> &sdf) const;
     void apply(const BitmapRef<float, N> &sdf) const;
@@ -39,6 +46,10 @@ public:
 
 
 private:
 private:
     BitmapRef<byte, 1> stencil;
     BitmapRef<byte, 1> stencil;
+    Projection projection;
+    double invRange;
+    double minDeviationRatio;
+    double minImproveRatio;
 
 
 };
 };
 
 

+ 36 - 12
core/generator-config.h

@@ -2,21 +2,17 @@
 #pragma once
 #pragma once
 
 
 #include <cstdlib>
 #include <cstdlib>
-
-#define MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD 1.001
+#include "BitmapRef.hpp"
 
 
 namespace msdfgen {
 namespace msdfgen {
 
 
-/// The configuration of the distance field generator algorithm.
-struct GeneratorConfig {
-    /// Specifies whether to use the version of the algorithm that supports overlapping contours with the same winding. May be set to false to improve performance when no such contours are present.
-    bool overlapSupport;
-
-    inline explicit GeneratorConfig(bool overlapSupport = true) : overlapSupport(overlapSupport) { }
-};
-
 /// The configuration of the MSDF error correction pass.
 /// The configuration of the MSDF error correction pass.
 struct ErrorCorrectionConfig {
 struct ErrorCorrectionConfig {
+    /// The default value of minDeviationRatio.
+    static const double defaultMinDeviationRatio;
+    /// The default value of minImproveRatio.
+    static const double defaultMinImproveRatio;
+
     /// Mode of operation.
     /// Mode of operation.
     enum Mode {
     enum Mode {
         /// Skips error correction pass.
         /// Skips error correction pass.
@@ -28,12 +24,40 @@ struct ErrorCorrectionConfig {
         /// Only corrects artifacts at edges.
         /// Only corrects artifacts at edges.
         EDGE_ONLY
         EDGE_ONLY
     } mode;
     } mode;
+    /// Configuration of whether to use an algorithm that computes the exact shape distance at the positions of suspected artifacts. This algorithm can be much slower.
+    enum DistanceCheckMode {
+        /// Never computes exact shape distance.
+        DO_NOT_CHECK_DISTANCE,
+        /// Only computes exact shape distance at edges. Provides a good balance between speed and precision.
+        CHECK_DISTANCE_AT_EDGE,
+        /// Computes and compares the exact shape distance for each suspected artifact.
+        ALWAYS_CHECK_DISTANCE
+    } distanceCheckMode;
     /// The minimum ratio between the actual and maximum expected distance delta to be considered an error.
     /// The minimum ratio between the actual and maximum expected distance delta to be considered an error.
-    double threshold;
+    double minDeviationRatio;
+    /// The minimum ratio between the pre-correction distance error and the post-correction distance error. Has no effect for DO_NOT_CHECK_DISTANCE.
+    double minImproveRatio;
     /// An optional buffer to avoid dynamic allocation. Must have at least as many bytes as the MSDF has pixels.
     /// An optional buffer to avoid dynamic allocation. Must have at least as many bytes as the MSDF has pixels.
     byte *buffer;
     byte *buffer;
 
 
-    inline explicit ErrorCorrectionConfig(Mode mode = EDGE_PRIORITY, double threshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD, byte *buffer = NULL) : mode(mode), threshold(threshold), buffer(buffer) { }
+    inline explicit ErrorCorrectionConfig(Mode mode = EDGE_PRIORITY, DistanceCheckMode distanceCheckMode = CHECK_DISTANCE_AT_EDGE, double minDeviationRatio = defaultMinDeviationRatio, double minImproveRatio = defaultMinImproveRatio, byte *buffer = NULL) : mode(mode), distanceCheckMode(distanceCheckMode), minDeviationRatio(minDeviationRatio), minImproveRatio(minImproveRatio), buffer(buffer) { }
+};
+
+/// The configuration of the distance field generator algorithm.
+struct GeneratorConfig {
+    /// Specifies whether to use the version of the algorithm that supports overlapping contours with the same winding. May be set to false to improve performance when no such contours are present.
+    bool overlapSupport;
+
+    inline explicit GeneratorConfig(bool overlapSupport = true) : overlapSupport(overlapSupport) { }
+};
+
+/// The configuration of the multi-channel distance field generator algorithm.
+struct MSDFGeneratorConfig : GeneratorConfig {
+    /// Configuration of the error correction pass.
+    ErrorCorrectionConfig errorCorrection;
+
+    inline MSDFGeneratorConfig() { }
+    inline explicit MSDFGeneratorConfig(bool overlapSupport, const ErrorCorrectionConfig &errorCorrection = ErrorCorrectionConfig()) : GeneratorConfig(overlapSupport), errorCorrection(errorCorrection) { }
 };
 };
 
 
 }
 }

+ 36 - 22
core/msdf-error-correction.cpp

@@ -4,66 +4,80 @@
 #include <vector>
 #include <vector>
 #include "arithmetics.hpp"
 #include "arithmetics.hpp"
 #include "Bitmap.h"
 #include "Bitmap.h"
+#include "contour-combiners.h"
 #include "MSDFErrorCorrection.h"
 #include "MSDFErrorCorrection.h"
 
 
 namespace msdfgen {
 namespace msdfgen {
 
 
 template <int N>
 template <int N>
-static void msdfErrorCorrectionInner(const BitmapRef<float, N> &sdf, const Shape &shape, const Projection &projection, double range, const ErrorCorrectionConfig &config) {
-    if (config.mode == ErrorCorrectionConfig::DISABLED)
+static void msdfErrorCorrectionInner(const BitmapRef<float, N> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
+    if (config.errorCorrection.mode == ErrorCorrectionConfig::DISABLED)
         return;
         return;
     Bitmap<byte, 1> stencilBuffer;
     Bitmap<byte, 1> stencilBuffer;
-    if (!config.buffer)
+    if (!config.errorCorrection.buffer)
         stencilBuffer = Bitmap<byte, 1>(sdf.width, sdf.height);
         stencilBuffer = Bitmap<byte, 1>(sdf.width, sdf.height);
     BitmapRef<byte, 1> stencil;
     BitmapRef<byte, 1> stencil;
-    stencil.pixels = config.buffer ? config.buffer : (byte *) stencilBuffer;
+    stencil.pixels = config.errorCorrection.buffer ? config.errorCorrection.buffer : (byte *) stencilBuffer;
     stencil.width = sdf.width, stencil.height = sdf.height;
     stencil.width = sdf.width, stencil.height = sdf.height;
-    MSDFErrorCorrection ec(stencil);
-    switch (config.mode) {
+    MSDFErrorCorrection ec(stencil, projection, range);
+    ec.setMinDeviationRatio(config.errorCorrection.minDeviationRatio);
+    ec.setMinImproveRatio(config.errorCorrection.minImproveRatio);
+    switch (config.errorCorrection.mode) {
         case ErrorCorrectionConfig::DISABLED:
         case ErrorCorrectionConfig::DISABLED:
         case ErrorCorrectionConfig::INDISCRIMINATE:
         case ErrorCorrectionConfig::INDISCRIMINATE:
             break;
             break;
         case ErrorCorrectionConfig::EDGE_PRIORITY:
         case ErrorCorrectionConfig::EDGE_PRIORITY:
-            ec.protectCorners(shape, projection);
-            ec.protectEdges<N>(sdf, projection, range);
+            ec.protectCorners(shape);
+            ec.protectEdges<N>(sdf);
             break;
             break;
         case ErrorCorrectionConfig::EDGE_ONLY:
         case ErrorCorrectionConfig::EDGE_ONLY:
             ec.protectAll();
             ec.protectAll();
             break;
             break;
     }
     }
-    ec.findErrors<N>(sdf, projection, range, config.threshold);
+    if (config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE || (config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE && config.errorCorrection.mode != ErrorCorrectionConfig::EDGE_ONLY)) {
+        ec.findErrors<N>(sdf);
+        if (config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE)
+            ec.protectAll();
+    }
+    if (config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE || config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE) {
+        if (config.overlapSupport)
+            ec.findErrors<OverlappingContourCombiner, N>(sdf, shape);
+        else
+            ec.findErrors<SimpleContourCombiner, N>(sdf, shape);
+    }
     ec.apply(sdf);
     ec.apply(sdf);
 }
 }
 
 
 template <int N>
 template <int N>
-static void msdfErrorCorrectionShapeless(const BitmapRef<float, N> &sdf, const Projection &projection, double range, double threshold, bool protectAll) {
+static void msdfErrorCorrectionShapeless(const BitmapRef<float, N> &sdf, const Projection &projection, double range, double minDeviationRatio, bool protectAll) {
     Bitmap<byte, 1> stencilBuffer(sdf.width, sdf.height);
     Bitmap<byte, 1> stencilBuffer(sdf.width, sdf.height);
-    MSDFErrorCorrection ec(stencilBuffer);
+    MSDFErrorCorrection ec(stencilBuffer, projection, range);
+    ec.setMinDeviationRatio(minDeviationRatio);
     if (protectAll)
     if (protectAll)
         ec.protectAll();
         ec.protectAll();
-    ec.findErrors<N>(sdf, projection, range, threshold);
+    ec.findErrors<N>(sdf);
     ec.apply(sdf);
     ec.apply(sdf);
 }
 }
 
 
-void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const ErrorCorrectionConfig &config) {
+void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
     msdfErrorCorrectionInner(sdf, shape, projection, range, config);
     msdfErrorCorrectionInner(sdf, shape, projection, range, config);
 }
 }
-void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, double range, const ErrorCorrectionConfig &config) {
+void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
     msdfErrorCorrectionInner(sdf, shape, projection, range, config);
     msdfErrorCorrectionInner(sdf, shape, projection, range, config);
 }
 }
 
 
-void msdfDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double threshold) {
-    msdfErrorCorrectionShapeless(sdf, projection, range, threshold, false);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, false);
 }
 }
-void msdfDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double threshold) {
-    msdfErrorCorrectionShapeless(sdf, projection, range, threshold, false);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, false);
 }
 }
 
 
-void msdfEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double threshold) {
-    msdfErrorCorrectionShapeless(sdf, projection, range, threshold, true);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, true);
 }
 }
-void msdfEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double threshold) {
-    msdfErrorCorrectionShapeless(sdf, projection, range, threshold, true);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, true);
 }
 }
 
 
 
 

+ 8 - 8
core/msdf-error-correction.h

@@ -10,16 +10,16 @@
 namespace msdfgen {
 namespace msdfgen {
 
 
 /// Predicts potential artifacts caused by the interpolation of the MSDF and corrects them by converting nearby texels to single-channel.
 /// Predicts potential artifacts caused by the interpolation of the MSDF and corrects them by converting nearby texels to single-channel.
-void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const ErrorCorrectionConfig &config = ErrorCorrectionConfig());
-void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, double range, const ErrorCorrectionConfig &config = ErrorCorrectionConfig());
+void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
 
 
-/// Applies the error correction to all discontiunous distances (INDISCRIMINATE mode). Does not need shape or translation.
-void msdfDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double threshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD);
-void msdfDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double threshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD);
+/// Applies the simplified error correction to all discontiunous distances (INDISCRIMINATE mode). Does not need shape or translation.
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
 
 
-/// Applies the error correction to edges only (EDGE_ONLY mode). Does not need shape or translation.
-void msdfEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double threshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD);
-void msdfEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double threshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD);
+/// Applies the simplified error correction to edges only (EDGE_ONLY mode). Does not need shape or translation.
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
 
 
 /// The original version of the error correction algorithm.
 /// The original version of the error correction algorithm.
 void msdfErrorCorrection_legacy(const BitmapRef<float, 3> &output, const Vector2 &threshold);
 void msdfErrorCorrection_legacy(const BitmapRef<float, 3> &output, const Vector2 &threshold);

+ 14 - 14
core/msdfgen.cpp

@@ -88,20 +88,20 @@ void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, co
         generateDistanceField<SimpleContourCombiner<PseudoDistanceSelector> >(output, shape, projection, range);
         generateDistanceField<SimpleContourCombiner<PseudoDistanceSelector> >(output, shape, projection, range);
 }
 }
 
 
-void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config, const ErrorCorrectionConfig &errorCorrectionConfig) {
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
     if (config.overlapSupport)
     if (config.overlapSupport)
         generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, projection, range);
         generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, projection, range);
     else
     else
         generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, projection, range);
         generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, projection, range);
-    msdfErrorCorrection(output, shape, projection, range, errorCorrectionConfig);
+    msdfErrorCorrection(output, shape, projection, range, config);
 }
 }
 
 
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config, const ErrorCorrectionConfig &errorCorrectionConfig) {
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
     if (config.overlapSupport)
     if (config.overlapSupport)
         generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range);
         generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range);
     else
     else
         generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range);
         generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range);
-    msdfErrorCorrection(output, shape, projection, range, errorCorrectionConfig);
+    msdfErrorCorrection(output, shape, projection, range, config);
 }
 }
 
 
 // Legacy API
 // Legacy API
@@ -114,12 +114,12 @@ void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, do
     generatePseudoSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport));
     generatePseudoSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport));
 }
 }
 
 
-void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double errorCorrectionThreshold, bool overlapSupport) {
-    generateMSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport), ErrorCorrectionConfig(ErrorCorrectionConfig::EDGE_PRIORITY, errorCorrectionThreshold));
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) {
+    generateMSDF(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(overlapSupport, errorCorrectionConfig));
 }
 }
 
 
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double errorCorrectionThreshold, bool overlapSupport) {
-    generateMTSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport), ErrorCorrectionConfig(ErrorCorrectionConfig::EDGE_PRIORITY, errorCorrectionThreshold));
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) {
+    generateMTSDF(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(overlapSupport, errorCorrectionConfig));
 }
 }
 
 
 // Legacy version
 // Legacy version
@@ -173,7 +173,7 @@ void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &sh
     }
     }
 }
 }
 
 
-void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold) {
+void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) {
 #ifdef MSDFGEN_USE_OPENMP
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel for
     #pragma omp parallel for
 #endif
 #endif
@@ -223,11 +223,11 @@ void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape,
         }
         }
     }
     }
 
 
-    if (edgeThreshold > 0)
-        msdfErrorCorrection(output, shape, Projection(scale, translate), range, ErrorCorrectionConfig(ErrorCorrectionConfig::EDGE_PRIORITY, edgeThreshold));
+    errorCorrectionConfig.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
+    msdfErrorCorrection(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(false, errorCorrectionConfig));
 }
 }
 
 
-void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold) {
+void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) {
 #ifdef MSDFGEN_USE_OPENMP
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel for
     #pragma omp parallel for
 #endif
 #endif
@@ -281,8 +281,8 @@ void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape,
         }
         }
     }
     }
 
 
-    if (edgeThreshold > 0)
-        msdfErrorCorrection(output, shape, Projection(scale, translate), range, ErrorCorrectionConfig(ErrorCorrectionConfig::EDGE_PRIORITY, edgeThreshold));
+    errorCorrectionConfig.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
+    msdfErrorCorrection(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(false, errorCorrectionConfig));
 }
 }
 
 
 }
 }

+ 104 - 26
main.cpp

@@ -310,8 +310,12 @@ static const char *helpText =
         "\tShifts all normalized distances in the output distance field by this value.\n"
         "\tShifts all normalized distances in the output distance field by this value.\n"
     "  -edgecolors <sequence>\n"
     "  -edgecolors <sequence>\n"
         "\tOverrides automatic edge coloring with the specified color sequence.\n"
         "\tOverrides automatic edge coloring with the specified color sequence.\n"
-    "  -errorcorrection <threshold>\n"
-        "\tChanges the threshold used to detect and correct potential artifacts. 0 disables error correction.\n"
+    "  -errorcorrection <mode>\n"
+        "\tChanges the MSDF/MTSDF error correction mode. Use -errorcorrection help for a list of valid modes.\n"
+    "  -errordeviationratio <ratio>\n"
+        "\tSets the minimum ratio between the actual and maximum expected distance delta to be considered an error.\n"
+    "  -errorimproveratio <ratio>\n"
+        "\tSets the minimum ratio between the pre-correction distance error and the post-correction distance error.\n"
     "  -estimateerror\n"
     "  -estimateerror\n"
         "\tComputes and prints the distance field's estimated fill error to the standard output.\n"
         "\tComputes and prints the distance field's estimated fill error to the standard output.\n"
     "  -exportshape <filename.txt>\n"
     "  -exportshape <filename.txt>\n"
@@ -371,6 +375,29 @@ static const char *helpText =
         "\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
         "\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
     "\n";
     "\n";
 
 
+static const char *errorCorrectionHelpText =
+    "\n"
+    "ERROR CORRECTION MODES\n"
+    "  auto-fast\n"
+        "\tDetects inversion artifacts and distance errors that do not affect edges by range testing.\n"
+    "  auto-full\n"
+        "\tDetects inversion artifacts and distance errors that do not affect edges by exact distance evaluation.\n"
+    "  auto-mixed (default)\n"
+        "\tDetects inversions by distance evaluation and distance errors that do not affect edges by range testing.\n"
+    "  disabled\n"
+        "\tDisables error correction.\n"
+    "  distance-fast\n"
+        "\tDetects distance errors by range testing. Does not care if edges and corners are affected.\n"
+    "  distance-full\n"
+        "\tDetects distance errors by exact distance evaluation. Does not care if edges and corners are affected, slow.\n"
+    "  edge-fast\n"
+        "\tDetects inversion artifacts only by range testing.\n"
+    "  edge-full\n"
+        "\tDetects inversion artifacts only by exact distance evaluation.\n"
+    "  help\n"
+        "\tDisplays this help.\n"
+    "\n";
+
 int main(int argc, const char * const *argv) {
 int main(int argc, const char * const *argv) {
     #define ABORT(msg) { puts(msg); return 1; }
     #define ABORT(msg) { puts(msg); return 1; }
 
 
@@ -398,7 +425,7 @@ int main(int argc, const char * const *argv) {
             false
             false
         #endif
         #endif
     );
     );
-    GeneratorConfig generatorConfig;
+    MSDFGeneratorConfig generatorConfig;
     generatorConfig.overlapSupport = !geometryPreproc;
     generatorConfig.overlapSupport = !geometryPreproc;
     bool scanlinePass = !geometryPreproc;
     bool scanlinePass = !geometryPreproc;
     FillRule fillRule = FILL_NONZERO;
     FillRule fillRule = FILL_NONZERO;
@@ -427,7 +454,6 @@ int main(int argc, const char * const *argv) {
     Vector2 scale = 1;
     Vector2 scale = 1;
     bool scaleSpecified = false;
     bool scaleSpecified = false;
     double angleThreshold = DEFAULT_ANGLE_THRESHOLD;
     double angleThreshold = DEFAULT_ANGLE_THRESHOLD;
-    ErrorCorrectionConfig errorCorrectionConfig;
     float outputDistanceShift = 0.f;
     float outputDistanceShift = 0.f;
     const char *edgeAssignment = NULL;
     const char *edgeAssignment = NULL;
     bool yFlip = false;
     bool yFlip = false;
@@ -441,6 +467,7 @@ int main(int argc, const char * const *argv) {
     } orientation = KEEP;
     } orientation = KEEP;
     unsigned long long coloringSeed = 0;
     unsigned long long coloringSeed = 0;
     void (*edgeColoring)(Shape &, double, unsigned long long) = edgeColoringSimple;
     void (*edgeColoring)(Shape &, double, unsigned long long) = edgeColoringSimple;
+    bool explicitErrorCorrectionMode = false;
 
 
     int argPos = 1;
     int argPos = 1;
     bool suggestHelp = false;
     bool suggestHelp = false;
@@ -572,7 +599,7 @@ int main(int argc, const char * const *argv) {
         }
         }
         ARG_CASE("-size", 2) {
         ARG_CASE("-size", 2) {
             unsigned w, h;
             unsigned w, h;
-            if (!parseUnsigned(w, argv[argPos+1]) || !parseUnsigned(h, argv[argPos+2]) || !w || !h)
+            if (!(parseUnsigned(w, argv[argPos+1]) && parseUnsigned(h, argv[argPos+2]) && w && h))
                 ABORT("Invalid size arguments. Use -size <width> <height> with two positive integers.");
                 ABORT("Invalid size arguments. Use -size <width> <height> with two positive integers.");
             width = w, height = h;
             width = w, height = h;
             argPos += 3;
             argPos += 3;
@@ -585,7 +612,7 @@ int main(int argc, const char * const *argv) {
         }
         }
         ARG_CASE("-range", 1) {
         ARG_CASE("-range", 1) {
             double r;
             double r;
-            if (!parseDouble(r, argv[argPos+1]) || r < 0)
+            if (!(parseDouble(r, argv[argPos+1]) && r > 0))
                 ABORT("Invalid range argument. Use -range <range> with a positive real number.");
                 ABORT("Invalid range argument. Use -range <range> with a positive real number.");
             rangeMode = RANGE_UNIT;
             rangeMode = RANGE_UNIT;
             range = r;
             range = r;
@@ -594,7 +621,7 @@ int main(int argc, const char * const *argv) {
         }
         }
         ARG_CASE("-pxrange", 1) {
         ARG_CASE("-pxrange", 1) {
             double r;
             double r;
-            if (!parseDouble(r, argv[argPos+1]) || r < 0)
+            if (!(parseDouble(r, argv[argPos+1]) && r > 0))
                 ABORT("Invalid range argument. Use -pxrange <range> with a positive real number.");
                 ABORT("Invalid range argument. Use -pxrange <range> with a positive real number.");
             rangeMode = RANGE_PX;
             rangeMode = RANGE_PX;
             pxRange = r;
             pxRange = r;
@@ -603,7 +630,7 @@ int main(int argc, const char * const *argv) {
         }
         }
         ARG_CASE("-scale", 1) {
         ARG_CASE("-scale", 1) {
             double s;
             double s;
-            if (!parseDouble(s, argv[argPos+1]) || s <= 0)
+            if (!(parseDouble(s, argv[argPos+1]) && s > 0))
                 ABORT("Invalid scale argument. Use -scale <scale> with a positive real number.");
                 ABORT("Invalid scale argument. Use -scale <scale> with a positive real number.");
             scale = s;
             scale = s;
             scaleSpecified = true;
             scaleSpecified = true;
@@ -612,7 +639,7 @@ int main(int argc, const char * const *argv) {
         }
         }
         ARG_CASE("-ascale", 2) {
         ARG_CASE("-ascale", 2) {
             double sx, sy;
             double sx, sy;
-            if (!parseDouble(sx, argv[argPos+1]) || !parseDouble(sy, argv[argPos+2]) || sx <= 0 || sy <= 0)
+            if (!(parseDouble(sx, argv[argPos+1]) && parseDouble(sy, argv[argPos+2]) && sx > 0 && sy > 0))
                 ABORT("Invalid scale arguments. Use -ascale <x> <y> with two positive real numbers.");
                 ABORT("Invalid scale arguments. Use -ascale <x> <y> with two positive real numbers.");
             scale.set(sx, sy);
             scale.set(sx, sy);
             scaleSpecified = true;
             scaleSpecified = true;
@@ -621,7 +648,7 @@ int main(int argc, const char * const *argv) {
         }
         }
         ARG_CASE("-translate", 2) {
         ARG_CASE("-translate", 2) {
             double tx, ty;
             double tx, ty;
-            if (!parseDouble(tx, argv[argPos+1]) || !parseDouble(ty, argv[argPos+2]))
+            if (!(parseDouble(tx, argv[argPos+1]) && parseDouble(ty, argv[argPos+2])))
                 ABORT("Invalid translate arguments. Use -translate <x> <y> with two real numbers.");
                 ABORT("Invalid translate arguments. Use -translate <x> <y> with two real numbers.");
             translate.set(tx, ty);
             translate.set(tx, ty);
             argPos += 3;
             argPos += 3;
@@ -636,13 +663,52 @@ int main(int argc, const char * const *argv) {
             continue;
             continue;
         }
         }
         ARG_CASE("-errorcorrection", 1) {
         ARG_CASE("-errorcorrection", 1) {
-            double ect;
-            if (!parseDouble(ect, argv[argPos+1]) && (ect >= 1 || ect == 0))
-                ABORT("Invalid error correction threshold. Use -errorcorrection <threshold> with a real number greater than or equal to 1 or 0 to disable.");
-            if (ect <= 0)
-                errorCorrectionConfig.mode = ErrorCorrectionConfig::DISABLED;
-            else
-                errorCorrectionConfig.threshold = ect;
+            if (!strcmp(argv[argPos+1], "disabled") || !strcmp(argv[argPos+1], "0") || !strcmp(argv[argPos+1], "none")) {
+                generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::DISABLED;
+                generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
+            } else if (!strcmp(argv[argPos+1], "default") || !strcmp(argv[argPos+1], "auto") || !strcmp(argv[argPos+1], "auto-mixed") || !strcmp(argv[argPos+1], "mixed")) {
+                generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_PRIORITY;
+                generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE;
+            } else if (!strcmp(argv[argPos+1], "auto-fast") || !strcmp(argv[argPos+1], "fast")) {
+                generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_PRIORITY;
+                generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
+            } else if (!strcmp(argv[argPos+1], "auto-full") || !strcmp(argv[argPos+1], "full")) {
+                generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_PRIORITY;
+                generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
+            } else if (!strcmp(argv[argPos+1], "distance") || !strcmp(argv[argPos+1], "distance-fast") || !strcmp(argv[argPos+1], "indiscriminate") || !strcmp(argv[argPos+1], "indiscriminate-fast")) {
+                generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::INDISCRIMINATE;
+                generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
+            } else if (!strcmp(argv[argPos+1], "distance-full") || !strcmp(argv[argPos+1], "indiscriminate-full")) {
+                generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::INDISCRIMINATE;
+                generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
+            } else if (!strcmp(argv[argPos+1], "edge-fast")) {
+                generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_ONLY;
+                generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
+            } else if (!strcmp(argv[argPos+1], "edge") || !strcmp(argv[argPos+1], "edge-full")) {
+                generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::EDGE_ONLY;
+                generatorConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE;
+            } else if (!strcmp(argv[argPos+1], "help")) {
+                puts(errorCorrectionHelpText);
+                return 0;
+            } else
+                puts("Unknown error correction mode. Use -errorcorrection help for more information.");
+            explicitErrorCorrectionMode = true;
+            argPos += 2;
+            continue;
+        }
+        ARG_CASE("-errordeviationratio", 1) {
+            double edr;
+            if (!(parseDouble(edr, argv[argPos+1]) && edr > 0))
+                ABORT("Invalid error deviation ratio. Use -errordeviationratio <ratio> with a positive real number.");
+            generatorConfig.errorCorrection.minDeviationRatio = edr;
+            argPos += 2;
+            continue;
+        }
+        ARG_CASE("-errorimproveratio", 1) {
+            double eir;
+            if (!(parseDouble(eir, argv[argPos+1]) && eir > 0))
+                ABORT("Invalid error improvement ratio. Use -errorimproveratio <ratio> with a positive real number.");
+            generatorConfig.errorCorrection.minImproveRatio = eir;
             argPos += 2;
             argPos += 2;
             continue;
             continue;
         }
         }
@@ -887,9 +953,21 @@ int main(int argc, const char * const *argv) {
     Bitmap<float, 1> sdf;
     Bitmap<float, 1> sdf;
     Bitmap<float, 3> msdf;
     Bitmap<float, 3> msdf;
     Bitmap<float, 4> mtsdf;
     Bitmap<float, 4> mtsdf;
-    ErrorCorrectionConfig initialErrorCorrectionConfig(errorCorrectionConfig);
-    if (scanlinePass)
-        initialErrorCorrectionConfig.mode = ErrorCorrectionConfig::DISABLED;
+    MSDFGeneratorConfig postErrorCorrectionConfig(generatorConfig);
+    if (scanlinePass) {
+        if (explicitErrorCorrectionMode && generatorConfig.errorCorrection.distanceCheckMode != ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE) {
+            const char *fallbackModeName = "unknown";
+            switch (generatorConfig.errorCorrection.mode) {
+                case ErrorCorrectionConfig::DISABLED: fallbackModeName = "disabled"; break;
+                case ErrorCorrectionConfig::INDISCRIMINATE: fallbackModeName = "distance-fast"; break;
+                case ErrorCorrectionConfig::EDGE_PRIORITY: fallbackModeName = "auto-fast"; break;
+                case ErrorCorrectionConfig::EDGE_ONLY: fallbackModeName = "edge-fast"; break;
+            }
+            printf("Selected error correction mode not compatible with scanline mode, falling back to %s.\n", fallbackModeName);
+        }
+        generatorConfig.errorCorrection.mode = ErrorCorrectionConfig::DISABLED;
+        postErrorCorrectionConfig.errorCorrection.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
+    }
     switch (mode) {
     switch (mode) {
         case SINGLE: {
         case SINGLE: {
             sdf = Bitmap<float, 1>(width, height);
             sdf = Bitmap<float, 1>(width, height);
@@ -914,9 +992,9 @@ int main(int argc, const char * const *argv) {
                 parseColoring(shape, edgeAssignment);
                 parseColoring(shape, edgeAssignment);
             msdf = Bitmap<float, 3>(width, height);
             msdf = Bitmap<float, 3>(width, height);
             if (legacyMode)
             if (legacyMode)
-                generateMSDF_legacy(msdf, shape, range, scale, translate, scanlinePass || errorCorrectionConfig.mode == ErrorCorrectionConfig::DISABLED ? 0 : errorCorrectionConfig.threshold);
+                generateMSDF_legacy(msdf, shape, range, scale, translate, generatorConfig.errorCorrection);
             else
             else
-                generateMSDF(msdf, shape, projection, range, generatorConfig, initialErrorCorrectionConfig);
+                generateMSDF(msdf, shape, projection, range, generatorConfig);
             break;
             break;
         }
         }
         case MULTI_AND_TRUE: {
         case MULTI_AND_TRUE: {
@@ -926,9 +1004,9 @@ int main(int argc, const char * const *argv) {
                 parseColoring(shape, edgeAssignment);
                 parseColoring(shape, edgeAssignment);
             mtsdf = Bitmap<float, 4>(width, height);
             mtsdf = Bitmap<float, 4>(width, height);
             if (legacyMode)
             if (legacyMode)
-                generateMTSDF_legacy(mtsdf, shape, range, scale, translate, scanlinePass || errorCorrectionConfig.mode == ErrorCorrectionConfig::DISABLED ? 0 : errorCorrectionConfig.threshold);
+                generateMTSDF_legacy(mtsdf, shape, range, scale, translate, generatorConfig.errorCorrection);
             else
             else
-                generateMTSDF(mtsdf, shape, projection, range, generatorConfig, initialErrorCorrectionConfig);
+                generateMTSDF(mtsdf, shape, projection, range, generatorConfig);
             break;
             break;
         }
         }
         default:;
         default:;
@@ -963,11 +1041,11 @@ int main(int argc, const char * const *argv) {
                 break;
                 break;
             case MULTI:
             case MULTI:
                 distanceSignCorrection(msdf, shape, projection, fillRule);
                 distanceSignCorrection(msdf, shape, projection, fillRule);
-                msdfErrorCorrection(msdf, shape, projection, range, errorCorrectionConfig);
+                msdfErrorCorrection(msdf, shape, projection, range, postErrorCorrectionConfig);
                 break;
                 break;
             case MULTI_AND_TRUE:
             case MULTI_AND_TRUE:
                 distanceSignCorrection(mtsdf, shape, projection, fillRule);
                 distanceSignCorrection(mtsdf, shape, projection, fillRule);
-                msdfErrorCorrection(msdf, shape, projection, range, errorCorrectionConfig);
+                msdfErrorCorrection(msdf, shape, projection, range, postErrorCorrectionConfig);
                 break;
                 break;
             default:;
             default:;
         }
         }

+ 6 - 6
msdfgen.h

@@ -45,21 +45,21 @@ void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Pr
 void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig());
 void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig());
 
 
 /// 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(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig(), const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig());
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
 
 
 /// Generates a multi-channel signed distance field with true distance in the alpha channel. Edge colors must be assigned first.
 /// Generates a multi-channel signed distance field with true distance in the alpha channel. Edge colors must be assigned first.
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig(), const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig());
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
 
 
 // Old version of the function API's kept for backwards compatibility
 // Old version of the function API's kept for backwards compatibility
 void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
 void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
 void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
 void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
-void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double errorCorrectionThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD, bool overlapSupport = true);
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double errorCorrectionThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD, bool overlapSupport = true);
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig(), bool overlapSupport = true);
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig(), bool overlapSupport = true);
 
 
 // Original simpler versions of the previous functions, which work well under normal circumstances, but cannot deal with overlapping contours.
 // Original simpler versions of the previous functions, which work well under normal circumstances, but cannot deal with overlapping contours.
 void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
 void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
 void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
 void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
-void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD);
-void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD);
+void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig = ErrorCorrectionConfig());
+void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig = ErrorCorrectionConfig());
 
 
 }
 }