diff --git a/.gitignore b/.gitignore
index d3bf790..03938c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ Debug Library/
Release Library/
x86/
x64/
+.vs/
*.exe
*.user
*.sdf
diff --git a/Msdfgen.vcxproj b/Msdfgen.vcxproj
index 8c508f1..423a69f 100644
--- a/Msdfgen.vcxproj
+++ b/Msdfgen.vcxproj
@@ -296,6 +296,7 @@
+
@@ -307,7 +308,9 @@
-
+
+
+
@@ -316,6 +319,8 @@
+
+
@@ -333,7 +338,9 @@
-
+
+
+
diff --git a/Msdfgen.vcxproj.filters b/Msdfgen.vcxproj.filters
index 5c1fd80..55220f7 100644
--- a/Msdfgen.vcxproj.filters
+++ b/Msdfgen.vcxproj.filters
@@ -102,7 +102,22 @@
Core
-
+
+ Core
+
+
+ Core
+
+
+ Core
+
+
+ Core
+
+
+ Core
+
+
Core
@@ -176,7 +191,13 @@
Core
-
+
+ Core
+
+
+ Core
+
+
Core
diff --git a/core/Scanline.cpp b/core/Scanline.cpp
index 556ef0a..8e5352d 100644
--- a/core/Scanline.cpp
+++ b/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::iterator intersection = intersections.begin(); intersection != intersections.end(); ++intersection) {
totalDirection += intersection->direction;
diff --git a/core/Shape.cpp b/core/Shape.cpp
index 6d1a0cc..a0df08e 100644
--- a/core/Shape.cpp
+++ b/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::const_iterator contour = contours.begin(); contour != contours.end(); ++contour)
- total += contour->edges.size();
+ total += (int) contour->edges.size();
return total;
}
diff --git a/core/ShapeDistanceFinder.h b/core/ShapeDistanceFinder.h
new file mode 100644
index 0000000..57df8d8
--- /dev/null
+++ b/core/ShapeDistanceFinder.h
@@ -0,0 +1,37 @@
+
+#pragma once
+
+#include
+#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 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 shapeEdgeCache;
+
+};
+
+typedef ShapeDistanceFinder > SimpleTrueShapeDistanceFinder;
+
+}
+
+#include "ShapeDistanceFinder.hpp"
diff --git a/core/ShapeDistanceFinder.hpp b/core/ShapeDistanceFinder.hpp
new file mode 100644
index 0000000..028738e
--- /dev/null
+++ b/core/ShapeDistanceFinder.hpp
@@ -0,0 +1,56 @@
+
+#include "ShapeDistanceFinder.h"
+
+namespace msdfgen {
+
+template
+ShapeDistanceFinder::ShapeDistanceFinder(const Shape &shape) : shape(shape), contourCombiner(shape), shapeEdgeCache(shape.edgeCount()) { }
+
+template
+typename ShapeDistanceFinder::DistanceType ShapeDistanceFinder::distance(const Point2 &origin) {
+ contourCombiner.reset(origin);
+ typename ContourCombiner::EdgeSelectorType::EdgeCache *edgeCache = &shapeEdgeCache[0];
+
+ for (std::vector::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::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
+typename ShapeDistanceFinder::DistanceType ShapeDistanceFinder::oneShotDistance(const Shape &shape, const Point2 &origin) {
+ ContourCombiner contourCombiner(shape);
+ contourCombiner.reset(origin);
+
+ for (std::vector::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::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();
+}
+
+}
diff --git a/core/bitmap-interpolation.hpp b/core/bitmap-interpolation.hpp
new file mode 100644
index 0000000..a14b0fb
--- /dev/null
+++ b/core/bitmap-interpolation.hpp
@@ -0,0 +1,25 @@
+
+#pragma once
+
+#include "arithmetics.hpp"
+#include "Vector2.h"
+#include "BitmapRef.hpp"
+
+namespace msdfgen {
+
+template
+static void interpolate(T *output, const BitmapConstRef &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);
+}
+
+}
diff --git a/core/msdf-edge-artifact-patcher.cpp b/core/msdf-edge-artifact-patcher.cpp
new file mode 100644
index 0000000..a1d8c76
--- /dev/null
+++ b/core/msdf-edge-artifact-patcher.cpp
@@ -0,0 +1,173 @@
+
+#include "msdf-edge-artifact-patcher.h"
+
+#include
+#include
+#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
+void findHotspots(std::vector &hotspots, const BitmapConstRef &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 class ContourCombiner, int N>
+static void msdfPatchEdgeArtifactsInner(const BitmapRef &sdf, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
+ ShapeDistanceFinder > distanceFinder(shape);
+ std::vector hotspots;
+ findHotspots(hotspots, BitmapConstRef(sdf));
+ std::vector > artifacts;
+ artifacts.reserve(hotspots.size());
+ for (std::vector::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(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(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 >::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 &sdf, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
+ if (overlapSupport)
+ msdfPatchEdgeArtifactsInner(sdf, shape, range, scale, translate);
+ else
+ msdfPatchEdgeArtifactsInner(sdf, shape, range, scale, translate);
+}
+
+void msdfPatchEdgeArtifacts(const BitmapRef &sdf, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
+ if (overlapSupport)
+ msdfPatchEdgeArtifactsInner(sdf, shape, range, scale, translate);
+ else
+ msdfPatchEdgeArtifactsInner(sdf, shape, range, scale, translate);
+}
+
+}
diff --git a/core/msdf-edge-artifact-patcher.h b/core/msdf-edge-artifact-patcher.h
new file mode 100644
index 0000000..b49355f
--- /dev/null
+++ b/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 &sdf, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
+void msdfPatchEdgeArtifacts(const BitmapRef &sdf, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
+
+}
diff --git a/core/msdf-error-correction.cpp b/core/msdf-error-correction.cpp
new file mode 100644
index 0000000..a5af696
--- /dev/null
+++ b/core/msdf-error-correction.cpp
@@ -0,0 +1,77 @@
+
+#include "msdf-error-correction.h"
+
+#include
+#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
+void msdfErrorCorrectionInner(const BitmapRef &output, const Vector2 &threshold) {
+ std::vector > 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 >::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 >::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 &output, const Vector2 &threshold) {
+ msdfErrorCorrectionInner(output, threshold);
+}
+void msdfErrorCorrection(const BitmapRef &output, const Vector2 &threshold) {
+ msdfErrorCorrectionInner(output, threshold);
+}
+
+}
diff --git a/core/msdf-error-correction.h b/core/msdf-error-correction.h
new file mode 100644
index 0000000..ec43bd7
--- /dev/null
+++ b/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 &output, const Vector2 &threshold);
+void msdfErrorCorrection(const BitmapRef &output, const Vector2 &threshold);
+
+}
diff --git a/core/msdfgen.cpp b/core/msdfgen.cpp
index a6534ac..ec0f2bf 100644
--- a/core/msdfgen.cpp
+++ b/core/msdfgen.cpp
@@ -4,6 +4,8 @@
#include
#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
void generateDistanceField(const typename DistancePixelConversion::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 shapeEdgeCache(edgeCount);
+ ShapeDistanceFinder distanceFinder(shape);
bool rightToLeft = false;
Point2 p;
#ifdef MSDFGEN_USE_OPENMP
@@ -62,26 +62,7 @@ void generateDistanceField(const typename DistancePixelConversion::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::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::convert(output(x, row), distance, range);
}
rightToLeft = !rightToLeft;
@@ -110,6 +91,7 @@ void generateMSDF(const BitmapRef &output, const Shape &shape, double
generateDistanceField >(output, shape, range, scale, translate);
if (edgeThreshold > 0)
msdfErrorCorrection(output, edgeThreshold/(scale*range));
+ msdfPatchEdgeArtifacts(output, shape, range, scale, translate, overlapSupport);
}
void generateMTSDF(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold, bool overlapSupport) {
@@ -119,74 +101,7 @@ void generateMTSDF(const BitmapRef &output, const Shape &shape, double
generateDistanceField >(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
-void msdfErrorCorrectionInner(const BitmapRef &output, const Vector2 &threshold) {
- std::vector > 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 >::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 >::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 &output, const Vector2 &threshold) {
- msdfErrorCorrectionInner(output, threshold);
-}
-void msdfErrorCorrection(const BitmapRef &output, const Vector2 &threshold) {
- msdfErrorCorrectionInner(output, threshold);
+ msdfPatchEdgeArtifacts(output, shape, range, scale, translate, overlapSupport);
}
// Legacy version
diff --git a/core/render-sdf.cpp b/core/render-sdf.cpp
index ad23d58..3de9e47 100644
--- a/core/render-sdf.cpp
+++ b/core/render-sdf.cpp
@@ -3,25 +3,10 @@
#include "arithmetics.hpp"
#include "pixel-conversion.hpp"
+#include "bitmap-interpolation.hpp"
namespace msdfgen {
-template
-static void sample(T *output, const BitmapConstRef &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 &output, const BitmapConstRef &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 &output, const BitmapConstRef &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 &output, const BitmapConstRef
}
void renderSDF(const BitmapRef &output, const BitmapConstRef &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 &output, const BitmapConstRef &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 &output, const BitmapConstRef
}
void renderSDF(const BitmapRef &output, const BitmapConstRef &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 &output, const BitmapConstRef &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);
diff --git a/core/estimate-sdf-error.cpp b/core/sdf-error-estimation.cpp
similarity index 99%
rename from core/estimate-sdf-error.cpp
rename to core/sdf-error-estimation.cpp
index 4628185..3c3243a 100644
--- a/core/estimate-sdf-error.cpp
+++ b/core/sdf-error-estimation.cpp
@@ -1,5 +1,5 @@
-#include "estimate-sdf-error.h"
+#include "sdf-error-estimation.h"
#include
#include "arithmetics.hpp"
diff --git a/core/estimate-sdf-error.h b/core/sdf-error-estimation.h
similarity index 100%
rename from core/estimate-sdf-error.h
rename to core/sdf-error-estimation.h
diff --git a/core/shape-description.cpp b/core/shape-description.cpp
index 0bbb795..b6595cf 100644
--- a/core/shape-description.cpp
+++ b/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 {
diff --git a/ext/import-svg.cpp b/ext/import-svg.cpp
index 95c865e..ce686b8 100644
--- a/ext/import-svg.cpp
+++ b/ext/import-svg.cpp
@@ -1,313 +1,310 @@
-
-#define _USE_MATH_DEFINES
-#include "import-svg.h"
-
-#include
-#include
-#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
+#include
+#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());
+}
+
+}
diff --git a/main.cpp b/main.cpp
index 9d64d51..dad9739 100644
--- a/main.cpp
+++ b/main.cpp
@@ -9,6 +9,7 @@
#ifdef MSDFGEN_STANDALONE
#define _USE_MATH_DEFINES
+#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
@@ -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 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 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(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(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::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
- for (std::vector::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:;
}
diff --git a/msdfgen.h b/msdfgen.h
index c726196..cebe6a5 100644
--- a/msdfgen.h
+++ b/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 &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 &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 &output, const Vector2 &threshold);
-void msdfErrorCorrection(const BitmapRef &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 &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
void generatePseudoSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);