Browse Source

Error correction algorithm expanded

Chlumsky 4 years ago
parent
commit
dc36f7140e
8 changed files with 418 additions and 134 deletions
  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 "equation-solver.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 {
 
 #define ARTIFACT_T_EPSILON .01
 #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(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);
 }
 
-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)
         if (!contour->edges.empty()) {
             const EdgeSegment *prevEdge = contour->edges.back();
@@ -87,10 +188,10 @@ static void protectExtremeChannels(byte *stencil, const float *msd, float m, int
 }
 
 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;
     // 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) {
         const float *left = sdf(0, y);
         const float *right = sdf(1, y);
@@ -106,7 +207,7 @@ void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf, cons
         }
     }
     // 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) {
         const float *bottom = sdf(0, y);
         const float *top = sdf(0, y+1);
@@ -122,7 +223,7 @@ void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf, cons
         }
     }
     // 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) {
         const float *lb = sdf(0, 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.
-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).
     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.
-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.
     double t[2];
     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.
             float xm = interpolatedMedian(a, l, q, t[i]);
             // 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.
             double tEnd[2];
             float em[2];
@@ -211,8 +317,7 @@ static bool hasDiagonalArtifactInner(double span, bool isProtected, float am, fl
                 em[0] = am, em[1] = dm;
                 tEnd[tEx0 > t[i]] = 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
             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;
                 tEnd[tEx1 > t[i]] = 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;
 }
 
 /// 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]);
     return (
         // Out of the pair, only report artifacts for the texel further from the edge to minimize side effects.
         fabsf(am-.5f) > fabsf(bm-.5f) && (
             // 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).
-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]);
     // 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)) {
@@ -272,42 +380,87 @@ static bool hasDiagonalArtifact(double span, bool isProtected, float am, const f
         };
         // Check points where each pair of color channels meets.
         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;
 }
 
 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.
-    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.
     for (int y = 0; y < sdf.height; ++y) {
         for (int x = 0; x < sdf.width; ++x) {
             const float *c = sdf(x, y);
             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;
             // 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*(
-                (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>
 void MSDFErrorCorrection::apply(const BitmapRef<float, N> &sdf) const {
     int texelCount = sdf.width*sdf.height;
@@ -328,10 +481,14 @@ BitmapConstRef<byte, 1> MSDFErrorCorrection::getStencil() const {
     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, 4> &sdf) const;
 

+ 16 - 5
core/MSDFErrorCorrection.h

@@ -20,17 +20,24 @@ public:
     };
 
     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.
-    void protectCorners(const Shape &shape, const Projection &projection);
+    void protectCorners(const Shape &shape);
     /// Flags all texels that contribute to edges as protected.
     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.
     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>
-    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.
     template <int N>
     void apply(const BitmapRef<float, N> &sdf) const;
@@ -39,6 +46,10 @@ public:
 
 private:
     BitmapRef<byte, 1> stencil;
+    Projection projection;
+    double invRange;
+    double minDeviationRatio;
+    double minImproveRatio;
 
 };
 

+ 36 - 12
core/generator-config.h

@@ -2,21 +2,17 @@
 #pragma once
 
 #include <cstdlib>
-
-#define MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD 1.001
+#include "BitmapRef.hpp"
 
 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.
 struct ErrorCorrectionConfig {
+    /// The default value of minDeviationRatio.
+    static const double defaultMinDeviationRatio;
+    /// The default value of minImproveRatio.
+    static const double defaultMinImproveRatio;
+
     /// Mode of operation.
     enum Mode {
         /// Skips error correction pass.
@@ -28,12 +24,40 @@ struct ErrorCorrectionConfig {
         /// Only corrects artifacts at edges.
         EDGE_ONLY
     } 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.
-    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.
     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 "arithmetics.hpp"
 #include "Bitmap.h"
+#include "contour-combiners.h"
 #include "MSDFErrorCorrection.h"
 
 namespace msdfgen {
 
 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;
     Bitmap<byte, 1> stencilBuffer;
-    if (!config.buffer)
+    if (!config.errorCorrection.buffer)
         stencilBuffer = Bitmap<byte, 1>(sdf.width, sdf.height);
     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;
-    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::INDISCRIMINATE:
             break;
         case ErrorCorrectionConfig::EDGE_PRIORITY:
-            ec.protectCorners(shape, projection);
-            ec.protectEdges<N>(sdf, projection, range);
+            ec.protectCorners(shape);
+            ec.protectEdges<N>(sdf);
             break;
         case ErrorCorrectionConfig::EDGE_ONLY:
             ec.protectAll();
             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);
 }
 
 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);
-    MSDFErrorCorrection ec(stencilBuffer);
+    MSDFErrorCorrection ec(stencilBuffer, projection, range);
+    ec.setMinDeviationRatio(minDeviationRatio);
     if (protectAll)
         ec.protectAll();
-    ec.findErrors<N>(sdf, projection, range, threshold);
+    ec.findErrors<N>(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);
 }
-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);
 }
 
-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 {
 
 /// 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.
 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);
 }
 
-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)
         generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, projection, range);
     else
         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)
         generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range);
     else
         generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range);
-    msdfErrorCorrection(output, shape, projection, range, errorCorrectionConfig);
+    msdfErrorCorrection(output, shape, projection, range, config);
 }
 
 // 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));
 }
 
-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
@@ -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
     #pragma omp parallel for
 #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
     #pragma omp parallel for
 #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"
     "  -edgecolors <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"
         "\tComputes and prints the distance field's estimated fill error to the standard output.\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"
     "\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) {
     #define ABORT(msg) { puts(msg); return 1; }
 
@@ -398,7 +425,7 @@ int main(int argc, const char * const *argv) {
             false
         #endif
     );
-    GeneratorConfig generatorConfig;
+    MSDFGeneratorConfig generatorConfig;
     generatorConfig.overlapSupport = !geometryPreproc;
     bool scanlinePass = !geometryPreproc;
     FillRule fillRule = FILL_NONZERO;
@@ -427,7 +454,6 @@ int main(int argc, const char * const *argv) {
     Vector2 scale = 1;
     bool scaleSpecified = false;
     double angleThreshold = DEFAULT_ANGLE_THRESHOLD;
-    ErrorCorrectionConfig errorCorrectionConfig;
     float outputDistanceShift = 0.f;
     const char *edgeAssignment = NULL;
     bool yFlip = false;
@@ -441,6 +467,7 @@ int main(int argc, const char * const *argv) {
     } orientation = KEEP;
     unsigned long long coloringSeed = 0;
     void (*edgeColoring)(Shape &, double, unsigned long long) = edgeColoringSimple;
+    bool explicitErrorCorrectionMode = false;
 
     int argPos = 1;
     bool suggestHelp = false;
@@ -572,7 +599,7 @@ int main(int argc, const char * const *argv) {
         }
         ARG_CASE("-size", 2) {
             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.");
             width = w, height = h;
             argPos += 3;
@@ -585,7 +612,7 @@ int main(int argc, const char * const *argv) {
         }
         ARG_CASE("-range", 1) {
             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.");
             rangeMode = RANGE_UNIT;
             range = r;
@@ -594,7 +621,7 @@ int main(int argc, const char * const *argv) {
         }
         ARG_CASE("-pxrange", 1) {
             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.");
             rangeMode = RANGE_PX;
             pxRange = r;
@@ -603,7 +630,7 @@ int main(int argc, const char * const *argv) {
         }
         ARG_CASE("-scale", 1) {
             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.");
             scale = s;
             scaleSpecified = true;
@@ -612,7 +639,7 @@ int main(int argc, const char * const *argv) {
         }
         ARG_CASE("-ascale", 2) {
             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.");
             scale.set(sx, sy);
             scaleSpecified = true;
@@ -621,7 +648,7 @@ int main(int argc, const char * const *argv) {
         }
         ARG_CASE("-translate", 2) {
             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.");
             translate.set(tx, ty);
             argPos += 3;
@@ -636,13 +663,52 @@ int main(int argc, const char * const *argv) {
             continue;
         }
         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;
             continue;
         }
@@ -887,9 +953,21 @@ int main(int argc, const char * const *argv) {
     Bitmap<float, 1> sdf;
     Bitmap<float, 3> msdf;
     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) {
         case SINGLE: {
             sdf = Bitmap<float, 1>(width, height);
@@ -914,9 +992,9 @@ int main(int argc, const char * const *argv) {
                 parseColoring(shape, edgeAssignment);
             msdf = Bitmap<float, 3>(width, height);
             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
-                generateMSDF(msdf, shape, projection, range, generatorConfig, initialErrorCorrectionConfig);
+                generateMSDF(msdf, shape, projection, range, generatorConfig);
             break;
         }
         case MULTI_AND_TRUE: {
@@ -926,9 +1004,9 @@ int main(int argc, const char * const *argv) {
                 parseColoring(shape, edgeAssignment);
             mtsdf = Bitmap<float, 4>(width, height);
             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
-                generateMTSDF(mtsdf, shape, projection, range, generatorConfig, initialErrorCorrectionConfig);
+                generateMTSDF(mtsdf, shape, projection, range, generatorConfig);
             break;
         }
         default:;
@@ -963,11 +1041,11 @@ int main(int argc, const char * const *argv) {
                 break;
             case MULTI:
                 distanceSignCorrection(msdf, shape, projection, fillRule);
-                msdfErrorCorrection(msdf, shape, projection, range, errorCorrectionConfig);
+                msdfErrorCorrection(msdf, shape, projection, range, postErrorCorrectionConfig);
                 break;
             case MULTI_AND_TRUE:
                 distanceSignCorrection(mtsdf, shape, projection, fillRule);
-                msdfErrorCorrection(msdf, shape, projection, range, errorCorrectionConfig);
+                msdfErrorCorrection(msdf, shape, projection, range, postErrorCorrectionConfig);
                 break;
             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());
 
 /// 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.
-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
 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 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.
 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 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());
 
 }