New MSDF edge artifact elimination routine, core algorithm refactor

This commit is contained in:
Chlumsky 2020-10-07 23:09:09 +02:00
parent 1537b787c0
commit 010f3c92f1
20 changed files with 785 additions and 470 deletions

1
.gitignore vendored
View File

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

View File

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

View File

@ -102,7 +102,22 @@
<ClInclude Include="core\save-tiff.h"> <ClInclude Include="core\save-tiff.h">
<Filter>Core</Filter> <Filter>Core</Filter>
</ClInclude> </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> <Filter>Core</Filter>
</ClInclude> </ClInclude>
</ItemGroup> </ItemGroup>
@ -176,7 +191,13 @@
<ClCompile Include="core\save-tiff.cpp"> <ClCompile Include="core\save-tiff.cpp">
<Filter>Core</Filter> <Filter>Core</Filter>
</ClCompile> </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> <Filter>Core</Filter>
</ClCompile> </ClCompile>
</ItemGroup> </ItemGroup>

View File

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

View File

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

View File

@ -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"

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -4,6 +4,8 @@
#include <vector> #include <vector>
#include "edge-selectors.h" #include "edge-selectors.h"
#include "contour-combiners.h" #include "contour-combiners.h"
#include "ShapeDistanceFinder.h"
#include "msdf-edge-artifact-patcher.h"
namespace msdfgen { namespace msdfgen {
@ -44,13 +46,11 @@ public:
template <class ContourCombiner> template <class ContourCombiner>
void generateDistanceField(const typename DistancePixelConversion<typename ContourCombiner::DistanceType>::BitmapRefType &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) { 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 #ifdef MSDFGEN_USE_OPENMP
#pragma omp parallel #pragma omp parallel
#endif #endif
{ {
ContourCombiner contourCombiner(shape); ShapeDistanceFinder<ContourCombiner> distanceFinder(shape);
std::vector<typename ContourCombiner::EdgeSelectorType::EdgeCache> shapeEdgeCache(edgeCount);
bool rightToLeft = false; bool rightToLeft = false;
Point2 p; Point2 p;
#ifdef MSDFGEN_USE_OPENMP #ifdef MSDFGEN_USE_OPENMP
@ -62,26 +62,7 @@ void generateDistanceField(const typename DistancePixelConversion<typename Conto
for (int col = 0; col < output.width; ++col) { for (int col = 0; col < output.width; ++col) {
int x = rightToLeft ? output.width-col-1 : col; int x = rightToLeft ? output.width-col-1 : col;
p.x = (x+.5)/scale.x-translate.x; p.x = (x+.5)/scale.x-translate.x;
typename ContourCombiner::DistanceType distance = distanceFinder.distance(p);
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();
DistancePixelConversion<typename ContourCombiner::DistanceType>::convert(output(x, row), distance, range); DistancePixelConversion<typename ContourCombiner::DistanceType>::convert(output(x, row), distance, range);
} }
rightToLeft = !rightToLeft; 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); generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, range, scale, translate);
if (edgeThreshold > 0) if (edgeThreshold > 0)
msdfErrorCorrection(output, edgeThreshold/(scale*range)); 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) { 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); generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, range, scale, translate);
if (edgeThreshold > 0) if (edgeThreshold > 0)
msdfErrorCorrection(output, edgeThreshold/(scale*range)); msdfErrorCorrection(output, edgeThreshold/(scale*range));
} msdfPatchEdgeArtifacts(output, shape, range, scale, translate, overlapSupport);
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);
} }
// Legacy version // Legacy version

View File

@ -3,25 +3,10 @@
#include "arithmetics.hpp" #include "arithmetics.hpp"
#include "pixel-conversion.hpp" #include "pixel-conversion.hpp"
#include "bitmap-interpolation.hpp"
namespace msdfgen { 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) { static float distVal(float dist, double pxRange) {
if (!pxRange) if (!pxRange)
return (float) (dist > .5f); 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) { 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); pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
for (int y = 0; y < output.height; ++y) for (int y = 0; y < output.height; ++y)
for (int x = 0; x < output.width; ++x) { for (int x = 0; x < output.width; ++x) {
float sd; 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); *output(x, y) = distVal(sd, pxRange);
} }
} }
void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, double 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); pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
for (int y = 0; y < output.height; ++y) for (int y = 0; y < output.height; ++y)
for (int x = 0; x < output.width; ++x) { for (int x = 0; x < output.width; ++x) {
float sd; 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); float v = distVal(sd, pxRange);
output(x, y)[0] = v; output(x, y)[0] = v;
output(x, y)[1] = 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) { 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); pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
for (int y = 0; y < output.height; ++y) for (int y = 0; y < output.height; ++y)
for (int x = 0; x < output.width; ++x) { for (int x = 0; x < output.width; ++x) {
float sd[3]; 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); *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) { 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); pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
for (int y = 0; y < output.height; ++y) for (int y = 0; y < output.height; ++y)
for (int x = 0; x < output.width; ++x) { for (int x = 0; x < output.width; ++x) {
float sd[3]; 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)[0] = distVal(sd[0], pxRange);
output(x, y)[1] = distVal(sd[1], pxRange); output(x, y)[1] = distVal(sd[1], pxRange);
output(x, y)[2] = distVal(sd[2], 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) { 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); pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
for (int y = 0; y < output.height; ++y) for (int y = 0; y < output.height; ++y)
for (int x = 0; x < output.width; ++x) { for (int x = 0; x < output.width; ++x) {
float sd[4]; 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); *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) { 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); pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
for (int y = 0; y < output.height; ++y) for (int y = 0; y < output.height; ++y)
for (int x = 0; x < output.width; ++x) { for (int x = 0; x < output.width; ++x) {
float sd[4]; 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)[0] = distVal(sd[0], pxRange);
output(x, y)[1] = distVal(sd[1], pxRange); output(x, y)[1] = distVal(sd[1], pxRange);
output(x, y)[2] = distVal(sd[2], pxRange); output(x, y)[2] = distVal(sd[2], pxRange);

View File

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

View File

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

View File

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

View File

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

View File

@ -21,17 +21,18 @@
#include "core/Shape.h" #include "core/Shape.h"
#include "core/BitmapRef.hpp" #include "core/BitmapRef.hpp"
#include "core/Bitmap.h" #include "core/Bitmap.h"
#include "core/bitmap-interpolation.hpp"
#include "core/pixel-conversion.hpp" #include "core/pixel-conversion.hpp"
#include "core/edge-coloring.h" #include "core/edge-coloring.h"
#include "core/msdf-error-correction.h"
#include "core/render-sdf.h" #include "core/render-sdf.h"
#include "core/rasterization.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-bmp.h"
#include "core/save-tiff.h" #include "core/save-tiff.h"
#include "core/shape-description.h" #include "core/shape-description.h"
#define MSDFGEN_VERSION "1.7" #define MSDFGEN_VERSION "1.7"
#define MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD 1.001
namespace msdfgen { 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. /// 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); 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. // Original simpler versions of the previous functions, which work well under normal circumstances, but cannot deal with overlapping contours.
void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate); void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate); void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);