mirror of https://github.com/Chlumsky/msdfgen.git
V1.4: Overlapping contours, cubic distance fix, guessorder
This commit is contained in:
parent
0215653a8a
commit
0e68504f44
|
|
@ -0,0 +1,12 @@
|
|||
Debug/
|
||||
Release/
|
||||
*.exe
|
||||
*.user
|
||||
*.sdf
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.iobj
|
||||
*.suo
|
||||
*.VC.opendb
|
||||
output.png
|
||||
render.png
|
||||
|
|
@ -7,18 +7,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Msdfgen", "Msdfgen.vcxproj"
|
|||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x64.Build.0 = Debug|x64
|
||||
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{84BE2D91-F071-4151-BE12-61460464C494}.Debug|x86.Build.0 = Debug|Win32
|
||||
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x64.ActiveCfg = Release|x64
|
||||
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x64.Build.0 = Release|x64
|
||||
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x86.ActiveCfg = Release|Win32
|
||||
{84BE2D91-F071-4151-BE12-61460464C494}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@
|
|||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<TargetName>msdfgen</TargetName>
|
||||
<OutDir>$(SolutionDir)\</OutDir>
|
||||
<OutDir>$(SolutionDir)\bin\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
|
|
@ -112,6 +112,7 @@
|
|||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<AdditionalLibraryDirectories>lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<GenerateDebugInformation>No</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
|
|
|
|||
17
README.md
17
README.md
|
|
@ -14,6 +14,17 @@ The following comparison demonstrates the improvement in image quality.
|
|||

|
||||

|
||||
|
||||
## New in version 1.4
|
||||
- The procedure of how contours are combined together has been reworked, and now supports overlapping contours,
|
||||
which are often present in fonts with auto-generated accented glyphs. Since this is a major change to the core algorithm,
|
||||
the original versions of all functions in [msdfgen.h](msdfgen.h) have been preserved with `_legacy` suffix,
|
||||
and can be enabled in the command line tool with **-legacy** switch.
|
||||
- A major bug has been fixed in the evaluation of signed distance of cubic curves, in which at least one of the control points
|
||||
lies at the endpoint. If you use an older version, you should update now.
|
||||
- In the standalone program, the orientation of the input is now being automatically detected by sampling the signed distance
|
||||
at an arbitrary point outside the shape's bounding box, and the output adjusted accordingly. This can be disabled
|
||||
by new option **-keeporder** or the pre-existing **-reverseorder**.
|
||||
|
||||
## Getting started
|
||||
|
||||
The project can be used either as a library or as a console program. is divided into two parts, **[core](core)**
|
||||
|
|
@ -86,7 +97,7 @@ in order to generate a distance field. Please note that all classes and function
|
|||
|
||||
- Acquire a `Shape` object. You can either load it via `loadGlyph` or `loadSvgShape`, or construct it manually.
|
||||
It consists of closed contours, which in turn consist of edges. An edge is represented by a `LinearEdge`, `QuadraticEdge`,
|
||||
or `CubicEdge`. You can construct them from two endpoints and 0 to 2 Bézier control points.
|
||||
or `CubicEdge`. You can construct them from two endpoints and 0 to 2 Bézier control points.
|
||||
- Normalize the shape using its `normalize` method and assign colors to edges if you need a multi-channel SDF.
|
||||
This can be performed automatically using the `edgeColoringSimple` heuristic, or manually by setting each edge's
|
||||
`color` member. Keep in mind that at least two color channels must be turned on in each edge, and iff two edges meet
|
||||
|
|
@ -163,7 +174,7 @@ The text shape description has the following syntax.
|
|||
- Points in a contour are separated with semicolons.
|
||||
- The last point of each contour must be equal to the first, or the symbol `#` can be used, which represents the first point.
|
||||
- There can be an edge segment specification between any two points, also separated by semicolons.
|
||||
This can include the edge's color (`c`, `m`, `y` or `w`) and/or one or two Bézier curve control points inside parentheses.
|
||||
This can include the edge's color (`c`, `m`, `y` or `w`) and/or one or two Bézier curve control points inside parentheses.
|
||||
|
||||
For example,
|
||||
```
|
||||
|
|
@ -173,4 +184,4 @@ would represent a square with magenta and yellow edges,
|
|||
```
|
||||
{ 0, 1; (+1.6, -0.8; -1.6, -0.8); # }
|
||||
```
|
||||
is a teardrop shape formed by a single cubic Bézier curve.
|
||||
is a teardrop shape formed by a single cubic Bézier curve.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
|
||||
#include "Contour.h"
|
||||
|
||||
#include "arithmetics.hpp"
|
||||
|
||||
namespace msdfgen {
|
||||
|
||||
static double shoelace(const Point2 &a, const Point2 &b) {
|
||||
return (b.x-a.x)*(a.y+b.y);
|
||||
}
|
||||
|
||||
void Contour::addEdge(const EdgeHolder &edge) {
|
||||
edges.push_back(edge);
|
||||
}
|
||||
|
|
@ -23,4 +29,30 @@ void Contour::bounds(double &l, double &b, double &r, double &t) const {
|
|||
(*edge)->bounds(l, b, r, t);
|
||||
}
|
||||
|
||||
int Contour::winding() const {
|
||||
if (edges.empty())
|
||||
return 0;
|
||||
double total = 0;
|
||||
if (edges.size() == 1) {
|
||||
Point2 a = edges[0]->point(0), b = edges[0]->point(1/3.), c = edges[0]->point(2/3.);
|
||||
total += shoelace(a, b);
|
||||
total += shoelace(b, c);
|
||||
total += shoelace(c, a);
|
||||
} else if (edges.size() == 2) {
|
||||
Point2 a = edges[0]->point(0), b = edges[0]->point(.5), c = edges[1]->point(0), d = edges[1]->point(.5);
|
||||
total += shoelace(a, b);
|
||||
total += shoelace(b, c);
|
||||
total += shoelace(c, d);
|
||||
total += shoelace(d, a);
|
||||
} else {
|
||||
Point2 prev = edges[edges.size()-1]->point(0);
|
||||
for (std::vector<EdgeHolder>::const_iterator edge = edges.begin(); edge != edges.end(); ++edge) {
|
||||
Point2 cur = (*edge)->point(0);
|
||||
total += shoelace(prev, cur);
|
||||
prev = cur;
|
||||
}
|
||||
}
|
||||
return sign(total);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ public:
|
|||
EdgeHolder & addEdge();
|
||||
/// Computes the bounding box of the contour.
|
||||
void bounds(double &l, double &b, double &r, double &t) const;
|
||||
/// Computes the winding of the contour. Returns 1 if positive, -1 if negative.
|
||||
int winding() const;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,12 @@ inline T clamp(T n, T a, T b) {
|
|||
return n >= a && n <= b ? n : n < a ? a : b;
|
||||
}
|
||||
|
||||
/// Returns 1 for positive values, -1 for negative values, and 0 for zero.
|
||||
template <typename T>
|
||||
inline int sign(T n) {
|
||||
return (T(0) < n)-(n < T(0));
|
||||
}
|
||||
|
||||
/// Returns 1 for non-negative values and -1 for negative values.
|
||||
template <typename T>
|
||||
inline int nonZeroSign(T n) {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ LinearSegment::LinearSegment(Point2 p0, Point2 p1, EdgeColor edgeColor) : EdgeSe
|
|||
}
|
||||
|
||||
QuadraticSegment::QuadraticSegment(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) : EdgeSegment(edgeColor) {
|
||||
if (p1 == p0 || p1 == p2)
|
||||
p1 = 0.5*(p0+p2);
|
||||
p[0] = p0;
|
||||
p[1] = p1;
|
||||
p[2] = p2;
|
||||
|
|
@ -84,7 +86,12 @@ Vector2 QuadraticSegment::direction(double param) const {
|
|||
}
|
||||
|
||||
Vector2 CubicSegment::direction(double param) const {
|
||||
return mix(mix(p[1]-p[0], p[2]-p[1], param), mix(p[2]-p[1], p[3]-p[2], param), param);
|
||||
Vector2 tangent = mix(mix(p[1]-p[0], p[2]-p[1], param), mix(p[2]-p[1], p[3]-p[2], param), param);
|
||||
if (!tangent) {
|
||||
if (param == 0) return p[2]-p[0];
|
||||
if (param == 1) return p[3]-p[1];
|
||||
}
|
||||
return tangent;
|
||||
}
|
||||
|
||||
SignedDistance LinearSegment::signedDistance(Point2 origin, double ¶m) const {
|
||||
|
|
@ -146,13 +153,15 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const
|
|||
Vector2 br = p[2]-p[1]-ab;
|
||||
Vector2 as = (p[3]-p[2])-(p[2]-p[1])-br;
|
||||
|
||||
double minDistance = nonZeroSign(crossProduct(ab, qa))*qa.length(); // distance from A
|
||||
param = -dotProduct(qa, ab)/dotProduct(ab, ab);
|
||||
Vector2 epDir = direction(0);
|
||||
double minDistance = nonZeroSign(crossProduct(epDir, qa))*qa.length(); // distance from A
|
||||
param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir);
|
||||
{
|
||||
double distance = nonZeroSign(crossProduct(p[3]-p[2], p[3]-origin))*(p[3]-origin).length(); // distance from B
|
||||
epDir = direction(1);
|
||||
double distance = nonZeroSign(crossProduct(epDir, p[3]-origin))*(p[3]-origin).length(); // distance from B
|
||||
if (fabs(distance) < fabs(minDistance)) {
|
||||
minDistance = distance;
|
||||
param = dotProduct(origin-p[2], p[3]-p[2])/dotProduct(p[3]-p[2], p[3]-p[2]);
|
||||
param = dotProduct(origin+epDir-p[3], epDir)/dotProduct(epDir, epDir);
|
||||
}
|
||||
}
|
||||
// Iterative minimum distance search
|
||||
|
|
@ -179,55 +188,11 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const
|
|||
if (param >= 0 && param <= 1)
|
||||
return SignedDistance(minDistance, 0);
|
||||
if (param < .5)
|
||||
return SignedDistance(minDistance, fabs(dotProduct(ab.normalize(), qa.normalize())));
|
||||
return SignedDistance(minDistance, fabs(dotProduct(direction(0).normalize(), qa.normalize())));
|
||||
else
|
||||
return SignedDistance(minDistance, fabs(dotProduct((p[3]-p[2]).normalize(), (p[3]-origin).normalize())));
|
||||
return SignedDistance(minDistance, fabs(dotProduct(direction(1).normalize(), (p[3]-origin).normalize())));
|
||||
}
|
||||
|
||||
// Original method by solving a fifth order polynomial
|
||||
/*SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const {
|
||||
Vector2 qa = p[0]-origin;
|
||||
Vector2 ab = p[1]-p[0];
|
||||
Vector2 br = p[2]-p[1]-ab;
|
||||
Vector2 as = (p[3]-p[2])-(p[2]-p[1])-br;
|
||||
double a = dotProduct(as, as);
|
||||
double b = 5*dotProduct(br, as);
|
||||
double c = 4*dotProduct(ab, as)+6*dotProduct(br, br);
|
||||
double d = 9*dotProduct(ab, br)+dotProduct(qa, as);
|
||||
double e = 3*dotProduct(ab, ab)+2*dotProduct(qa, br);
|
||||
double f = dotProduct(qa, ab);
|
||||
double t[5];
|
||||
int solutions = solveQuintic(t, a, b, c, d, e, f);
|
||||
|
||||
double minDistance = nonZeroSign(crossProduct(ab, qa))*qa.length(); // distance from A
|
||||
param = -dotProduct(qa, ab)/dotProduct(ab, ab);
|
||||
{
|
||||
double distance = nonZeroSign(crossProduct(p[3]-p[2], p[3]-origin))*(p[3]-origin).length(); // distance from B
|
||||
if (fabs(distance) < fabs(minDistance)) {
|
||||
minDistance = distance;
|
||||
param = dotProduct(origin-p[2], p[3]-p[2])/dotProduct(p[3]-p[2], p[3]-p[2]);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < solutions; ++i) {
|
||||
if (t[i] > 0 && t[i] < 1) {
|
||||
Point2 endpoint = p[0]+3*t[i]*ab+3*t[i]*t[i]*br+t[i]*t[i]*t[i]*as;
|
||||
Vector2 dirVec = t[i]*t[i]*as+2*t[i]*br+ab;
|
||||
double distance = nonZeroSign(crossProduct(dirVec, endpoint-origin))*(endpoint-origin).length();
|
||||
if (fabs(distance) <= fabs(minDistance)) {
|
||||
minDistance = distance;
|
||||
param = t[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (param >= 0 && param <= 1)
|
||||
return SignedDistance(minDistance, 0);
|
||||
if (param < .5)
|
||||
return SignedDistance(minDistance, fabs(dotProduct(ab.normalize(), qa.normalize())));
|
||||
else
|
||||
return SignedDistance(minDistance, fabs(dotProduct((p[3]-p[2]).normalize(), (p[3]-origin).normalize())));
|
||||
}*/
|
||||
|
||||
static void pointBounds(Point2 p, double &l, double &b, double &r, double &t) {
|
||||
if (p.x < l) l = p.x;
|
||||
if (p.y < b) b = p.y;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,4 @@ int solveQuadratic(double x[2], double a, double b, double c);
|
|||
// ax^3 + bx^2 + cx + d = 0
|
||||
int solveCubic(double x[3], double a, double b, double c, double d);
|
||||
|
||||
// ax^5 + bx^4 + cx^3 + dx^2 + ex + f = 0
|
||||
//int solveQuintic(double x[5], double a, double b, double c, double d, double e, double f);
|
||||
|
||||
}
|
||||
|
|
|
|||
376
core/msdfgen.cpp
376
core/msdfgen.cpp
|
|
@ -5,56 +5,10 @@
|
|||
|
||||
namespace msdfgen {
|
||||
|
||||
void generateSDF(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
|
||||
int w = output.width(), h = output.height();
|
||||
#ifdef MSDFGEN_USE_OPENMP
|
||||
#pragma omp parallel for
|
||||
#endif
|
||||
for (int y = 0; y < h; ++y) {
|
||||
int row = shape.inverseYAxis ? h-y-1 : y;
|
||||
for (int x = 0; x < w; ++x) {
|
||||
double dummy;
|
||||
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
||||
SignedDistance minDistance;
|
||||
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
||||
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
||||
SignedDistance distance = (*edge)->signedDistance(p, dummy);
|
||||
if (distance < minDistance)
|
||||
minDistance = distance;
|
||||
}
|
||||
output(x, row) = float(minDistance.distance/range+.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generatePseudoSDF(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
|
||||
int w = output.width(), h = output.height();
|
||||
#ifdef MSDFGEN_USE_OPENMP
|
||||
#pragma omp parallel for
|
||||
#endif
|
||||
for (int y = 0; y < h; ++y) {
|
||||
int row = shape.inverseYAxis ? h-y-1 : y;
|
||||
for (int x = 0; x < w; ++x) {
|
||||
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
||||
SignedDistance minDistance;
|
||||
const EdgeHolder *nearEdge = NULL;
|
||||
double nearParam = 0;
|
||||
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) {
|
||||
double param;
|
||||
SignedDistance distance = (*edge)->signedDistance(p, param);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
nearEdge = &*edge;
|
||||
nearParam = param;
|
||||
}
|
||||
}
|
||||
if (nearEdge)
|
||||
(*nearEdge)->distanceToPseudoDistance(minDistance, p, nearParam);
|
||||
output(x, row) = float(minDistance.distance/range+.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
struct MultiDistance {
|
||||
double r, g, b;
|
||||
double med;
|
||||
};
|
||||
|
||||
static inline bool pixelClash(const FloatRGB &a, const FloatRGB &b, double threshold) {
|
||||
// Only consider pair where both are on the inside or both are on the outside
|
||||
|
|
@ -108,7 +62,329 @@ void msdfErrorCorrection(Bitmap<FloatRGB> &output, const Vector2 &threshold) {
|
|||
}
|
||||
}
|
||||
|
||||
void generateSDF(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
|
||||
int contourCount = shape.contours.size();
|
||||
int w = output.width(), h = output.height();
|
||||
std::vector<int> windings;
|
||||
windings.reserve(contourCount);
|
||||
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
||||
windings.push_back(contour->winding());
|
||||
|
||||
#ifdef MSDFGEN_USE_OPENMP
|
||||
#pragma omp parallel
|
||||
#endif
|
||||
{
|
||||
std::vector<double> contourSD;
|
||||
contourSD.resize(contourCount);
|
||||
#ifdef MSDFGEN_USE_OPENMP
|
||||
#pragma omp for
|
||||
#endif
|
||||
for (int y = 0; y < h; ++y) {
|
||||
int row = shape.inverseYAxis ? h-y-1 : y;
|
||||
for (int x = 0; x < w; ++x) {
|
||||
double dummy;
|
||||
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
||||
double negDist = -SignedDistance::INFINITE.distance;
|
||||
double posDist = SignedDistance::INFINITE.distance;
|
||||
int winding = 0;
|
||||
|
||||
std::vector<Contour>::const_iterator contour = shape.contours.begin();
|
||||
for (int i = 0; i < contourCount; ++i, ++contour) {
|
||||
SignedDistance minDistance;
|
||||
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;
|
||||
}
|
||||
contourSD[i] = minDistance.distance;
|
||||
if (windings[i] > 0 && minDistance.distance >= 0 && fabs(minDistance.distance) < fabs(posDist))
|
||||
posDist = minDistance.distance;
|
||||
if (windings[i] < 0 && minDistance.distance <= 0 && fabs(minDistance.distance) < fabs(negDist))
|
||||
negDist = minDistance.distance;
|
||||
}
|
||||
|
||||
double sd = SignedDistance::INFINITE.distance;
|
||||
if (posDist >= 0 && fabs(posDist) <= fabs(negDist)) {
|
||||
sd = posDist;
|
||||
winding = 1;
|
||||
for (int i = 0; i < contourCount; ++i)
|
||||
if (windings[i] > 0 && contourSD[i] > sd && fabs(contourSD[i]) < fabs(negDist))
|
||||
sd = contourSD[i];
|
||||
} else if (negDist <= 0 && fabs(negDist) <= fabs(posDist)) {
|
||||
sd = negDist;
|
||||
winding = -1;
|
||||
for (int i = 0; i < contourCount; ++i)
|
||||
if (windings[i] < 0 && contourSD[i] < sd && fabs(contourSD[i]) < fabs(posDist))
|
||||
sd = contourSD[i];
|
||||
}
|
||||
for (int i = 0; i < contourCount; ++i)
|
||||
if (windings[i] != winding && fabs(contourSD[i]) < fabs(sd))
|
||||
sd = contourSD[i];
|
||||
|
||||
output(x, row) = float(sd/range+.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generatePseudoSDF(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
|
||||
int contourCount = shape.contours.size();
|
||||
int w = output.width(), h = output.height();
|
||||
std::vector<int> windings;
|
||||
windings.reserve(contourCount);
|
||||
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
||||
windings.push_back(contour->winding());
|
||||
|
||||
#ifdef MSDFGEN_USE_OPENMP
|
||||
#pragma omp parallel
|
||||
#endif
|
||||
{
|
||||
std::vector<double> contourSD;
|
||||
contourSD.resize(contourCount);
|
||||
#ifdef MSDFGEN_USE_OPENMP
|
||||
#pragma omp for
|
||||
#endif
|
||||
for (int y = 0; y < h; ++y) {
|
||||
int row = shape.inverseYAxis ? h-y-1 : y;
|
||||
for (int x = 0; x < w; ++x) {
|
||||
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
||||
double sd = SignedDistance::INFINITE.distance;
|
||||
double negDist = -SignedDistance::INFINITE.distance;
|
||||
double posDist = SignedDistance::INFINITE.distance;
|
||||
int winding = 0;
|
||||
|
||||
std::vector<Contour>::const_iterator contour = shape.contours.begin();
|
||||
for (int i = 0; i < contourCount; ++i, ++contour) {
|
||||
SignedDistance minDistance;
|
||||
const EdgeHolder *nearEdge = NULL;
|
||||
double nearParam = 0;
|
||||
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
||||
double param;
|
||||
SignedDistance distance = (*edge)->signedDistance(p, param);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
nearEdge = &*edge;
|
||||
nearParam = param;
|
||||
}
|
||||
}
|
||||
if (fabs(minDistance.distance) < fabs(sd)) {
|
||||
sd = minDistance.distance;
|
||||
winding = -windings[i];
|
||||
}
|
||||
if (nearEdge)
|
||||
(*nearEdge)->distanceToPseudoDistance(minDistance, p, nearParam);
|
||||
contourSD[i] = minDistance.distance;
|
||||
if (windings[i] > 0 && minDistance.distance >= 0 && fabs(minDistance.distance) < fabs(posDist))
|
||||
posDist = minDistance.distance;
|
||||
if (windings[i] < 0 && minDistance.distance <= 0 && fabs(minDistance.distance) < fabs(negDist))
|
||||
negDist = minDistance.distance;
|
||||
}
|
||||
|
||||
double psd = SignedDistance::INFINITE.distance;
|
||||
if (posDist >= 0 && fabs(posDist) <= fabs(negDist)) {
|
||||
psd = posDist;
|
||||
winding = 1;
|
||||
for (int i = 0; i < contourCount; ++i)
|
||||
if (windings[i] > 0 && contourSD[i] > psd && fabs(contourSD[i]) < fabs(negDist))
|
||||
psd = contourSD[i];
|
||||
} else if (negDist <= 0 && fabs(negDist) <= fabs(posDist)) {
|
||||
psd = negDist;
|
||||
winding = -1;
|
||||
for (int i = 0; i < contourCount; ++i)
|
||||
if (windings[i] < 0 && contourSD[i] < psd && fabs(contourSD[i]) < fabs(posDist))
|
||||
psd = contourSD[i];
|
||||
}
|
||||
for (int i = 0; i < contourCount; ++i)
|
||||
if (windings[i] != winding && fabs(contourSD[i]) < fabs(psd))
|
||||
psd = contourSD[i];
|
||||
|
||||
output(x, row) = float(psd/range+.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generateMSDF(Bitmap<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold) {
|
||||
int contourCount = shape.contours.size();
|
||||
int w = output.width(), h = output.height();
|
||||
std::vector<int> windings;
|
||||
windings.reserve(contourCount);
|
||||
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
||||
windings.push_back(contour->winding());
|
||||
|
||||
#ifdef MSDFGEN_USE_OPENMP
|
||||
#pragma omp parallel
|
||||
#endif
|
||||
{
|
||||
std::vector<MultiDistance> contourSD;
|
||||
contourSD.resize(contourCount);
|
||||
#ifdef MSDFGEN_USE_OPENMP
|
||||
#pragma omp for
|
||||
#endif
|
||||
for (int y = 0; y < h; ++y) {
|
||||
int row = shape.inverseYAxis ? h-y-1 : y;
|
||||
for (int x = 0; x < w; ++x) {
|
||||
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
||||
|
||||
struct EdgePoint {
|
||||
SignedDistance minDistance;
|
||||
const EdgeHolder *nearEdge;
|
||||
double nearParam;
|
||||
} sr, sg, sb;
|
||||
sr.nearEdge = sg.nearEdge = sb.nearEdge = NULL;
|
||||
sr.nearParam = sg.nearParam = sb.nearParam = 0;
|
||||
double d = fabs(SignedDistance::INFINITE.distance);
|
||||
double negDist = -SignedDistance::INFINITE.distance;
|
||||
double posDist = SignedDistance::INFINITE.distance;
|
||||
int winding = 0;
|
||||
|
||||
std::vector<Contour>::const_iterator contour = shape.contours.begin();
|
||||
for (int i = 0; i < contourCount; ++i, ++contour) {
|
||||
EdgePoint r, g, b;
|
||||
r.nearEdge = g.nearEdge = b.nearEdge = NULL;
|
||||
r.nearParam = g.nearParam = b.nearParam = 0;
|
||||
|
||||
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
||||
double param;
|
||||
SignedDistance distance = (*edge)->signedDistance(p, param);
|
||||
if ((*edge)->color&RED && distance < r.minDistance) {
|
||||
r.minDistance = distance;
|
||||
r.nearEdge = &*edge;
|
||||
r.nearParam = param;
|
||||
}
|
||||
if ((*edge)->color&GREEN && distance < g.minDistance) {
|
||||
g.minDistance = distance;
|
||||
g.nearEdge = &*edge;
|
||||
g.nearParam = param;
|
||||
}
|
||||
if ((*edge)->color&BLUE && distance < b.minDistance) {
|
||||
b.minDistance = distance;
|
||||
b.nearEdge = &*edge;
|
||||
b.nearParam = param;
|
||||
}
|
||||
}
|
||||
if (r.minDistance < sr.minDistance)
|
||||
sr = r;
|
||||
if (g.minDistance < sg.minDistance)
|
||||
sg = g;
|
||||
if (b.minDistance < sb.minDistance)
|
||||
sb = b;
|
||||
|
||||
double medMinDistance = fabs(median(r.minDistance.distance, g.minDistance.distance, b.minDistance.distance));
|
||||
if (medMinDistance < d) {
|
||||
d = medMinDistance;
|
||||
winding = -windings[i];
|
||||
}
|
||||
if (r.nearEdge)
|
||||
(*r.nearEdge)->distanceToPseudoDistance(r.minDistance, p, r.nearParam);
|
||||
if (g.nearEdge)
|
||||
(*g.nearEdge)->distanceToPseudoDistance(g.minDistance, p, g.nearParam);
|
||||
if (b.nearEdge)
|
||||
(*b.nearEdge)->distanceToPseudoDistance(b.minDistance, p, b.nearParam);
|
||||
medMinDistance = median(r.minDistance.distance, g.minDistance.distance, b.minDistance.distance);
|
||||
contourSD[i].r = r.minDistance.distance;
|
||||
contourSD[i].g = g.minDistance.distance;
|
||||
contourSD[i].b = b.minDistance.distance;
|
||||
contourSD[i].med = medMinDistance;
|
||||
if (windings[i] > 0 && medMinDistance >= 0 && fabs(medMinDistance) < fabs(posDist))
|
||||
posDist = medMinDistance;
|
||||
if (windings[i] < 0 && medMinDistance <= 0 && fabs(medMinDistance) < fabs(negDist))
|
||||
negDist = medMinDistance;
|
||||
}
|
||||
if (sr.nearEdge)
|
||||
(*sr.nearEdge)->distanceToPseudoDistance(sr.minDistance, p, sr.nearParam);
|
||||
if (sg.nearEdge)
|
||||
(*sg.nearEdge)->distanceToPseudoDistance(sg.minDistance, p, sg.nearParam);
|
||||
if (sb.nearEdge)
|
||||
(*sb.nearEdge)->distanceToPseudoDistance(sb.minDistance, p, sb.nearParam);
|
||||
|
||||
MultiDistance msd;
|
||||
msd.r = msd.g = msd.b = msd.med = SignedDistance::INFINITE.distance;
|
||||
if (posDist >= 0 && fabs(posDist) <= fabs(negDist)) {
|
||||
msd.med = SignedDistance::INFINITE.distance;
|
||||
winding = 1;
|
||||
for (int i = 0; i < contourCount; ++i)
|
||||
if (windings[i] > 0 && contourSD[i].med > msd.med && fabs(contourSD[i].med) < fabs(negDist))
|
||||
msd = contourSD[i];
|
||||
} else if (negDist <= 0 && fabs(negDist) <= fabs(posDist)) {
|
||||
msd.med = -SignedDistance::INFINITE.distance;
|
||||
winding = -1;
|
||||
for (int i = 0; i < contourCount; ++i)
|
||||
if (windings[i] < 0 && contourSD[i].med < msd.med && fabs(contourSD[i].med) < fabs(posDist))
|
||||
msd = contourSD[i];
|
||||
}
|
||||
for (int i = 0; i < contourCount; ++i)
|
||||
if (windings[i] != winding && fabs(contourSD[i].med) < fabs(msd.med))
|
||||
msd = contourSD[i];
|
||||
if (median(sr.minDistance.distance, sg.minDistance.distance, sb.minDistance.distance) == msd.med) {
|
||||
msd.r = sr.minDistance.distance;
|
||||
msd.g = sg.minDistance.distance;
|
||||
msd.b = sb.minDistance.distance;
|
||||
}
|
||||
|
||||
output(x, row).r = float(msd.r/range+.5);
|
||||
output(x, row).g = float(msd.g/range+.5);
|
||||
output(x, row).b = float(msd.b/range+.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (edgeThreshold > 0)
|
||||
msdfErrorCorrection(output, edgeThreshold/(scale*range));
|
||||
}
|
||||
|
||||
void generateSDF_legacy(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
|
||||
int w = output.width(), h = output.height();
|
||||
#ifdef MSDFGEN_USE_OPENMP
|
||||
#pragma omp parallel for
|
||||
#endif
|
||||
for (int y = 0; y < h; ++y) {
|
||||
int row = shape.inverseYAxis ? h-y-1 : y;
|
||||
for (int x = 0; x < w; ++x) {
|
||||
double dummy;
|
||||
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
||||
SignedDistance minDistance;
|
||||
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
||||
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
||||
SignedDistance distance = (*edge)->signedDistance(p, dummy);
|
||||
if (distance < minDistance)
|
||||
minDistance = distance;
|
||||
}
|
||||
output(x, row) = float(minDistance.distance/range+.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generatePseudoSDF_legacy(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
|
||||
int w = output.width(), h = output.height();
|
||||
#ifdef MSDFGEN_USE_OPENMP
|
||||
#pragma omp parallel for
|
||||
#endif
|
||||
for (int y = 0; y < h; ++y) {
|
||||
int row = shape.inverseYAxis ? h-y-1 : y;
|
||||
for (int x = 0; x < w; ++x) {
|
||||
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
||||
SignedDistance minDistance;
|
||||
const EdgeHolder *nearEdge = NULL;
|
||||
double nearParam = 0;
|
||||
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) {
|
||||
double param;
|
||||
SignedDistance distance = (*edge)->signedDistance(p, param);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
nearEdge = &*edge;
|
||||
nearParam = param;
|
||||
}
|
||||
}
|
||||
if (nearEdge)
|
||||
(*nearEdge)->distanceToPseudoDistance(minDistance, p, nearParam);
|
||||
output(x, row) = float(minDistance.distance/range+.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generateMSDF_legacy(Bitmap<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold) {
|
||||
int w = output.width(), h = output.height();
|
||||
#ifdef MSDFGEN_USE_OPENMP
|
||||
#pragma omp parallel for
|
||||
|
|
|
|||
|
|
@ -53,12 +53,10 @@ static bool readDouble(double &output, const char *&pathDef) {
|
|||
}
|
||||
|
||||
static void consumeOptionalComma(const char *&pathDef) {
|
||||
while (*pathDef == ' ') {
|
||||
while (*pathDef == ' ')
|
||||
++pathDef;
|
||||
}
|
||||
if (*pathDef == ',') {
|
||||
if (*pathDef == ',')
|
||||
++pathDef;
|
||||
}
|
||||
}
|
||||
|
||||
static bool buildFromPath(Shape &shape, const char *pathDef) {
|
||||
|
|
@ -74,48 +72,38 @@ static bool buildFromPath(Shape &shape, const char *pathDef) {
|
|||
|
||||
while (true) {
|
||||
switch (nodeType) {
|
||||
case 'M':
|
||||
case 'm':
|
||||
case 'M': case 'm':
|
||||
REQUIRE(contourStart);
|
||||
REQUIRE(readCoord(node, pathDef));
|
||||
if (nodeType == 'm') {
|
||||
if (nodeType == 'm')
|
||||
node += prevNode;
|
||||
}
|
||||
startPoint = node;
|
||||
--nodeType; // to 'L' or 'l'
|
||||
break;
|
||||
case 'Z':
|
||||
case 'z':
|
||||
case 'Z': case 'z':
|
||||
if (prevNode != startPoint)
|
||||
contour.addEdge(new LinearSegment(prevNode, startPoint));
|
||||
prevNode = startPoint;
|
||||
goto NEXT_CONTOUR;
|
||||
case 'L':
|
||||
case 'l':
|
||||
case 'L': case 'l':
|
||||
REQUIRE(readCoord(node, pathDef));
|
||||
if (nodeType == 'l') {
|
||||
if (nodeType == 'l')
|
||||
node += prevNode;
|
||||
}
|
||||
contour.addEdge(new LinearSegment(prevNode, node));
|
||||
break;
|
||||
case 'H':
|
||||
case 'h':
|
||||
case 'H': case 'h':
|
||||
REQUIRE(readDouble(node.x, pathDef));
|
||||
if (nodeType == 'h') {
|
||||
if (nodeType == 'h')
|
||||
node.x += prevNode.x;
|
||||
}
|
||||
contour.addEdge(new LinearSegment(prevNode, node));
|
||||
break;
|
||||
case 'V':
|
||||
case 'v':
|
||||
case 'V': case 'v':
|
||||
REQUIRE(readDouble(node.y, pathDef));
|
||||
if (nodeType == 'v') {
|
||||
if (nodeType == 'v')
|
||||
node.y += prevNode.y;
|
||||
}
|
||||
contour.addEdge(new LinearSegment(prevNode, node));
|
||||
break;
|
||||
case 'Q':
|
||||
case 'q':
|
||||
case 'Q': case 'q':
|
||||
REQUIRE(readCoord(controlPoint[0], pathDef));
|
||||
consumeOptionalComma(pathDef);
|
||||
REQUIRE(readCoord(node, pathDef));
|
||||
|
|
@ -126,8 +114,7 @@ static bool buildFromPath(Shape &shape, const char *pathDef) {
|
|||
contour.addEdge(new QuadraticSegment(prevNode, controlPoint[0], node));
|
||||
break;
|
||||
// TODO T, t
|
||||
case 'C':
|
||||
case 'c':
|
||||
case 'C': case 'c':
|
||||
REQUIRE(readCoord(controlPoint[0], pathDef));
|
||||
consumeOptionalComma(pathDef);
|
||||
REQUIRE(readCoord(controlPoint[1], pathDef));
|
||||
|
|
@ -140,9 +127,8 @@ static bool buildFromPath(Shape &shape, const char *pathDef) {
|
|||
}
|
||||
contour.addEdge(new CubicSegment(prevNode, controlPoint[0], controlPoint[1], node));
|
||||
break;
|
||||
case 'S':
|
||||
case 's':
|
||||
controlPoint[0] = node * 2 - controlPoint[1];
|
||||
case 'S': case 's':
|
||||
controlPoint[0] = node+node-controlPoint[1];
|
||||
REQUIRE(readCoord(controlPoint[1], pathDef));
|
||||
consumeOptionalComma(pathDef);
|
||||
REQUIRE(readCoord(node, pathDef));
|
||||
|
|
|
|||
70
main.cpp
70
main.cpp
|
|
@ -1,8 +1,8 @@
|
|||
|
||||
/*
|
||||
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.3 (2016-12-07) - standalone console program
|
||||
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.4 (2017-02-09) - standalone console program
|
||||
* --------------------------------------------------------------------------------------------
|
||||
* A utility by Viktor Chlumsky, (c) 2014 - 2016
|
||||
* A utility by Viktor Chlumsky, (c) 2014 - 2017
|
||||
*
|
||||
*/
|
||||
|
||||
|
|
@ -300,6 +300,10 @@ static const char *helpText =
|
|||
"\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
|
||||
" -help\n"
|
||||
"\tDisplays this help.\n"
|
||||
" -keeporder\n"
|
||||
"\tDisables the detection of shape orientation and keeps it as is.\n"
|
||||
" -legacy\n"
|
||||
"\tUses the original (legacy) distance field algorithms.\n"
|
||||
" -o <filename>\n"
|
||||
"\tSets the output file name. The default value is \"output.png\".\n"
|
||||
" -printmetrics\n"
|
||||
|
|
@ -320,12 +324,12 @@ static const char *helpText =
|
|||
"\tRenders an image preview without flattening the color channels.\n"
|
||||
" -translate <x> <y>\n"
|
||||
"\tSets the translation of the shape in shape units.\n"
|
||||
" -yflip\n"
|
||||
"\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
|
||||
" -reverseorder\n"
|
||||
"\tReverses the order of the points in each contour.\n"
|
||||
"\tDisables the detection of shape orientation and reverses the order of its vertices.\n"
|
||||
" -seed <n>\n"
|
||||
"\tSets the random seed for edge coloring heuristic.\n"
|
||||
" -yflip\n"
|
||||
"\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
|
||||
"\n";
|
||||
|
||||
int main(int argc, const char * const *argv) {
|
||||
|
|
@ -346,6 +350,7 @@ int main(int argc, const char * const *argv) {
|
|||
MULTI,
|
||||
METRICS
|
||||
} mode = MULTI;
|
||||
bool legacyMode = false;
|
||||
Format format = AUTO;
|
||||
const char *input = NULL;
|
||||
const char *output = "output.png";
|
||||
|
|
@ -375,7 +380,11 @@ int main(int argc, const char * const *argv) {
|
|||
bool yFlip = false;
|
||||
bool printMetrics = false;
|
||||
bool skipColoring = false;
|
||||
bool reverseOrder = false;
|
||||
enum {
|
||||
KEEP,
|
||||
REVERSE,
|
||||
GUESS
|
||||
} orientation = GUESS;
|
||||
unsigned long long coloringSeed = 0;
|
||||
|
||||
int argPos = 1;
|
||||
|
|
@ -433,6 +442,11 @@ int main(int argc, const char * const *argv) {
|
|||
argPos += 1;
|
||||
continue;
|
||||
}
|
||||
ARG_CASE("-legacy", 0) {
|
||||
legacyMode = true;
|
||||
argPos += 1;
|
||||
continue;
|
||||
}
|
||||
ARG_CASE("-format", 1) {
|
||||
if (!strcmp(argv[argPos+1], "auto")) format = AUTO;
|
||||
else if (!strcmp(argv[argPos+1], "png")) SETFORMAT(PNG, "png");
|
||||
|
|
@ -566,8 +580,18 @@ int main(int argc, const char * const *argv) {
|
|||
argPos += 1;
|
||||
continue;
|
||||
}
|
||||
ARG_CASE("-keeporder", 0) {
|
||||
orientation = KEEP;
|
||||
argPos += 1;
|
||||
continue;
|
||||
}
|
||||
ARG_CASE("-reverseorder", 0) {
|
||||
reverseOrder = true;
|
||||
orientation = REVERSE;
|
||||
argPos += 1;
|
||||
continue;
|
||||
}
|
||||
ARG_CASE("-guessorder", 0) {
|
||||
orientation = GUESS;
|
||||
argPos += 1;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -653,7 +677,7 @@ int main(int argc, const char * const *argv) {
|
|||
} bounds = {
|
||||
LARGE_VALUE, LARGE_VALUE, -LARGE_VALUE, -LARGE_VALUE
|
||||
};
|
||||
if (autoFrame || mode == METRICS || printMetrics)
|
||||
if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS)
|
||||
shape.bounds(bounds.l, bounds.b, bounds.r, bounds.t);
|
||||
|
||||
// Auto-frame
|
||||
|
|
@ -719,12 +743,18 @@ int main(int argc, const char * const *argv) {
|
|||
switch (mode) {
|
||||
case SINGLE: {
|
||||
sdf = Bitmap<float>(width, height);
|
||||
generateSDF(sdf, shape, range, scale, translate);
|
||||
if (legacyMode)
|
||||
generateSDF_legacy(sdf, shape, range, scale, translate);
|
||||
else
|
||||
generateSDF(sdf, shape, range, scale, translate);
|
||||
break;
|
||||
}
|
||||
case PSEUDO: {
|
||||
sdf = Bitmap<float>(width, height);
|
||||
generatePseudoSDF(sdf, shape, range, scale, translate);
|
||||
if (legacyMode)
|
||||
generatePseudoSDF_legacy(sdf, shape, range, scale, translate);
|
||||
else
|
||||
generatePseudoSDF(sdf, shape, range, scale, translate);
|
||||
break;
|
||||
}
|
||||
case MULTI: {
|
||||
|
|
@ -733,14 +763,30 @@ int main(int argc, const char * const *argv) {
|
|||
if (edgeAssignment)
|
||||
parseColoring(shape, edgeAssignment);
|
||||
msdf = Bitmap<FloatRGB>(width, height);
|
||||
generateMSDF(msdf, shape, range, scale, translate, edgeThreshold);
|
||||
if (legacyMode)
|
||||
generateMSDF_legacy(msdf, shape, range, scale, translate, edgeThreshold);
|
||||
else
|
||||
generateMSDF(msdf, shape, range, scale, translate, edgeThreshold);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (reverseOrder) {
|
||||
if (orientation == GUESS) {
|
||||
// Get sign of signed distance outside bounds
|
||||
Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
|
||||
double dummy;
|
||||
SignedDistance minDistance;
|
||||
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
||||
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
||||
SignedDistance distance = (*edge)->signedDistance(p, dummy);
|
||||
if (distance < minDistance)
|
||||
minDistance = distance;
|
||||
}
|
||||
orientation = minDistance.distance <= 0 ? KEEP : REVERSE;
|
||||
}
|
||||
if (orientation == REVERSE) {
|
||||
invertColor(sdf);
|
||||
invertColor(msdf);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
#pragma once
|
||||
|
||||
/*
|
||||
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.1 (2016-05-08) - extensions
|
||||
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.4 (2017-02-09) - extensions
|
||||
* ----------------------------------------------------------------------------
|
||||
* A utility by Viktor Chlumsky, (c) 2014 - 2016
|
||||
* A utility by Viktor Chlumsky, (c) 2014 - 2017
|
||||
*
|
||||
* The extension module provides ways to easily load input and save output using popular formats.
|
||||
*
|
||||
|
|
|
|||
11
msdfgen.h
11
msdfgen.h
|
|
@ -2,9 +2,9 @@
|
|||
#pragma once
|
||||
|
||||
/*
|
||||
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.3 (2016-12-07)
|
||||
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR v1.4 (2017-02-09)
|
||||
* ---------------------------------------------------------------
|
||||
* A utility by Viktor Chlumsky, (c) 2014 - 2016
|
||||
* A utility by Viktor Chlumsky, (c) 2014 - 2017
|
||||
*
|
||||
* The technique used to generate multi-channel distance fields in this code
|
||||
* has been developed by Viktor Chlumsky in 2014 for his master's thesis,
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
#include "core/save-bmp.h"
|
||||
#include "core/shape-description.h"
|
||||
|
||||
#define MSDFGEN_VERSION "1.3"
|
||||
#define MSDFGEN_VERSION "1.4"
|
||||
|
||||
namespace msdfgen {
|
||||
|
||||
|
|
@ -37,4 +37,9 @@ void generatePseudoSDF(Bitmap<float> &output, const Shape &shape, double range,
|
|||
/// Generates a multi-channel signed distance field. Edge colors must be assigned first! (see edgeColoringSimple)
|
||||
void generateMSDF(Bitmap<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.00000001);
|
||||
|
||||
// Original simpler versions of the previous functions, which work well under normal circumstances, but cannot deal with overlapping contours.
|
||||
void generateSDF_legacy(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
|
||||
void generatePseudoSDF_legacy(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
|
||||
void generateMSDF_legacy(Bitmap<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.00000001);
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue