Преглед на файлове

Complete rework of MSDF error correction, merged with artifact patcher

Viktor Chlumský преди 4 години
родител
ревизия
5f27bdfb1c
променени са 13 файла, в които са добавени 537 реда и са изтрити 267 реда
  1. 2 2
      CHANGELOG.md
  2. 2 2
      Msdfgen.vcxproj
  3. 4 4
      Msdfgen.vcxproj.filters
  4. 338 0
      core/MSDFErrorCorrection.cpp
  5. 45 0
      core/MSDFErrorCorrection.h
  6. 15 7
      core/generator-config.h
  7. 0 174
      core/msdf-artifact-patcher.cpp
  8. 0 15
      core/msdf-artifact-patcher.h
  9. 68 12
      core/msdf-error-correction.cpp
  10. 16 8
      core/msdf-error-correction.h
  11. 29 27
      core/msdfgen.cpp
  12. 14 12
      main.cpp
  13. 4 4
      msdfgen.h

+ 2 - 2
CHANGELOG.md

@@ -57,13 +57,13 @@
 ## Version 1.3 (2016-12-07)
 
 - Fixed `-reverseorder` switch.
-- Fixed glyph loading to use the proper method of acquiring the outlines from FreeType.
+- Fixed glyph loading to use the proper method of acquiring outlines from FreeType.
 
 ## Version 1.2 (2016-07-20)
 
 - Added option to specify that shape vertices are listed in reverse order (`-reverseorder`).
 - Added option to set a seed for the edge coloring heuristic (-seed \<n\>), which can be used to adjust the output.
-- Fixed parsing of glyph contours starting that start with a curve control point.
+- Fixed parsing of glyph contours that start with a curve control point.
 
 ## Version 1.1 (2016-05-08)
 

+ 2 - 2
Msdfgen.vcxproj

@@ -466,9 +466,9 @@
     <ClInclude Include="core\equation-solver.h" />
     <ClInclude Include="core\generator-config.h" />
     <ClInclude Include="core\msdf-error-correction.h" />
+    <ClInclude Include="core\MSDFErrorCorrection.h" />
     <ClInclude Include="core\Projection.h" />
     <ClInclude Include="core\sdf-error-estimation.h" />
-    <ClInclude Include="core\msdf-artifact-patcher.h" />
     <ClInclude Include="core\pixel-conversion.hpp" />
     <ClInclude Include="core\rasterization.h" />
     <ClInclude Include="core\render-sdf.h" />
@@ -498,9 +498,9 @@
     <ClCompile Include="core\EdgeHolder.cpp" />
     <ClCompile Include="core\equation-solver.cpp" />
     <ClCompile Include="core\msdf-error-correction.cpp" />
+    <ClCompile Include="core\MSDFErrorCorrection.cpp" />
     <ClCompile Include="core\Projection.cpp" />
     <ClCompile Include="core\sdf-error-estimation.cpp" />
-    <ClCompile Include="core\msdf-artifact-patcher.cpp" />
     <ClCompile Include="core\rasterization.cpp" />
     <ClCompile Include="core\render-sdf.cpp" />
     <ClCompile Include="core\save-bmp.cpp" />

+ 4 - 4
Msdfgen.vcxproj.filters

@@ -123,10 +123,10 @@
     <ClInclude Include="core\Projection.h">
       <Filter>Core</Filter>
     </ClInclude>
-    <ClInclude Include="core\msdf-artifact-patcher.h">
+    <ClInclude Include="core\generator-config.h">
       <Filter>Core</Filter>
     </ClInclude>
-    <ClInclude Include="core\generator-config.h">
+    <ClInclude Include="core\MSDFErrorCorrection.h">
       <Filter>Core</Filter>
     </ClInclude>
   </ItemGroup>
@@ -209,10 +209,10 @@
     <ClCompile Include="ext\resolve-shape-geometry.cpp">
       <Filter>Extensions</Filter>
     </ClCompile>
-    <ClCompile Include="core\msdf-artifact-patcher.cpp">
+    <ClCompile Include="core\Projection.cpp">
       <Filter>Core</Filter>
     </ClCompile>
-    <ClCompile Include="core\Projection.cpp">
+    <ClCompile Include="core\MSDFErrorCorrection.cpp">
       <Filter>Core</Filter>
     </ClCompile>
   </ItemGroup>

+ 338 - 0
core/MSDFErrorCorrection.cpp

@@ -0,0 +1,338 @@
+
+#include "MSDFErrorCorrection.h"
+
+#include <cstring>
+#include "arithmetics.hpp"
+#include "equation-solver.h"
+#include "EdgeColor.h"
+
+namespace msdfgen {
+
+#define ARTIFACT_T_EPSILON .01
+#define PROTECTION_RADIUS_TOLERANCE 1.001
+
+MSDFErrorCorrection::MSDFErrorCorrection() { }
+
+MSDFErrorCorrection::MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil) : stencil(stencil) {
+    memset(stencil.pixels, 0, sizeof(byte)*stencil.width*stencil.height);
+}
+
+void MSDFErrorCorrection::protectCorners(const Shape &shape, const Projection &projection) {
+    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();
+            for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
+                int commonColor = prevEdge->color&(*edge)->color;
+                // If the color changes from prevEdge to edge, this is a corner.
+                if (!(commonColor&(commonColor-1))) {
+                    // Find the four texels that envelop the corner and mark them as protected.
+                    Point2 p = projection.project((*edge)->point(0));
+                    if (shape.inverseYAxis)
+                        p.y = stencil.height-p.y;
+                    int l = (int) floor(p.x-.5);
+                    int b = (int) floor(p.y-.5);
+                    int r = l+1;
+                    int t = b+1;
+                    // Check that the positions are within bounds.
+                    if (l < stencil.width && b < stencil.height && r >= 0 && t >= 0) {
+                        if (l >= 0 && b >= 0)
+                            *stencil(l, b) |= (byte) PROTECTED;
+                        if (r < stencil.width && b >= 0)
+                            *stencil(r, b) |= (byte) PROTECTED;
+                        if (l >= 0 && t < stencil.height)
+                            *stencil(l, t) |= (byte) PROTECTED;
+                        if (r < stencil.width && t < stencil.height)
+                            *stencil(r, t) |= (byte) PROTECTED;
+                    }
+                }
+                prevEdge = *edge;
+            }
+        }
+}
+
+/// Determines if the channel contributes to an edge between the two texels a, b.
+static bool edgeBetweenTexelsChannel(const float *a, const float *b, int channel) {
+    // Find interpolation ratio t (0 < t < 1) where an edge is expected (mix(a[channel], b[channel], t) == 0.5).
+    double t = (a[channel]-.5)/(a[channel]-b[channel]);
+    if (t > 0 && t < 1) {
+        // Interpolate channel values at t.
+        float c[3] = {
+            mix(a[0], b[0], t),
+            mix(a[1], b[1], t),
+            mix(a[2], b[2], t)
+        };
+        // This is only an edge if the zero-distance channel is the median.
+        return median(c[0], c[1], c[2]) == c[channel];
+    }
+    return false;
+}
+
+/// Returns a bit mask of which channels contribute to an edge between the two texels a, b.
+static int edgeBetweenTexels(const float *a, const float *b) {
+    return (
+        RED*edgeBetweenTexelsChannel(a, b, 0)+
+        GREEN*edgeBetweenTexelsChannel(a, b, 1)+
+        BLUE*edgeBetweenTexelsChannel(a, b, 2)
+    );
+}
+
+/// Marks texel as protected if one of its non-median channels is present in the channel mask.
+static void protectExtremeChannels(byte *stencil, const float *msd, float m, int mask) {
+    if (
+        (mask&RED && msd[0] != m) ||
+        (mask&GREEN && msd[1] != m) ||
+        (mask&BLUE && msd[2] != m)
+    )
+        *stencil |= (byte) MSDFErrorCorrection::PROTECTED;
+}
+
+template <int N>
+void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf, const Projection &projection, double range) {
+    float radius;
+    // Horizontal texel pairs
+    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(1/range, 0)).length());
+    for (int y = 0; y < sdf.height; ++y) {
+        const float *left = sdf(0, y);
+        const float *right = sdf(1, y);
+        for (int x = 0; x < sdf.width-1; ++x) {
+            float lm = median(left[0], left[1], left[2]);
+            float rm = median(right[0], right[1], right[2]);
+            if (fabsf(lm-.5f)+fabsf(rm-.5f) < radius) {
+                int mask = edgeBetweenTexels(left, right);
+                protectExtremeChannels(stencil(x, y), left, lm, mask);
+                protectExtremeChannels(stencil(x+1, y), right, rm, mask);
+            }
+            left += N, right += N;
+        }
+    }
+    // Vertical texel pairs
+    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(0, 1/range)).length());
+    for (int y = 0; y < sdf.height-1; ++y) {
+        const float *bottom = sdf(0, y);
+        const float *top = sdf(0, y+1);
+        for (int x = 0; x < sdf.width; ++x) {
+            float bm = median(bottom[0], bottom[1], bottom[2]);
+            float tm = median(top[0], top[1], top[2]);
+            if (fabsf(bm-.5f)+fabsf(tm-.5f) < radius) {
+                int mask = edgeBetweenTexels(bottom, top);
+                protectExtremeChannels(stencil(x, y), bottom, bm, mask);
+                protectExtremeChannels(stencil(x, y+1), top, tm, mask);
+            }
+            bottom += N, top += N;
+        }
+    }
+    // Diagonal texel pairs
+    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(1/range)).length());
+    for (int y = 0; y < sdf.height-1; ++y) {
+        const float *lb = sdf(0, y);
+        const float *rb = sdf(1, y);
+        const float *lt = sdf(0, y+1);
+        const float *rt = sdf(1, y+1);
+        for (int x = 0; x < sdf.width-1; ++x) {
+            float mlb = median(lb[0], lb[1], lb[2]);
+            float mrb = median(rb[0], rb[1], rb[2]);
+            float mlt = median(lt[0], lt[1], lt[2]);
+            float mrt = median(rt[0], rt[1], rt[2]);
+            if (fabsf(mlb-.5f)+fabsf(mrt-.5f) < radius) {
+                int mask = edgeBetweenTexels(lb, rt);
+                protectExtremeChannels(stencil(x, y), lb, mlb, mask);
+                protectExtremeChannels(stencil(x+1, y+1), rt, mrt, mask);
+            }
+            if (fabsf(mrb-.5f)+fabsf(mlt-.5f) < radius) {
+                int mask = edgeBetweenTexels(rb, lt);
+                protectExtremeChannels(stencil(x+1, y), rb, mrb, mask);
+                protectExtremeChannels(stencil(x, y+1), lt, mlt, mask);
+            }
+            lb += N, rb += N, lt += N, rt += N;
+        }
+    }
+}
+
+void MSDFErrorCorrection::protectAll() {
+    byte *end = stencil.pixels+stencil.width*stencil.height;
+    for (byte *mask = stencil.pixels; mask < end; ++mask)
+        *mask |= (byte) PROTECTED;
+}
+
+/// Returns the median of the linear interpolation of texels a, b at t.
+static float interpolatedMedian(const float *a, const float *b, double t) {
+    return median(
+        mix(a[0], b[0], t),
+        mix(a[1], b[1], t),
+        mix(a[2], b[2], t)
+    );
+}
+/// Returns the median of the bilinear interpolation with the given constant, linear, and quadratic terms at t.
+static float interpolatedMedian(const float *a, const float *l, const float *q, double t) {
+    return float(median(
+        t*(t*q[0]+l[0])+a[0],
+        t*(t*q[1]+l[1])+a[1],
+        t*(t*q[2]+l[2])+a[2]
+    ));
+}
+
+/// Determines if the interpolated median xm is an artifact.
+static bool isArtifact(bool isProtected, double axSpan, double bxSpan, float am, float bm, float xm) {
+    return (
+        // For protected texels, only report an artifact if it would cause fill inversion (change between positive and negative distance).
+        (!isProtected || (am > .5f && bm > .5f && xm < .5f) || (am < .5f && bm < .5f && xm > .5f)) &&
+        // This is an artifact if the interpolated median is outside the range of possible values based on its distance from a, b.
+        !(xm >= am-axSpan && xm <= am+axSpan && xm >= bm-bxSpan && xm <= bm+bxSpan)
+    );
+}
+
+/// 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) {
+    // 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));
+}
+
+/// 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) {
+    // 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);
+    for (int i = 0; i < solutions; ++i) {
+        // Solutions t[i] == 0 and t[i] == 1 are singularities and occur very often because two channels are usually equal at texels.
+        if (t[i] > ARTIFACT_T_EPSILON && t[i] < 1-ARTIFACT_T_EPSILON) {
+            // 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;
+            // Additionally, check xm against the interpolated medians at the local extremes tEx0, tEx1.
+            double tEnd[2];
+            float em[2];
+            // tEx0
+            if (tEx0 > 0 && tEx0 < 1) {
+                tEnd[0] = 0, tEnd[1] = 1;
+                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;
+            }
+            // tEx1
+            if (tEx1 > 0 && tEx1 < 1) {
+                tEnd[0] = 0, tEnd[1] = 1;
+                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;
+            }
+        }
+    }
+    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) {
+    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])
+        )
+    );
+}
+
+/// 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) {
+    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)) {
+        float abc[3] = {
+            a[0]-b[0]-c[0],
+            a[1]-b[1]-c[1],
+            a[2]-b[2]-c[2]
+        };
+        // Compute the linear terms for bilinear interpolation.
+        float l[3] = {
+            -a[0]-abc[0],
+            -a[1]-abc[1],
+            -a[2]-abc[2]
+        };
+        // Compute the quadratic terms for bilinear interpolation.
+        float q[3] = {
+            d[0]+abc[0],
+            d[1]+abc[1],
+            d[2]+abc[2]
+        };
+        // Compute interpolation ratios tEx (0 < tEx[i] < 1) for the local extremes of each color channel (the derivative 2*q[i]*tEx[i]+l[i] == 0).
+        double tEx[3] = {
+            -.5*l[0]/q[0],
+            -.5*l[1]/q[1],
+            -.5*l[2]/q[2]
+        };
+        // 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])
+        );
+    }
+    return false;
+}
+
+template <int N>
+void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf, const Projection &projection, double range, double threshold) {
+    // 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();
+    // 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;
+            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)))
+            ));
+        }
+    }
+}
+
+template <int N>
+void MSDFErrorCorrection::apply(const BitmapRef<float, N> &sdf) const {
+    int texelCount = sdf.width*sdf.height;
+    const byte *mask = stencil.pixels;
+    float *texel = sdf.pixels;
+    for (int i = 0; i < texelCount; ++i) {
+        if (*mask&ERROR) {
+            // Set all color channels to the median.
+            float m = median(texel[0], texel[1], texel[2]);
+            texel[0] = m, texel[1] = m, texel[2] = m;
+        }
+        ++mask;
+        texel += N;
+    }
+}
+
+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::apply(const BitmapRef<float, 3> &sdf) const;
+template void MSDFErrorCorrection::apply(const BitmapRef<float, 4> &sdf) const;
+
+}

+ 45 - 0
core/MSDFErrorCorrection.h

@@ -0,0 +1,45 @@
+
+#pragma once
+
+#include "Projection.h"
+#include "Shape.h"
+#include "BitmapRef.hpp"
+
+namespace msdfgen {
+
+/// Performs error correction on a computed MSDF to eliminate interpolation artifacts. This is a low-level class, you may want to use the API in msdf-error-correction.h instead.
+class MSDFErrorCorrection {
+
+public:
+    /// Stencil flags.
+    enum Flags {
+        /// Texel marked as potentially causing interpolation errors.
+        ERROR = 1,
+        /// Texel marked as protected. Protected texels are only given the error flag if they cause inversion artifacts.
+        PROTECTED = 2
+    };
+
+    MSDFErrorCorrection();
+    explicit MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil);
+    /// Flags all texels that are interpolated at corners as protected.
+    void protectCorners(const Shape &shape, const Projection &projection);
+    /// Flags all texels that contribute to edges as protected.
+    template <int N>
+    void protectEdges(const BitmapConstRef<float, N> &sdf, const Projection &projection, double range);
+    /// Flags all texels as protected.
+    void protectAll();
+    /// Flags texels that are expected to cause interpolation artifacts.
+    template <int N>
+    void findErrors(const BitmapConstRef<float, N> &sdf, const Projection &projection, double range, double threshold);
+    /// 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;
+    /// Returns the stencil in its current state (see Flags).
+    BitmapConstRef<byte, 1> getStencil() const;
+
+private:
+    BitmapRef<byte, 1> stencil;
+
+};
+
+}

+ 15 - 7
core/generator-config.h

@@ -1,7 +1,9 @@
 
 #pragma once
 
-#define MSDFGEN_DEFAULT_ARTIFACT_PATCHER_MIN_IMPROVE_RATIO 1.001
+#include <cstdlib>
+
+#define MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD 1.001
 
 namespace msdfgen {
 
@@ -13,19 +15,25 @@ struct GeneratorConfig {
     inline explicit GeneratorConfig(bool overlapSupport = true) : overlapSupport(overlapSupport) { }
 };
 
-/// The configuration of the artifact patcher that processes the generated MSDF and fixes potential interpolation errors.
-struct ArtifactPatcherConfig {
-    /// The mode of operation.
+/// The configuration of the MSDF error correction pass.
+struct ErrorCorrectionConfig {
+    /// Mode of operation.
     enum Mode {
+        /// Skips error correction pass.
         DISABLED,
+        /// Corrects all discontinuities of the distance field regardless if edges are adversely affected.
         INDISCRIMINATE,
+        /// Corrects artifacts at edges and other discontinuous distances only if it does not affect edges or corners.
         EDGE_PRIORITY,
+        /// Only corrects artifacts at edges.
         EDGE_ONLY
     } mode;
-    /// The minimum ratio of improvement required to patch a pixel. Must be greater than or equal to 1.
-    double minImproveRatio;
+    /// The minimum ratio between the actual and maximum expected distance delta to be considered an error.
+    double threshold;
+    /// An optional buffer to avoid dynamic allocation. Must have at least as many bytes as the MSDF has pixels.
+    byte *buffer;
 
-    inline explicit ArtifactPatcherConfig(Mode mode = EDGE_PRIORITY, double minImproveRatio = MSDFGEN_DEFAULT_ARTIFACT_PATCHER_MIN_IMPROVE_RATIO) : mode(mode), minImproveRatio(minImproveRatio) { }
+    inline explicit ErrorCorrectionConfig(Mode mode = EDGE_PRIORITY, double threshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD, byte *buffer = NULL) : mode(mode), threshold(threshold), buffer(buffer) { }
 };
 
 }

+ 0 - 174
core/msdf-artifact-patcher.cpp

@@ -1,174 +0,0 @@
-
-#include "msdf-artifact-patcher.h"
-
-#include <cstring>
-#include <vector>
-#include <utility>
-#include "arithmetics.hpp"
-#include "equation-solver.h"
-#include "bitmap-interpolation.hpp"
-#include "edge-selectors.h"
-#include "contour-combiners.h"
-#include "ShapeDistanceFinder.h"
-
-namespace msdfgen {
-
-static bool isHotspot(float am, float bm, float xm) {
-    return (am > .5f && bm > .5f && xm < .5f) || (am < .5f && bm < .5f && xm > .5f);
-    // A much more aggressive version for the entire distance field (not just edges): return median(am, bm, xm) != xm;
-}
-
-static int findLinearChannelHotspots(double t[1], const float *a, const float *b, float dA, float dB) {
-    int found = 0;
-    double x = (double) dA/(dA-dB);
-    if (x > 0 && x < 1) {
-        float am = median(a[0], a[1], a[2]);
-        float bm = median(b[0], b[1], b[2]);
-        float xm = median(
-            mix(a[0], b[0], x),
-            mix(a[1], b[1], x),
-            mix(a[2], b[2], x)
-        );
-        if (isHotspot(am, bm, xm))
-            t[found++] = x;
-    }
-    return found;
-}
-
-static int findDiagonalChannelHotspots(double t[2], const float *a, const float *b, const float *c, const float *d, float dA, float dB, float dC, float dD) {
-    int found = 0;
-    double x[2];
-    int solutions = solveQuadratic(x, (dD-dC)-(dB-dA), dC+dB-2*dA, dA);
-    for (int i = 0; i < solutions; ++i)
-        if (x[i] > 0 && x[i] < 1) {
-            float am = median(a[0], a[1], a[2]);
-            float dm = median(d[0], d[1], d[2]);
-            float xm = median(
-                mix(mix(a[0], b[0], x[i]), mix(c[0], d[0], x[i]), x[i]),
-                mix(mix(a[1], b[1], x[i]), mix(c[1], d[1], x[i]), x[i]),
-                mix(mix(a[2], b[2], x[i]), mix(c[2], d[2], x[i]), x[i])
-            );
-            if (isHotspot(am, dm, xm))
-                t[found++] = x[i];
-        }
-    return found;
-}
-
-static int findLinearHotspots(double t[3], const float *a, const float *b) {
-    int found = 0;
-    found += findLinearChannelHotspots(t+found, a, b, a[1]-a[0], b[1]-b[0]);
-    found += findLinearChannelHotspots(t+found, a, b, a[2]-a[1], b[2]-b[1]);
-    found += findLinearChannelHotspots(t+found, a, b, a[0]-a[2], b[0]-b[2]);
-    return found;
-}
-
-static int findDiagonalHotspots(double t[6], const float *a, const float *b, const float *c, const float *d) {
-    int found = 0;
-    found += findDiagonalChannelHotspots(t+found, a, b, c, d, a[1]-a[0], b[1]-b[0], c[1]-c[0], d[1]-d[0]);
-    found += findDiagonalChannelHotspots(t+found, a, b, c, d, a[2]-a[1], b[2]-b[1], c[2]-c[1], d[2]-d[1]);
-    found += findDiagonalChannelHotspots(t+found, a, b, c, d, a[0]-a[2], b[0]-b[2], c[0]-c[2], d[0]-d[2]);
-    return found;
-}
-
-template <int N>
-void findHotspots(std::vector<Point2> &hotspots, const BitmapConstRef<float, N> &sdf) {
-    // All hotspots intersect either the horizontal, vertical, or diagonal line that connects neighboring texels
-    // Horizontal:
-    for (int y = 0; y < sdf.height; ++y) {
-        const float *left = sdf(0, y);
-        const float *right = sdf(1, y);
-        for (int x = 0; x < sdf.width-1; ++x) {
-            double t[3];
-            int found = findLinearHotspots(t, left, right);
-            for (int i = 0; i < found; ++i)
-                hotspots.push_back(Point2(x+.5+t[i], y+.5));
-            left += N, right += N;
-        }
-    }
-    // Vertical:
-    for (int y = 0; y < sdf.height-1; ++y) {
-        const float *bottom = sdf(0, y);
-        const float *top = sdf(0, y+1);
-        for (int x = 0; x < sdf.width; ++x) {
-            double t[3];
-            int found = findLinearHotspots(t, bottom, top);
-            for (int i = 0; i < found; ++i)
-                hotspots.push_back(Point2(x+.5, y+.5+t[i]));
-            bottom += N, top += N;
-        }
-    }
-    // Diagonal:
-    for (int y = 0; y < sdf.height-1; ++y) {
-        const float *lb = sdf(0, y);
-        const float *rb = sdf(1, y);
-        const float *lt = sdf(0, y+1);
-        const float *rt = sdf(1, y+1);
-        for (int x = 0; x < sdf.width-1; ++x) {
-            double t[6];
-            int found = 0;
-            found = findDiagonalHotspots(t, lb, rb, lt, rt);
-            for (int i = 0; i < found; ++i)
-                hotspots.push_back(Point2(x+.5+t[i], y+.5+t[i]));
-            found = findDiagonalHotspots(t, lt, rt, lb, rb);
-            for (int i = 0; i < found; ++i)
-                hotspots.push_back(Point2(x+.5+t[i], y+1.5-t[i]));
-            lb += N, rb += N, lt += N, rt += N;
-        }
-    }
-}
-
-template <template <typename> class ContourCombiner, int N>
-static void msdfPatchArtifactsInner(const BitmapRef<float, N> &sdf, const Shape &shape, const Projection &projection, double range) {
-    ShapeDistanceFinder<ContourCombiner<PseudoDistanceSelector> > distanceFinder(shape);
-    std::vector<Point2> hotspots;
-    findHotspots(hotspots, BitmapConstRef<float, N>(sdf));
-    std::vector<std::pair<int, int> > artifacts;
-    artifacts.reserve(hotspots.size());
-    for (std::vector<Point2>::const_iterator hotspot = hotspots.begin(); hotspot != hotspots.end(); ++hotspot) {
-        Point2 pos = projection.unproject(*hotspot);
-        double actualDistance = distanceFinder.distance(pos);
-        float sd = float(actualDistance/range+.5);
-
-        // Store hotspot's closest texel's current color
-        float *subject = sdf((int) hotspot->x, (int) hotspot->y);
-        float texel[N];
-        memcpy(texel, subject, N*sizeof(float));
-        // Sample signed distance at hotspot
-        float msd[N];
-        interpolate(msd, BitmapConstRef<float, N>(sdf), *hotspot);
-        float oldSsd = median(msd[0], msd[1], msd[2]);
-        // Flatten hotspot's closest texel
-        float med = median(subject[0], subject[1], subject[2]);
-        subject[0] = med, subject[1] = med, subject[2] = med;
-        // Sample signed distance at hotspot after flattening
-        interpolate(msd, BitmapConstRef<float, N>(sdf), *hotspot);
-        float newSsd = median(msd[0], msd[1], msd[2]);
-        // Revert modified texel
-        memcpy(subject, texel, N*sizeof(float));
-
-        // Consider hotspot an artifact if flattening improved the sample
-        if (fabsf(newSsd-sd) < fabsf(oldSsd-sd))
-            artifacts.push_back(std::make_pair((int) hotspot->x, (int) hotspot->y));
-    }
-    for (std::vector<std::pair<int, int> >::const_iterator artifact = artifacts.begin(); artifact != artifacts.end(); ++artifact) {
-        float *pixel = sdf(artifact->first, artifact->second);
-        float med = median(pixel[0], pixel[1], pixel[2]);
-        pixel[0] = med, pixel[1] = med, pixel[2] = med;
-    }
-}
-
-void msdfPatchArtifacts(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &generatorConfig, const ArtifactPatcherConfig &config) {
-    if (generatorConfig.overlapSupport)
-        msdfPatchArtifactsInner<OverlappingContourCombiner>(sdf, shape, projection, range);
-    else
-        msdfPatchArtifactsInner<SimpleContourCombiner>(sdf, shape, projection, range);
-}
-
-void msdfPatchArtifacts(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &generatorConfig, const ArtifactPatcherConfig &config) {
-    if (generatorConfig.overlapSupport)
-        msdfPatchArtifactsInner<OverlappingContourCombiner>(sdf, shape, projection, range);
-    else
-        msdfPatchArtifactsInner<SimpleContourCombiner>(sdf, shape, projection, range);
-}
-
-}

+ 0 - 15
core/msdf-artifact-patcher.h

@@ -1,15 +0,0 @@
-
-#pragma once
-
-#include "Vector2.h"
-#include "Shape.h"
-#include "Projection.h"
-#include "BitmapRef.hpp"
-#include "generator-config.h"
-
-namespace msdfgen {
-
-void msdfPatchArtifacts(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &generatorConfig = GeneratorConfig(), const ArtifactPatcherConfig &config = ArtifactPatcherConfig());
-void msdfPatchArtifacts(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &generatorConfig = GeneratorConfig(), const ArtifactPatcherConfig &config = ArtifactPatcherConfig());
-
-}

+ 68 - 12
core/msdf-error-correction.cpp

@@ -3,9 +3,72 @@
 
 #include <vector>
 #include "arithmetics.hpp"
+#include "Bitmap.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)
+        return;
+    Bitmap<byte, 1> stencilBuffer;
+    if (!config.buffer)
+        stencilBuffer = Bitmap<byte, 1>(sdf.width, sdf.height);
+    BitmapRef<byte, 1> stencil;
+    stencil.pixels = config.buffer ? config.buffer : (byte *) stencilBuffer;
+    stencil.width = sdf.width, stencil.height = sdf.height;
+    MSDFErrorCorrection ec(stencil);
+    switch (config.mode) {
+        case ErrorCorrectionConfig::DISABLED:
+        case ErrorCorrectionConfig::INDISCRIMINATE:
+            break;
+        case ErrorCorrectionConfig::EDGE_PRIORITY:
+            ec.protectCorners(shape, projection);
+            ec.protectEdges<N>(sdf, projection, range);
+            break;
+        case ErrorCorrectionConfig::EDGE_ONLY:
+            ec.protectAll();
+            break;
+    }
+    ec.findErrors<N>(sdf, projection, range, config.threshold);
+    ec.apply(sdf);
+}
+
+template <int N>
+static void msdfErrorCorrectionShapeless(const BitmapRef<float, N> &sdf, const Projection &projection, double range, double threshold, bool protectAll) {
+    Bitmap<byte, 1> stencilBuffer(sdf.width, sdf.height);
+    MSDFErrorCorrection ec(stencilBuffer);
+    if (protectAll)
+        ec.protectAll();
+    ec.findErrors<N>(sdf, projection, range, threshold);
+    ec.apply(sdf);
+}
+
+void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const ErrorCorrectionConfig &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) {
+    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 msdfDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double threshold) {
+    msdfErrorCorrectionShapeless(sdf, projection, range, threshold, false);
+}
+
+void msdfEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double threshold) {
+    msdfErrorCorrectionShapeless(sdf, projection, range, threshold, true);
+}
+void msdfEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double threshold) {
+    msdfErrorCorrectionShapeless(sdf, projection, range, threshold, true);
+}
+
+
+// Legacy version
+
 inline static bool detectClash(const float *a, const float *b, double threshold) {
     // Sort channels so that pairs (a0, b0), (a1, b1), (a2, b2) go from biggest to smallest absolute difference
     float a0 = a[0], a1 = a[1], a2 = a[2];
@@ -29,7 +92,7 @@ inline static bool detectClash(const float *a, const float *b, double threshold)
 }
 
 template <int N>
-void msdfErrorCorrectionInner(const BitmapRef<float, N> &output, const Vector2 &threshold) {
+static void msdfErrorCorrectionInner_legacy(const BitmapRef<float, N> &output, const Vector2 &threshold) {
     std::vector<std::pair<int, int> > clashes;
     int w = output.width, h = output.height;
     for (int y = 0; y < h; ++y)
@@ -67,18 +130,11 @@ void msdfErrorCorrectionInner(const BitmapRef<float, N> &output, const Vector2 &
 #endif
 }
 
-void msdfErrorCorrection(const BitmapRef<float, 3> &output, const Vector2 &threshold) {
-    msdfErrorCorrectionInner(output, threshold);
-}
-void msdfErrorCorrection(const BitmapRef<float, 4> &output, const Vector2 &threshold) {
-    msdfErrorCorrectionInner(output, threshold);
-}
-
-void msdfErrorCorrection(const BitmapRef<float, 3> &output, double threshold, const Projection &projection, double range) {
-    msdfErrorCorrectionInner(output, projection.unprojectVector(Vector2(threshold/range)));
+void msdfErrorCorrection_legacy(const BitmapRef<float, 3> &output, const Vector2 &threshold) {
+    msdfErrorCorrectionInner_legacy(output, threshold);
 }
-void msdfErrorCorrection(const BitmapRef<float, 4> &output, double threshold, const Projection &projection, double range) {
-    msdfErrorCorrectionInner(output, projection.unprojectVector(Vector2(threshold/range)));
+void msdfErrorCorrection_legacy(const BitmapRef<float, 4> &output, const Vector2 &threshold) {
+    msdfErrorCorrectionInner_legacy(output, threshold);
 }
 
 }

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

@@ -3,18 +3,26 @@
 
 #include "Vector2.h"
 #include "Projection.h"
+#include "Shape.h"
 #include "BitmapRef.hpp"
-
-#define MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD 1.001
+#include "generator-config.h"
 
 namespace msdfgen {
 
-/// Attempts to patch multi-channel signed distance field values that may cause interpolation artifacts. (Already called by generateMSDF)
-void msdfErrorCorrection(const BitmapRef<float, 3> &output, const Vector2 &threshold);
-void msdfErrorCorrection(const BitmapRef<float, 4> &output, const Vector2 &threshold);
+/// 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());
+
+/// 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 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);
 
-// Alternate API - threshold specified in pixels
-void msdfErrorCorrection(const BitmapRef<float, 3> &output, double threshold, const Projection &projection, double range);
-void msdfErrorCorrection(const BitmapRef<float, 4> &output, double threshold, const Projection &projection, double range);
+/// 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, 4> &output, const Vector2 &threshold);
 
 }

+ 29 - 27
core/msdfgen.cpp

@@ -5,7 +5,6 @@
 #include "edge-selectors.h"
 #include "contour-combiners.h"
 #include "ShapeDistanceFinder.h"
-#include "msdf-artifact-patcher.h"
 
 namespace msdfgen {
 
@@ -14,38 +13,45 @@ class DistancePixelConversion;
 
 template <>
 class DistancePixelConversion<double> {
+    double invRange;
 public:
     typedef BitmapRef<float, 1> BitmapRefType;
-    inline static void convert(float *pixels, double distance, double range) {
-        *pixels = float(distance/range+.5);
+    inline explicit DistancePixelConversion(double range) : invRange(1/range) { }
+    inline void operator()(float *pixels, double distance) const {
+        *pixels = float(invRange*distance+.5);
     }
 };
 
 template <>
 class DistancePixelConversion<MultiDistance> {
+    double invRange;
 public:
     typedef BitmapRef<float, 3> BitmapRefType;
-    inline static void convert(float *pixels, const MultiDistance &distance, double range) {
-        pixels[0] = float(distance.r/range+.5);
-        pixels[1] = float(distance.g/range+.5);
-        pixels[2] = float(distance.b/range+.5);
+    inline explicit DistancePixelConversion(double range) : invRange(1/range) { }
+    inline void operator()(float *pixels, const MultiDistance &distance) const {
+        pixels[0] = float(invRange*distance.r+.5);
+        pixels[1] = float(invRange*distance.g+.5);
+        pixels[2] = float(invRange*distance.b+.5);
     }
 };
 
 template <>
 class DistancePixelConversion<MultiAndTrueDistance> {
+    double invRange;
 public:
     typedef BitmapRef<float, 4> BitmapRefType;
-    inline static void convert(float *pixels, const MultiAndTrueDistance &distance, double range) {
-        pixels[0] = float(distance.r/range+.5);
-        pixels[1] = float(distance.g/range+.5);
-        pixels[2] = float(distance.b/range+.5);
-        pixels[3] = float(distance.a/range+.5);
+    inline explicit DistancePixelConversion(double range) : invRange(1/range) { }
+    inline void operator()(float *pixels, const MultiAndTrueDistance &distance) const {
+        pixels[0] = float(invRange*distance.r+.5);
+        pixels[1] = float(invRange*distance.g+.5);
+        pixels[2] = float(invRange*distance.b+.5);
+        pixels[3] = float(invRange*distance.a+.5);
     }
 };
 
 template <class ContourCombiner>
 void generateDistanceField(const typename DistancePixelConversion<typename ContourCombiner::DistanceType>::BitmapRefType &output, const Shape &shape, const Projection &projection, double range) {
+    DistancePixelConversion<typename ContourCombiner::DistanceType> distancePixelConversion(range);
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel
 #endif
@@ -61,7 +67,7 @@ void generateDistanceField(const typename DistancePixelConversion<typename Conto
                 int x = rightToLeft ? output.width-col-1 : col;
                 Point2 p = projection.unproject(Point2(x+.5, y+.5));
                 typename ContourCombiner::DistanceType distance = distanceFinder.distance(p);
-                DistancePixelConversion<typename ContourCombiner::DistanceType>::convert(output(x, row), distance, range);
+                distancePixelConversion(output(x, row), distance);
             }
             rightToLeft = !rightToLeft;
         }
@@ -82,24 +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 ArtifactPatcherConfig &artifactPatcherConfig) {
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config, const ErrorCorrectionConfig &errorCorrectionConfig) {
     if (config.overlapSupport)
         generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, projection, range);
     else
         generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, projection, range);
-    if (artifactPatcherConfig.minImproveRatio > 0) // TEMPORARILY SERVES AS ERROR CORRECTION THRESHOLD
-        msdfErrorCorrection(output, artifactPatcherConfig.minImproveRatio, projection, range);
-    msdfPatchArtifacts(output, shape, projection, range, config, artifactPatcherConfig);
+    msdfErrorCorrection(output, shape, projection, range, errorCorrectionConfig);
 }
 
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config, const ArtifactPatcherConfig &artifactPatcherConfig) {
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config, const ErrorCorrectionConfig &errorCorrectionConfig) {
     if (config.overlapSupport)
         generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range);
     else
         generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range);
-    if (artifactPatcherConfig.minImproveRatio > 0) // TEMPORARILY SERVES AS ERROR CORRECTION THRESHOLD
-        msdfErrorCorrection(output, artifactPatcherConfig.minImproveRatio, projection, range);
-    msdfPatchArtifacts(output, shape, projection, range, config, artifactPatcherConfig);
+    msdfErrorCorrection(output, shape, projection, range, errorCorrectionConfig);
 }
 
 // Legacy API
@@ -112,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 errorMinImproveRatio, bool overlapSupport) {
-    generateMSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport), ArtifactPatcherConfig(ArtifactPatcherConfig::EDGE_PRIORITY, errorMinImproveRatio));
+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 generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double errorMinImproveRatio, bool overlapSupport) {
-    generateMTSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport), ArtifactPatcherConfig(ArtifactPatcherConfig::EDGE_PRIORITY, errorMinImproveRatio));
+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));
 }
 
 // Legacy version
@@ -222,7 +224,7 @@ void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape,
     }
 
     if (edgeThreshold > 0)
-        msdfErrorCorrection(output, edgeThreshold/(scale*range));
+        msdfErrorCorrection(output, shape, Projection(scale, translate), range, ErrorCorrectionConfig(ErrorCorrectionConfig::EDGE_PRIORITY, edgeThreshold));
 }
 
 void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold) {
@@ -280,7 +282,7 @@ void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape,
     }
 
     if (edgeThreshold > 0)
-        msdfErrorCorrection(output, edgeThreshold/(scale*range));
+        msdfErrorCorrection(output, shape, Projection(scale, translate), range, ErrorCorrectionConfig(ErrorCorrectionConfig::EDGE_PRIORITY, edgeThreshold));
 }
 
 }

+ 14 - 12
main.cpp

@@ -427,8 +427,7 @@ int main(int argc, const char * const *argv) {
     Vector2 scale = 1;
     bool scaleSpecified = false;
     double angleThreshold = DEFAULT_ANGLE_THRESHOLD;
-    double errorCorrectionThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD;
-    ArtifactPatcherConfig artifactPatcherConfig;
+    ErrorCorrectionConfig errorCorrectionConfig;
     float outputDistanceShift = 0.f;
     const char *edgeAssignment = NULL;
     bool yFlip = false;
@@ -640,7 +639,10 @@ int main(int argc, const char * const *argv) {
             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.");
-            errorCorrectionThreshold = ect;
+            if (ect <= 0)
+                errorCorrectionConfig.mode = ErrorCorrectionConfig::DISABLED;
+            else
+                errorCorrectionConfig.threshold = ect;
             argPos += 2;
             continue;
         }
@@ -885,7 +887,9 @@ int main(int argc, const char * const *argv) {
     Bitmap<float, 1> sdf;
     Bitmap<float, 3> msdf;
     Bitmap<float, 4> mtsdf;
-    artifactPatcherConfig.minImproveRatio = errorCorrectionThreshold; // TEMPORARILY SERVES AS ERROR CORRECTION THRESHOLD
+    ErrorCorrectionConfig initialErrorCorrectionConfig(errorCorrectionConfig);
+    if (scanlinePass)
+        initialErrorCorrectionConfig.mode = ErrorCorrectionConfig::DISABLED;
     switch (mode) {
         case SINGLE: {
             sdf = Bitmap<float, 1>(width, height);
@@ -910,9 +914,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 ? 0 : errorCorrectionThreshold);
+                generateMSDF_legacy(msdf, shape, range, scale, translate, scanlinePass || errorCorrectionConfig.mode == ErrorCorrectionConfig::DISABLED ? 0 : errorCorrectionConfig.threshold);
             else
-                generateMSDF(msdf, shape, projection, range, generatorConfig, artifactPatcherConfig);
+                generateMSDF(msdf, shape, projection, range, generatorConfig, initialErrorCorrectionConfig);
             break;
         }
         case MULTI_AND_TRUE: {
@@ -922,9 +926,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 ? 0 : errorCorrectionThreshold);
+                generateMTSDF_legacy(mtsdf, shape, range, scale, translate, scanlinePass || errorCorrectionConfig.mode == ErrorCorrectionConfig::DISABLED ? 0 : errorCorrectionConfig.threshold);
             else
-                generateMTSDF(mtsdf, shape, projection, range, generatorConfig, artifactPatcherConfig);
+                generateMTSDF(mtsdf, shape, projection, range, generatorConfig, initialErrorCorrectionConfig);
             break;
         }
         default:;
@@ -959,13 +963,11 @@ int main(int argc, const char * const *argv) {
                 break;
             case MULTI:
                 distanceSignCorrection(msdf, shape, projection, fillRule);
-                if (errorCorrectionThreshold > 0)
-                    msdfErrorCorrection(msdf, errorCorrectionThreshold/(scale*range));
+                msdfErrorCorrection(msdf, shape, projection, range, errorCorrectionConfig);
                 break;
             case MULTI_AND_TRUE:
                 distanceSignCorrection(mtsdf, shape, projection, fillRule);
-                if (errorCorrectionThreshold > 0)
-                    msdfErrorCorrection(mtsdf, errorCorrectionThreshold/(scale*range));
+                msdfErrorCorrection(msdf, shape, projection, range, errorCorrectionConfig);
                 break;
             default:;
         }

+ 4 - 4
msdfgen.h

@@ -45,16 +45,16 @@ 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 ArtifactPatcherConfig &artifactPatcherConfig = ArtifactPatcherConfig());
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig(), const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig());
 
 /// 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 ArtifactPatcherConfig &artifactPatcherConfig = ArtifactPatcherConfig());
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig(), const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig());
 
 // 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 errorMinImproveRatio = MSDFGEN_DEFAULT_ARTIFACT_PATCHER_MIN_IMPROVE_RATIO, bool overlapSupport = true);
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double errorMinImproveRatio = MSDFGEN_DEFAULT_ARTIFACT_PATCHER_MIN_IMPROVE_RATIO, 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);
 
 // 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);