Parcourir la source

New MSDF edge artifact elimination routine, core algorithm refactor

Chlumsky il y a 4 ans
Parent
commit
010f3c92f1

+ 1 - 0
.gitignore

@@ -4,6 +4,7 @@ Debug Library/
 Release Library/
 x86/
 x64/
+.vs/
 *.exe
 *.user
 *.sdf

+ 9 - 2
Msdfgen.vcxproj

@@ -296,6 +296,7 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClInclude Include="core\arithmetics.hpp" />
+    <ClInclude Include="core\bitmap-interpolation.hpp" />
     <ClInclude Include="core\Bitmap.h" />
     <ClInclude Include="core\Bitmap.hpp" />
     <ClInclude Include="core\BitmapRef.hpp" />
@@ -307,7 +308,9 @@
     <ClInclude Include="core\EdgeColor.h" />
     <ClInclude Include="core\EdgeHolder.h" />
     <ClInclude Include="core\equation-solver.h" />
-    <ClInclude Include="core\estimate-sdf-error.h" />
+    <ClInclude Include="core\msdf-error-correction.h" />
+    <ClInclude Include="core\sdf-error-estimation.h" />
+    <ClInclude Include="core\msdf-edge-artifact-patcher.h" />
     <ClInclude Include="core\pixel-conversion.hpp" />
     <ClInclude Include="core\rasterization.h" />
     <ClInclude Include="core\render-sdf.h" />
@@ -316,6 +319,8 @@
     <ClInclude Include="core\Scanline.h" />
     <ClInclude Include="core\shape-description.h" />
     <ClInclude Include="core\Shape.h" />
+    <ClInclude Include="core\ShapeDistanceFinder.h" />
+    <ClInclude Include="core\ShapeDistanceFinder.hpp" />
     <ClInclude Include="core\SignedDistance.h" />
     <ClInclude Include="core\Vector2.h" />
     <ClInclude Include="ext\import-font.h" />
@@ -333,7 +338,9 @@
     <ClCompile Include="core\edge-selectors.cpp" />
     <ClCompile Include="core\EdgeHolder.cpp" />
     <ClCompile Include="core\equation-solver.cpp" />
-    <ClCompile Include="core\estimate-sdf-error.cpp" />
+    <ClCompile Include="core\msdf-error-correction.cpp" />
+    <ClCompile Include="core\sdf-error-estimation.cpp" />
+    <ClCompile Include="core\msdf-edge-artifact-patcher.cpp" />
     <ClCompile Include="core\rasterization.cpp" />
     <ClCompile Include="core\render-sdf.cpp" />
     <ClCompile Include="core\save-bmp.cpp" />

+ 23 - 2
Msdfgen.vcxproj.filters

@@ -102,7 +102,22 @@
     <ClInclude Include="core\save-tiff.h">
       <Filter>Core</Filter>
     </ClInclude>
-    <ClInclude Include="core\estimate-sdf-error.h">
+    <ClInclude Include="core\bitmap-interpolation.hpp">
+      <Filter>Core</Filter>
+    </ClInclude>
+    <ClInclude Include="core\ShapeDistanceFinder.h">
+      <Filter>Core</Filter>
+    </ClInclude>
+    <ClInclude Include="core\ShapeDistanceFinder.hpp">
+      <Filter>Core</Filter>
+    </ClInclude>
+    <ClInclude Include="core\sdf-error-estimation.h">
+      <Filter>Core</Filter>
+    </ClInclude>
+    <ClInclude Include="core\msdf-error-correction.h">
+      <Filter>Core</Filter>
+    </ClInclude>
+    <ClInclude Include="core\msdf-edge-artifact-patcher.h">
       <Filter>Core</Filter>
     </ClInclude>
   </ItemGroup>
@@ -176,7 +191,13 @@
     <ClCompile Include="core\save-tiff.cpp">
       <Filter>Core</Filter>
     </ClCompile>
-    <ClCompile Include="core\estimate-sdf-error.cpp">
+    <ClCompile Include="core\sdf-error-estimation.cpp">
+      <Filter>Core</Filter>
+    </ClCompile>
+    <ClCompile Include="core\msdf-error-correction.cpp">
+      <Filter>Core</Filter>
+    </ClCompile>
+    <ClCompile Include="core\msdf-edge-artifact-patcher.cpp">
       <Filter>Core</Filter>
     </ClCompile>
   </ItemGroup>

+ 2 - 2
core/Scanline.cpp

@@ -65,8 +65,8 @@ Scanline::Scanline() : lastIndex(0) { }
 
 void Scanline::preprocess() {
     lastIndex = 0;
-    if (!this->intersections.empty()) {
-        qsort(&this->intersections[0], this->intersections.size(), sizeof(Intersection), compareIntersections);
+    if (!intersections.empty()) {
+        qsort(&intersections[0], intersections.size(), sizeof(Intersection), compareIntersections);
         int totalDirection = 0;
         for (std::vector<Intersection>::iterator intersection = intersections.begin(); intersection != intersections.end(); ++intersection) {
             totalDirection += intersection->direction;

+ 1 - 1
core/Shape.cpp

@@ -94,7 +94,7 @@ void Shape::scanline(Scanline &line, double y) const {
 int Shape::edgeCount() const {
     int total = 0;
     for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour)
-        total += contour->edges.size();
+        total += (int) contour->edges.size();
     return total;
 }
 

+ 37 - 0
core/ShapeDistanceFinder.h

@@ -0,0 +1,37 @@
+
+#pragma once
+
+#include <vector>
+#include "Vector2.h"
+#include "edge-selectors.h"
+#include "contour-combiners.h"
+
+namespace msdfgen {
+
+/// Finds the distance between a point and a Shape. ContourCombiner dictates the distance metric and its data type.
+template <class ContourCombiner>
+class ShapeDistanceFinder {
+
+public:
+    typedef typename ContourCombiner::DistanceType DistanceType;
+
+    // Passed shape object must persist until the distance finder is destroyed!
+    explicit ShapeDistanceFinder(const Shape &shape);
+    /// Finds the distance from origin. Not thread-safe! Is fastest when subsequent queries are close together.
+    DistanceType distance(const Point2 &origin);
+
+    /// Finds the distance between shape and origin. Does not allocate result cache used to optimize performance of multiple queries.
+    static DistanceType oneShotDistance(const Shape &shape, const Point2 &origin);
+
+private:
+    const Shape &shape;
+    ContourCombiner contourCombiner;
+    std::vector<typename ContourCombiner::EdgeSelectorType::EdgeCache> shapeEdgeCache;
+
+};
+
+typedef ShapeDistanceFinder<SimpleContourCombiner<TrueDistanceSelector> > SimpleTrueShapeDistanceFinder;
+
+}
+
+#include "ShapeDistanceFinder.hpp"

+ 56 - 0
core/ShapeDistanceFinder.hpp

@@ -0,0 +1,56 @@
+
+#include "ShapeDistanceFinder.h"
+
+namespace msdfgen {
+
+template <class ContourCombiner>
+ShapeDistanceFinder<ContourCombiner>::ShapeDistanceFinder(const Shape &shape) : shape(shape), contourCombiner(shape), shapeEdgeCache(shape.edgeCount()) { }
+
+template <class ContourCombiner>
+typename ShapeDistanceFinder<ContourCombiner>::DistanceType ShapeDistanceFinder<ContourCombiner>::distance(const Point2 &origin) {
+    contourCombiner.reset(origin);
+    typename ContourCombiner::EdgeSelectorType::EdgeCache *edgeCache = &shapeEdgeCache[0];
+
+    for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) {
+        if (!contour->edges.empty()) {
+            typename ContourCombiner::EdgeSelectorType &edgeSelector = contourCombiner.edgeSelector(int(contour-shape.contours.begin()));
+
+            const EdgeSegment *prevEdge = contour->edges.size() >= 2 ? *(contour->edges.end()-2) : *contour->edges.begin();
+            const EdgeSegment *curEdge = contour->edges.back();
+            for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
+                const EdgeSegment *nextEdge = *edge;
+                edgeSelector.addEdge(*edgeCache++, prevEdge, curEdge, nextEdge);
+                prevEdge = curEdge;
+                curEdge = nextEdge;
+            }
+        }
+    }
+
+    return contourCombiner.distance();
+}
+
+template <class ContourCombiner>
+typename ShapeDistanceFinder<ContourCombiner>::DistanceType ShapeDistanceFinder<ContourCombiner>::oneShotDistance(const Shape &shape, const Point2 &origin) {
+    ContourCombiner contourCombiner(shape);
+    contourCombiner.reset(origin);
+
+    for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) {
+        if (!contour->edges.empty()) {
+            typename ContourCombiner::EdgeSelectorType &edgeSelector = contourCombiner.edgeSelector(int(contour-shape.contours.begin()));
+
+            const EdgeSegment *prevEdge = contour->edges.size() >= 2 ? *(contour->edges.end()-2) : *contour->edges.begin();
+            const EdgeSegment *curEdge = contour->edges.back();
+            for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
+                const EdgeSegment *nextEdge = *edge;
+                typename ContourCombiner::EdgeSelectorType::EdgeCache dummy;
+                edgeSelector.addEdge(dummy, prevEdge, curEdge, nextEdge);
+                prevEdge = curEdge;
+                curEdge = nextEdge;
+            }
+        }
+    }
+
+    return contourCombiner.distance();
+}
+
+}

+ 25 - 0
core/bitmap-interpolation.hpp

@@ -0,0 +1,25 @@
+
+#pragma once
+
+#include "arithmetics.hpp"
+#include "Vector2.h"
+#include "BitmapRef.hpp"
+
+namespace msdfgen {
+
+template <typename T, int N>
+static void interpolate(T *output, const BitmapConstRef<T, N> &bitmap, Point2 pos) {
+    pos -= .5;
+    int l = (int) floor(pos.x);
+    int b = (int) floor(pos.y);
+    int r = l+1;
+    int t = b+1;
+    double lr = pos.x-l;
+    double bt = pos.y-b;
+    l = clamp(l, bitmap.width-1), r = clamp(r, bitmap.width-1);
+    b = clamp(b, bitmap.height-1), t = clamp(t, bitmap.height-1);
+    for (int i = 0; i < N; ++i)
+        output[i] = mix(mix(bitmap(l, b)[i], bitmap(r, b)[i], lr), mix(bitmap(l, t)[i], bitmap(r, t)[i], lr), bt);
+}
+
+}

+ 173 - 0
core/msdf-edge-artifact-patcher.cpp

@@ -0,0 +1,173 @@
+
+#include "msdf-edge-artifact-patcher.h"
+
+#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 bm = median(b[0], b[1], b[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, bm, 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 msdfPatchEdgeArtifactsInner(const BitmapRef<float, N> &sdf, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
+    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 = *hotspot/scale-translate;
+        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 msdfPatchEdgeArtifacts(const BitmapRef<float, 3> &sdf, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
+    if (overlapSupport)
+        msdfPatchEdgeArtifactsInner<OverlappingContourCombiner>(sdf, shape, range, scale, translate);
+    else
+        msdfPatchEdgeArtifactsInner<SimpleContourCombiner>(sdf, shape, range, scale, translate);
+}
+
+void msdfPatchEdgeArtifacts(const BitmapRef<float, 4> &sdf, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
+    if (overlapSupport)
+        msdfPatchEdgeArtifactsInner<OverlappingContourCombiner>(sdf, shape, range, scale, translate);
+    else
+        msdfPatchEdgeArtifactsInner<SimpleContourCombiner>(sdf, shape, range, scale, translate);
+}
+
+}

+ 13 - 0
core/msdf-edge-artifact-patcher.h

@@ -0,0 +1,13 @@
+
+#pragma once
+
+#include "Vector2.h"
+#include "Shape.h"
+#include "BitmapRef.hpp"
+
+namespace msdfgen {
+
+void msdfPatchEdgeArtifacts(const BitmapRef<float, 3> &sdf, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
+void msdfPatchEdgeArtifacts(const BitmapRef<float, 4> &sdf, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
+
+}

+ 77 - 0
core/msdf-error-correction.cpp

@@ -0,0 +1,77 @@
+
+#include "msdf-error-correction.h"
+
+#include <vector>
+#include "arithmetics.hpp"
+
+namespace msdfgen {
+
+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];
+    float b0 = b[0], b1 = b[1], b2 = b[2];
+    float tmp;
+    if (fabsf(b0-a0) < fabsf(b1-a1)) {
+        tmp = a0, a0 = a1, a1 = tmp;
+        tmp = b0, b0 = b1, b1 = tmp;
+    }
+    if (fabsf(b1-a1) < fabsf(b2-a2)) {
+        tmp = a1, a1 = a2, a2 = tmp;
+        tmp = b1, b1 = b2, b2 = tmp;
+        if (fabsf(b0-a0) < fabsf(b1-a1)) {
+            tmp = a0, a0 = a1, a1 = tmp;
+            tmp = b0, b0 = b1, b1 = tmp;
+        }
+    }
+    return (fabsf(b1-a1) >= threshold) &&
+        !(b0 == b1 && b0 == b2) && // Ignore if other pixel has been equalized
+        fabsf(a2-.5f) >= fabsf(b2-.5f); // Out of the pair, only flag the pixel farther from a shape edge
+}
+
+template <int N>
+void msdfErrorCorrectionInner(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)
+        for (int x = 0; x < w; ++x) {
+            if (
+                (x > 0 && detectClash(output(x, y), output(x-1, y), threshold.x)) ||
+                (x < w-1 && detectClash(output(x, y), output(x+1, y), threshold.x)) ||
+                (y > 0 && detectClash(output(x, y), output(x, y-1), threshold.y)) ||
+                (y < h-1 && detectClash(output(x, y), output(x, y+1), threshold.y))
+            )
+                clashes.push_back(std::make_pair(x, y));
+        }
+    for (std::vector<std::pair<int, int> >::const_iterator clash = clashes.begin(); clash != clashes.end(); ++clash) {
+        float *pixel = output(clash->first, clash->second);
+        float med = median(pixel[0], pixel[1], pixel[2]);
+        pixel[0] = med, pixel[1] = med, pixel[2] = med;
+    }
+#ifndef MSDFGEN_NO_DIAGONAL_CLASH_DETECTION
+    clashes.clear();
+    for (int y = 0; y < h; ++y)
+        for (int x = 0; x < w; ++x) {
+            if (
+                (x > 0 && y > 0 && detectClash(output(x, y), output(x-1, y-1), threshold.x+threshold.y)) ||
+                (x < w-1 && y > 0 && detectClash(output(x, y), output(x+1, y-1), threshold.x+threshold.y)) ||
+                (x > 0 && y < h-1 && detectClash(output(x, y), output(x-1, y+1), threshold.x+threshold.y)) ||
+                (x < w-1 && y < h-1 && detectClash(output(x, y), output(x+1, y+1), threshold.x+threshold.y))
+            )
+                clashes.push_back(std::make_pair(x, y));
+        }
+    for (std::vector<std::pair<int, int> >::const_iterator clash = clashes.begin(); clash != clashes.end(); ++clash) {
+        float *pixel = output(clash->first, clash->second);
+        float med = median(pixel[0], pixel[1], pixel[2]);
+        pixel[0] = med, pixel[1] = med, pixel[2] = med;
+    }
+#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);
+}
+
+}

+ 15 - 0
core/msdf-error-correction.h

@@ -0,0 +1,15 @@
+
+#pragma once
+
+#include "Vector2.h"
+#include "BitmapRef.hpp"
+
+#define MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD 1.001
+
+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);
+
+}

+ 6 - 91
core/msdfgen.cpp

@@ -4,6 +4,8 @@
 #include <vector>
 #include "edge-selectors.h"
 #include "contour-combiners.h"
+#include "ShapeDistanceFinder.h"
+#include "msdf-edge-artifact-patcher.h"
 
 namespace msdfgen {
 
@@ -44,13 +46,11 @@ public:
 
 template <class ContourCombiner>
 void generateDistanceField(const typename DistancePixelConversion<typename ContourCombiner::DistanceType>::BitmapRefType &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
-    int edgeCount = shape.edgeCount();
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel
 #endif
     {
-        ContourCombiner contourCombiner(shape);
-        std::vector<typename ContourCombiner::EdgeSelectorType::EdgeCache> shapeEdgeCache(edgeCount);
+        ShapeDistanceFinder<ContourCombiner> distanceFinder(shape);
         bool rightToLeft = false;
         Point2 p;
 #ifdef MSDFGEN_USE_OPENMP
@@ -62,26 +62,7 @@ void generateDistanceField(const typename DistancePixelConversion<typename Conto
             for (int col = 0; col < output.width; ++col) {
                 int x = rightToLeft ? output.width-col-1 : col;
                 p.x = (x+.5)/scale.x-translate.x;
-
-                contourCombiner.reset(p);
-                typename ContourCombiner::EdgeSelectorType::EdgeCache *edgeCache = &shapeEdgeCache[0];
-
-                for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) {
-                    if (!contour->edges.empty()) {
-                        typename ContourCombiner::EdgeSelectorType &edgeSelector = contourCombiner.edgeSelector(int(contour-shape.contours.begin()));
-
-                        const EdgeSegment *prevEdge = contour->edges.size() >= 2 ? *(contour->edges.end()-2) : *contour->edges.begin();
-                        const EdgeSegment *curEdge = contour->edges.back();
-                        for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
-                            const EdgeSegment *nextEdge = *edge;
-                            edgeSelector.addEdge(*edgeCache++, prevEdge, curEdge, nextEdge);
-                            prevEdge = curEdge;
-                            curEdge = nextEdge;
-                        }
-                    }
-                }
-
-                typename ContourCombiner::DistanceType distance = contourCombiner.distance();
+                typename ContourCombiner::DistanceType distance = distanceFinder.distance(p);
                 DistancePixelConversion<typename ContourCombiner::DistanceType>::convert(output(x, row), distance, range);
             }
             rightToLeft = !rightToLeft;
@@ -110,6 +91,7 @@ void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double
         generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, range, scale, translate);
     if (edgeThreshold > 0)
         msdfErrorCorrection(output, edgeThreshold/(scale*range));
+    msdfPatchEdgeArtifacts(output, shape, range, scale, translate, overlapSupport);
 }
 
 void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold, bool overlapSupport) {
@@ -119,74 +101,7 @@ void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double
         generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, range, scale, translate);
     if (edgeThreshold > 0)
         msdfErrorCorrection(output, edgeThreshold/(scale*range));
-}
-
-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];
-    float b0 = b[0], b1 = b[1], b2 = b[2];
-    float tmp;
-    if (fabsf(b0-a0) < fabsf(b1-a1)) {
-        tmp = a0, a0 = a1, a1 = tmp;
-        tmp = b0, b0 = b1, b1 = tmp;
-    }
-    if (fabsf(b1-a1) < fabsf(b2-a2)) {
-        tmp = a1, a1 = a2, a2 = tmp;
-        tmp = b1, b1 = b2, b2 = tmp;
-        if (fabsf(b0-a0) < fabsf(b1-a1)) {
-            tmp = a0, a0 = a1, a1 = tmp;
-            tmp = b0, b0 = b1, b1 = tmp;
-        }
-    }
-    return (fabsf(b1-a1) >= threshold) &&
-        !(b0 == b1 && b0 == b2) && // Ignore if other pixel has been equalized
-        fabsf(a2-.5f) >= fabsf(b2-.5f); // Out of the pair, only flag the pixel farther from a shape edge
-}
-
-template <int N>
-void msdfErrorCorrectionInner(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)
-        for (int x = 0; x < w; ++x) {
-            if (
-                (x > 0 && detectClash(output(x, y), output(x-1, y), threshold.x)) ||
-                (x < w-1 && detectClash(output(x, y), output(x+1, y), threshold.x)) ||
-                (y > 0 && detectClash(output(x, y), output(x, y-1), threshold.y)) ||
-                (y < h-1 && detectClash(output(x, y), output(x, y+1), threshold.y))
-            )
-                clashes.push_back(std::make_pair(x, y));
-        }
-    for (std::vector<std::pair<int, int> >::const_iterator clash = clashes.begin(); clash != clashes.end(); ++clash) {
-        float *pixel = output(clash->first, clash->second);
-        float med = median(pixel[0], pixel[1], pixel[2]);
-        pixel[0] = med, pixel[1] = med, pixel[2] = med;
-    }
-#ifndef MSDFGEN_NO_DIAGONAL_CLASH_DETECTION
-    clashes.clear();
-    for (int y = 0; y < h; ++y)
-        for (int x = 0; x < w; ++x) {
-            if (
-                (x > 0 && y > 0 && detectClash(output(x, y), output(x-1, y-1), threshold.x+threshold.y)) ||
-                (x < w-1 && y > 0 && detectClash(output(x, y), output(x+1, y-1), threshold.x+threshold.y)) ||
-                (x > 0 && y < h-1 && detectClash(output(x, y), output(x-1, y+1), threshold.x+threshold.y)) ||
-                (x < w-1 && y < h-1 && detectClash(output(x, y), output(x+1, y+1), threshold.x+threshold.y))
-            )
-                clashes.push_back(std::make_pair(x, y));
-        }
-    for (std::vector<std::pair<int, int> >::const_iterator clash = clashes.begin(); clash != clashes.end(); ++clash) {
-        float *pixel = output(clash->first, clash->second);
-        float med = median(pixel[0], pixel[1], pixel[2]);
-        pixel[0] = med, pixel[1] = med, pixel[2] = med;
-    }
-#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);
+    msdfPatchEdgeArtifacts(output, shape, range, scale, translate, overlapSupport);
 }
 
 // Legacy version

+ 13 - 22
core/render-sdf.cpp

@@ -3,25 +3,10 @@
 
 #include "arithmetics.hpp"
 #include "pixel-conversion.hpp"
+#include "bitmap-interpolation.hpp"
 
 namespace msdfgen {
 
-template <typename T, int N>
-static void sample(T *output, const BitmapConstRef<T, N> &bitmap, Point2 pos) {
-    double x = pos.x*bitmap.width-.5;
-    double y = pos.y*bitmap.height-.5;
-    int l = (int) floor(x);
-    int b = (int) floor(y);
-    int r = l+1;
-    int t = b+1;
-    double lr = x-l;
-    double bt = y-b;
-    l = clamp(l, bitmap.width-1), r = clamp(r, bitmap.width-1);
-    b = clamp(b, bitmap.height-1), t = clamp(t, bitmap.height-1);
-    for (int i = 0; i < N; ++i)
-        output[i] = mix(mix(bitmap(l, b)[i], bitmap(r, b)[i], lr), mix(bitmap(l, t)[i], bitmap(r, t)[i], lr), bt);
-}
-
 static float distVal(float dist, double pxRange) {
     if (!pxRange)
         return (float) (dist > .5f);
@@ -29,21 +14,23 @@ static float distVal(float dist, double pxRange) {
 }
 
 void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1> &sdf, double pxRange) {
+    Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
     pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
     for (int y = 0; y < output.height; ++y)
         for (int x = 0; x < output.width; ++x) {
             float sd;
-            sample(&sd, sdf, Point2((x+.5)/output.width, (y+.5)/output.height));
+            interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
             *output(x, y) = distVal(sd, pxRange);
         }
 }
 
 void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, double pxRange) {
+    Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
     pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
     for (int y = 0; y < output.height; ++y)
         for (int x = 0; x < output.width; ++x) {
             float sd;
-            sample(&sd, sdf, Point2((x+.5)/output.width, (y+.5)/output.height));
+            interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
             float v = distVal(sd, pxRange);
             output(x, y)[0] = v;
             output(x, y)[1] = v;
@@ -52,21 +39,23 @@ void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1>
 }
 
 void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, double pxRange) {
+    Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
     pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
     for (int y = 0; y < output.height; ++y)
         for (int x = 0; x < output.width; ++x) {
             float sd[3];
-            sample(sd, sdf, Point2((x+.5)/output.width, (y+.5)/output.height));
+            interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
             *output(x, y) = distVal(median(sd[0], sd[1], sd[2]), pxRange);
         }
 }
 
 void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &sdf, double pxRange) {
+    Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
     pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
     for (int y = 0; y < output.height; ++y)
         for (int x = 0; x < output.width; ++x) {
             float sd[3];
-            sample(sd, sdf, Point2((x+.5)/output.width, (y+.5)/output.height));
+            interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
             output(x, y)[0] = distVal(sd[0], pxRange);
             output(x, y)[1] = distVal(sd[1], pxRange);
             output(x, y)[2] = distVal(sd[2], pxRange);
@@ -74,21 +63,23 @@ void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3>
 }
 
 void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &sdf, double pxRange) {
+    Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
     pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
     for (int y = 0; y < output.height; ++y)
         for (int x = 0; x < output.width; ++x) {
             float sd[4];
-            sample(sd, sdf, Point2((x+.5)/output.width, (y+.5)/output.height));
+            interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
             *output(x, y) = distVal(median(sd[0], sd[1], sd[2]), pxRange);
         }
 }
 
 void renderSDF(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &sdf, double pxRange) {
+    Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
     pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
     for (int y = 0; y < output.height; ++y)
         for (int x = 0; x < output.width; ++x) {
             float sd[4];
-            sample(sd, sdf, Point2((x+.5)/output.width, (y+.5)/output.height));
+            interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
             output(x, y)[0] = distVal(sd[0], pxRange);
             output(x, y)[1] = distVal(sd[1], pxRange);
             output(x, y)[2] = distVal(sd[2], pxRange);

+ 1 - 1
core/estimate-sdf-error.cpp → core/sdf-error-estimation.cpp

@@ -1,5 +1,5 @@
 
-#include "estimate-sdf-error.h"
+#include "sdf-error-estimation.h"
 
 #include <cmath>
 #include "arithmetics.hpp"

+ 0 - 0
core/estimate-sdf-error.h → core/sdf-error-estimation.h


+ 1 - 4
core/shape-description.cpp

@@ -1,12 +1,9 @@
 
+#define _CRT_SECURE_NO_WARNINGS
 #include "shape-description.h"
 
 namespace msdfgen {
 
-#ifdef _WIN32
-    #pragma warning(disable:4996)
-#endif
-
 int readCharF(FILE *input) {
     int c = '\0';
     do {

+ 310 - 313
ext/import-svg.cpp

@@ -1,313 +1,310 @@
-
-#define _USE_MATH_DEFINES
-#include "import-svg.h"
-
-#include <cstdio>
-#include <tinyxml2.h>
-#include "../core/arithmetics.hpp"
-
-#ifdef _WIN32
-    #pragma warning(disable:4996)
-#endif
-
-#define ARC_SEGMENTS_PER_PI 2
-#define ENDPOINT_SNAP_RANGE_PROPORTION (1/16384.)
-
-namespace msdfgen {
-
-#if defined(_DEBUG) || !NDEBUG
-#define REQUIRE(cond) { if (!(cond)) { fprintf(stderr, "SVG Parse Error (%s:%d): " #cond "\n", __FILE__, __LINE__); return false; } }
-#else
-#define REQUIRE(cond) { if (!(cond)) return false; }
-#endif
-
-static void skipExtraChars(const char *&pathDef) {
-    while (*pathDef == ',' || *pathDef == ' ' || *pathDef == '\t' || *pathDef == '\r' || *pathDef == '\n')
-        ++pathDef;
-}
-
-static bool readNodeType(char &output, const char *&pathDef) {
-    skipExtraChars(pathDef);
-    char nodeType = *pathDef;
-    if (nodeType && nodeType != '+' && nodeType != '-' && nodeType != '.' && nodeType != ',' && (nodeType < '0' || nodeType > '9')) {
-        ++pathDef;
-        output = nodeType;
-        return true;
-    }
-    return false;
-}
-
-static bool readCoord(Point2 &output, const char *&pathDef) {
-    skipExtraChars(pathDef);
-    int shift;
-    double x, y;
-    if (sscanf(pathDef, "%lf%lf%n", &x, &y, &shift) == 2 || sscanf(pathDef, "%lf , %lf%n", &x, &y, &shift) == 2) {
-        output.x = x;
-        output.y = y;
-        pathDef += shift;
-        return true;
-    }
-    return false;
-}
-
-static bool readDouble(double &output, const char *&pathDef) {
-    skipExtraChars(pathDef);
-    int shift;
-    double v;
-    if (sscanf(pathDef, "%lf%n", &v, &shift) == 1) {
-        pathDef += shift;
-        output = v;
-        return true;
-    }
-    return false;
-}
-
-static bool readBool(bool &output, const char *&pathDef) {
-    skipExtraChars(pathDef);
-    int shift;
-    int v;
-    if (sscanf(pathDef, "%d%n", &v, &shift) == 1) {
-        pathDef += shift;
-        output = v != 0;
-        return true;
-    }
-    return false;
-}
-
-static double arcAngle(Vector2 u, Vector2 v) {
-    return nonZeroSign(crossProduct(u, v))*acos(clamp(dotProduct(u, v)/(u.length()*v.length()), -1., +1.));
-}
-
-static Vector2 rotateVector(Vector2 v, Vector2 direction) {
-    return Vector2(direction.x*v.x-direction.y*v.y, direction.y*v.x+direction.x*v.y);
-}
-
-static void addArcApproximate(Contour &contour, Point2 startPoint, Point2 endPoint, Vector2 radius, double rotation, bool largeArc, bool sweep) {
-    if (endPoint == startPoint)
-        return;
-    if (radius.x == 0 || radius.y == 0)
-        return contour.addEdge(new LinearSegment(startPoint, endPoint));
-
-    radius.x = fabs(radius.x);
-    radius.y = fabs(radius.y);
-    Vector2 axis(cos(rotation), sin(rotation));
-
-    Vector2 rm = rotateVector(.5*(startPoint-endPoint), Vector2(axis.x, -axis.y));
-    Vector2 rm2 = rm*rm;
-    Vector2 radius2 = radius*radius;
-    double radiusGap = rm2.x/radius2.x+rm2.y/radius2.y;
-    if (radiusGap > 1) {
-        radius *= sqrt(radiusGap);
-        radius2 = radius*radius;
-    }
-    double dq = (radius2.x*rm2.y+radius2.y*rm2.x);
-    double pq = radius2.x*radius2.y/dq-1;
-    double q = (largeArc == sweep ? -1 : +1)*sqrt(max(pq, 0.));
-    Vector2 rc(q*radius.x*rm.y/radius.y, -q*radius.y*rm.x/radius.x);
-    Point2 center = .5*(startPoint+endPoint)+rotateVector(rc, axis);
-
-    double angleStart = arcAngle(Vector2(1, 0), (rm-rc)/radius);
-    double angleExtent = arcAngle((rm-rc)/radius, (-rm-rc)/radius);
-    if (!sweep && angleExtent > 0)
-        angleExtent -= 2*M_PI;
-    else if (sweep && angleExtent < 0)
-        angleExtent += 2*M_PI;
-
-    int segments = (int) ceil(ARC_SEGMENTS_PER_PI/M_PI*fabs(angleExtent));
-    double angleIncrement = angleExtent/segments;
-    double cl = 4/3.*sin(.5*angleIncrement)/(1+cos(.5*angleIncrement));
-
-    Point2 prevNode = startPoint;
-    double angle = angleStart;
-    for (int i = 0; i < segments; ++i) {
-        Point2 controlPoint[2];
-        Vector2 d(cos(angle), sin(angle));
-        controlPoint[0] = center+rotateVector(Vector2(d.x-cl*d.y, d.y+cl*d.x)*radius, axis);
-        angle += angleIncrement;
-        d.set(cos(angle), sin(angle));
-        controlPoint[1] = center+rotateVector(Vector2(d.x+cl*d.y, d.y-cl*d.x)*radius, axis);
-        Point2 node = i == segments-1 ? endPoint : center+rotateVector(d*radius, axis);
-        contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
-        prevNode = node;
-    }
-}
-
-static bool buildFromPath(Shape &shape, const char *pathDef, double size) {
-    char nodeType = '\0';
-    char prevNodeType = '\0';
-    Point2 prevNode(0, 0);
-    bool nodeTypePreread = false;
-    while (nodeTypePreread || readNodeType(nodeType, pathDef)) {
-        nodeTypePreread = false;
-        Contour &contour = shape.addContour();
-        bool contourStart = true;
-
-        Point2 startPoint;
-        Point2 controlPoint[2];
-        Point2 node;
-
-        while (*pathDef) {
-            switch (nodeType) {
-                case 'M': case 'm':
-                    if (!contourStart) {
-                        nodeTypePreread = true;
-                        goto NEXT_CONTOUR;
-                    }
-                    REQUIRE(readCoord(node, pathDef));
-                    if (nodeType == 'm')
-                        node += prevNode;
-                    startPoint = node;
-                    --nodeType; // to 'L' or 'l'
-                    break;
-                case 'Z': case 'z':
-                    REQUIRE(!contourStart);
-                    goto NEXT_CONTOUR;
-                case 'L': case 'l':
-                    REQUIRE(readCoord(node, pathDef));
-                    if (nodeType == 'l')
-                        node += prevNode;
-                    contour.addEdge(new LinearSegment(prevNode, node));
-                    break;
-                case 'H': case 'h':
-                    REQUIRE(readDouble(node.x, pathDef));
-                    if (nodeType == 'h')
-                        node.x += prevNode.x;
-                    contour.addEdge(new LinearSegment(prevNode, node));
-                    break;
-                case 'V': case 'v':
-                    REQUIRE(readDouble(node.y, pathDef));
-                    if (nodeType == 'v')
-                        node.y += prevNode.y;
-                    contour.addEdge(new LinearSegment(prevNode, node));
-                    break;
-                case 'Q': case 'q':
-                    REQUIRE(readCoord(controlPoint[0], pathDef));
-                    REQUIRE(readCoord(node, pathDef));
-                    if (nodeType == 'q') {
-                        controlPoint[0] += prevNode;
-                        node += prevNode;
-                    }
-                    contour.addEdge(new QuadraticSegment(prevNode, controlPoint[0], node));
-                    break;
-                case 'T': case 't':
-                    if (prevNodeType == 'Q' || prevNodeType == 'q' || prevNodeType == 'T' || prevNodeType == 't')
-                        controlPoint[0] = node+node-controlPoint[0];
-                    else
-                        controlPoint[0] = node;
-                    REQUIRE(readCoord(node, pathDef));
-                    if (nodeType == 't')
-                        node += prevNode;
-                    contour.addEdge(new QuadraticSegment(prevNode, controlPoint[0], node));
-                    break;
-                case 'C': case 'c':
-                    REQUIRE(readCoord(controlPoint[0], pathDef));
-                    REQUIRE(readCoord(controlPoint[1], pathDef));
-                    REQUIRE(readCoord(node, pathDef));
-                    if (nodeType == 'c') {
-                        controlPoint[0] += prevNode;
-                        controlPoint[1] += prevNode;
-                        node += prevNode;
-                    }
-                    contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
-                    break;
-                case 'S': case 's':
-                    if (prevNodeType == 'C' || prevNodeType == 'c' || prevNodeType == 'S' || prevNodeType == 's')
-                        controlPoint[0] = node+node-controlPoint[1];
-                    else
-                        controlPoint[0] = node;
-                    REQUIRE(readCoord(controlPoint[1], pathDef));
-                    REQUIRE(readCoord(node, pathDef));
-                    if (nodeType == 's') {
-                        controlPoint[1] += prevNode;
-                        node += prevNode;
-                    }
-                    contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
-                    break;
-                case 'A': case 'a':
-                    {
-                        Vector2 radius;
-                        double angle;
-                        bool largeArg;
-                        bool sweep;
-                        REQUIRE(readCoord(radius, pathDef));
-                        REQUIRE(readDouble(angle, pathDef));
-                        REQUIRE(readBool(largeArg, pathDef));
-                        REQUIRE(readBool(sweep, pathDef));
-                        REQUIRE(readCoord(node, pathDef));
-                        if (nodeType == 'a')
-                            node += prevNode;
-                        angle *= M_PI/180.0;
-                        addArcApproximate(contour, prevNode, node, radius, angle, largeArg, sweep);
-                    }
-                    break;
-                default:
-                    REQUIRE(!"Unknown node type");
-            }
-            contourStart &= nodeType == 'M' || nodeType == 'm';
-            prevNode = node;
-            prevNodeType = nodeType;
-            readNodeType(nodeType, pathDef);
-        }
-    NEXT_CONTOUR:
-        // Fix contour if it isn't properly closed
-        if (!contour.edges.empty() && prevNode != startPoint) {
-            if ((contour.edges.back()->point(1)-contour.edges[0]->point(0)).length() < ENDPOINT_SNAP_RANGE_PROPORTION*size)
-                contour.edges.back()->moveEndPoint(contour.edges[0]->point(0));
-            else
-                contour.addEdge(new LinearSegment(prevNode, startPoint));
-        }
-        prevNode = startPoint;
-        prevNodeType = '\0';
-    }
-    return true;
-}
-
-bool loadSvgShape(Shape &output, const char *filename, int pathIndex, Vector2 *dimensions) {
-    tinyxml2::XMLDocument doc;
-    if (doc.LoadFile(filename))
-        return false;
-    tinyxml2::XMLElement *root = doc.FirstChildElement("svg");
-    if (!root)
-        return false;
-
-    tinyxml2::XMLElement *path = NULL;
-    if (pathIndex > 0) {
-        path = root->FirstChildElement("path");
-        if (!path) {
-            tinyxml2::XMLElement *g = root->FirstChildElement("g");
-            if (g)
-                path = g->FirstChildElement("path");
-        }
-        while (path && --pathIndex > 0)
-            path = path->NextSiblingElement("path");
-    } else {
-        path = root->LastChildElement("path");
-        if (!path) {
-            tinyxml2::XMLElement *g = root->LastChildElement("g");
-            if (g)
-                path = g->LastChildElement("path");
-        }
-        while (path && ++pathIndex < 0)
-            path = path->PreviousSiblingElement("path");
-     }
-    if (!path)
-        return false;
-    const char *pd = path->Attribute("d");
-    if (!pd)
-        return false;
-
-    output.contours.clear();
-    output.inverseYAxis = true;
-    Vector2 dims(root->DoubleAttribute("width"), root->DoubleAttribute("height"));
-    if (!dims) {
-        double left, top;
-        const char *viewBox = root->Attribute("viewBox");
-        if (viewBox)
-            sscanf(viewBox, "%lf %lf %lf %lf", &left, &top, &dims.x, &dims.y);
-    }
-    if (dimensions)
-        *dimensions = dims;
-    return buildFromPath(output, pd, dims.length());
-}
-
-}
+
+#define _USE_MATH_DEFINES
+#define _CRT_SECURE_NO_WARNINGS
+#include "import-svg.h"
+
+#include <cstdio>
+#include <tinyxml2.h>
+#include "../core/arithmetics.hpp"
+
+#define ARC_SEGMENTS_PER_PI 2
+#define ENDPOINT_SNAP_RANGE_PROPORTION (1/16384.)
+
+namespace msdfgen {
+
+#if defined(_DEBUG) || !NDEBUG
+#define REQUIRE(cond) { if (!(cond)) { fprintf(stderr, "SVG Parse Error (%s:%d): " #cond "\n", __FILE__, __LINE__); return false; } }
+#else
+#define REQUIRE(cond) { if (!(cond)) return false; }
+#endif
+
+static void skipExtraChars(const char *&pathDef) {
+    while (*pathDef == ',' || *pathDef == ' ' || *pathDef == '\t' || *pathDef == '\r' || *pathDef == '\n')
+        ++pathDef;
+}
+
+static bool readNodeType(char &output, const char *&pathDef) {
+    skipExtraChars(pathDef);
+    char nodeType = *pathDef;
+    if (nodeType && nodeType != '+' && nodeType != '-' && nodeType != '.' && nodeType != ',' && (nodeType < '0' || nodeType > '9')) {
+        ++pathDef;
+        output = nodeType;
+        return true;
+    }
+    return false;
+}
+
+static bool readCoord(Point2 &output, const char *&pathDef) {
+    skipExtraChars(pathDef);
+    int shift;
+    double x, y;
+    if (sscanf(pathDef, "%lf%lf%n", &x, &y, &shift) == 2 || sscanf(pathDef, "%lf , %lf%n", &x, &y, &shift) == 2) {
+        output.x = x;
+        output.y = y;
+        pathDef += shift;
+        return true;
+    }
+    return false;
+}
+
+static bool readDouble(double &output, const char *&pathDef) {
+    skipExtraChars(pathDef);
+    int shift;
+    double v;
+    if (sscanf(pathDef, "%lf%n", &v, &shift) == 1) {
+        pathDef += shift;
+        output = v;
+        return true;
+    }
+    return false;
+}
+
+static bool readBool(bool &output, const char *&pathDef) {
+    skipExtraChars(pathDef);
+    int shift;
+    int v;
+    if (sscanf(pathDef, "%d%n", &v, &shift) == 1) {
+        pathDef += shift;
+        output = v != 0;
+        return true;
+    }
+    return false;
+}
+
+static double arcAngle(Vector2 u, Vector2 v) {
+    return nonZeroSign(crossProduct(u, v))*acos(clamp(dotProduct(u, v)/(u.length()*v.length()), -1., +1.));
+}
+
+static Vector2 rotateVector(Vector2 v, Vector2 direction) {
+    return Vector2(direction.x*v.x-direction.y*v.y, direction.y*v.x+direction.x*v.y);
+}
+
+static void addArcApproximate(Contour &contour, Point2 startPoint, Point2 endPoint, Vector2 radius, double rotation, bool largeArc, bool sweep) {
+    if (endPoint == startPoint)
+        return;
+    if (radius.x == 0 || radius.y == 0)
+        return contour.addEdge(new LinearSegment(startPoint, endPoint));
+
+    radius.x = fabs(radius.x);
+    radius.y = fabs(radius.y);
+    Vector2 axis(cos(rotation), sin(rotation));
+
+    Vector2 rm = rotateVector(.5*(startPoint-endPoint), Vector2(axis.x, -axis.y));
+    Vector2 rm2 = rm*rm;
+    Vector2 radius2 = radius*radius;
+    double radiusGap = rm2.x/radius2.x+rm2.y/radius2.y;
+    if (radiusGap > 1) {
+        radius *= sqrt(radiusGap);
+        radius2 = radius*radius;
+    }
+    double dq = (radius2.x*rm2.y+radius2.y*rm2.x);
+    double pq = radius2.x*radius2.y/dq-1;
+    double q = (largeArc == sweep ? -1 : +1)*sqrt(max(pq, 0.));
+    Vector2 rc(q*radius.x*rm.y/radius.y, -q*radius.y*rm.x/radius.x);
+    Point2 center = .5*(startPoint+endPoint)+rotateVector(rc, axis);
+
+    double angleStart = arcAngle(Vector2(1, 0), (rm-rc)/radius);
+    double angleExtent = arcAngle((rm-rc)/radius, (-rm-rc)/radius);
+    if (!sweep && angleExtent > 0)
+        angleExtent -= 2*M_PI;
+    else if (sweep && angleExtent < 0)
+        angleExtent += 2*M_PI;
+
+    int segments = (int) ceil(ARC_SEGMENTS_PER_PI/M_PI*fabs(angleExtent));
+    double angleIncrement = angleExtent/segments;
+    double cl = 4/3.*sin(.5*angleIncrement)/(1+cos(.5*angleIncrement));
+
+    Point2 prevNode = startPoint;
+    double angle = angleStart;
+    for (int i = 0; i < segments; ++i) {
+        Point2 controlPoint[2];
+        Vector2 d(cos(angle), sin(angle));
+        controlPoint[0] = center+rotateVector(Vector2(d.x-cl*d.y, d.y+cl*d.x)*radius, axis);
+        angle += angleIncrement;
+        d.set(cos(angle), sin(angle));
+        controlPoint[1] = center+rotateVector(Vector2(d.x+cl*d.y, d.y-cl*d.x)*radius, axis);
+        Point2 node = i == segments-1 ? endPoint : center+rotateVector(d*radius, axis);
+        contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
+        prevNode = node;
+    }
+}
+
+static bool buildFromPath(Shape &shape, const char *pathDef, double size) {
+    char nodeType = '\0';
+    char prevNodeType = '\0';
+    Point2 prevNode(0, 0);
+    bool nodeTypePreread = false;
+    while (nodeTypePreread || readNodeType(nodeType, pathDef)) {
+        nodeTypePreread = false;
+        Contour &contour = shape.addContour();
+        bool contourStart = true;
+
+        Point2 startPoint;
+        Point2 controlPoint[2];
+        Point2 node;
+
+        while (*pathDef) {
+            switch (nodeType) {
+                case 'M': case 'm':
+                    if (!contourStart) {
+                        nodeTypePreread = true;
+                        goto NEXT_CONTOUR;
+                    }
+                    REQUIRE(readCoord(node, pathDef));
+                    if (nodeType == 'm')
+                        node += prevNode;
+                    startPoint = node;
+                    --nodeType; // to 'L' or 'l'
+                    break;
+                case 'Z': case 'z':
+                    REQUIRE(!contourStart);
+                    goto NEXT_CONTOUR;
+                case 'L': case 'l':
+                    REQUIRE(readCoord(node, pathDef));
+                    if (nodeType == 'l')
+                        node += prevNode;
+                    contour.addEdge(new LinearSegment(prevNode, node));
+                    break;
+                case 'H': case 'h':
+                    REQUIRE(readDouble(node.x, pathDef));
+                    if (nodeType == 'h')
+                        node.x += prevNode.x;
+                    contour.addEdge(new LinearSegment(prevNode, node));
+                    break;
+                case 'V': case 'v':
+                    REQUIRE(readDouble(node.y, pathDef));
+                    if (nodeType == 'v')
+                        node.y += prevNode.y;
+                    contour.addEdge(new LinearSegment(prevNode, node));
+                    break;
+                case 'Q': case 'q':
+                    REQUIRE(readCoord(controlPoint[0], pathDef));
+                    REQUIRE(readCoord(node, pathDef));
+                    if (nodeType == 'q') {
+                        controlPoint[0] += prevNode;
+                        node += prevNode;
+                    }
+                    contour.addEdge(new QuadraticSegment(prevNode, controlPoint[0], node));
+                    break;
+                case 'T': case 't':
+                    if (prevNodeType == 'Q' || prevNodeType == 'q' || prevNodeType == 'T' || prevNodeType == 't')
+                        controlPoint[0] = node+node-controlPoint[0];
+                    else
+                        controlPoint[0] = node;
+                    REQUIRE(readCoord(node, pathDef));
+                    if (nodeType == 't')
+                        node += prevNode;
+                    contour.addEdge(new QuadraticSegment(prevNode, controlPoint[0], node));
+                    break;
+                case 'C': case 'c':
+                    REQUIRE(readCoord(controlPoint[0], pathDef));
+                    REQUIRE(readCoord(controlPoint[1], pathDef));
+                    REQUIRE(readCoord(node, pathDef));
+                    if (nodeType == 'c') {
+                        controlPoint[0] += prevNode;
+                        controlPoint[1] += prevNode;
+                        node += prevNode;
+                    }
+                    contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
+                    break;
+                case 'S': case 's':
+                    if (prevNodeType == 'C' || prevNodeType == 'c' || prevNodeType == 'S' || prevNodeType == 's')
+                        controlPoint[0] = node+node-controlPoint[1];
+                    else
+                        controlPoint[0] = node;
+                    REQUIRE(readCoord(controlPoint[1], pathDef));
+                    REQUIRE(readCoord(node, pathDef));
+                    if (nodeType == 's') {
+                        controlPoint[1] += prevNode;
+                        node += prevNode;
+                    }
+                    contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
+                    break;
+                case 'A': case 'a':
+                    {
+                        Vector2 radius;
+                        double angle;
+                        bool largeArg;
+                        bool sweep;
+                        REQUIRE(readCoord(radius, pathDef));
+                        REQUIRE(readDouble(angle, pathDef));
+                        REQUIRE(readBool(largeArg, pathDef));
+                        REQUIRE(readBool(sweep, pathDef));
+                        REQUIRE(readCoord(node, pathDef));
+                        if (nodeType == 'a')
+                            node += prevNode;
+                        angle *= M_PI/180.0;
+                        addArcApproximate(contour, prevNode, node, radius, angle, largeArg, sweep);
+                    }
+                    break;
+                default:
+                    REQUIRE(!"Unknown node type");
+            }
+            contourStart &= nodeType == 'M' || nodeType == 'm';
+            prevNode = node;
+            prevNodeType = nodeType;
+            readNodeType(nodeType, pathDef);
+        }
+    NEXT_CONTOUR:
+        // Fix contour if it isn't properly closed
+        if (!contour.edges.empty() && prevNode != startPoint) {
+            if ((contour.edges.back()->point(1)-contour.edges[0]->point(0)).length() < ENDPOINT_SNAP_RANGE_PROPORTION*size)
+                contour.edges.back()->moveEndPoint(contour.edges[0]->point(0));
+            else
+                contour.addEdge(new LinearSegment(prevNode, startPoint));
+        }
+        prevNode = startPoint;
+        prevNodeType = '\0';
+    }
+    return true;
+}
+
+bool loadSvgShape(Shape &output, const char *filename, int pathIndex, Vector2 *dimensions) {
+    tinyxml2::XMLDocument doc;
+    if (doc.LoadFile(filename))
+        return false;
+    tinyxml2::XMLElement *root = doc.FirstChildElement("svg");
+    if (!root)
+        return false;
+
+    tinyxml2::XMLElement *path = NULL;
+    if (pathIndex > 0) {
+        path = root->FirstChildElement("path");
+        if (!path) {
+            tinyxml2::XMLElement *g = root->FirstChildElement("g");
+            if (g)
+                path = g->FirstChildElement("path");
+        }
+        while (path && --pathIndex > 0)
+            path = path->NextSiblingElement("path");
+    } else {
+        path = root->LastChildElement("path");
+        if (!path) {
+            tinyxml2::XMLElement *g = root->LastChildElement("g");
+            if (g)
+                path = g->LastChildElement("path");
+        }
+        while (path && ++pathIndex < 0)
+            path = path->PreviousSiblingElement("path");
+     }
+    if (!path)
+        return false;
+    const char *pd = path->Attribute("d");
+    if (!pd)
+        return false;
+
+    output.contours.clear();
+    output.inverseYAxis = true;
+    Vector2 dims(root->DoubleAttribute("width"), root->DoubleAttribute("height"));
+    if (!dims) {
+        double left, top;
+        const char *viewBox = root->Attribute("viewBox");
+        if (viewBox)
+            sscanf(viewBox, "%lf %lf %lf %lf", &left, &top, &dims.x, &dims.y);
+    }
+    if (dimensions)
+        *dimensions = dims;
+    return buildFromPath(output, pd, dims.length());
+}
+
+}

+ 19 - 26
main.cpp

@@ -9,6 +9,7 @@
 #ifdef MSDFGEN_STANDALONE
 
 #define _USE_MATH_DEFINES
+#define _CRT_SECURE_NO_WARNINGS
 #include <cstdio>
 #include <cmath>
 #include <cstring>
@@ -16,11 +17,10 @@
 #include "msdfgen.h"
 #include "msdfgen-ext.h"
 
-#ifdef _WIN32
-    #pragma warning(disable:4996)
-#endif
+#include "core/ShapeDistanceFinder.h"
 
 #define SDF_ERROR_ESTIMATE_PRECISION 19
+#define DEFAULT_ANGLE_THRESHOLD 3.
 
 using namespace msdfgen;
 
@@ -387,8 +387,8 @@ int main(int argc, const char * const *argv) {
     Vector2 translate;
     Vector2 scale = 1;
     bool scaleSpecified = false;
-    double angleThreshold = 3;
-    double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD;
+    double angleThreshold = DEFAULT_ANGLE_THRESHOLD;
+    double errorCorrectionThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD;
     bool defEdgeAssignment = true;
     const char *edgeAssignment = NULL;
     bool yFlip = false;
@@ -570,10 +570,10 @@ int main(int argc, const char * const *argv) {
             continue;
         }
         ARG_CASE("-errorcorrection", 1) {
-            double et;
-            if (!parseDouble(et, argv[argPos+1]) || et < 0)
-                ABORT("Invalid error correction threshold. Use -errorcorrection <threshold> with a real number larger or equal to 1.");
-            edgeThreshold = et;
+            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;
             argPos += 2;
             continue;
         }
@@ -820,9 +820,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 : edgeThreshold);
+                generateMSDF_legacy(msdf, shape, range, scale, translate, scanlinePass ? 0 : errorCorrectionThreshold);
             else
-                generateMSDF(msdf, shape, range, scale, translate, scanlinePass ? 0 : edgeThreshold, overlapSupport);
+                generateMSDF(msdf, shape, range, scale, translate, errorCorrectionThreshold, overlapSupport);
             break;
         }
         case MULTI_AND_TRUE: {
@@ -832,9 +832,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 : edgeThreshold);
+                generateMTSDF_legacy(mtsdf, shape, range, scale, translate, scanlinePass ? 0 : errorCorrectionThreshold);
             else
-                generateMTSDF(mtsdf, shape, range, scale, translate, scanlinePass ? 0 : edgeThreshold, overlapSupport);
+                generateMTSDF(mtsdf, shape, range, scale, translate, errorCorrectionThreshold, overlapSupport);
             break;
         }
         default:;
@@ -843,15 +843,8 @@ int main(int argc, const char * const *argv) {
     if (orientation == GUESS) {
         // Get sign of signed distance outside bounds
         Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
-        double dummy;
-        SignedDistance minDistance;
-        for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
-            for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
-                SignedDistance distance = (*edge)->signedDistance(p, dummy);
-                if (distance < minDistance)
-                    minDistance = distance;
-            }
-        orientation = minDistance.distance <= 0 ? KEEP : REVERSE;
+        double distance = SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
+        orientation = distance <= 0 ? KEEP : REVERSE;
     }
     if (orientation == REVERSE) {
         switch (mode) {
@@ -876,13 +869,13 @@ int main(int argc, const char * const *argv) {
                 break;
             case MULTI:
                 distanceSignCorrection(msdf, shape, scale, translate, fillRule);
-                if (edgeThreshold > 0)
-                    msdfErrorCorrection(msdf, edgeThreshold/(scale*range));
+                if (errorCorrectionThreshold > 0)
+                    msdfErrorCorrection(msdf, errorCorrectionThreshold/(scale*range));
                 break;
             case MULTI_AND_TRUE:
                 distanceSignCorrection(mtsdf, shape, scale, translate, fillRule);
-                if (edgeThreshold > 0)
-                    msdfErrorCorrection(mtsdf, edgeThreshold/(scale*range));
+                if (errorCorrectionThreshold > 0)
+                    msdfErrorCorrection(mtsdf, errorCorrectionThreshold/(scale*range));
                 break;
             default:;
         }

+ 3 - 6
msdfgen.h

@@ -21,17 +21,18 @@
 #include "core/Shape.h"
 #include "core/BitmapRef.hpp"
 #include "core/Bitmap.h"
+#include "core/bitmap-interpolation.hpp"
 #include "core/pixel-conversion.hpp"
 #include "core/edge-coloring.h"
+#include "core/msdf-error-correction.h"
 #include "core/render-sdf.h"
 #include "core/rasterization.h"
-#include "core/estimate-sdf-error.h"
+#include "core/sdf-error-estimation.h"
 #include "core/save-bmp.h"
 #include "core/save-tiff.h"
 #include "core/shape-description.h"
 
 #define MSDFGEN_VERSION "1.7"
-#define MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD 1.001
 
 namespace msdfgen {
 
@@ -47,10 +48,6 @@ void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double
 /// 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, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD, bool overlapSupport = true);
 
-/// Resolves 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);
-
 // 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);