4929 lines
199 KiB
C++
4929 lines
199 KiB
C++
|
|
/*
|
|
* MULTI-CHANNEL SIGNED DISTANCE FIELD GENERATOR
|
|
* ---------------------------------------------
|
|
* https://github.com/Chlumsky/msdfgen
|
|
*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2014 - 2024 Viktor Chlumsky
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
#ifndef _CRT_SECURE_NO_WARNINGS
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#endif
|
|
|
|
#include "msdfgen.h"
|
|
|
|
#include <queue>
|
|
|
|
#ifdef MSDFGEN_USE_FREETYPE
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
#include FT_OUTLINE_H
|
|
#ifndef MSDFGEN_DISABLE_VARIABLE_FONTS
|
|
#include FT_MULTIPLE_MASTERS_H
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wshadow"
|
|
#elif defined(_MSC_VER)
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4456 4457 4458 6246)
|
|
#endif
|
|
|
|
#ifndef M_PI
|
|
#define M_PI 3.1415926535897932384626433832795
|
|
#endif
|
|
|
|
#ifdef MSDFGEN_PARENT_NAMESPACE
|
|
namespace MSDFGEN_PARENT_NAMESPACE {
|
|
#endif
|
|
|
|
#ifndef MSDFGEN_CUBE_ROOT
|
|
#define MSDFGEN_CUBE_ROOT(x) pow((x), 1/3.)
|
|
#endif
|
|
|
|
namespace msdfgen {
|
|
|
|
int solveQuadratic(double x[2], double a, double b, double c) {
|
|
// a == 0 -> linear equation
|
|
if (a == 0 || fabs(b) > 1e12*fabs(a)) {
|
|
// a == 0, b == 0 -> no solution
|
|
if (b == 0) {
|
|
if (c == 0)
|
|
return -1; // 0 == 0
|
|
return 0;
|
|
}
|
|
x[0] = -c/b;
|
|
return 1;
|
|
}
|
|
double dscr = b*b-4*a*c;
|
|
if (dscr > 0) {
|
|
dscr = sqrt(dscr);
|
|
x[0] = (-b+dscr)/(2*a);
|
|
x[1] = (-b-dscr)/(2*a);
|
|
return 2;
|
|
} else if (dscr == 0) {
|
|
x[0] = -b/(2*a);
|
|
return 1;
|
|
} else
|
|
return 0;
|
|
}
|
|
|
|
static int solveCubicNormed(double x[3], double a, double b, double c) {
|
|
double a2 = a*a;
|
|
double q = 1/9.*(a2-3*b);
|
|
double r = 1/54.*(a*(2*a2-9*b)+27*c);
|
|
double r2 = r*r;
|
|
double q3 = q*q*q;
|
|
a *= 1/3.;
|
|
if (r2 < q3) {
|
|
double t = r/sqrt(q3);
|
|
if (t < -1) t = -1;
|
|
if (t > 1) t = 1;
|
|
t = acos(t);
|
|
q = -2*sqrt(q);
|
|
x[0] = q*cos(1/3.*t)-a;
|
|
x[1] = q*cos(1/3.*(t+2*M_PI))-a;
|
|
x[2] = q*cos(1/3.*(t-2*M_PI))-a;
|
|
return 3;
|
|
} else {
|
|
double u = (r < 0 ? 1 : -1)*MSDFGEN_CUBE_ROOT(fabs(r)+sqrt(r2-q3));
|
|
double v = u == 0 ? 0 : q/u;
|
|
x[0] = (u+v)-a;
|
|
if (u == v || fabs(u-v) < 1e-12*fabs(u+v)) {
|
|
x[1] = -.5*(u+v)-a;
|
|
return 2;
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int solveCubic(double x[3], double a, double b, double c, double d) {
|
|
if (a != 0) {
|
|
double bn = b/a;
|
|
if (fabs(bn) < 1e6) // Above this ratio, the numerical error gets larger than if we treated a as zero
|
|
return solveCubicNormed(x, bn, c/a, d/a);
|
|
}
|
|
return solveQuadratic(x, b, c, d);
|
|
}
|
|
|
|
Projection::Projection() : scale(1), translate(0) { }
|
|
|
|
Projection::Projection(const Vector2 &scale, const Vector2 &translate) : scale(scale), translate(translate) { }
|
|
|
|
Point2 Projection::project(const Point2 &coord) const {
|
|
return scale*(coord+translate);
|
|
}
|
|
|
|
Point2 Projection::unproject(const Point2 &coord) const {
|
|
return coord/scale-translate;
|
|
}
|
|
|
|
Vector2 Projection::projectVector(const Vector2 &vector) const {
|
|
return scale*vector;
|
|
}
|
|
|
|
Vector2 Projection::unprojectVector(const Vector2 &vector) const {
|
|
return vector/scale;
|
|
}
|
|
|
|
double Projection::projectX(double x) const {
|
|
return scale.x*(x+translate.x);
|
|
}
|
|
|
|
double Projection::projectY(double y) const {
|
|
return scale.y*(y+translate.y);
|
|
}
|
|
|
|
double Projection::unprojectX(double x) const {
|
|
return x/scale.x-translate.x;
|
|
}
|
|
|
|
double Projection::unprojectY(double y) const {
|
|
return y/scale.y-translate.y;
|
|
}
|
|
|
|
DistanceMapping DistanceMapping::inverse(Range range) {
|
|
double rangeWidth = range.upper-range.lower;
|
|
return DistanceMapping(rangeWidth, range.lower/(rangeWidth ? rangeWidth : 1));
|
|
}
|
|
|
|
DistanceMapping::DistanceMapping() : scale(1), translate(0) { }
|
|
|
|
DistanceMapping::DistanceMapping(Range range) : scale(1/(range.upper-range.lower)), translate(-range.lower) { }
|
|
|
|
double DistanceMapping::operator()(double d) const {
|
|
return scale*(d+translate);
|
|
}
|
|
|
|
double DistanceMapping::operator()(Delta d) const {
|
|
return scale*d.value;
|
|
}
|
|
|
|
DistanceMapping DistanceMapping::inverse() const {
|
|
return DistanceMapping(1/scale, -scale*translate);
|
|
}
|
|
|
|
static int compareIntersections(const void *a, const void *b) {
|
|
return sign(reinterpret_cast<const Scanline::Intersection *>(a)->x-reinterpret_cast<const Scanline::Intersection *>(b)->x);
|
|
}
|
|
|
|
bool interpretFillRule(int intersections, FillRule fillRule) {
|
|
switch (fillRule) {
|
|
case FILL_NONZERO:
|
|
return intersections != 0;
|
|
case FILL_ODD:
|
|
return intersections&1;
|
|
case FILL_POSITIVE:
|
|
return intersections > 0;
|
|
case FILL_NEGATIVE:
|
|
return intersections < 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
double Scanline::overlap(const Scanline &a, const Scanline &b, double xFrom, double xTo, FillRule fillRule) {
|
|
double total = 0;
|
|
bool aInside = false, bInside = false;
|
|
int ai = 0, bi = 0;
|
|
double ax = !a.intersections.empty() ? a.intersections[ai].x : xTo;
|
|
double bx = !b.intersections.empty() ? b.intersections[bi].x : xTo;
|
|
while (ax < xFrom || bx < xFrom) {
|
|
double xNext = min(ax, bx);
|
|
if (ax == xNext && ai < (int) a.intersections.size()) {
|
|
aInside = interpretFillRule(a.intersections[ai].direction, fillRule);
|
|
ax = ++ai < (int) a.intersections.size() ? a.intersections[ai].x : xTo;
|
|
}
|
|
if (bx == xNext && bi < (int) b.intersections.size()) {
|
|
bInside = interpretFillRule(b.intersections[bi].direction, fillRule);
|
|
bx = ++bi < (int) b.intersections.size() ? b.intersections[bi].x : xTo;
|
|
}
|
|
}
|
|
double x = xFrom;
|
|
while (ax < xTo || bx < xTo) {
|
|
double xNext = min(ax, bx);
|
|
if (aInside == bInside)
|
|
total += xNext-x;
|
|
if (ax == xNext && ai < (int) a.intersections.size()) {
|
|
aInside = interpretFillRule(a.intersections[ai].direction, fillRule);
|
|
ax = ++ai < (int) a.intersections.size() ? a.intersections[ai].x : xTo;
|
|
}
|
|
if (bx == xNext && bi < (int) b.intersections.size()) {
|
|
bInside = interpretFillRule(b.intersections[bi].direction, fillRule);
|
|
bx = ++bi < (int) b.intersections.size() ? b.intersections[bi].x : xTo;
|
|
}
|
|
x = xNext;
|
|
}
|
|
if (aInside == bInside)
|
|
total += xTo-x;
|
|
return total;
|
|
}
|
|
|
|
Scanline::Scanline() : lastIndex(0) { }
|
|
|
|
void Scanline::preprocess() {
|
|
lastIndex = 0;
|
|
if (!intersections.empty()) {
|
|
qsort(&intersections[0], intersections.size(), sizeof(Intersection), compareIntersections);
|
|
int totalDirection = 0;
|
|
for (std::vector<Intersection>::iterator intersection = intersections.begin(); intersection != intersections.end(); ++intersection) {
|
|
totalDirection += intersection->direction;
|
|
intersection->direction = totalDirection;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Scanline::setIntersections(const std::vector<Intersection> &intersections) {
|
|
this->intersections = intersections;
|
|
preprocess();
|
|
}
|
|
|
|
#ifdef MSDFGEN_USE_CPP11
|
|
void Scanline::setIntersections(std::vector<Intersection> &&intersections) {
|
|
this->intersections = (std::vector<Intersection> &&) intersections;
|
|
preprocess();
|
|
}
|
|
#endif
|
|
|
|
int Scanline::moveTo(double x) const {
|
|
if (intersections.empty())
|
|
return -1;
|
|
int index = lastIndex;
|
|
if (x < intersections[index].x) {
|
|
do {
|
|
if (index == 0) {
|
|
lastIndex = 0;
|
|
return -1;
|
|
}
|
|
--index;
|
|
} while (x < intersections[index].x);
|
|
} else {
|
|
while (index < (int) intersections.size()-1 && x >= intersections[index+1].x)
|
|
++index;
|
|
}
|
|
lastIndex = index;
|
|
return index;
|
|
}
|
|
|
|
int Scanline::countIntersections(double x) const {
|
|
return moveTo(x)+1;
|
|
}
|
|
|
|
int Scanline::sumIntersections(double x) const {
|
|
int index = moveTo(x);
|
|
if (index >= 0)
|
|
return intersections[index].direction;
|
|
return 0;
|
|
}
|
|
|
|
bool Scanline::filled(double x, FillRule fillRule) const {
|
|
return interpretFillRule(sumIntersections(x), fillRule);
|
|
}
|
|
|
|
}
|
|
|
|
#define MSDFGEN_USE_BEZIER_SOLVER
|
|
|
|
namespace msdfgen {
|
|
|
|
EdgeSegment *EdgeSegment::create(Point2 p0, Point2 p1, EdgeColor edgeColor) {
|
|
return new LinearSegment(p0, p1, edgeColor);
|
|
}
|
|
|
|
EdgeSegment *EdgeSegment::create(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) {
|
|
if (!crossProduct(p1-p0, p2-p1))
|
|
return new LinearSegment(p0, p2, edgeColor);
|
|
return new QuadraticSegment(p0, p1, p2, edgeColor);
|
|
}
|
|
|
|
EdgeSegment *EdgeSegment::create(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) {
|
|
Vector2 p12 = p2-p1;
|
|
if (!crossProduct(p1-p0, p12) && !crossProduct(p12, p3-p2))
|
|
return new LinearSegment(p0, p3, edgeColor);
|
|
if ((p12 = 1.5*p1-.5*p0) == 1.5*p2-.5*p3)
|
|
return new QuadraticSegment(p0, p12, p3, edgeColor);
|
|
return new CubicSegment(p0, p1, p2, p3, edgeColor);
|
|
}
|
|
|
|
void EdgeSegment::distanceToPerpendicularDistance(SignedDistance &distance, Point2 origin, double param) const {
|
|
if (param < 0) {
|
|
Vector2 dir = direction(0).normalize();
|
|
Vector2 aq = origin-point(0);
|
|
double ts = dotProduct(aq, dir);
|
|
if (ts < 0) {
|
|
double perpendicularDistance = crossProduct(aq, dir);
|
|
if (fabs(perpendicularDistance) <= fabs(distance.distance)) {
|
|
distance.distance = perpendicularDistance;
|
|
distance.dot = 0;
|
|
}
|
|
}
|
|
} else if (param > 1) {
|
|
Vector2 dir = direction(1).normalize();
|
|
Vector2 bq = origin-point(1);
|
|
double ts = dotProduct(bq, dir);
|
|
if (ts > 0) {
|
|
double perpendicularDistance = crossProduct(bq, dir);
|
|
if (fabs(perpendicularDistance) <= fabs(distance.distance)) {
|
|
distance.distance = perpendicularDistance;
|
|
distance.dot = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LinearSegment::LinearSegment(Point2 p0, Point2 p1, EdgeColor edgeColor) : EdgeSegment(edgeColor) {
|
|
p[0] = p0;
|
|
p[1] = p1;
|
|
}
|
|
|
|
QuadraticSegment::QuadraticSegment(Point2 p0, Point2 p1, Point2 p2, EdgeColor edgeColor) : EdgeSegment(edgeColor) {
|
|
p[0] = p0;
|
|
p[1] = p1;
|
|
p[2] = p2;
|
|
}
|
|
|
|
CubicSegment::CubicSegment(Point2 p0, Point2 p1, Point2 p2, Point2 p3, EdgeColor edgeColor) : EdgeSegment(edgeColor) {
|
|
p[0] = p0;
|
|
p[1] = p1;
|
|
p[2] = p2;
|
|
p[3] = p3;
|
|
}
|
|
|
|
LinearSegment *LinearSegment::clone() const {
|
|
return new LinearSegment(p[0], p[1], color);
|
|
}
|
|
|
|
QuadraticSegment *QuadraticSegment::clone() const {
|
|
return new QuadraticSegment(p[0], p[1], p[2], color);
|
|
}
|
|
|
|
CubicSegment *CubicSegment::clone() const {
|
|
return new CubicSegment(p[0], p[1], p[2], p[3], color);
|
|
}
|
|
|
|
int LinearSegment::type() const {
|
|
return (int) EDGE_TYPE;
|
|
}
|
|
|
|
int QuadraticSegment::type() const {
|
|
return (int) EDGE_TYPE;
|
|
}
|
|
|
|
int CubicSegment::type() const {
|
|
return (int) EDGE_TYPE;
|
|
}
|
|
|
|
const Point2 *LinearSegment::controlPoints() const {
|
|
return p;
|
|
}
|
|
|
|
const Point2 *QuadraticSegment::controlPoints() const {
|
|
return p;
|
|
}
|
|
|
|
const Point2 *CubicSegment::controlPoints() const {
|
|
return p;
|
|
}
|
|
|
|
Point2 LinearSegment::point(double param) const {
|
|
return mix(p[0], p[1], param);
|
|
}
|
|
|
|
Point2 QuadraticSegment::point(double param) const {
|
|
return mix(mix(p[0], p[1], param), mix(p[1], p[2], param), param);
|
|
}
|
|
|
|
Point2 CubicSegment::point(double param) const {
|
|
Vector2 p12 = mix(p[1], p[2], param);
|
|
return mix(mix(mix(p[0], p[1], param), p12, param), mix(p12, mix(p[2], p[3], param), param), param);
|
|
}
|
|
|
|
Vector2 LinearSegment::direction(double param) const {
|
|
return p[1]-p[0];
|
|
}
|
|
|
|
Vector2 QuadraticSegment::direction(double param) const {
|
|
Vector2 tangent = mix(p[1]-p[0], p[2]-p[1], param);
|
|
if (!tangent)
|
|
return p[2]-p[0];
|
|
return tangent;
|
|
}
|
|
|
|
Vector2 CubicSegment::direction(double param) const {
|
|
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;
|
|
}
|
|
|
|
Vector2 LinearSegment::directionChange(double param) const {
|
|
return Vector2();
|
|
}
|
|
|
|
Vector2 QuadraticSegment::directionChange(double param) const {
|
|
return (p[2]-p[1])-(p[1]-p[0]);
|
|
}
|
|
|
|
Vector2 CubicSegment::directionChange(double param) const {
|
|
return mix((p[2]-p[1])-(p[1]-p[0]), (p[3]-p[2])-(p[2]-p[1]), param);
|
|
}
|
|
|
|
double LinearSegment::length() const {
|
|
return (p[1]-p[0]).length();
|
|
}
|
|
|
|
double QuadraticSegment::length() const {
|
|
Vector2 ab = p[1]-p[0];
|
|
Vector2 br = p[2]-p[1]-ab;
|
|
double abab = dotProduct(ab, ab);
|
|
double abbr = dotProduct(ab, br);
|
|
double brbr = dotProduct(br, br);
|
|
double abLen = sqrt(abab);
|
|
double brLen = sqrt(brbr);
|
|
double crs = crossProduct(ab, br);
|
|
double h = sqrt(abab+abbr+abbr+brbr);
|
|
return (
|
|
brLen*((abbr+brbr)*h-abbr*abLen)+
|
|
crs*crs*log((brLen*h+abbr+brbr)/(brLen*abLen+abbr))
|
|
)/(brbr*brLen);
|
|
}
|
|
|
|
SignedDistance LinearSegment::signedDistance(Point2 origin, double ¶m) const {
|
|
Vector2 aq = origin-p[0];
|
|
Vector2 ab = p[1]-p[0];
|
|
param = dotProduct(aq, ab)/dotProduct(ab, ab);
|
|
Vector2 eq = p[param > .5]-origin;
|
|
double endpointDistance = eq.length();
|
|
if (param > 0 && param < 1) {
|
|
double orthoDistance = dotProduct(ab.getOrthonormal(false), aq);
|
|
if (fabs(orthoDistance) < endpointDistance)
|
|
return SignedDistance(orthoDistance, 0);
|
|
}
|
|
return SignedDistance(nonZeroSign(crossProduct(aq, ab))*endpointDistance, fabs(dotProduct(ab.normalize(), eq.normalize())));
|
|
}
|
|
|
|
#ifdef MSDFGEN_USE_BEZIER_SOLVER
|
|
|
|
SignedDistance QuadraticSegment::signedDistance(Point2 origin, double ¶m) const {
|
|
Vector2 ap = origin-p[0];
|
|
Vector2 bp = origin-p[2];
|
|
Vector2 q = 2*(p[1]-p[0]);
|
|
Vector2 r = p[2]-2*p[1]+p[0];
|
|
double aSqD = ap.squaredLength();
|
|
double bSqD = bp.squaredLength();
|
|
double t = quadraticNearPoint(ap, q, r);
|
|
if (t > 0 && t < 1) {
|
|
Vector2 tp = ap-(q+r*t)*t;
|
|
double tSqD = tp.squaredLength();
|
|
if (tSqD < aSqD && tSqD < bSqD) {
|
|
param = t;
|
|
return SignedDistance(nonZeroSign(crossProduct(tp, q+2*r*t))*sqrt(tSqD), 0);
|
|
}
|
|
}
|
|
if (bSqD < aSqD) {
|
|
Vector2 d = q+r+r;
|
|
if (!d)
|
|
d = p[2]-p[0];
|
|
param = dotProduct(bp, d)/d.squaredLength()+1;
|
|
return SignedDistance(nonZeroSign(crossProduct(bp, d))*sqrt(bSqD), dotProduct(bp.normalize(), d.normalize()));
|
|
}
|
|
if (!q)
|
|
q = p[2]-p[0];
|
|
param = dotProduct(ap, q)/q.squaredLength();
|
|
return SignedDistance(nonZeroSign(crossProduct(ap, q))*sqrt(aSqD), -dotProduct(ap.normalize(), q.normalize()));
|
|
}
|
|
|
|
SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const {
|
|
Vector2 ap = origin-p[0];
|
|
Vector2 bp = origin-p[3];
|
|
Vector2 q = 3*(p[1]-p[0]);
|
|
Vector2 r = 3*(p[2]-p[1])-q;
|
|
Vector2 s = p[3]-3*(p[2]-p[1])-p[0];
|
|
double aSqD = ap.squaredLength();
|
|
double bSqD = bp.squaredLength();
|
|
double tSqD;
|
|
double t = cubicNearPoint(ap, q, r, s, tSqD);
|
|
if (t > 0 && t < 1) {
|
|
if (tSqD < aSqD && tSqD < bSqD) {
|
|
param = t;
|
|
return SignedDistance(nonZeroSign(crossProduct(ap-(q+(r+s*t)*t)*t, q+(r+r+3*s*t)*t))*sqrt(tSqD), 0);
|
|
}
|
|
}
|
|
if (bSqD < aSqD) {
|
|
Vector2 d = q+r+r+3*s;
|
|
if (!d)
|
|
d = p[3]-p[1];
|
|
param = dotProduct(bp, d)/d.squaredLength()+1;
|
|
return SignedDistance(nonZeroSign(crossProduct(bp, d))*sqrt(bSqD), dotProduct(bp.normalize(), d.normalize()));
|
|
}
|
|
if (!q)
|
|
q = p[2]-p[0];
|
|
param = dotProduct(ap, q)/q.squaredLength();
|
|
return SignedDistance(nonZeroSign(crossProduct(ap, q))*sqrt(aSqD), -dotProduct(ap.normalize(), q.normalize()));
|
|
}
|
|
|
|
#else
|
|
|
|
SignedDistance QuadraticSegment::signedDistance(Point2 origin, double ¶m) const {
|
|
Vector2 qa = p[0]-origin;
|
|
Vector2 ab = p[1]-p[0];
|
|
Vector2 br = p[2]-p[1]-ab;
|
|
double a = dotProduct(br, br);
|
|
double b = 3*dotProduct(ab, br);
|
|
double c = 2*dotProduct(ab, ab)+dotProduct(qa, br);
|
|
double d = dotProduct(qa, ab);
|
|
double t[3];
|
|
int solutions = solveCubic(t, a, b, c, d);
|
|
|
|
Vector2 epDir = direction(0);
|
|
double minDistance = nonZeroSign(crossProduct(epDir, qa))*qa.length(); // distance from A
|
|
param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir);
|
|
{
|
|
epDir = direction(1);
|
|
double distance = (p[2]-origin).length(); // distance from B
|
|
if (distance < fabs(minDistance)) {
|
|
minDistance = nonZeroSign(crossProduct(epDir, p[2]-origin))*distance;
|
|
param = dotProduct(origin-p[1], epDir)/dotProduct(epDir, epDir);
|
|
}
|
|
}
|
|
for (int i = 0; i < solutions; ++i) {
|
|
if (t[i] > 0 && t[i] < 1) {
|
|
Point2 qe = qa+2*t[i]*ab+t[i]*t[i]*br;
|
|
double distance = qe.length();
|
|
if (distance <= fabs(minDistance)) {
|
|
minDistance = nonZeroSign(crossProduct(ab+t[i]*br, qe))*distance;
|
|
param = t[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (param >= 0 && param <= 1)
|
|
return SignedDistance(minDistance, 0);
|
|
if (param < .5)
|
|
return SignedDistance(minDistance, fabs(dotProduct(direction(0).normalize(), qa.normalize())));
|
|
else
|
|
return SignedDistance(minDistance, fabs(dotProduct(direction(1).normalize(), (p[2]-origin).normalize())));
|
|
}
|
|
|
|
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;
|
|
|
|
Vector2 epDir = direction(0);
|
|
double minDistance = nonZeroSign(crossProduct(epDir, qa))*qa.length(); // distance from A
|
|
param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir);
|
|
{
|
|
epDir = direction(1);
|
|
double distance = (p[3]-origin).length(); // distance from B
|
|
if (distance < fabs(minDistance)) {
|
|
minDistance = nonZeroSign(crossProduct(epDir, p[3]-origin))*distance;
|
|
param = dotProduct(epDir-(p[3]-origin), epDir)/dotProduct(epDir, epDir);
|
|
}
|
|
}
|
|
// Iterative minimum distance search
|
|
for (int i = 0; i <= MSDFGEN_CUBIC_SEARCH_STARTS; ++i) {
|
|
double t = (double) i/MSDFGEN_CUBIC_SEARCH_STARTS;
|
|
Vector2 qe = qa+3*t*ab+3*t*t*br+t*t*t*as;
|
|
for (int step = 0; step < MSDFGEN_CUBIC_SEARCH_STEPS; ++step) {
|
|
// Improve t
|
|
Vector2 d1 = 3*ab+6*t*br+3*t*t*as;
|
|
Vector2 d2 = 6*br+6*t*as;
|
|
t -= dotProduct(qe, d1)/(dotProduct(d1, d1)+dotProduct(qe, d2));
|
|
if (t <= 0 || t >= 1)
|
|
break;
|
|
qe = qa+3*t*ab+3*t*t*br+t*t*t*as;
|
|
double distance = qe.length();
|
|
if (distance < fabs(minDistance)) {
|
|
minDistance = nonZeroSign(crossProduct(d1, qe))*distance;
|
|
param = t;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (param >= 0 && param <= 1)
|
|
return SignedDistance(minDistance, 0);
|
|
if (param < .5)
|
|
return SignedDistance(minDistance, fabs(dotProduct(direction(0).normalize(), qa.normalize())));
|
|
else
|
|
return SignedDistance(minDistance, fabs(dotProduct(direction(1).normalize(), (p[3]-origin).normalize())));
|
|
}
|
|
|
|
#endif
|
|
|
|
int LinearSegment::scanlineIntersections(double x[3], int dy[3], double y) const {
|
|
return horizontalScanlineIntersections(x, dy, y);
|
|
}
|
|
|
|
int QuadraticSegment::scanlineIntersections(double x[3], int dy[3], double y) const {
|
|
return horizontalScanlineIntersections(x, dy, y);
|
|
}
|
|
|
|
int CubicSegment::scanlineIntersections(double x[3], int dy[3], double y) const {
|
|
return horizontalScanlineIntersections(x, dy, y);
|
|
}
|
|
|
|
int LinearSegment::horizontalScanlineIntersections(double x[3], int dy[3], double y) const {
|
|
if ((y >= p[0].y && y < p[1].y) || (y >= p[1].y && y < p[0].y)) {
|
|
double param = (y-p[0].y)/(p[1].y-p[0].y);
|
|
x[0] = mix(p[0].x, p[1].x, param);
|
|
dy[0] = sign(p[1].y-p[0].y);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int LinearSegment::verticalScanlineIntersections(double y[3], int dx[3], double x) const {
|
|
if ((x >= p[0].x && x < p[1].x) || (x >= p[1].x && x < p[0].x)) {
|
|
double param = (x-p[0].x)/(p[1].x-p[0].x);
|
|
y[0] = mix(p[0].y, p[1].y, param);
|
|
dx[0] = sign(p[1].x-p[0].x);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int QuadraticSegment::horizontalScanlineIntersections(double x[3], int dy[3], double y) const {
|
|
int total = 0;
|
|
int nextDY = y > p[0].y ? 1 : -1;
|
|
x[total] = p[0].x;
|
|
if (p[0].y == y) {
|
|
if (p[0].y < p[1].y || (p[0].y == p[1].y && p[0].y < p[2].y))
|
|
dy[total++] = 1;
|
|
else
|
|
nextDY = 1;
|
|
}
|
|
{
|
|
Vector2 ab = p[1]-p[0];
|
|
Vector2 br = p[2]-p[1]-ab;
|
|
double t[2];
|
|
int solutions = solveQuadratic(t, br.y, 2*ab.y, p[0].y-y);
|
|
// Sort solutions
|
|
double tmp;
|
|
if (solutions >= 2 && t[0] > t[1])
|
|
tmp = t[0], t[0] = t[1], t[1] = tmp;
|
|
for (int i = 0; i < solutions && total < 2; ++i) {
|
|
if (t[i] >= 0 && t[i] <= 1) {
|
|
x[total] = p[0].x+2*t[i]*ab.x+t[i]*t[i]*br.x;
|
|
if (nextDY*(ab.y+t[i]*br.y) >= 0) {
|
|
dy[total++] = nextDY;
|
|
nextDY = -nextDY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (p[2].y == y) {
|
|
if (nextDY > 0 && total > 0) {
|
|
--total;
|
|
nextDY = -1;
|
|
}
|
|
if ((p[2].y < p[1].y || (p[2].y == p[1].y && p[2].y < p[0].y)) && total < 2) {
|
|
x[total] = p[2].x;
|
|
if (nextDY < 0) {
|
|
dy[total++] = -1;
|
|
nextDY = 1;
|
|
}
|
|
}
|
|
}
|
|
if (nextDY != (y >= p[2].y ? 1 : -1)) {
|
|
if (total > 0)
|
|
--total;
|
|
else {
|
|
if (fabs(p[2].y-y) < fabs(p[0].y-y))
|
|
x[total] = p[2].x;
|
|
dy[total++] = nextDY;
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
int QuadraticSegment::verticalScanlineIntersections(double y[3], int dx[3], double x) const {
|
|
int total = 0;
|
|
int nextDX = x > p[0].x ? 1 : -1;
|
|
y[total] = p[0].y;
|
|
if (p[0].x == x) {
|
|
if (p[0].x < p[1].x || (p[0].x == p[1].x && p[0].x < p[2].x))
|
|
dx[total++] = 1;
|
|
else
|
|
nextDX = 1;
|
|
}
|
|
{
|
|
Vector2 ab = p[1]-p[0];
|
|
Vector2 br = p[2]-p[1]-ab;
|
|
double t[2];
|
|
int solutions = solveQuadratic(t, br.x, 2*ab.x, p[0].x-x);
|
|
// Sort solutions
|
|
double tmp;
|
|
if (solutions >= 2 && t[0] > t[1])
|
|
tmp = t[0], t[0] = t[1], t[1] = tmp;
|
|
for (int i = 0; i < solutions && total < 2; ++i) {
|
|
if (t[i] >= 0 && t[i] <= 1) {
|
|
y[total] = p[0].y+2*t[i]*ab.y+t[i]*t[i]*br.y;
|
|
if (nextDX*(ab.x+t[i]*br.x) >= 0) {
|
|
dx[total++] = nextDX;
|
|
nextDX = -nextDX;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (p[2].x == x) {
|
|
if (nextDX > 0 && total > 0) {
|
|
--total;
|
|
nextDX = -1;
|
|
}
|
|
if ((p[2].x < p[1].x || (p[2].x == p[1].x && p[2].x < p[0].x)) && total < 2) {
|
|
y[total] = p[2].y;
|
|
if (nextDX < 0) {
|
|
dx[total++] = -1;
|
|
nextDX = 1;
|
|
}
|
|
}
|
|
}
|
|
if (nextDX != (x >= p[2].x ? 1 : -1)) {
|
|
if (total > 0)
|
|
--total;
|
|
else {
|
|
if (fabs(p[2].x-x) < fabs(p[0].x-x))
|
|
y[total] = p[2].y;
|
|
dx[total++] = nextDX;
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
int CubicSegment::horizontalScanlineIntersections(double x[3], int dy[3], double y) const {
|
|
int total = 0;
|
|
int nextDY = y > p[0].y ? 1 : -1;
|
|
x[total] = p[0].x;
|
|
if (p[0].y == y) {
|
|
if (p[0].y < p[1].y || (p[0].y == p[1].y && (p[0].y < p[2].y || (p[0].y == p[2].y && p[0].y < p[3].y))))
|
|
dy[total++] = 1;
|
|
else
|
|
nextDY = 1;
|
|
}
|
|
{
|
|
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 t[3];
|
|
int solutions = solveCubic(t, as.y, 3*br.y, 3*ab.y, p[0].y-y);
|
|
// Sort solutions
|
|
double tmp;
|
|
if (solutions >= 2) {
|
|
if (t[0] > t[1])
|
|
tmp = t[0], t[0] = t[1], t[1] = tmp;
|
|
if (solutions >= 3 && t[1] > t[2]) {
|
|
tmp = t[1], t[1] = t[2], t[2] = tmp;
|
|
if (t[0] > t[1])
|
|
tmp = t[0], t[0] = t[1], t[1] = tmp;
|
|
}
|
|
}
|
|
for (int i = 0; i < solutions && total < 3; ++i) {
|
|
if (t[i] >= 0 && t[i] <= 1) {
|
|
x[total] = p[0].x+3*t[i]*ab.x+3*t[i]*t[i]*br.x+t[i]*t[i]*t[i]*as.x;
|
|
if (nextDY*(ab.y+2*t[i]*br.y+t[i]*t[i]*as.y) >= 0) {
|
|
dy[total++] = nextDY;
|
|
nextDY = -nextDY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (p[3].y == y) {
|
|
if (nextDY > 0 && total > 0) {
|
|
--total;
|
|
nextDY = -1;
|
|
}
|
|
if ((p[3].y < p[2].y || (p[3].y == p[2].y && (p[3].y < p[1].y || (p[3].y == p[1].y && p[3].y < p[0].y)))) && total < 3) {
|
|
x[total] = p[3].x;
|
|
if (nextDY < 0) {
|
|
dy[total++] = -1;
|
|
nextDY = 1;
|
|
}
|
|
}
|
|
}
|
|
if (nextDY != (y >= p[3].y ? 1 : -1)) {
|
|
if (total > 0)
|
|
--total;
|
|
else {
|
|
if (fabs(p[3].y-y) < fabs(p[0].y-y))
|
|
x[total] = p[3].x;
|
|
dy[total++] = nextDY;
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
int CubicSegment::verticalScanlineIntersections(double y[3], int dx[3], double x) const {
|
|
int total = 0;
|
|
int nextDX = x > p[0].x ? 1 : -1;
|
|
y[total] = p[0].y;
|
|
if (p[0].x == x) {
|
|
if (p[0].x < p[1].x || (p[0].x == p[1].x && (p[0].x < p[2].x || (p[0].x == p[2].x && p[0].x < p[3].x))))
|
|
dx[total++] = 1;
|
|
else
|
|
nextDX = 1;
|
|
}
|
|
{
|
|
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 t[3];
|
|
int solutions = solveCubic(t, as.x, 3*br.x, 3*ab.x, p[0].x-x);
|
|
// Sort solutions
|
|
double tmp;
|
|
if (solutions >= 2) {
|
|
if (t[0] > t[1])
|
|
tmp = t[0], t[0] = t[1], t[1] = tmp;
|
|
if (solutions >= 3 && t[1] > t[2]) {
|
|
tmp = t[1], t[1] = t[2], t[2] = tmp;
|
|
if (t[0] > t[1])
|
|
tmp = t[0], t[0] = t[1], t[1] = tmp;
|
|
}
|
|
}
|
|
for (int i = 0; i < solutions && total < 3; ++i) {
|
|
if (t[i] >= 0 && t[i] <= 1) {
|
|
y[total] = p[0].y+3*t[i]*ab.y+3*t[i]*t[i]*br.y+t[i]*t[i]*t[i]*as.y;
|
|
if (nextDX*(ab.x+2*t[i]*br.x+t[i]*t[i]*as.x) >= 0) {
|
|
dx[total++] = nextDX;
|
|
nextDX = -nextDX;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (p[3].x == x) {
|
|
if (nextDX > 0 && total > 0) {
|
|
--total;
|
|
nextDX = -1;
|
|
}
|
|
if ((p[3].x < p[2].x || (p[3].x == p[2].x && (p[3].x < p[1].x || (p[3].x == p[1].x && p[3].x < p[0].x)))) && total < 3) {
|
|
y[total] = p[3].y;
|
|
if (nextDX < 0) {
|
|
dx[total++] = -1;
|
|
nextDX = 1;
|
|
}
|
|
}
|
|
}
|
|
if (nextDX != (x >= p[3].x ? 1 : -1)) {
|
|
if (total > 0)
|
|
--total;
|
|
else {
|
|
if (fabs(p[3].x-x) < fabs(p[0].x-x))
|
|
y[total] = p[3].y;
|
|
dx[total++] = nextDX;
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
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;
|
|
if (p.x > r) r = p.x;
|
|
if (p.y > t) t = p.y;
|
|
}
|
|
|
|
void LinearSegment::bound(double &l, double &b, double &r, double &t) const {
|
|
pointBounds(p[0], l, b, r, t);
|
|
pointBounds(p[1], l, b, r, t);
|
|
}
|
|
|
|
void QuadraticSegment::bound(double &l, double &b, double &r, double &t) const {
|
|
pointBounds(p[0], l, b, r, t);
|
|
pointBounds(p[2], l, b, r, t);
|
|
Vector2 bot = (p[1]-p[0])-(p[2]-p[1]);
|
|
if (bot.x) {
|
|
double param = (p[1].x-p[0].x)/bot.x;
|
|
if (param > 0 && param < 1)
|
|
pointBounds(point(param), l, b, r, t);
|
|
}
|
|
if (bot.y) {
|
|
double param = (p[1].y-p[0].y)/bot.y;
|
|
if (param > 0 && param < 1)
|
|
pointBounds(point(param), l, b, r, t);
|
|
}
|
|
}
|
|
|
|
void CubicSegment::bound(double &l, double &b, double &r, double &t) const {
|
|
pointBounds(p[0], l, b, r, t);
|
|
pointBounds(p[3], l, b, r, t);
|
|
Vector2 a0 = p[1]-p[0];
|
|
Vector2 a1 = 2*(p[2]-p[1]-a0);
|
|
Vector2 a2 = p[3]-3*p[2]+3*p[1]-p[0];
|
|
double params[2];
|
|
int solutions;
|
|
solutions = solveQuadratic(params, a2.x, a1.x, a0.x);
|
|
for (int i = 0; i < solutions; ++i)
|
|
if (params[i] > 0 && params[i] < 1)
|
|
pointBounds(point(params[i]), l, b, r, t);
|
|
solutions = solveQuadratic(params, a2.y, a1.y, a0.y);
|
|
for (int i = 0; i < solutions; ++i)
|
|
if (params[i] > 0 && params[i] < 1)
|
|
pointBounds(point(params[i]), l, b, r, t);
|
|
}
|
|
|
|
void LinearSegment::reverse() {
|
|
Point2 tmp = p[0];
|
|
p[0] = p[1];
|
|
p[1] = tmp;
|
|
}
|
|
|
|
void QuadraticSegment::reverse() {
|
|
Point2 tmp = p[0];
|
|
p[0] = p[2];
|
|
p[2] = tmp;
|
|
}
|
|
|
|
void CubicSegment::reverse() {
|
|
Point2 tmp = p[0];
|
|
p[0] = p[3];
|
|
p[3] = tmp;
|
|
tmp = p[1];
|
|
p[1] = p[2];
|
|
p[2] = tmp;
|
|
}
|
|
|
|
void LinearSegment::moveStartPoint(Point2 to) {
|
|
p[0] = to;
|
|
}
|
|
|
|
void QuadraticSegment::moveStartPoint(Point2 to) {
|
|
Vector2 origSDir = p[0]-p[1];
|
|
Point2 origP1 = p[1];
|
|
p[1] += crossProduct(p[0]-p[1], to-p[0])/crossProduct(p[0]-p[1], p[2]-p[1])*(p[2]-p[1]);
|
|
p[0] = to;
|
|
if (dotProduct(origSDir, p[0]-p[1]) < 0)
|
|
p[1] = origP1;
|
|
}
|
|
|
|
void CubicSegment::moveStartPoint(Point2 to) {
|
|
p[1] += to-p[0];
|
|
p[0] = to;
|
|
}
|
|
|
|
void LinearSegment::moveEndPoint(Point2 to) {
|
|
p[1] = to;
|
|
}
|
|
|
|
void QuadraticSegment::moveEndPoint(Point2 to) {
|
|
Vector2 origEDir = p[2]-p[1];
|
|
Point2 origP1 = p[1];
|
|
p[1] += crossProduct(p[2]-p[1], to-p[2])/crossProduct(p[2]-p[1], p[0]-p[1])*(p[0]-p[1]);
|
|
p[2] = to;
|
|
if (dotProduct(origEDir, p[2]-p[1]) < 0)
|
|
p[1] = origP1;
|
|
}
|
|
|
|
void CubicSegment::moveEndPoint(Point2 to) {
|
|
p[2] += to-p[3];
|
|
p[3] = to;
|
|
}
|
|
|
|
void LinearSegment::splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const {
|
|
part0 = new LinearSegment(p[0], point(1/3.), color);
|
|
part1 = new LinearSegment(point(1/3.), point(2/3.), color);
|
|
part2 = new LinearSegment(point(2/3.), p[1], color);
|
|
}
|
|
|
|
void QuadraticSegment::splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const {
|
|
part0 = new QuadraticSegment(p[0], mix(p[0], p[1], 1/3.), point(1/3.), color);
|
|
part1 = new QuadraticSegment(point(1/3.), mix(mix(p[0], p[1], 5/9.), mix(p[1], p[2], 4/9.), .5), point(2/3.), color);
|
|
part2 = new QuadraticSegment(point(2/3.), mix(p[1], p[2], 2/3.), p[2], color);
|
|
}
|
|
|
|
void CubicSegment::splitInThirds(EdgeSegment *&part0, EdgeSegment *&part1, EdgeSegment *&part2) const {
|
|
part0 = new CubicSegment(p[0], p[0] == p[1] ? p[0] : mix(p[0], p[1], 1/3.), mix(mix(p[0], p[1], 1/3.), mix(p[1], p[2], 1/3.), 1/3.), point(1/3.), color);
|
|
part1 = new CubicSegment(point(1/3.),
|
|
mix(mix(mix(p[0], p[1], 1/3.), mix(p[1], p[2], 1/3.), 1/3.), mix(mix(p[1], p[2], 1/3.), mix(p[2], p[3], 1/3.), 1/3.), 2/3.),
|
|
mix(mix(mix(p[0], p[1], 2/3.), mix(p[1], p[2], 2/3.), 2/3.), mix(mix(p[1], p[2], 2/3.), mix(p[2], p[3], 2/3.), 2/3.), 1/3.),
|
|
point(2/3.), color);
|
|
part2 = new CubicSegment(point(2/3.), mix(mix(p[1], p[2], 2/3.), mix(p[2], p[3], 2/3.), 2/3.), p[2] == p[3] ? p[3] : mix(p[2], p[3], 2/3.), p[3], color);
|
|
}
|
|
|
|
EdgeSegment *QuadraticSegment::convertToCubic() const {
|
|
return new CubicSegment(p[0], mix(p[0], p[1], 2/3.), mix(p[1], p[2], 1/3.), p[2], color);
|
|
}
|
|
|
|
void EdgeHolder::swap(EdgeHolder &a, EdgeHolder &b) {
|
|
EdgeSegment *tmp = a.edgeSegment;
|
|
a.edgeSegment = b.edgeSegment;
|
|
b.edgeSegment = tmp;
|
|
}
|
|
|
|
EdgeHolder::EdgeHolder(const EdgeHolder &orig) : edgeSegment(orig.edgeSegment ? orig.edgeSegment->clone() : NULL) { }
|
|
|
|
#ifdef MSDFGEN_USE_CPP11
|
|
EdgeHolder::EdgeHolder(EdgeHolder &&orig) : edgeSegment(orig.edgeSegment) {
|
|
orig.edgeSegment = NULL;
|
|
}
|
|
#endif
|
|
|
|
EdgeHolder::~EdgeHolder() {
|
|
delete edgeSegment;
|
|
}
|
|
|
|
EdgeHolder &EdgeHolder::operator=(const EdgeHolder &orig) {
|
|
if (this != &orig) {
|
|
delete edgeSegment;
|
|
edgeSegment = orig.edgeSegment ? orig.edgeSegment->clone() : NULL;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
#ifdef MSDFGEN_USE_CPP11
|
|
EdgeHolder &EdgeHolder::operator=(EdgeHolder &&orig) {
|
|
if (this != &orig) {
|
|
delete edgeSegment;
|
|
edgeSegment = orig.edgeSegment;
|
|
orig.edgeSegment = NULL;
|
|
}
|
|
return *this;
|
|
}
|
|
#endif
|
|
|
|
EdgeSegment &EdgeHolder::operator*() {
|
|
return *edgeSegment;
|
|
}
|
|
|
|
const EdgeSegment &EdgeHolder::operator*() const {
|
|
return *edgeSegment;
|
|
}
|
|
|
|
EdgeSegment *EdgeHolder::operator->() {
|
|
return edgeSegment;
|
|
}
|
|
|
|
const EdgeSegment *EdgeHolder::operator->() const {
|
|
return edgeSegment;
|
|
}
|
|
|
|
EdgeHolder::operator EdgeSegment *() {
|
|
return edgeSegment;
|
|
}
|
|
|
|
EdgeHolder::operator const EdgeSegment *() const {
|
|
return edgeSegment;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
#ifdef MSDFGEN_USE_CPP11
|
|
void Contour::addEdge(EdgeHolder &&edge) {
|
|
edges.push_back((EdgeHolder &&) edge);
|
|
}
|
|
#endif
|
|
|
|
EdgeHolder &Contour::addEdge() {
|
|
edges.resize(edges.size()+1);
|
|
return edges.back();
|
|
}
|
|
|
|
static void boundPoint(double &l, double &b, double &r, double &t, Point2 p) {
|
|
if (p.x < l) l = p.x;
|
|
if (p.y < b) b = p.y;
|
|
if (p.x > r) r = p.x;
|
|
if (p.y > t) t = p.y;
|
|
}
|
|
|
|
void Contour::bound(double &l, double &b, double &r, double &t) const {
|
|
for (std::vector<EdgeHolder>::const_iterator edge = edges.begin(); edge != edges.end(); ++edge)
|
|
(*edge)->bound(l, b, r, t);
|
|
}
|
|
|
|
void Contour::boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const {
|
|
if (edges.empty())
|
|
return;
|
|
Vector2 prevDir = edges.back()->direction(1).normalize(true);
|
|
for (std::vector<EdgeHolder>::const_iterator edge = edges.begin(); edge != edges.end(); ++edge) {
|
|
Vector2 dir = -(*edge)->direction(0).normalize(true);
|
|
if (polarity*crossProduct(prevDir, dir) >= 0) {
|
|
double miterLength = miterLimit;
|
|
double q = .5*(1-dotProduct(prevDir, dir));
|
|
if (q > 0)
|
|
miterLength = min(1/sqrt(q), miterLimit);
|
|
Point2 miter = (*edge)->point(0)+border*miterLength*(prevDir+dir).normalize(true);
|
|
boundPoint(l, b, r, t, miter);
|
|
}
|
|
prevDir = (*edge)->direction(1).normalize(true);
|
|
}
|
|
}
|
|
|
|
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.back()->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);
|
|
}
|
|
|
|
void Contour::reverse() {
|
|
for (int i = (int) edges.size()/2; i > 0; --i)
|
|
EdgeHolder::swap(edges[i-1], edges[edges.size()-i]);
|
|
for (std::vector<EdgeHolder>::iterator edge = edges.begin(); edge != edges.end(); ++edge)
|
|
(*edge)->reverse();
|
|
}
|
|
|
|
}
|
|
|
|
#define DECONVERGE_OVERSHOOT 1.11111111111111111 // moves control points slightly more than necessary to account for floating-point errors
|
|
|
|
namespace msdfgen {
|
|
|
|
Shape::Shape() : inverseYAxis(false) { }
|
|
|
|
void Shape::addContour(const Contour &contour) {
|
|
contours.push_back(contour);
|
|
}
|
|
|
|
#ifdef MSDFGEN_USE_CPP11
|
|
void Shape::addContour(Contour &&contour) {
|
|
contours.push_back((Contour &&) contour);
|
|
}
|
|
#endif
|
|
|
|
Contour &Shape::addContour() {
|
|
contours.resize(contours.size()+1);
|
|
return contours.back();
|
|
}
|
|
|
|
bool Shape::validate() const {
|
|
for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour) {
|
|
if (!contour->edges.empty()) {
|
|
Point2 corner = contour->edges.back()->point(1);
|
|
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
|
if (!*edge)
|
|
return false;
|
|
if ((*edge)->point(0) != corner)
|
|
return false;
|
|
corner = (*edge)->point(1);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void deconvergeEdge(EdgeHolder &edgeHolder, int param, Vector2 vector) {
|
|
switch (edgeHolder->type()) {
|
|
case (int) QuadraticSegment::EDGE_TYPE:
|
|
edgeHolder = static_cast<const QuadraticSegment *>(&*edgeHolder)->convertToCubic();
|
|
// fallthrough
|
|
case (int) CubicSegment::EDGE_TYPE:
|
|
{
|
|
Point2 *p = static_cast<CubicSegment *>(&*edgeHolder)->p;
|
|
switch (param) {
|
|
case 0:
|
|
p[1] += (p[1]-p[0]).length()*vector;
|
|
break;
|
|
case 1:
|
|
p[2] += (p[2]-p[3]).length()*vector;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Shape::normalize() {
|
|
for (std::vector<Contour>::iterator contour = contours.begin(); contour != contours.end(); ++contour) {
|
|
if (contour->edges.size() == 1) {
|
|
EdgeSegment *parts[3] = { };
|
|
contour->edges[0]->splitInThirds(parts[0], parts[1], parts[2]);
|
|
contour->edges.clear();
|
|
contour->edges.push_back(EdgeHolder(parts[0]));
|
|
contour->edges.push_back(EdgeHolder(parts[1]));
|
|
contour->edges.push_back(EdgeHolder(parts[2]));
|
|
} else {
|
|
// Push apart convergent edge segments
|
|
EdgeHolder *prevEdge = &contour->edges.back();
|
|
for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
|
Vector2 prevDir = (*prevEdge)->direction(1).normalize();
|
|
Vector2 curDir = (*edge)->direction(0).normalize();
|
|
if (dotProduct(prevDir, curDir) < MSDFGEN_CORNER_DOT_EPSILON-1) {
|
|
double factor = DECONVERGE_OVERSHOOT*sqrt(1-(MSDFGEN_CORNER_DOT_EPSILON-1)*(MSDFGEN_CORNER_DOT_EPSILON-1))/(MSDFGEN_CORNER_DOT_EPSILON-1);
|
|
Vector2 axis = factor*(curDir-prevDir).normalize();
|
|
// Determine curve ordering using third-order derivative (t = 0) of crossProduct((*prevEdge)->point(1-t)-p0, (*edge)->point(t)-p0) where p0 is the corner (*edge)->point(0)
|
|
if (crossProduct((*prevEdge)->directionChange(1), (*edge)->direction(0))+crossProduct((*edge)->directionChange(0), (*prevEdge)->direction(1)) < 0)
|
|
axis = -axis;
|
|
deconvergeEdge(*prevEdge, 1, axis.getOrthogonal(true));
|
|
deconvergeEdge(*edge, 0, axis.getOrthogonal(false));
|
|
}
|
|
prevEdge = &*edge;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Shape::bound(double &l, double &b, double &r, double &t) const {
|
|
for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour)
|
|
contour->bound(l, b, r, t);
|
|
}
|
|
|
|
void Shape::boundMiters(double &l, double &b, double &r, double &t, double border, double miterLimit, int polarity) const {
|
|
for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour)
|
|
contour->boundMiters(l, b, r, t, border, miterLimit, polarity);
|
|
}
|
|
|
|
Shape::Bounds Shape::getBounds(double border, double miterLimit, int polarity) const {
|
|
static const double LARGE_VALUE = 1e240;
|
|
Shape::Bounds bounds = { +LARGE_VALUE, +LARGE_VALUE, -LARGE_VALUE, -LARGE_VALUE };
|
|
bound(bounds.l, bounds.b, bounds.r, bounds.t);
|
|
if (border > 0) {
|
|
bounds.l -= border, bounds.b -= border;
|
|
bounds.r += border, bounds.t += border;
|
|
if (miterLimit > 0)
|
|
boundMiters(bounds.l, bounds.b, bounds.r, bounds.t, border, miterLimit, polarity);
|
|
}
|
|
return bounds;
|
|
}
|
|
|
|
void Shape::scanline(Scanline &line, double y) const {
|
|
std::vector<Scanline::Intersection> intersections;
|
|
double x[3];
|
|
int dy[3];
|
|
for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour) {
|
|
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
|
int n = (*edge)->scanlineIntersections(x, dy, y);
|
|
for (int i = 0; i < n; ++i) {
|
|
Scanline::Intersection intersection = { x[i], dy[i] };
|
|
intersections.push_back(intersection);
|
|
}
|
|
}
|
|
}
|
|
#ifdef MSDFGEN_USE_CPP11
|
|
line.setIntersections((std::vector<Scanline::Intersection> &&) intersections);
|
|
#else
|
|
line.setIntersections(intersections);
|
|
#endif
|
|
}
|
|
|
|
int Shape::edgeCount() const {
|
|
int total = 0;
|
|
for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour)
|
|
total += (int) contour->edges.size();
|
|
return total;
|
|
}
|
|
|
|
void Shape::orientContours() {
|
|
struct Intersection {
|
|
double x;
|
|
int direction;
|
|
int contourIndex;
|
|
|
|
static int compare(const void *a, const void *b) {
|
|
return sign(reinterpret_cast<const Intersection *>(a)->x-reinterpret_cast<const Intersection *>(b)->x);
|
|
}
|
|
};
|
|
|
|
const double ratio = .5*(sqrt(5)-1); // an irrational number to minimize chance of intersecting a corner or other point of interest
|
|
std::vector<int> orientations(contours.size());
|
|
std::vector<Intersection> intersections;
|
|
for (int i = 0; i < (int) contours.size(); ++i) {
|
|
if (!orientations[i] && !contours[i].edges.empty()) {
|
|
// Find an Y that crosses the contour
|
|
double y0 = contours[i].edges.front()->point(0).y;
|
|
double y1 = y0;
|
|
for (std::vector<EdgeHolder>::const_iterator edge = contours[i].edges.begin(); edge != contours[i].edges.end() && y0 == y1; ++edge)
|
|
y1 = (*edge)->point(1).y;
|
|
for (std::vector<EdgeHolder>::const_iterator edge = contours[i].edges.begin(); edge != contours[i].edges.end() && y0 == y1; ++edge)
|
|
y1 = (*edge)->point(ratio).y; // in case all endpoints are in a horizontal line
|
|
double y = mix(y0, y1, ratio);
|
|
// Scanline through whole shape at Y
|
|
double x[3];
|
|
int dy[3];
|
|
for (int j = 0; j < (int) contours.size(); ++j) {
|
|
for (std::vector<EdgeHolder>::const_iterator edge = contours[j].edges.begin(); edge != contours[j].edges.end(); ++edge) {
|
|
int n = (*edge)->scanlineIntersections(x, dy, y);
|
|
for (int k = 0; k < n; ++k) {
|
|
Intersection intersection = { x[k], dy[k], j };
|
|
intersections.push_back(intersection);
|
|
}
|
|
}
|
|
}
|
|
if (!intersections.empty()) {
|
|
qsort(&intersections[0], intersections.size(), sizeof(Intersection), &Intersection::compare);
|
|
// Disqualify multiple intersections
|
|
for (int j = 1; j < (int) intersections.size(); ++j)
|
|
if (intersections[j].x == intersections[j-1].x)
|
|
intersections[j].direction = intersections[j-1].direction = 0;
|
|
// Inspect scanline and deduce orientations of intersected contours
|
|
for (int j = 0; j < (int) intersections.size(); ++j)
|
|
if (intersections[j].direction)
|
|
orientations[intersections[j].contourIndex] += 2*((j&1)^(intersections[j].direction > 0))-1;
|
|
intersections.clear();
|
|
}
|
|
}
|
|
}
|
|
// Reverse contours that have the opposite orientation
|
|
for (int i = 0; i < (int) contours.size(); ++i)
|
|
if (orientations[i] < 0)
|
|
contours[i].reverse();
|
|
}
|
|
|
|
/**
|
|
* For each position < n, this function will return -1, 0, or 1,
|
|
* depending on whether the position is closer to the beginning, middle, or end, respectively.
|
|
* It is guaranteed that the output will be balanced in that the total for positions 0 through n-1 will be zero.
|
|
*/
|
|
static int symmetricalTrichotomy(int position, int n) {
|
|
return int(3+2.875*position/(n-1)-1.4375+.5)-3;
|
|
}
|
|
|
|
static bool isCorner(const Vector2 &aDir, const Vector2 &bDir, double crossThreshold) {
|
|
return dotProduct(aDir, bDir) <= 0 || fabs(crossProduct(aDir, bDir)) > crossThreshold;
|
|
}
|
|
|
|
static double estimateEdgeLength(const EdgeSegment *edge) {
|
|
double len = 0;
|
|
Point2 prev = edge->point(0);
|
|
for (int i = 1; i <= MSDFGEN_EDGE_LENGTH_PRECISION; ++i) {
|
|
Point2 cur = edge->point(1./MSDFGEN_EDGE_LENGTH_PRECISION*i);
|
|
len += (cur-prev).length();
|
|
prev = cur;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static int seedExtract2(unsigned long long &seed) {
|
|
int v = int(seed)&1;
|
|
seed >>= 1;
|
|
return v;
|
|
}
|
|
|
|
static int seedExtract3(unsigned long long &seed) {
|
|
int v = int(seed%3);
|
|
seed /= 3;
|
|
return v;
|
|
}
|
|
|
|
static EdgeColor initColor(unsigned long long &seed) {
|
|
static const EdgeColor colors[3] = { CYAN, MAGENTA, YELLOW };
|
|
return colors[seedExtract3(seed)];
|
|
}
|
|
|
|
static void switchColor(EdgeColor &color, unsigned long long &seed) {
|
|
int shifted = color<<(1+seedExtract2(seed));
|
|
color = EdgeColor((shifted|shifted>>3)&WHITE);
|
|
}
|
|
|
|
static void switchColor(EdgeColor &color, unsigned long long &seed, EdgeColor banned) {
|
|
EdgeColor combined = EdgeColor(color&banned);
|
|
if (combined == RED || combined == GREEN || combined == BLUE)
|
|
color = EdgeColor(combined^WHITE);
|
|
else
|
|
switchColor(color, seed);
|
|
}
|
|
|
|
void edgeColoringSimple(Shape &shape, double angleThreshold, unsigned long long seed) {
|
|
double crossThreshold = sin(angleThreshold);
|
|
EdgeColor color = initColor(seed);
|
|
std::vector<int> corners;
|
|
for (std::vector<Contour>::iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) {
|
|
if (contour->edges.empty())
|
|
continue;
|
|
{ // Identify corners
|
|
corners.clear();
|
|
Vector2 prevDirection = contour->edges.back()->direction(1);
|
|
int index = 0;
|
|
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge, ++index) {
|
|
if (isCorner(prevDirection.normalize(), (*edge)->direction(0).normalize(), crossThreshold))
|
|
corners.push_back(index);
|
|
prevDirection = (*edge)->direction(1);
|
|
}
|
|
}
|
|
|
|
// Smooth contour
|
|
if (corners.empty()) {
|
|
switchColor(color, seed);
|
|
for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge)
|
|
(*edge)->color = color;
|
|
}
|
|
// "Teardrop" case
|
|
else if (corners.size() == 1) {
|
|
EdgeColor colors[3];
|
|
switchColor(color, seed);
|
|
colors[0] = color;
|
|
colors[1] = WHITE;
|
|
switchColor(color, seed);
|
|
colors[2] = color;
|
|
int corner = corners[0];
|
|
if (contour->edges.size() >= 3) {
|
|
int m = (int) contour->edges.size();
|
|
for (int i = 0; i < m; ++i)
|
|
contour->edges[(corner+i)%m]->color = colors[1+symmetricalTrichotomy(i, m)];
|
|
} else if (contour->edges.size() >= 1) {
|
|
// Less than three edge segments for three colors => edges must be split
|
|
EdgeSegment *parts[7] = { };
|
|
contour->edges[0]->splitInThirds(parts[0+3*corner], parts[1+3*corner], parts[2+3*corner]);
|
|
if (contour->edges.size() >= 2) {
|
|
contour->edges[1]->splitInThirds(parts[3-3*corner], parts[4-3*corner], parts[5-3*corner]);
|
|
parts[0]->color = parts[1]->color = colors[0];
|
|
parts[2]->color = parts[3]->color = colors[1];
|
|
parts[4]->color = parts[5]->color = colors[2];
|
|
} else {
|
|
parts[0]->color = colors[0];
|
|
parts[1]->color = colors[1];
|
|
parts[2]->color = colors[2];
|
|
}
|
|
contour->edges.clear();
|
|
for (int i = 0; parts[i]; ++i)
|
|
contour->edges.push_back(EdgeHolder(parts[i]));
|
|
}
|
|
}
|
|
// Multiple corners
|
|
else {
|
|
int cornerCount = (int) corners.size();
|
|
int spline = 0;
|
|
int start = corners[0];
|
|
int m = (int) contour->edges.size();
|
|
switchColor(color, seed);
|
|
EdgeColor initialColor = color;
|
|
for (int i = 0; i < m; ++i) {
|
|
int index = (start+i)%m;
|
|
if (spline+1 < cornerCount && corners[spline+1] == index) {
|
|
++spline;
|
|
switchColor(color, seed, EdgeColor((spline == cornerCount-1)*initialColor));
|
|
}
|
|
contour->edges[index]->color = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct EdgeColoringInkTrapCorner {
|
|
int index;
|
|
double prevEdgeLengthEstimate;
|
|
bool minor;
|
|
EdgeColor color;
|
|
};
|
|
|
|
void edgeColoringInkTrap(Shape &shape, double angleThreshold, unsigned long long seed) {
|
|
typedef EdgeColoringInkTrapCorner Corner;
|
|
double crossThreshold = sin(angleThreshold);
|
|
EdgeColor color = initColor(seed);
|
|
std::vector<Corner> corners;
|
|
for (std::vector<Contour>::iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) {
|
|
if (contour->edges.empty())
|
|
continue;
|
|
double splineLength = 0;
|
|
{ // Identify corners
|
|
corners.clear();
|
|
Vector2 prevDirection = contour->edges.back()->direction(1);
|
|
int index = 0;
|
|
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge, ++index) {
|
|
if (isCorner(prevDirection.normalize(), (*edge)->direction(0).normalize(), crossThreshold)) {
|
|
Corner corner = { index, splineLength };
|
|
corners.push_back(corner);
|
|
splineLength = 0;
|
|
}
|
|
splineLength += estimateEdgeLength(*edge);
|
|
prevDirection = (*edge)->direction(1);
|
|
}
|
|
}
|
|
|
|
// Smooth contour
|
|
if (corners.empty()) {
|
|
switchColor(color, seed);
|
|
for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge)
|
|
(*edge)->color = color;
|
|
}
|
|
// "Teardrop" case
|
|
else if (corners.size() == 1) {
|
|
EdgeColor colors[3];
|
|
switchColor(color, seed);
|
|
colors[0] = color;
|
|
colors[1] = WHITE;
|
|
switchColor(color, seed);
|
|
colors[2] = color;
|
|
int corner = corners[0].index;
|
|
if (contour->edges.size() >= 3) {
|
|
int m = (int) contour->edges.size();
|
|
for (int i = 0; i < m; ++i)
|
|
contour->edges[(corner+i)%m]->color = colors[1+symmetricalTrichotomy(i, m)];
|
|
} else if (contour->edges.size() >= 1) {
|
|
// Less than three edge segments for three colors => edges must be split
|
|
EdgeSegment *parts[7] = { };
|
|
contour->edges[0]->splitInThirds(parts[0+3*corner], parts[1+3*corner], parts[2+3*corner]);
|
|
if (contour->edges.size() >= 2) {
|
|
contour->edges[1]->splitInThirds(parts[3-3*corner], parts[4-3*corner], parts[5-3*corner]);
|
|
parts[0]->color = parts[1]->color = colors[0];
|
|
parts[2]->color = parts[3]->color = colors[1];
|
|
parts[4]->color = parts[5]->color = colors[2];
|
|
} else {
|
|
parts[0]->color = colors[0];
|
|
parts[1]->color = colors[1];
|
|
parts[2]->color = colors[2];
|
|
}
|
|
contour->edges.clear();
|
|
for (int i = 0; parts[i]; ++i)
|
|
contour->edges.push_back(EdgeHolder(parts[i]));
|
|
}
|
|
}
|
|
// Multiple corners
|
|
else {
|
|
int cornerCount = (int) corners.size();
|
|
int majorCornerCount = cornerCount;
|
|
if (cornerCount > 3) {
|
|
corners.begin()->prevEdgeLengthEstimate += splineLength;
|
|
for (int i = 0; i < cornerCount; ++i) {
|
|
if (
|
|
corners[i].prevEdgeLengthEstimate > corners[(i+1)%cornerCount].prevEdgeLengthEstimate &&
|
|
corners[(i+1)%cornerCount].prevEdgeLengthEstimate < corners[(i+2)%cornerCount].prevEdgeLengthEstimate
|
|
) {
|
|
corners[i].minor = true;
|
|
--majorCornerCount;
|
|
}
|
|
}
|
|
}
|
|
EdgeColor initialColor = BLACK;
|
|
for (int i = 0; i < cornerCount; ++i) {
|
|
if (!corners[i].minor) {
|
|
--majorCornerCount;
|
|
switchColor(color, seed, EdgeColor(!majorCornerCount*initialColor));
|
|
corners[i].color = color;
|
|
if (!initialColor)
|
|
initialColor = color;
|
|
}
|
|
}
|
|
for (int i = 0; i < cornerCount; ++i) {
|
|
if (corners[i].minor) {
|
|
EdgeColor nextColor = corners[(i+1)%cornerCount].color;
|
|
corners[i].color = EdgeColor((color&nextColor)^WHITE);
|
|
} else
|
|
color = corners[i].color;
|
|
}
|
|
int spline = 0;
|
|
int start = corners[0].index;
|
|
color = corners[0].color;
|
|
int m = (int) contour->edges.size();
|
|
for (int i = 0; i < m; ++i) {
|
|
int index = (start+i)%m;
|
|
if (spline+1 < cornerCount && corners[spline+1].index == index)
|
|
color = corners[++spline].color;
|
|
contour->edges[index]->color = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// EDGE COLORING BY DISTANCE - EXPERIMENTAL IMPLEMENTATION - WORK IN PROGRESS
|
|
#define MAX_RECOLOR_STEPS 16
|
|
#define EDGE_DISTANCE_PRECISION 16
|
|
|
|
static double edgeToEdgeDistance(const EdgeSegment &a, const EdgeSegment &b, int precision) {
|
|
if (a.point(0) == b.point(0) || a.point(0) == b.point(1) || a.point(1) == b.point(0) || a.point(1) == b.point(1))
|
|
return 0;
|
|
double iFac = 1./precision;
|
|
double minDistance = (b.point(0)-a.point(0)).length();
|
|
for (int i = 0; i <= precision; ++i) {
|
|
double t = iFac*i;
|
|
double d = fabs(a.signedDistance(b.point(t), t).distance);
|
|
minDistance = min(minDistance, d);
|
|
}
|
|
for (int i = 0; i <= precision; ++i) {
|
|
double t = iFac*i;
|
|
double d = fabs(b.signedDistance(a.point(t), t).distance);
|
|
minDistance = min(minDistance, d);
|
|
}
|
|
return minDistance;
|
|
}
|
|
|
|
static double splineToSplineDistance(EdgeSegment *const *edgeSegments, int aStart, int aEnd, int bStart, int bEnd, int precision) {
|
|
double minDistance = DBL_MAX;
|
|
for (int ai = aStart; ai < aEnd; ++ai)
|
|
for (int bi = bStart; bi < bEnd && minDistance; ++bi) {
|
|
double d = edgeToEdgeDistance(*edgeSegments[ai], *edgeSegments[bi], precision);
|
|
minDistance = min(minDistance, d);
|
|
}
|
|
return minDistance;
|
|
}
|
|
|
|
static void colorSecondDegreeGraph(int *coloring, const int *const *edgeMatrix, int vertexCount, unsigned long long seed) {
|
|
for (int i = 0; i < vertexCount; ++i) {
|
|
int possibleColors = 7;
|
|
for (int j = 0; j < i; ++j) {
|
|
if (edgeMatrix[i][j])
|
|
possibleColors &= ~(1<<coloring[j]);
|
|
}
|
|
int color = 0;
|
|
switch (possibleColors) {
|
|
case 1:
|
|
color = 0;
|
|
break;
|
|
case 2:
|
|
color = 1;
|
|
break;
|
|
case 3:
|
|
color = seedExtract2(seed); // 0 or 1
|
|
break;
|
|
case 4:
|
|
color = 2;
|
|
break;
|
|
case 5:
|
|
color = (int) !seedExtract2(seed)<<1; // 2 or 0
|
|
break;
|
|
case 6:
|
|
color = seedExtract2(seed)+1; // 1 or 2
|
|
break;
|
|
case 7:
|
|
color = (seedExtract3(seed)+i)%3; // 0 or 1 or 2
|
|
break;
|
|
}
|
|
coloring[i] = color;
|
|
}
|
|
}
|
|
|
|
static int vertexPossibleColors(const int *coloring, const int *edgeVector, int vertexCount) {
|
|
int usedColors = 0;
|
|
for (int i = 0; i < vertexCount; ++i)
|
|
if (edgeVector[i])
|
|
usedColors |= 1<<coloring[i];
|
|
return 7&~usedColors;
|
|
}
|
|
|
|
static void uncolorSameNeighbors(std::queue<int> &uncolored, int *coloring, const int *const *edgeMatrix, int vertex, int vertexCount) {
|
|
for (int i = vertex+1; i < vertexCount; ++i) {
|
|
if (edgeMatrix[vertex][i] && coloring[i] == coloring[vertex]) {
|
|
coloring[i] = -1;
|
|
uncolored.push(i);
|
|
}
|
|
}
|
|
for (int i = 0; i < vertex; ++i) {
|
|
if (edgeMatrix[vertex][i] && coloring[i] == coloring[vertex]) {
|
|
coloring[i] = -1;
|
|
uncolored.push(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool tryAddEdge(int *coloring, int *const *edgeMatrix, int vertexCount, int vertexA, int vertexB, int *coloringBuffer) {
|
|
static const int FIRST_POSSIBLE_COLOR[8] = { -1, 0, 1, 0, 2, 2, 1, 0 };
|
|
edgeMatrix[vertexA][vertexB] = 1;
|
|
edgeMatrix[vertexB][vertexA] = 1;
|
|
if (coloring[vertexA] != coloring[vertexB])
|
|
return true;
|
|
int bPossibleColors = vertexPossibleColors(coloring, edgeMatrix[vertexB], vertexCount);
|
|
if (bPossibleColors) {
|
|
coloring[vertexB] = FIRST_POSSIBLE_COLOR[bPossibleColors];
|
|
return true;
|
|
}
|
|
memcpy(coloringBuffer, coloring, sizeof(int)*vertexCount);
|
|
std::queue<int> uncolored;
|
|
{
|
|
int *coloring = coloringBuffer;
|
|
coloring[vertexB] = FIRST_POSSIBLE_COLOR[7&~(1<<coloring[vertexA])];
|
|
uncolorSameNeighbors(uncolored, coloring, edgeMatrix, vertexB, vertexCount);
|
|
int step = 0;
|
|
while (!uncolored.empty() && step < MAX_RECOLOR_STEPS) {
|
|
int i = uncolored.front();
|
|
uncolored.pop();
|
|
int possibleColors = vertexPossibleColors(coloring, edgeMatrix[i], vertexCount);
|
|
if (possibleColors) {
|
|
coloring[i] = FIRST_POSSIBLE_COLOR[possibleColors];
|
|
continue;
|
|
}
|
|
do {
|
|
coloring[i] = step++%3;
|
|
} while (edgeMatrix[i][vertexA] && coloring[i] == coloring[vertexA]);
|
|
uncolorSameNeighbors(uncolored, coloring, edgeMatrix, i, vertexCount);
|
|
}
|
|
}
|
|
if (!uncolored.empty()) {
|
|
edgeMatrix[vertexA][vertexB] = 0;
|
|
edgeMatrix[vertexB][vertexA] = 0;
|
|
return false;
|
|
}
|
|
memcpy(coloring, coloringBuffer, sizeof(int)*vertexCount);
|
|
return true;
|
|
}
|
|
|
|
static int cmpDoublePtr(const void *a, const void *b) {
|
|
return sign(**reinterpret_cast<const double *const *>(a)-**reinterpret_cast<const double *const *>(b));
|
|
}
|
|
|
|
void edgeColoringByDistance(Shape &shape, double angleThreshold, unsigned long long seed) {
|
|
|
|
std::vector<EdgeSegment *> edgeSegments;
|
|
std::vector<int> splineStarts;
|
|
|
|
double crossThreshold = sin(angleThreshold);
|
|
std::vector<int> corners;
|
|
for (std::vector<Contour>::iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
|
if (!contour->edges.empty()) {
|
|
// Identify corners
|
|
corners.clear();
|
|
Vector2 prevDirection = contour->edges.back()->direction(1);
|
|
int index = 0;
|
|
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge, ++index) {
|
|
if (isCorner(prevDirection.normalize(), (*edge)->direction(0).normalize(), crossThreshold))
|
|
corners.push_back(index);
|
|
prevDirection = (*edge)->direction(1);
|
|
}
|
|
|
|
splineStarts.push_back((int) edgeSegments.size());
|
|
// Smooth contour
|
|
if (corners.empty())
|
|
for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge)
|
|
edgeSegments.push_back(&**edge);
|
|
// "Teardrop" case
|
|
else if (corners.size() == 1) {
|
|
int corner = corners[0];
|
|
if (contour->edges.size() >= 3) {
|
|
int m = (int) contour->edges.size();
|
|
for (int i = 0; i < m; ++i) {
|
|
if (i == m/2)
|
|
splineStarts.push_back((int) edgeSegments.size());
|
|
if (symmetricalTrichotomy(i, m))
|
|
edgeSegments.push_back(&*contour->edges[(corner+i)%m]);
|
|
else
|
|
contour->edges[(corner+i)%m]->color = WHITE;
|
|
}
|
|
} else if (contour->edges.size() >= 1) {
|
|
// Less than three edge segments for three colors => edges must be split
|
|
EdgeSegment *parts[7] = { };
|
|
contour->edges[0]->splitInThirds(parts[0+3*corner], parts[1+3*corner], parts[2+3*corner]);
|
|
if (contour->edges.size() >= 2) {
|
|
contour->edges[1]->splitInThirds(parts[3-3*corner], parts[4-3*corner], parts[5-3*corner]);
|
|
edgeSegments.push_back(parts[0]);
|
|
edgeSegments.push_back(parts[1]);
|
|
parts[2]->color = parts[3]->color = WHITE;
|
|
splineStarts.push_back((int) edgeSegments.size());
|
|
edgeSegments.push_back(parts[4]);
|
|
edgeSegments.push_back(parts[5]);
|
|
} else {
|
|
edgeSegments.push_back(parts[0]);
|
|
parts[1]->color = WHITE;
|
|
splineStarts.push_back((int) edgeSegments.size());
|
|
edgeSegments.push_back(parts[2]);
|
|
}
|
|
contour->edges.clear();
|
|
for (int i = 0; parts[i]; ++i)
|
|
contour->edges.push_back(EdgeHolder(parts[i]));
|
|
}
|
|
}
|
|
// Multiple corners
|
|
else {
|
|
int cornerCount = (int) corners.size();
|
|
int spline = 0;
|
|
int start = corners[0];
|
|
int m = (int) contour->edges.size();
|
|
for (int i = 0; i < m; ++i) {
|
|
int index = (start+i)%m;
|
|
if (spline+1 < cornerCount && corners[spline+1] == index) {
|
|
splineStarts.push_back((int) edgeSegments.size());
|
|
++spline;
|
|
}
|
|
edgeSegments.push_back(&*contour->edges[index]);
|
|
}
|
|
}
|
|
}
|
|
splineStarts.push_back((int) edgeSegments.size());
|
|
|
|
int segmentCount = (int) edgeSegments.size();
|
|
int splineCount = (int) splineStarts.size()-1;
|
|
if (!splineCount)
|
|
return;
|
|
|
|
std::vector<double> distanceMatrixStorage(splineCount*splineCount);
|
|
std::vector<double *> distanceMatrix(splineCount);
|
|
for (int i = 0; i < splineCount; ++i)
|
|
distanceMatrix[i] = &distanceMatrixStorage[i*splineCount];
|
|
const double *distanceMatrixBase = &distanceMatrixStorage[0];
|
|
|
|
for (int i = 0; i < splineCount; ++i) {
|
|
distanceMatrix[i][i] = -1;
|
|
for (int j = i+1; j < splineCount; ++j) {
|
|
double dist = splineToSplineDistance(&edgeSegments[0], splineStarts[i], splineStarts[i+1], splineStarts[j], splineStarts[j+1], EDGE_DISTANCE_PRECISION);
|
|
distanceMatrix[i][j] = dist;
|
|
distanceMatrix[j][i] = dist;
|
|
}
|
|
}
|
|
|
|
std::vector<const double *> graphEdgeDistances;
|
|
graphEdgeDistances.reserve(splineCount*(splineCount-1)/2);
|
|
for (int i = 0; i < splineCount; ++i)
|
|
for (int j = i+1; j < splineCount; ++j)
|
|
graphEdgeDistances.push_back(&distanceMatrix[i][j]);
|
|
int graphEdgeCount = (int) graphEdgeDistances.size();
|
|
if (!graphEdgeDistances.empty())
|
|
qsort(&graphEdgeDistances[0], graphEdgeDistances.size(), sizeof(const double *), &cmpDoublePtr);
|
|
|
|
std::vector<int> edgeMatrixStorage(splineCount*splineCount);
|
|
std::vector<int *> edgeMatrix(splineCount);
|
|
for (int i = 0; i < splineCount; ++i)
|
|
edgeMatrix[i] = &edgeMatrixStorage[i*splineCount];
|
|
int nextEdge = 0;
|
|
for (; nextEdge < graphEdgeCount && !*graphEdgeDistances[nextEdge]; ++nextEdge) {
|
|
int elem = (int) (graphEdgeDistances[nextEdge]-distanceMatrixBase);
|
|
int row = elem/splineCount;
|
|
int col = elem%splineCount;
|
|
edgeMatrix[row][col] = 1;
|
|
edgeMatrix[col][row] = 1;
|
|
}
|
|
|
|
std::vector<int> coloring(2*splineCount);
|
|
colorSecondDegreeGraph(&coloring[0], &edgeMatrix[0], splineCount, seed);
|
|
for (; nextEdge < graphEdgeCount; ++nextEdge) {
|
|
int elem = (int) (graphEdgeDistances[nextEdge]-distanceMatrixBase);
|
|
tryAddEdge(&coloring[0], &edgeMatrix[0], splineCount, elem/splineCount, elem%splineCount, &coloring[splineCount]);
|
|
}
|
|
|
|
const EdgeColor colors[3] = { YELLOW, CYAN, MAGENTA };
|
|
int spline = -1;
|
|
for (int i = 0; i < segmentCount; ++i) {
|
|
if (splineStarts[spline+1] == i)
|
|
++spline;
|
|
edgeSegments[i]->color = colors[coloring[spline]];
|
|
}
|
|
}
|
|
|
|
#define DISTANCE_DELTA_FACTOR 1.001
|
|
|
|
TrueDistanceSelector::EdgeCache::EdgeCache() : absDistance(0) { }
|
|
|
|
void TrueDistanceSelector::reset(const Point2 &p) {
|
|
double delta = DISTANCE_DELTA_FACTOR*(p-this->p).length();
|
|
minDistance.distance += nonZeroSign(minDistance.distance)*delta;
|
|
this->p = p;
|
|
}
|
|
|
|
void TrueDistanceSelector::addEdge(EdgeCache &cache, const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge) {
|
|
double delta = DISTANCE_DELTA_FACTOR*(p-cache.point).length();
|
|
if (cache.absDistance-delta <= fabs(minDistance.distance)) {
|
|
double dummy;
|
|
SignedDistance distance = edge->signedDistance(p, dummy);
|
|
if (distance < minDistance)
|
|
minDistance = distance;
|
|
cache.point = p;
|
|
cache.absDistance = fabs(distance.distance);
|
|
}
|
|
}
|
|
|
|
void TrueDistanceSelector::merge(const TrueDistanceSelector &other) {
|
|
if (other.minDistance < minDistance)
|
|
minDistance = other.minDistance;
|
|
}
|
|
|
|
TrueDistanceSelector::DistanceType TrueDistanceSelector::distance() const {
|
|
return minDistance.distance;
|
|
}
|
|
|
|
PerpendicularDistanceSelectorBase::EdgeCache::EdgeCache() : absDistance(0), aDomainDistance(0), bDomainDistance(0), aPerpendicularDistance(0), bPerpendicularDistance(0) { }
|
|
|
|
bool PerpendicularDistanceSelectorBase::getPerpendicularDistance(double &distance, const Vector2 &ep, const Vector2 &edgeDir) {
|
|
double ts = dotProduct(ep, edgeDir);
|
|
if (ts > 0) {
|
|
double perpendicularDistance = crossProduct(ep, edgeDir);
|
|
if (fabs(perpendicularDistance) < fabs(distance)) {
|
|
distance = perpendicularDistance;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
PerpendicularDistanceSelectorBase::PerpendicularDistanceSelectorBase() : minNegativePerpendicularDistance(-fabs(minTrueDistance.distance)), minPositivePerpendicularDistance(fabs(minTrueDistance.distance)), nearEdge(NULL), nearEdgeParam(0) { }
|
|
|
|
void PerpendicularDistanceSelectorBase::reset(double delta) {
|
|
minTrueDistance.distance += nonZeroSign(minTrueDistance.distance)*delta;
|
|
minNegativePerpendicularDistance = -fabs(minTrueDistance.distance);
|
|
minPositivePerpendicularDistance = fabs(minTrueDistance.distance);
|
|
nearEdge = NULL;
|
|
nearEdgeParam = 0;
|
|
}
|
|
|
|
bool PerpendicularDistanceSelectorBase::isEdgeRelevant(const EdgeCache &cache, const EdgeSegment *edge, const Point2 &p) const {
|
|
double delta = DISTANCE_DELTA_FACTOR*(p-cache.point).length();
|
|
return (
|
|
cache.absDistance-delta <= fabs(minTrueDistance.distance) ||
|
|
fabs(cache.aDomainDistance) < delta ||
|
|
fabs(cache.bDomainDistance) < delta ||
|
|
(cache.aDomainDistance > 0 && (cache.aPerpendicularDistance < 0 ?
|
|
cache.aPerpendicularDistance+delta >= minNegativePerpendicularDistance :
|
|
cache.aPerpendicularDistance-delta <= minPositivePerpendicularDistance
|
|
)) ||
|
|
(cache.bDomainDistance > 0 && (cache.bPerpendicularDistance < 0 ?
|
|
cache.bPerpendicularDistance+delta >= minNegativePerpendicularDistance :
|
|
cache.bPerpendicularDistance-delta <= minPositivePerpendicularDistance
|
|
))
|
|
);
|
|
}
|
|
|
|
void PerpendicularDistanceSelectorBase::addEdgeTrueDistance(const EdgeSegment *edge, const SignedDistance &distance, double param) {
|
|
if (distance < minTrueDistance) {
|
|
minTrueDistance = distance;
|
|
nearEdge = edge;
|
|
nearEdgeParam = param;
|
|
}
|
|
}
|
|
|
|
void PerpendicularDistanceSelectorBase::addEdgePerpendicularDistance(double distance) {
|
|
if (distance <= 0 && distance > minNegativePerpendicularDistance)
|
|
minNegativePerpendicularDistance = distance;
|
|
if (distance >= 0 && distance < minPositivePerpendicularDistance)
|
|
minPositivePerpendicularDistance = distance;
|
|
}
|
|
|
|
void PerpendicularDistanceSelectorBase::merge(const PerpendicularDistanceSelectorBase &other) {
|
|
if (other.minTrueDistance < minTrueDistance) {
|
|
minTrueDistance = other.minTrueDistance;
|
|
nearEdge = other.nearEdge;
|
|
nearEdgeParam = other.nearEdgeParam;
|
|
}
|
|
if (other.minNegativePerpendicularDistance > minNegativePerpendicularDistance)
|
|
minNegativePerpendicularDistance = other.minNegativePerpendicularDistance;
|
|
if (other.minPositivePerpendicularDistance < minPositivePerpendicularDistance)
|
|
minPositivePerpendicularDistance = other.minPositivePerpendicularDistance;
|
|
}
|
|
|
|
double PerpendicularDistanceSelectorBase::computeDistance(const Point2 &p) const {
|
|
double minDistance = minTrueDistance.distance < 0 ? minNegativePerpendicularDistance : minPositivePerpendicularDistance;
|
|
if (nearEdge) {
|
|
SignedDistance distance = minTrueDistance;
|
|
nearEdge->distanceToPerpendicularDistance(distance, p, nearEdgeParam);
|
|
if (fabs(distance.distance) < fabs(minDistance))
|
|
minDistance = distance.distance;
|
|
}
|
|
return minDistance;
|
|
}
|
|
|
|
SignedDistance PerpendicularDistanceSelectorBase::trueDistance() const {
|
|
return minTrueDistance;
|
|
}
|
|
|
|
void PerpendicularDistanceSelector::reset(const Point2 &p) {
|
|
double delta = DISTANCE_DELTA_FACTOR*(p-this->p).length();
|
|
PerpendicularDistanceSelectorBase::reset(delta);
|
|
this->p = p;
|
|
}
|
|
|
|
void PerpendicularDistanceSelector::addEdge(EdgeCache &cache, const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge) {
|
|
if (isEdgeRelevant(cache, edge, p)) {
|
|
double param;
|
|
SignedDistance distance = edge->signedDistance(p, param);
|
|
addEdgeTrueDistance(edge, distance, param);
|
|
cache.point = p;
|
|
cache.absDistance = fabs(distance.distance);
|
|
|
|
Vector2 ap = p-edge->point(0);
|
|
Vector2 bp = p-edge->point(1);
|
|
Vector2 aDir = edge->direction(0).normalize(true);
|
|
Vector2 bDir = edge->direction(1).normalize(true);
|
|
Vector2 prevDir = prevEdge->direction(1).normalize(true);
|
|
Vector2 nextDir = nextEdge->direction(0).normalize(true);
|
|
double add = dotProduct(ap, (prevDir+aDir).normalize(true));
|
|
double bdd = -dotProduct(bp, (bDir+nextDir).normalize(true));
|
|
if (add > 0) {
|
|
double pd = distance.distance;
|
|
if (getPerpendicularDistance(pd, ap, -aDir))
|
|
addEdgePerpendicularDistance(pd = -pd);
|
|
cache.aPerpendicularDistance = pd;
|
|
}
|
|
if (bdd > 0) {
|
|
double pd = distance.distance;
|
|
if (getPerpendicularDistance(pd, bp, bDir))
|
|
addEdgePerpendicularDistance(pd);
|
|
cache.bPerpendicularDistance = pd;
|
|
}
|
|
cache.aDomainDistance = add;
|
|
cache.bDomainDistance = bdd;
|
|
}
|
|
}
|
|
|
|
PerpendicularDistanceSelector::DistanceType PerpendicularDistanceSelector::distance() const {
|
|
return computeDistance(p);
|
|
}
|
|
|
|
void MultiDistanceSelector::reset(const Point2 &p) {
|
|
double delta = DISTANCE_DELTA_FACTOR*(p-this->p).length();
|
|
r.reset(delta);
|
|
g.reset(delta);
|
|
b.reset(delta);
|
|
this->p = p;
|
|
}
|
|
|
|
void MultiDistanceSelector::addEdge(EdgeCache &cache, const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge) {
|
|
if (
|
|
(edge->color&RED && r.isEdgeRelevant(cache, edge, p)) ||
|
|
(edge->color&GREEN && g.isEdgeRelevant(cache, edge, p)) ||
|
|
(edge->color&BLUE && b.isEdgeRelevant(cache, edge, p))
|
|
) {
|
|
double param;
|
|
SignedDistance distance = edge->signedDistance(p, param);
|
|
if (edge->color&RED)
|
|
r.addEdgeTrueDistance(edge, distance, param);
|
|
if (edge->color&GREEN)
|
|
g.addEdgeTrueDistance(edge, distance, param);
|
|
if (edge->color&BLUE)
|
|
b.addEdgeTrueDistance(edge, distance, param);
|
|
cache.point = p;
|
|
cache.absDistance = fabs(distance.distance);
|
|
|
|
Vector2 ap = p-edge->point(0);
|
|
Vector2 bp = p-edge->point(1);
|
|
Vector2 aDir = edge->direction(0).normalize(true);
|
|
Vector2 bDir = edge->direction(1).normalize(true);
|
|
Vector2 prevDir = prevEdge->direction(1).normalize(true);
|
|
Vector2 nextDir = nextEdge->direction(0).normalize(true);
|
|
double add = dotProduct(ap, (prevDir+aDir).normalize(true));
|
|
double bdd = -dotProduct(bp, (bDir+nextDir).normalize(true));
|
|
if (add > 0) {
|
|
double pd = distance.distance;
|
|
if (PerpendicularDistanceSelectorBase::getPerpendicularDistance(pd, ap, -aDir)) {
|
|
pd = -pd;
|
|
if (edge->color&RED)
|
|
r.addEdgePerpendicularDistance(pd);
|
|
if (edge->color&GREEN)
|
|
g.addEdgePerpendicularDistance(pd);
|
|
if (edge->color&BLUE)
|
|
b.addEdgePerpendicularDistance(pd);
|
|
}
|
|
cache.aPerpendicularDistance = pd;
|
|
}
|
|
if (bdd > 0) {
|
|
double pd = distance.distance;
|
|
if (PerpendicularDistanceSelectorBase::getPerpendicularDistance(pd, bp, bDir)) {
|
|
if (edge->color&RED)
|
|
r.addEdgePerpendicularDistance(pd);
|
|
if (edge->color&GREEN)
|
|
g.addEdgePerpendicularDistance(pd);
|
|
if (edge->color&BLUE)
|
|
b.addEdgePerpendicularDistance(pd);
|
|
}
|
|
cache.bPerpendicularDistance = pd;
|
|
}
|
|
cache.aDomainDistance = add;
|
|
cache.bDomainDistance = bdd;
|
|
}
|
|
}
|
|
|
|
void MultiDistanceSelector::merge(const MultiDistanceSelector &other) {
|
|
r.merge(other.r);
|
|
g.merge(other.g);
|
|
b.merge(other.b);
|
|
}
|
|
|
|
MultiDistanceSelector::DistanceType MultiDistanceSelector::distance() const {
|
|
MultiDistance multiDistance;
|
|
multiDistance.r = r.computeDistance(p);
|
|
multiDistance.g = g.computeDistance(p);
|
|
multiDistance.b = b.computeDistance(p);
|
|
return multiDistance;
|
|
}
|
|
|
|
SignedDistance MultiDistanceSelector::trueDistance() const {
|
|
SignedDistance distance = r.trueDistance();
|
|
if (g.trueDistance() < distance)
|
|
distance = g.trueDistance();
|
|
if (b.trueDistance() < distance)
|
|
distance = b.trueDistance();
|
|
return distance;
|
|
}
|
|
|
|
MultiAndTrueDistanceSelector::DistanceType MultiAndTrueDistanceSelector::distance() const {
|
|
MultiDistance multiDistance = MultiDistanceSelector::distance();
|
|
MultiAndTrueDistance mtd;
|
|
mtd.r = multiDistance.r;
|
|
mtd.g = multiDistance.g;
|
|
mtd.b = multiDistance.b;
|
|
mtd.a = trueDistance().distance;
|
|
return mtd;
|
|
}
|
|
|
|
static void initDistance(double &distance) {
|
|
distance = -DBL_MAX;
|
|
}
|
|
|
|
static void initDistance(MultiDistance &distance) {
|
|
distance.r = -DBL_MAX;
|
|
distance.g = -DBL_MAX;
|
|
distance.b = -DBL_MAX;
|
|
}
|
|
|
|
static void initDistance(MultiAndTrueDistance &distance) {
|
|
distance.r = -DBL_MAX;
|
|
distance.g = -DBL_MAX;
|
|
distance.b = -DBL_MAX;
|
|
distance.a = -DBL_MAX;
|
|
}
|
|
|
|
static double resolveDistance(double distance) {
|
|
return distance;
|
|
}
|
|
|
|
static double resolveDistance(const MultiDistance &distance) {
|
|
return median(distance.r, distance.g, distance.b);
|
|
}
|
|
|
|
template <class EdgeSelector>
|
|
SimpleContourCombiner<EdgeSelector>::SimpleContourCombiner(const Shape &shape) { }
|
|
|
|
template <class EdgeSelector>
|
|
void SimpleContourCombiner<EdgeSelector>::reset(const Point2 &p) {
|
|
shapeEdgeSelector.reset(p);
|
|
}
|
|
|
|
template <class EdgeSelector>
|
|
EdgeSelector &SimpleContourCombiner<EdgeSelector>::edgeSelector(int) {
|
|
return shapeEdgeSelector;
|
|
}
|
|
|
|
template <class EdgeSelector>
|
|
typename SimpleContourCombiner<EdgeSelector>::DistanceType SimpleContourCombiner<EdgeSelector>::distance() const {
|
|
return shapeEdgeSelector.distance();
|
|
}
|
|
|
|
template class SimpleContourCombiner<TrueDistanceSelector>;
|
|
template class SimpleContourCombiner<PerpendicularDistanceSelector>;
|
|
template class SimpleContourCombiner<MultiDistanceSelector>;
|
|
template class SimpleContourCombiner<MultiAndTrueDistanceSelector>;
|
|
|
|
template <class EdgeSelector>
|
|
OverlappingContourCombiner<EdgeSelector>::OverlappingContourCombiner(const Shape &shape) {
|
|
windings.reserve(shape.contours.size());
|
|
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
|
windings.push_back(contour->winding());
|
|
edgeSelectors.resize(shape.contours.size());
|
|
}
|
|
|
|
template <class EdgeSelector>
|
|
void OverlappingContourCombiner<EdgeSelector>::reset(const Point2 &p) {
|
|
this->p = p;
|
|
for (typename std::vector<EdgeSelector>::iterator contourEdgeSelector = edgeSelectors.begin(); contourEdgeSelector != edgeSelectors.end(); ++contourEdgeSelector)
|
|
contourEdgeSelector->reset(p);
|
|
}
|
|
|
|
template <class EdgeSelector>
|
|
EdgeSelector &OverlappingContourCombiner<EdgeSelector>::edgeSelector(int i) {
|
|
return edgeSelectors[i];
|
|
}
|
|
|
|
template <class EdgeSelector>
|
|
typename OverlappingContourCombiner<EdgeSelector>::DistanceType OverlappingContourCombiner<EdgeSelector>::distance() const {
|
|
int contourCount = (int) edgeSelectors.size();
|
|
EdgeSelector shapeEdgeSelector;
|
|
EdgeSelector innerEdgeSelector;
|
|
EdgeSelector outerEdgeSelector;
|
|
shapeEdgeSelector.reset(p);
|
|
innerEdgeSelector.reset(p);
|
|
outerEdgeSelector.reset(p);
|
|
for (int i = 0; i < contourCount; ++i) {
|
|
DistanceType edgeDistance = edgeSelectors[i].distance();
|
|
shapeEdgeSelector.merge(edgeSelectors[i]);
|
|
if (windings[i] > 0 && resolveDistance(edgeDistance) >= 0)
|
|
innerEdgeSelector.merge(edgeSelectors[i]);
|
|
if (windings[i] < 0 && resolveDistance(edgeDistance) <= 0)
|
|
outerEdgeSelector.merge(edgeSelectors[i]);
|
|
}
|
|
|
|
DistanceType shapeDistance = shapeEdgeSelector.distance();
|
|
DistanceType innerDistance = innerEdgeSelector.distance();
|
|
DistanceType outerDistance = outerEdgeSelector.distance();
|
|
double innerScalarDistance = resolveDistance(innerDistance);
|
|
double outerScalarDistance = resolveDistance(outerDistance);
|
|
DistanceType distance;
|
|
initDistance(distance);
|
|
|
|
int winding = 0;
|
|
if (innerScalarDistance >= 0 && fabs(innerScalarDistance) <= fabs(outerScalarDistance)) {
|
|
distance = innerDistance;
|
|
winding = 1;
|
|
for (int i = 0; i < contourCount; ++i)
|
|
if (windings[i] > 0) {
|
|
DistanceType contourDistance = edgeSelectors[i].distance();
|
|
if (fabs(resolveDistance(contourDistance)) < fabs(outerScalarDistance) && resolveDistance(contourDistance) > resolveDistance(distance))
|
|
distance = contourDistance;
|
|
}
|
|
} else if (outerScalarDistance <= 0 && fabs(outerScalarDistance) < fabs(innerScalarDistance)) {
|
|
distance = outerDistance;
|
|
winding = -1;
|
|
for (int i = 0; i < contourCount; ++i)
|
|
if (windings[i] < 0) {
|
|
DistanceType contourDistance = edgeSelectors[i].distance();
|
|
if (fabs(resolveDistance(contourDistance)) < fabs(innerScalarDistance) && resolveDistance(contourDistance) < resolveDistance(distance))
|
|
distance = contourDistance;
|
|
}
|
|
} else
|
|
return shapeDistance;
|
|
|
|
for (int i = 0; i < contourCount; ++i)
|
|
if (windings[i] != winding) {
|
|
DistanceType contourDistance = edgeSelectors[i].distance();
|
|
if (resolveDistance(contourDistance)*resolveDistance(distance) >= 0 && fabs(resolveDistance(contourDistance)) < fabs(resolveDistance(distance)))
|
|
distance = contourDistance;
|
|
}
|
|
if (resolveDistance(distance) == resolveDistance(shapeDistance))
|
|
distance = shapeDistance;
|
|
return distance;
|
|
}
|
|
|
|
template class OverlappingContourCombiner<TrueDistanceSelector>;
|
|
template class OverlappingContourCombiner<PerpendicularDistanceSelector>;
|
|
template class OverlappingContourCombiner<MultiDistanceSelector>;
|
|
template class OverlappingContourCombiner<MultiAndTrueDistanceSelector>;
|
|
|
|
}
|
|
|
|
#define ESTSDF_MAX_DIST 1e24f // Cannot be FLT_MAX because it might be divided by range, which could be < 1
|
|
|
|
namespace msdfgen {
|
|
|
|
void approximateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const SDFTransformation &transformation) {
|
|
struct Entry {
|
|
float absDist;
|
|
int bitmapX, bitmapY;
|
|
Point2 nearPoint;
|
|
|
|
bool operator<(const Entry &other) const {
|
|
return absDist > other.absDist;
|
|
}
|
|
} entry;
|
|
|
|
float *firstRow = output.pixels;
|
|
ptrdiff_t stride = output.width;
|
|
if (shape.inverseYAxis) {
|
|
firstRow += (output.height-1)*stride;
|
|
stride = -stride;
|
|
}
|
|
#define ESTSDF_PIXEL_AT(x, y) ((firstRow+(y)*stride)[x])
|
|
|
|
for (float *p = output.pixels, *end = output.pixels+output.width*output.height; p < end; ++p)
|
|
*p = -ESTSDF_MAX_DIST;
|
|
|
|
Vector2 invScale = transformation.unprojectVector(Vector2(1));
|
|
DistanceMapping invDistanceMapping = transformation.distanceMapping.inverse();
|
|
float dLimit = float(max(fabs(invDistanceMapping(0)), fabs(invDistanceMapping(1))));
|
|
std::priority_queue<Entry> queue;
|
|
double x[3], y[3];
|
|
int dx[3], dy[3];
|
|
|
|
// Horizontal scanlines
|
|
for (int bitmapY = 0; bitmapY < output.height; ++bitmapY) {
|
|
float *row = firstRow+bitmapY*stride;
|
|
double y = transformation.unprojectY(bitmapY+.5);
|
|
entry.bitmapY = bitmapY;
|
|
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) {
|
|
int n = (*edge)->horizontalScanlineIntersections(x, dy, y);
|
|
for (int i = 0; i < n; ++i) {
|
|
double bitmapX = transformation.projectX(x[i]);
|
|
double bitmapX0 = floor(bitmapX-.5)+.5;
|
|
double bitmapX1 = bitmapX0+1;
|
|
if (bitmapX1 > 0 && bitmapX0 < output.width) {
|
|
float sd0 = float(dy[i]*invScale.x*(bitmapX0-bitmapX));
|
|
float sd1 = float(dy[i]*invScale.x*(bitmapX1-bitmapX));
|
|
if (sd0 == 0.f) {
|
|
if (sd1 == 0.f)
|
|
continue;
|
|
sd0 = -.000001f*float(sign(sd1));
|
|
}
|
|
if (sd1 == 0.f)
|
|
sd1 = -.000001f*float(sign(sd0));
|
|
if (bitmapX0 > 0) {
|
|
entry.absDist = fabsf(sd0);
|
|
entry.bitmapX = int(bitmapX0);
|
|
float &sd = row[entry.bitmapX];
|
|
if (entry.absDist < fabsf(sd)) {
|
|
sd = sd0;
|
|
entry.nearPoint = Point2(x[i], y);
|
|
queue.push(entry);
|
|
} else if (sd == -sd0)
|
|
sd = -ESTSDF_MAX_DIST;
|
|
}
|
|
if (bitmapX1 < output.width) {
|
|
entry.absDist = fabsf(sd1);
|
|
entry.bitmapX = int(bitmapX1);
|
|
float &sd = row[entry.bitmapX];
|
|
if (entry.absDist < fabsf(sd)) {
|
|
sd = sd1;
|
|
entry.nearPoint = Point2(x[i], y);
|
|
queue.push(entry);
|
|
} else if (sd == -sd1)
|
|
sd = -ESTSDF_MAX_DIST;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Bake in distance signs
|
|
for (int y = 0; y < output.height; ++y) {
|
|
float *row = firstRow+y*stride;
|
|
int x = 0;
|
|
for (; x < output.width && row[x] == -ESTSDF_MAX_DIST; ++x);
|
|
if (x < output.width) {
|
|
bool flip = row[x] > 0;
|
|
if (flip) {
|
|
for (int i = 0; i < x; ++i)
|
|
row[i] = ESTSDF_MAX_DIST;
|
|
}
|
|
for (; x < output.width; ++x) {
|
|
if (row[x] != -ESTSDF_MAX_DIST)
|
|
flip = row[x] > 0;
|
|
else if (flip)
|
|
row[x] = ESTSDF_MAX_DIST;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Vertical scanlines
|
|
for (int bitmapX = 0; bitmapX < output.width; ++bitmapX) {
|
|
double x = transformation.unprojectX(bitmapX+.5);
|
|
entry.bitmapX = bitmapX;
|
|
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) {
|
|
int n = (*edge)->verticalScanlineIntersections(y, dx, x);
|
|
for (int i = 0; i < n; ++i) {
|
|
double bitmapY = transformation.projectY(y[i]);
|
|
double bitmapY0 = floor(bitmapY-.5)+.5;
|
|
double bitmapY1 = bitmapY0+1;
|
|
if (bitmapY0 > 0 && bitmapY1 < output.height) {
|
|
float sd0 = float(dx[i]*invScale.y*(bitmapY-bitmapY0));
|
|
float sd1 = float(dx[i]*invScale.y*(bitmapY-bitmapY1));
|
|
if (sd0 == 0.f) {
|
|
if (sd1 == 0.f)
|
|
continue;
|
|
sd0 = -.000001f*float(sign(sd1));
|
|
}
|
|
if (sd1 == 0.f)
|
|
sd1 = -.000001f*float(sign(sd0));
|
|
if (bitmapY0 > 0) {
|
|
entry.absDist = fabsf(sd0);
|
|
entry.bitmapY = int(bitmapY0);
|
|
float &sd = ESTSDF_PIXEL_AT(bitmapX, entry.bitmapY);
|
|
if (entry.absDist < fabsf(sd)) {
|
|
sd = sd0;
|
|
entry.nearPoint = Point2(x, y[i]);
|
|
queue.push(entry);
|
|
}
|
|
}
|
|
if (bitmapY1 < output.height) {
|
|
entry.absDist = fabsf(sd1);
|
|
entry.bitmapY = int(bitmapY1);
|
|
float &sd = ESTSDF_PIXEL_AT(bitmapX, entry.bitmapY);
|
|
if (entry.absDist < fabsf(sd)) {
|
|
sd = sd1;
|
|
entry.nearPoint = Point2(x, y[i]);
|
|
queue.push(entry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (queue.empty())
|
|
return;
|
|
|
|
while (!queue.empty()) {
|
|
Entry entry = queue.top();
|
|
queue.pop();
|
|
Entry newEntry = entry;
|
|
newEntry.bitmapX = entry.bitmapX-1;
|
|
if (newEntry.bitmapX >= 0) {
|
|
float &sd = ESTSDF_PIXEL_AT(newEntry.bitmapX, newEntry.bitmapY);
|
|
if (fabsf(sd) == ESTSDF_MAX_DIST) {
|
|
Point2 shapeCoord = transformation.unproject(Point2(newEntry.bitmapX+.5, newEntry.bitmapY+.5));
|
|
newEntry.absDist = float((shapeCoord-entry.nearPoint).length());
|
|
sd = float(sign(sd))*newEntry.absDist;
|
|
if (newEntry.absDist < dLimit)
|
|
queue.push(newEntry);
|
|
}
|
|
}
|
|
newEntry.bitmapX = entry.bitmapX+1;
|
|
if (newEntry.bitmapX < output.width) {
|
|
float &sd = ESTSDF_PIXEL_AT(newEntry.bitmapX, newEntry.bitmapY);
|
|
if (fabsf(sd) == ESTSDF_MAX_DIST) {
|
|
Point2 shapeCoord = transformation.unproject(Point2(newEntry.bitmapX+.5, newEntry.bitmapY+.5));
|
|
newEntry.absDist = float((shapeCoord-entry.nearPoint).length());
|
|
sd = float(sign(sd))*newEntry.absDist;
|
|
if (newEntry.absDist < dLimit)
|
|
queue.push(newEntry);
|
|
}
|
|
}
|
|
newEntry.bitmapX = entry.bitmapX;
|
|
newEntry.bitmapY = entry.bitmapY-1;
|
|
if (newEntry.bitmapY >= 0) {
|
|
float &sd = ESTSDF_PIXEL_AT(newEntry.bitmapX, newEntry.bitmapY);
|
|
if (fabsf(sd) == ESTSDF_MAX_DIST) {
|
|
Point2 shapeCoord = transformation.unproject(Point2(newEntry.bitmapX+.5, newEntry.bitmapY+.5));
|
|
newEntry.absDist = float((shapeCoord-entry.nearPoint).length());
|
|
sd = float(sign(sd))*newEntry.absDist;
|
|
if (newEntry.absDist < dLimit)
|
|
queue.push(newEntry);
|
|
}
|
|
}
|
|
newEntry.bitmapY = entry.bitmapY+1;
|
|
if (newEntry.bitmapY < output.height) {
|
|
float &sd = ESTSDF_PIXEL_AT(newEntry.bitmapX, newEntry.bitmapY);
|
|
if (fabsf(sd) == ESTSDF_MAX_DIST) {
|
|
Point2 shapeCoord = transformation.unproject(Point2(newEntry.bitmapX+.5, newEntry.bitmapY+.5));
|
|
newEntry.absDist = float((shapeCoord-entry.nearPoint).length());
|
|
sd = float(sign(sd))*newEntry.absDist;
|
|
if (newEntry.absDist < dLimit)
|
|
queue.push(newEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (float *p = output.pixels, *end = output.pixels+output.width*output.height; p < end; ++p)
|
|
*p = transformation.distanceMapping(*p);
|
|
}
|
|
|
|
template <int N>
|
|
static void msdfErrorCorrectionInner(const BitmapRef<float, N> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
|
|
if (config.errorCorrection.mode == ErrorCorrectionConfig::DISABLED)
|
|
return;
|
|
Bitmap<byte, 1> stencilBuffer;
|
|
if (!config.errorCorrection.buffer)
|
|
stencilBuffer = Bitmap<byte, 1>(sdf.width, sdf.height);
|
|
BitmapRef<byte, 1> stencil;
|
|
stencil.pixels = config.errorCorrection.buffer ? config.errorCorrection.buffer : (byte *) stencilBuffer;
|
|
stencil.width = sdf.width, stencil.height = sdf.height;
|
|
MSDFErrorCorrection ec(stencil, transformation);
|
|
ec.setMinDeviationRatio(config.errorCorrection.minDeviationRatio);
|
|
ec.setMinImproveRatio(config.errorCorrection.minImproveRatio);
|
|
switch (config.errorCorrection.mode) {
|
|
case ErrorCorrectionConfig::DISABLED:
|
|
case ErrorCorrectionConfig::INDISCRIMINATE:
|
|
break;
|
|
case ErrorCorrectionConfig::EDGE_PRIORITY:
|
|
ec.protectCorners(shape);
|
|
ec.protectEdges<N>(sdf);
|
|
break;
|
|
case ErrorCorrectionConfig::EDGE_ONLY:
|
|
ec.protectAll();
|
|
break;
|
|
}
|
|
if (config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE || (config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE && config.errorCorrection.mode != ErrorCorrectionConfig::EDGE_ONLY)) {
|
|
ec.findErrors<N>(sdf);
|
|
if (config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE)
|
|
ec.protectAll();
|
|
}
|
|
if (config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::ALWAYS_CHECK_DISTANCE || config.errorCorrection.distanceCheckMode == ErrorCorrectionConfig::CHECK_DISTANCE_AT_EDGE) {
|
|
if (config.overlapSupport)
|
|
ec.findErrors<OverlappingContourCombiner, N>(sdf, shape);
|
|
else
|
|
ec.findErrors<SimpleContourCombiner, N>(sdf, shape);
|
|
}
|
|
ec.apply(sdf);
|
|
}
|
|
|
|
template <int N>
|
|
static void msdfErrorCorrectionShapeless(const BitmapRef<float, N> &sdf, const SDFTransformation &transformation, double minDeviationRatio, bool protectAll) {
|
|
Bitmap<byte, 1> stencilBuffer(sdf.width, sdf.height);
|
|
MSDFErrorCorrection ec(stencilBuffer, transformation);
|
|
ec.setMinDeviationRatio(minDeviationRatio);
|
|
if (protectAll)
|
|
ec.protectAll();
|
|
ec.findErrors<N>(sdf);
|
|
ec.apply(sdf);
|
|
}
|
|
|
|
void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
|
|
msdfErrorCorrectionInner(sdf, shape, transformation, config);
|
|
}
|
|
void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
|
|
msdfErrorCorrectionInner(sdf, shape, transformation, config);
|
|
}
|
|
void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config) {
|
|
msdfErrorCorrectionInner(sdf, shape, SDFTransformation(projection, range), config);
|
|
}
|
|
void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config) {
|
|
msdfErrorCorrectionInner(sdf, shape, SDFTransformation(projection, range), config);
|
|
}
|
|
|
|
void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const SDFTransformation &transformation, double minDeviationRatio) {
|
|
msdfErrorCorrectionShapeless(sdf, transformation, minDeviationRatio, false);
|
|
}
|
|
void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const SDFTransformation &transformation, double minDeviationRatio) {
|
|
msdfErrorCorrectionShapeless(sdf, transformation, minDeviationRatio, false);
|
|
}
|
|
void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, Range range, double minDeviationRatio) {
|
|
msdfErrorCorrectionShapeless(sdf, SDFTransformation(projection, range), minDeviationRatio, false);
|
|
}
|
|
void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, Range range, double minDeviationRatio) {
|
|
msdfErrorCorrectionShapeless(sdf, SDFTransformation(projection, range), minDeviationRatio, false);
|
|
}
|
|
|
|
void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const SDFTransformation &transformation, double minDeviationRatio) {
|
|
msdfErrorCorrectionShapeless(sdf, transformation, minDeviationRatio, true);
|
|
}
|
|
void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const SDFTransformation &transformation, double minDeviationRatio) {
|
|
msdfErrorCorrectionShapeless(sdf, transformation, minDeviationRatio, true);
|
|
}
|
|
void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, Range range, double minDeviationRatio) {
|
|
msdfErrorCorrectionShapeless(sdf, SDFTransformation(projection, range), minDeviationRatio, true);
|
|
}
|
|
void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, Range range, double minDeviationRatio) {
|
|
msdfErrorCorrectionShapeless(sdf, SDFTransformation(projection, range), minDeviationRatio, true);
|
|
}
|
|
|
|
// Legacy version
|
|
|
|
inline static bool detectClash(const float *a, const float *b, double threshold) {
|
|
// Sort channels so that pairs (a0, b0), (a1, b1), (a2, b2) go from biggest to smallest absolute difference
|
|
float a0 = a[0], a1 = a[1], a2 = a[2];
|
|
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>
|
|
static void msdfErrorCorrectionInner_legacy(const BitmapRef<float, N> &output, const Vector2 &threshold) {
|
|
std::vector<std::pair<int, int> > clashes;
|
|
int w = output.width, h = output.height;
|
|
for (int y = 0; y < h; ++y)
|
|
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_legacy(const BitmapRef<float, 3> &output, const Vector2 &threshold) {
|
|
msdfErrorCorrectionInner_legacy(output, threshold);
|
|
}
|
|
void msdfErrorCorrection_legacy(const BitmapRef<float, 4> &output, const Vector2 &threshold) {
|
|
msdfErrorCorrectionInner_legacy(output, threshold);
|
|
}
|
|
|
|
#define ARTIFACT_T_EPSILON .01
|
|
#define PROTECTION_RADIUS_TOLERANCE 1.001
|
|
|
|
#define CLASSIFIER_FLAG_CANDIDATE 0x01
|
|
#define CLASSIFIER_FLAG_ARTIFACT 0x02
|
|
|
|
MSDFGEN_PUBLIC const double ErrorCorrectionConfig::defaultMinDeviationRatio = 1.11111111111111111;
|
|
MSDFGEN_PUBLIC const double ErrorCorrectionConfig::defaultMinImproveRatio = 1.11111111111111111;
|
|
|
|
/// The base artifact classifier recognizes artifacts based on the contents of the SDF alone.
|
|
class BaseArtifactClassifier {
|
|
public:
|
|
inline BaseArtifactClassifier(double span, bool protectedFlag) : span(span), protectedFlag(protectedFlag) { }
|
|
/// Evaluates if the median value xm interpolated at xt in the range between am at at and bm at bt indicates an artifact.
|
|
inline int rangeTest(double at, double bt, double xt, float am, float bm, float xm) const {
|
|
// For protected texels, only consider inversion artifacts (interpolated median has different sign than boundaries). For the rest, it is sufficient that the interpolated median is outside its boundaries.
|
|
if ((am > .5f && bm > .5f && xm <= .5f) || (am < .5f && bm < .5f && xm >= .5f) || (!protectedFlag && median(am, bm, xm) != xm)) {
|
|
double axSpan = (xt-at)*span, bxSpan = (bt-xt)*span;
|
|
// Check if the interpolated median's value is in the expected range based on its distance (span) from boundaries a, b.
|
|
if (!(xm >= am-axSpan && xm <= am+axSpan && xm >= bm-bxSpan && xm <= bm+bxSpan))
|
|
return CLASSIFIER_FLAG_CANDIDATE|CLASSIFIER_FLAG_ARTIFACT;
|
|
return CLASSIFIER_FLAG_CANDIDATE;
|
|
}
|
|
return 0;
|
|
}
|
|
/// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact.
|
|
inline bool evaluate(double t, float m, int flags) const {
|
|
return (flags&2) != 0;
|
|
}
|
|
private:
|
|
double span;
|
|
bool protectedFlag;
|
|
};
|
|
|
|
/// The shape distance checker evaluates the exact shape distance to find additional artifacts at a significant performance cost.
|
|
template <template <typename> class ContourCombiner, int N>
|
|
class ShapeDistanceChecker {
|
|
public:
|
|
class ArtifactClassifier : public BaseArtifactClassifier {
|
|
public:
|
|
inline ArtifactClassifier(ShapeDistanceChecker *parent, const Vector2 &direction, double span) : BaseArtifactClassifier(span, parent->protectedFlag), parent(parent), direction(direction) { }
|
|
/// Returns true if the combined results of the tests performed on the median value m interpolated at t indicate an artifact.
|
|
inline bool evaluate(double t, float m, int flags) const {
|
|
if (flags&CLASSIFIER_FLAG_CANDIDATE) {
|
|
// Skip expensive distance evaluation if the point has already been classified as an artifact by the base classifier.
|
|
if (flags&CLASSIFIER_FLAG_ARTIFACT)
|
|
return true;
|
|
Vector2 tVector = t*direction;
|
|
float oldMSD[N], newMSD[3];
|
|
// Compute the color that would be currently interpolated at the artifact candidate's position.
|
|
Point2 sdfCoord = parent->sdfCoord+tVector;
|
|
interpolate(oldMSD, parent->sdf, sdfCoord);
|
|
// Compute the color that would be interpolated at the artifact candidate's position if error correction was applied on the current texel.
|
|
double aWeight = (1-fabs(tVector.x))*(1-fabs(tVector.y));
|
|
float aPSD = median(parent->msd[0], parent->msd[1], parent->msd[2]);
|
|
newMSD[0] = float(oldMSD[0]+aWeight*(aPSD-parent->msd[0]));
|
|
newMSD[1] = float(oldMSD[1]+aWeight*(aPSD-parent->msd[1]));
|
|
newMSD[2] = float(oldMSD[2]+aWeight*(aPSD-parent->msd[2]));
|
|
// Compute the evaluated distance (interpolated median) before and after error correction, as well as the exact shape distance.
|
|
float oldPSD = median(oldMSD[0], oldMSD[1], oldMSD[2]);
|
|
float newPSD = median(newMSD[0], newMSD[1], newMSD[2]);
|
|
float refPSD = float(parent->distanceMapping(parent->distanceFinder.distance(parent->shapeCoord+tVector*parent->texelSize)));
|
|
// Compare the differences of the exact distance and the before and after distances.
|
|
return parent->minImproveRatio*fabsf(newPSD-refPSD) < double(fabsf(oldPSD-refPSD));
|
|
}
|
|
return false;
|
|
}
|
|
private:
|
|
ShapeDistanceChecker *parent;
|
|
Vector2 direction;
|
|
};
|
|
Point2 shapeCoord, sdfCoord;
|
|
const float *msd;
|
|
bool protectedFlag;
|
|
inline ShapeDistanceChecker(const BitmapConstRef<float, N> &sdf, const Shape &shape, const Projection &projection, DistanceMapping distanceMapping, double minImproveRatio) : distanceFinder(shape), sdf(sdf), distanceMapping(distanceMapping), minImproveRatio(minImproveRatio) {
|
|
texelSize = projection.unprojectVector(Vector2(1));
|
|
if (shape.inverseYAxis)
|
|
texelSize.y = -texelSize.y;
|
|
}
|
|
inline ArtifactClassifier classifier(const Vector2 &direction, double span) {
|
|
return ArtifactClassifier(this, direction, span);
|
|
}
|
|
private:
|
|
ShapeDistanceFinder<ContourCombiner<PerpendicularDistanceSelector> > distanceFinder;
|
|
BitmapConstRef<float, N> sdf;
|
|
DistanceMapping distanceMapping;
|
|
Vector2 texelSize;
|
|
double minImproveRatio;
|
|
};
|
|
|
|
MSDFErrorCorrection::MSDFErrorCorrection() { }
|
|
|
|
MSDFErrorCorrection::MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const SDFTransformation &transformation) : stencil(stencil), transformation(transformation) {
|
|
minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio;
|
|
minImproveRatio = ErrorCorrectionConfig::defaultMinImproveRatio;
|
|
memset(stencil.pixels, 0, sizeof(byte)*stencil.width*stencil.height);
|
|
}
|
|
|
|
void MSDFErrorCorrection::setMinDeviationRatio(double minDeviationRatio) {
|
|
this->minDeviationRatio = minDeviationRatio;
|
|
}
|
|
|
|
void MSDFErrorCorrection::setMinImproveRatio(double minImproveRatio) {
|
|
this->minImproveRatio = minImproveRatio;
|
|
}
|
|
|
|
void MSDFErrorCorrection::protectCorners(const Shape &shape) {
|
|
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
|
|
if (!contour->edges.empty()) {
|
|
const EdgeSegment *prevEdge = contour->edges.back();
|
|
for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
|
|
int commonColor = prevEdge->color&(*edge)->color;
|
|
// If the color changes from prevEdge to edge, this is a corner.
|
|
if (!(commonColor&(commonColor-1))) {
|
|
// Find the four texels that envelop the corner and mark them as protected.
|
|
Point2 p = transformation.project((*edge)->point(0));
|
|
int l = (int) floor(p.x-.5);
|
|
int b = (int) floor(p.y-.5);
|
|
if (shape.inverseYAxis)
|
|
b = stencil.height-b-2;
|
|
int r = l+1;
|
|
int t = b+1;
|
|
// Check that the positions are within bounds.
|
|
if (l < stencil.width && b < stencil.height && r >= 0 && t >= 0) {
|
|
if (l >= 0 && b >= 0)
|
|
*stencil(l, b) |= (byte) PROTECTED;
|
|
if (r < stencil.width && b >= 0)
|
|
*stencil(r, b) |= (byte) PROTECTED;
|
|
if (l >= 0 && t < stencil.height)
|
|
*stencil(l, t) |= (byte) PROTECTED;
|
|
if (r < stencil.width && t < stencil.height)
|
|
*stencil(r, t) |= (byte) PROTECTED;
|
|
}
|
|
}
|
|
prevEdge = *edge;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Determines if the channel contributes to an edge between the two texels a, b.
|
|
static bool edgeBetweenTexelsChannel(const float *a, const float *b, int channel) {
|
|
// Find interpolation ratio t (0 < t < 1) where an edge is expected (mix(a[channel], b[channel], t) == 0.5).
|
|
double t = (a[channel]-.5)/(a[channel]-b[channel]);
|
|
if (t > 0 && t < 1) {
|
|
// Interpolate channel values at t.
|
|
float c[3] = {
|
|
mix(a[0], b[0], t),
|
|
mix(a[1], b[1], t),
|
|
mix(a[2], b[2], t)
|
|
};
|
|
// This is only an edge if the zero-distance channel is the median.
|
|
return median(c[0], c[1], c[2]) == c[channel];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Returns a bit mask of which channels contribute to an edge between the two texels a, b.
|
|
static int edgeBetweenTexels(const float *a, const float *b) {
|
|
return (
|
|
RED*edgeBetweenTexelsChannel(a, b, 0)+
|
|
GREEN*edgeBetweenTexelsChannel(a, b, 1)+
|
|
BLUE*edgeBetweenTexelsChannel(a, b, 2)
|
|
);
|
|
}
|
|
|
|
/// Marks texel as protected if one of its non-median channels is present in the channel mask.
|
|
static void protectExtremeChannels(byte *stencil, const float *msd, float m, int mask) {
|
|
if (
|
|
(mask&RED && msd[0] != m) ||
|
|
(mask&GREEN && msd[1] != m) ||
|
|
(mask&BLUE && msd[2] != m)
|
|
)
|
|
*stencil |= (byte) MSDFErrorCorrection::PROTECTED;
|
|
}
|
|
|
|
template <int N>
|
|
void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf) {
|
|
float radius;
|
|
// Horizontal texel pairs
|
|
radius = float(PROTECTION_RADIUS_TOLERANCE*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)), 0)).length());
|
|
for (int y = 0; y < sdf.height; ++y) {
|
|
const float *left = sdf(0, y);
|
|
const float *right = sdf(1, y);
|
|
for (int x = 0; x < sdf.width-1; ++x) {
|
|
float lm = median(left[0], left[1], left[2]);
|
|
float rm = median(right[0], right[1], right[2]);
|
|
if (fabsf(lm-.5f)+fabsf(rm-.5f) < radius) {
|
|
int mask = edgeBetweenTexels(left, right);
|
|
protectExtremeChannels(stencil(x, y), left, lm, mask);
|
|
protectExtremeChannels(stencil(x+1, y), right, rm, mask);
|
|
}
|
|
left += N, right += N;
|
|
}
|
|
}
|
|
// Vertical texel pairs
|
|
radius = float(PROTECTION_RADIUS_TOLERANCE*transformation.unprojectVector(Vector2(0, transformation.distanceMapping(DistanceMapping::Delta(1)))).length());
|
|
for (int y = 0; y < sdf.height-1; ++y) {
|
|
const float *bottom = sdf(0, y);
|
|
const float *top = sdf(0, y+1);
|
|
for (int x = 0; x < sdf.width; ++x) {
|
|
float bm = median(bottom[0], bottom[1], bottom[2]);
|
|
float tm = median(top[0], top[1], top[2]);
|
|
if (fabsf(bm-.5f)+fabsf(tm-.5f) < radius) {
|
|
int mask = edgeBetweenTexels(bottom, top);
|
|
protectExtremeChannels(stencil(x, y), bottom, bm, mask);
|
|
protectExtremeChannels(stencil(x, y+1), top, tm, mask);
|
|
}
|
|
bottom += N, top += N;
|
|
}
|
|
}
|
|
// Diagonal texel pairs
|
|
radius = float(PROTECTION_RADIUS_TOLERANCE*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)))).length());
|
|
for (int y = 0; y < sdf.height-1; ++y) {
|
|
const float *lb = sdf(0, y);
|
|
const float *rb = sdf(1, y);
|
|
const float *lt = sdf(0, y+1);
|
|
const float *rt = sdf(1, y+1);
|
|
for (int x = 0; x < sdf.width-1; ++x) {
|
|
float mlb = median(lb[0], lb[1], lb[2]);
|
|
float mrb = median(rb[0], rb[1], rb[2]);
|
|
float mlt = median(lt[0], lt[1], lt[2]);
|
|
float mrt = median(rt[0], rt[1], rt[2]);
|
|
if (fabsf(mlb-.5f)+fabsf(mrt-.5f) < radius) {
|
|
int mask = edgeBetweenTexels(lb, rt);
|
|
protectExtremeChannels(stencil(x, y), lb, mlb, mask);
|
|
protectExtremeChannels(stencil(x+1, y+1), rt, mrt, mask);
|
|
}
|
|
if (fabsf(mrb-.5f)+fabsf(mlt-.5f) < radius) {
|
|
int mask = edgeBetweenTexels(rb, lt);
|
|
protectExtremeChannels(stencil(x+1, y), rb, mrb, mask);
|
|
protectExtremeChannels(stencil(x, y+1), lt, mlt, mask);
|
|
}
|
|
lb += N, rb += N, lt += N, rt += N;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MSDFErrorCorrection::protectAll() {
|
|
byte *end = stencil.pixels+stencil.width*stencil.height;
|
|
for (byte *mask = stencil.pixels; mask < end; ++mask)
|
|
*mask |= (byte) PROTECTED;
|
|
}
|
|
|
|
/// Returns the median of the linear interpolation of texels a, b at t.
|
|
static float interpolatedMedian(const float *a, const float *b, double t) {
|
|
return median(
|
|
mix(a[0], b[0], t),
|
|
mix(a[1], b[1], t),
|
|
mix(a[2], b[2], t)
|
|
);
|
|
}
|
|
/// Returns the median of the bilinear interpolation with the given constant, linear, and quadratic terms at t.
|
|
static float interpolatedMedian(const float *a, const float *l, const float *q, double t) {
|
|
return float(median(
|
|
t*(t*q[0]+l[0])+a[0],
|
|
t*(t*q[1]+l[1])+a[1],
|
|
t*(t*q[2]+l[2])+a[2]
|
|
));
|
|
}
|
|
|
|
/// Determines if the interpolated median xm is an artifact.
|
|
static bool isArtifact(bool isProtected, double axSpan, double bxSpan, float am, float bm, float xm) {
|
|
return (
|
|
// For protected texels, only report an artifact if it would cause fill inversion (change between positive and negative distance).
|
|
(!isProtected || (am > .5f && bm > .5f && xm <= .5f) || (am < .5f && bm < .5f && xm >= .5f)) &&
|
|
// This is an artifact if the interpolated median is outside the range of possible values based on its distance from a, b.
|
|
!(xm >= am-axSpan && xm <= am+axSpan && xm >= bm-bxSpan && xm <= bm+bxSpan)
|
|
);
|
|
}
|
|
|
|
/// Checks if a linear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values.
|
|
template <class ArtifactClassifier>
|
|
static bool hasLinearArtifactInner(const ArtifactClassifier &artifactClassifier, float am, float bm, const float *a, const float *b, float dA, float dB) {
|
|
// Find interpolation ratio t (0 < t < 1) where two color channels are equal (mix(dA, dB, t) == 0).
|
|
double t = (double) dA/(dA-dB);
|
|
if (t > ARTIFACT_T_EPSILON && t < 1-ARTIFACT_T_EPSILON) {
|
|
// Interpolate median at t and let the classifier decide if its value indicates an artifact.
|
|
float xm = interpolatedMedian(a, b, t);
|
|
return artifactClassifier.evaluate(t, xm, artifactClassifier.rangeTest(0, 1, t, am, bm, xm));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Checks if a bilinear interpolation artifact will occur at a point where two specific color channels are equal - such points have extreme median values.
|
|
template <class ArtifactClassifier>
|
|
static bool hasDiagonalArtifactInner(const ArtifactClassifier &artifactClassifier, float am, float dm, const float *a, const float *l, const float *q, float dA, float dBC, float dD, double tEx0, double tEx1) {
|
|
// Find interpolation ratios t (0 < t[i] < 1) where two color channels are equal.
|
|
double t[2];
|
|
int solutions = solveQuadratic(t, dD-dBC+dA, dBC-dA-dA, dA);
|
|
for (int i = 0; i < solutions; ++i) {
|
|
// Solutions t[i] == 0 and t[i] == 1 are singularities and occur very often because two channels are usually equal at texels.
|
|
if (t[i] > ARTIFACT_T_EPSILON && t[i] < 1-ARTIFACT_T_EPSILON) {
|
|
// Interpolate median xm at t.
|
|
float xm = interpolatedMedian(a, l, q, t[i]);
|
|
// Determine if xm deviates too much from medians of a, d.
|
|
int rangeFlags = artifactClassifier.rangeTest(0, 1, t[i], am, dm, xm);
|
|
// Additionally, check xm against the interpolated medians at the local extremes tEx0, tEx1.
|
|
double tEnd[2];
|
|
float em[2];
|
|
// tEx0
|
|
if (tEx0 > 0 && tEx0 < 1) {
|
|
tEnd[0] = 0, tEnd[1] = 1;
|
|
em[0] = am, em[1] = dm;
|
|
tEnd[tEx0 > t[i]] = tEx0;
|
|
em[tEx0 > t[i]] = interpolatedMedian(a, l, q, tEx0);
|
|
rangeFlags |= artifactClassifier.rangeTest(tEnd[0], tEnd[1], t[i], em[0], em[1], xm);
|
|
}
|
|
// tEx1
|
|
if (tEx1 > 0 && tEx1 < 1) {
|
|
tEnd[0] = 0, tEnd[1] = 1;
|
|
em[0] = am, em[1] = dm;
|
|
tEnd[tEx1 > t[i]] = tEx1;
|
|
em[tEx1 > t[i]] = interpolatedMedian(a, l, q, tEx1);
|
|
rangeFlags |= artifactClassifier.rangeTest(tEnd[0], tEnd[1], t[i], em[0], em[1], xm);
|
|
}
|
|
if (artifactClassifier.evaluate(t[i], xm, rangeFlags))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Checks if a linear interpolation artifact will occur inbetween two horizontally or vertically adjacent texels a, b.
|
|
template <class ArtifactClassifier>
|
|
static bool hasLinearArtifact(const ArtifactClassifier &artifactClassifier, float am, const float *a, const float *b) {
|
|
float bm = median(b[0], b[1], b[2]);
|
|
return (
|
|
// Out of the pair, only report artifacts for the texel further from the edge to minimize side effects.
|
|
fabsf(am-.5f) >= fabsf(bm-.5f) && (
|
|
// Check points where each pair of color channels meets.
|
|
hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[1]-a[0], b[1]-b[0]) ||
|
|
hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[2]-a[1], b[2]-b[1]) ||
|
|
hasLinearArtifactInner(artifactClassifier, am, bm, a, b, a[0]-a[2], b[0]-b[2])
|
|
)
|
|
);
|
|
}
|
|
|
|
/// Checks if a bilinear interpolation artifact will occur inbetween two diagonally adjacent texels a, d (with b, c forming the other diagonal).
|
|
template <class ArtifactClassifier>
|
|
static bool hasDiagonalArtifact(const ArtifactClassifier &artifactClassifier, float am, const float *a, const float *b, const float *c, const float *d) {
|
|
float dm = median(d[0], d[1], d[2]);
|
|
// Out of the pair, only report artifacts for the texel further from the edge to minimize side effects.
|
|
if (fabsf(am-.5f) >= fabsf(dm-.5f)) {
|
|
float abc[3] = {
|
|
a[0]-b[0]-c[0],
|
|
a[1]-b[1]-c[1],
|
|
a[2]-b[2]-c[2]
|
|
};
|
|
// Compute the linear terms for bilinear interpolation.
|
|
float l[3] = {
|
|
-a[0]-abc[0],
|
|
-a[1]-abc[1],
|
|
-a[2]-abc[2]
|
|
};
|
|
// Compute the quadratic terms for bilinear interpolation.
|
|
float q[3] = {
|
|
d[0]+abc[0],
|
|
d[1]+abc[1],
|
|
d[2]+abc[2]
|
|
};
|
|
// Compute interpolation ratios tEx (0 < tEx[i] < 1) for the local extremes of each color channel (the derivative 2*q[i]*tEx[i]+l[i] == 0).
|
|
double tEx[3] = {
|
|
-.5*l[0]/q[0],
|
|
-.5*l[1]/q[1],
|
|
-.5*l[2]/q[2]
|
|
};
|
|
// Check points where each pair of color channels meets.
|
|
return (
|
|
hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[1]-a[0], b[1]-b[0]+c[1]-c[0], d[1]-d[0], tEx[0], tEx[1]) ||
|
|
hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[2]-a[1], b[2]-b[1]+c[2]-c[1], d[2]-d[1], tEx[1], tEx[2]) ||
|
|
hasDiagonalArtifactInner(artifactClassifier, am, dm, a, l, q, a[0]-a[2], b[0]-b[2]+c[0]-c[2], d[0]-d[2], tEx[2], tEx[0])
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <int N>
|
|
void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf) {
|
|
// Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.
|
|
double hSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)), 0)).length();
|
|
double vSpan = minDeviationRatio*transformation.unprojectVector(Vector2(0, transformation.distanceMapping(DistanceMapping::Delta(1)))).length();
|
|
double dSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)))).length();
|
|
// Inspect all texels.
|
|
for (int y = 0; y < sdf.height; ++y) {
|
|
for (int x = 0; x < sdf.width; ++x) {
|
|
const float *c = sdf(x, y);
|
|
float cm = median(c[0], c[1], c[2]);
|
|
bool protectedFlag = (*stencil(x, y)&PROTECTED) != 0;
|
|
const float *l = NULL, *b = NULL, *r = NULL, *t = NULL;
|
|
// Mark current texel c with the error flag if an artifact occurs when it's interpolated with any of its 8 neighbors.
|
|
*stencil(x, y) |= (byte) (ERROR*(
|
|
(x > 0 && ((l = sdf(x-1, y)), hasLinearArtifact(BaseArtifactClassifier(hSpan, protectedFlag), cm, c, l))) ||
|
|
(y > 0 && ((b = sdf(x, y-1)), hasLinearArtifact(BaseArtifactClassifier(vSpan, protectedFlag), cm, c, b))) ||
|
|
(x < sdf.width-1 && ((r = sdf(x+1, y)), hasLinearArtifact(BaseArtifactClassifier(hSpan, protectedFlag), cm, c, r))) ||
|
|
(y < sdf.height-1 && ((t = sdf(x, y+1)), hasLinearArtifact(BaseArtifactClassifier(vSpan, protectedFlag), cm, c, t))) ||
|
|
(x > 0 && y > 0 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, l, b, sdf(x-1, y-1))) ||
|
|
(x < sdf.width-1 && y > 0 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, r, b, sdf(x+1, y-1))) ||
|
|
(x > 0 && y < sdf.height-1 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, l, t, sdf(x-1, y+1))) ||
|
|
(x < sdf.width-1 && y < sdf.height-1 && hasDiagonalArtifact(BaseArtifactClassifier(dSpan, protectedFlag), cm, c, r, t, sdf(x+1, y+1)))
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
template <template <typename> class ContourCombiner, int N>
|
|
void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf, const Shape &shape) {
|
|
// Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.
|
|
double hSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)), 0)).length();
|
|
double vSpan = minDeviationRatio*transformation.unprojectVector(Vector2(0, transformation.distanceMapping(DistanceMapping::Delta(1)))).length();
|
|
double dSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)))).length();
|
|
#ifdef MSDFGEN_USE_OPENMP
|
|
#pragma omp parallel
|
|
#endif
|
|
{
|
|
ShapeDistanceChecker<ContourCombiner, N> shapeDistanceChecker(sdf, shape, transformation, transformation.distanceMapping, minImproveRatio);
|
|
bool rightToLeft = false;
|
|
// Inspect all texels.
|
|
#ifdef MSDFGEN_USE_OPENMP
|
|
#pragma omp for
|
|
#endif
|
|
for (int y = 0; y < sdf.height; ++y) {
|
|
int row = shape.inverseYAxis ? sdf.height-y-1 : y;
|
|
for (int col = 0; col < sdf.width; ++col) {
|
|
int x = rightToLeft ? sdf.width-col-1 : col;
|
|
if ((*stencil(x, row)&ERROR))
|
|
continue;
|
|
const float *c = sdf(x, row);
|
|
shapeDistanceChecker.shapeCoord = transformation.unproject(Point2(x+.5, y+.5));
|
|
shapeDistanceChecker.sdfCoord = Point2(x+.5, row+.5);
|
|
shapeDistanceChecker.msd = c;
|
|
shapeDistanceChecker.protectedFlag = (*stencil(x, row)&PROTECTED) != 0;
|
|
float cm = median(c[0], c[1], c[2]);
|
|
const float *l = NULL, *b = NULL, *r = NULL, *t = NULL;
|
|
// Mark current texel c with the error flag if an artifact occurs when it's interpolated with any of its 8 neighbors.
|
|
*stencil(x, row) |= (byte) (ERROR*(
|
|
(x > 0 && ((l = sdf(x-1, row)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(-1, 0), hSpan), cm, c, l))) ||
|
|
(row > 0 && ((b = sdf(x, row-1)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(0, -1), vSpan), cm, c, b))) ||
|
|
(x < sdf.width-1 && ((r = sdf(x+1, row)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(+1, 0), hSpan), cm, c, r))) ||
|
|
(row < sdf.height-1 && ((t = sdf(x, row+1)), hasLinearArtifact(shapeDistanceChecker.classifier(Vector2(0, +1), vSpan), cm, c, t))) ||
|
|
(x > 0 && row > 0 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(-1, -1), dSpan), cm, c, l, b, sdf(x-1, row-1))) ||
|
|
(x < sdf.width-1 && row > 0 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(+1, -1), dSpan), cm, c, r, b, sdf(x+1, row-1))) ||
|
|
(x > 0 && row < sdf.height-1 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(-1, +1), dSpan), cm, c, l, t, sdf(x-1, row+1))) ||
|
|
(x < sdf.width-1 && row < sdf.height-1 && hasDiagonalArtifact(shapeDistanceChecker.classifier(Vector2(+1, +1), dSpan), cm, c, r, t, sdf(x+1, row+1)))
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <int N>
|
|
void MSDFErrorCorrection::apply(const BitmapRef<float, N> &sdf) const {
|
|
int texelCount = sdf.width*sdf.height;
|
|
const byte *mask = stencil.pixels;
|
|
float *texel = sdf.pixels;
|
|
for (int i = 0; i < texelCount; ++i) {
|
|
if (*mask&ERROR) {
|
|
// Set all color channels to the median.
|
|
float m = median(texel[0], texel[1], texel[2]);
|
|
texel[0] = m, texel[1] = m, texel[2] = m;
|
|
}
|
|
++mask;
|
|
texel += N;
|
|
}
|
|
}
|
|
|
|
BitmapConstRef<byte, 1> MSDFErrorCorrection::getStencil() const {
|
|
return stencil;
|
|
}
|
|
|
|
template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 3> &sdf);
|
|
template void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, 4> &sdf);
|
|
template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 3> &sdf);
|
|
template void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, 4> &sdf);
|
|
template void MSDFErrorCorrection::findErrors<SimpleContourCombiner>(const BitmapConstRef<float, 3> &sdf, const Shape &shape);
|
|
template void MSDFErrorCorrection::findErrors<SimpleContourCombiner>(const BitmapConstRef<float, 4> &sdf, const Shape &shape);
|
|
template void MSDFErrorCorrection::findErrors<OverlappingContourCombiner>(const BitmapConstRef<float, 3> &sdf, const Shape &shape);
|
|
template void MSDFErrorCorrection::findErrors<OverlappingContourCombiner>(const BitmapConstRef<float, 4> &sdf, const Shape &shape);
|
|
template void MSDFErrorCorrection::apply(const BitmapRef<float, 3> &sdf) const;
|
|
template void MSDFErrorCorrection::apply(const BitmapRef<float, 4> &sdf) const;
|
|
|
|
template <typename DistanceType>
|
|
class DistancePixelConversion;
|
|
|
|
template <>
|
|
class DistancePixelConversion<double> {
|
|
DistanceMapping mapping;
|
|
public:
|
|
typedef BitmapRef<float, 1> BitmapRefType;
|
|
inline explicit DistancePixelConversion(DistanceMapping mapping) : mapping(mapping) { }
|
|
inline void operator()(float *pixels, double distance) const {
|
|
*pixels = float(mapping(distance));
|
|
}
|
|
};
|
|
|
|
template <>
|
|
class DistancePixelConversion<MultiDistance> {
|
|
DistanceMapping mapping;
|
|
public:
|
|
typedef BitmapRef<float, 3> BitmapRefType;
|
|
inline explicit DistancePixelConversion(DistanceMapping mapping) : mapping(mapping) { }
|
|
inline void operator()(float *pixels, const MultiDistance &distance) const {
|
|
pixels[0] = float(mapping(distance.r));
|
|
pixels[1] = float(mapping(distance.g));
|
|
pixels[2] = float(mapping(distance.b));
|
|
}
|
|
};
|
|
|
|
template <>
|
|
class DistancePixelConversion<MultiAndTrueDistance> {
|
|
DistanceMapping mapping;
|
|
public:
|
|
typedef BitmapRef<float, 4> BitmapRefType;
|
|
inline explicit DistancePixelConversion(DistanceMapping mapping) : mapping(mapping) { }
|
|
inline void operator()(float *pixels, const MultiAndTrueDistance &distance) const {
|
|
pixels[0] = float(mapping(distance.r));
|
|
pixels[1] = float(mapping(distance.g));
|
|
pixels[2] = float(mapping(distance.b));
|
|
pixels[3] = float(mapping(distance.a));
|
|
}
|
|
};
|
|
|
|
template <class ContourCombiner>
|
|
void generateDistanceField(const typename DistancePixelConversion<typename ContourCombiner::DistanceType>::BitmapRefType &output, const Shape &shape, const SDFTransformation &transformation) {
|
|
DistancePixelConversion<typename ContourCombiner::DistanceType> distancePixelConversion(transformation.distanceMapping);
|
|
#ifdef MSDFGEN_USE_OPENMP
|
|
#pragma omp parallel
|
|
#endif
|
|
{
|
|
ShapeDistanceFinder<ContourCombiner> distanceFinder(shape);
|
|
bool rightToLeft = false;
|
|
#ifdef MSDFGEN_USE_OPENMP
|
|
#pragma omp for
|
|
#endif
|
|
for (int y = 0; y < output.height; ++y) {
|
|
int row = shape.inverseYAxis ? output.height-y-1 : y;
|
|
for (int col = 0; col < output.width; ++col) {
|
|
int x = rightToLeft ? output.width-col-1 : col;
|
|
Point2 p = transformation.unproject(Point2(x+.5, y+.5));
|
|
typename ContourCombiner::DistanceType distance = distanceFinder.distance(p);
|
|
distancePixelConversion(output(x, row), distance);
|
|
}
|
|
rightToLeft = !rightToLeft;
|
|
}
|
|
}
|
|
}
|
|
|
|
void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const SDFTransformation &transformation, const GeneratorConfig &config) {
|
|
if (config.overlapSupport)
|
|
generateDistanceField<OverlappingContourCombiner<TrueDistanceSelector> >(output, shape, transformation);
|
|
else
|
|
generateDistanceField<SimpleContourCombiner<TrueDistanceSelector> >(output, shape, transformation);
|
|
}
|
|
|
|
void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, const SDFTransformation &transformation, const GeneratorConfig &config) {
|
|
if (config.overlapSupport)
|
|
generateDistanceField<OverlappingContourCombiner<PerpendicularDistanceSelector> >(output, shape, transformation);
|
|
else
|
|
generateDistanceField<SimpleContourCombiner<PerpendicularDistanceSelector> >(output, shape, transformation);
|
|
}
|
|
|
|
void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
|
|
if (config.overlapSupport)
|
|
generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, transformation);
|
|
else
|
|
generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, transformation);
|
|
msdfErrorCorrection(output, shape, transformation, config);
|
|
}
|
|
|
|
void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
|
|
if (config.overlapSupport)
|
|
generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, transformation);
|
|
else
|
|
generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, transformation);
|
|
msdfErrorCorrection(output, shape, transformation, config);
|
|
}
|
|
|
|
void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config) {
|
|
if (config.overlapSupport)
|
|
generateDistanceField<OverlappingContourCombiner<TrueDistanceSelector> >(output, shape, SDFTransformation(projection, range));
|
|
else
|
|
generateDistanceField<SimpleContourCombiner<TrueDistanceSelector> >(output, shape, SDFTransformation(projection, range));
|
|
}
|
|
|
|
void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config) {
|
|
if (config.overlapSupport)
|
|
generateDistanceField<OverlappingContourCombiner<PerpendicularDistanceSelector> >(output, shape, SDFTransformation(projection, range));
|
|
else
|
|
generateDistanceField<SimpleContourCombiner<PerpendicularDistanceSelector> >(output, shape, SDFTransformation(projection, range));
|
|
}
|
|
|
|
void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config) {
|
|
if (config.overlapSupport)
|
|
generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, SDFTransformation(projection, range));
|
|
else
|
|
generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, SDFTransformation(projection, range));
|
|
msdfErrorCorrection(output, shape, SDFTransformation(projection, range), config);
|
|
}
|
|
|
|
void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config) {
|
|
if (config.overlapSupport)
|
|
generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, SDFTransformation(projection, range));
|
|
else
|
|
generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, SDFTransformation(projection, range));
|
|
msdfErrorCorrection(output, shape, SDFTransformation(projection, range), config);
|
|
}
|
|
|
|
// Legacy API
|
|
|
|
void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config) {
|
|
generatePSDF(output, shape, SDFTransformation(projection, range), config);
|
|
}
|
|
|
|
void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
|
|
generateSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport));
|
|
}
|
|
|
|
void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
|
|
generatePSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport));
|
|
}
|
|
|
|
void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
|
|
generatePSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport));
|
|
}
|
|
|
|
void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) {
|
|
generateMSDF(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(overlapSupport, errorCorrectionConfig));
|
|
}
|
|
|
|
void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) {
|
|
generateMTSDF(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(overlapSupport, errorCorrectionConfig));
|
|
}
|
|
|
|
// Legacy version
|
|
|
|
void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate) {
|
|
DistanceMapping distanceMapping(range);
|
|
#ifdef MSDFGEN_USE_OPENMP
|
|
#pragma omp parallel for
|
|
#endif
|
|
for (int y = 0; y < output.height; ++y) {
|
|
int row = shape.inverseYAxis ? output.height-y-1 : y;
|
|
for (int x = 0; x < output.width; ++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(distanceMapping(minDistance.distance));
|
|
}
|
|
}
|
|
}
|
|
|
|
void generatePSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate) {
|
|
DistanceMapping distanceMapping(range);
|
|
#ifdef MSDFGEN_USE_OPENMP
|
|
#pragma omp parallel for
|
|
#endif
|
|
for (int y = 0; y < output.height; ++y) {
|
|
int row = shape.inverseYAxis ? output.height-y-1 : y;
|
|
for (int x = 0; x < output.width; ++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)->distanceToPerpendicularDistance(minDistance, p, nearParam);
|
|
*output(x, row) = float(distanceMapping(minDistance.distance));
|
|
}
|
|
}
|
|
}
|
|
|
|
void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate) {
|
|
generatePSDF_legacy(output, shape, range, scale, translate);
|
|
}
|
|
|
|
void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) {
|
|
DistanceMapping distanceMapping(range);
|
|
#ifdef MSDFGEN_USE_OPENMP
|
|
#pragma omp parallel for
|
|
#endif
|
|
for (int y = 0; y < output.height; ++y) {
|
|
int row = shape.inverseYAxis ? output.height-y-1 : y;
|
|
for (int x = 0; x < output.width; ++x) {
|
|
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
|
|
|
struct {
|
|
SignedDistance minDistance;
|
|
const EdgeHolder *nearEdge;
|
|
double nearParam;
|
|
} r, g, b;
|
|
r.nearEdge = g.nearEdge = b.nearEdge = NULL;
|
|
r.nearParam = g.nearParam = b.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 ((*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.nearEdge)
|
|
(*r.nearEdge)->distanceToPerpendicularDistance(r.minDistance, p, r.nearParam);
|
|
if (g.nearEdge)
|
|
(*g.nearEdge)->distanceToPerpendicularDistance(g.minDistance, p, g.nearParam);
|
|
if (b.nearEdge)
|
|
(*b.nearEdge)->distanceToPerpendicularDistance(b.minDistance, p, b.nearParam);
|
|
output(x, row)[0] = float(distanceMapping(r.minDistance.distance));
|
|
output(x, row)[1] = float(distanceMapping(g.minDistance.distance));
|
|
output(x, row)[2] = float(distanceMapping(b.minDistance.distance));
|
|
}
|
|
}
|
|
|
|
errorCorrectionConfig.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
|
|
msdfErrorCorrection(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(false, errorCorrectionConfig));
|
|
}
|
|
|
|
void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) {
|
|
DistanceMapping distanceMapping(range);
|
|
#ifdef MSDFGEN_USE_OPENMP
|
|
#pragma omp parallel for
|
|
#endif
|
|
for (int y = 0; y < output.height; ++y) {
|
|
int row = shape.inverseYAxis ? output.height-y-1 : y;
|
|
for (int x = 0; x < output.width; ++x) {
|
|
Point2 p = Vector2(x+.5, y+.5)/scale-translate;
|
|
|
|
SignedDistance minDistance;
|
|
struct {
|
|
SignedDistance minDistance;
|
|
const EdgeHolder *nearEdge;
|
|
double nearParam;
|
|
} r, g, b;
|
|
r.nearEdge = g.nearEdge = b.nearEdge = NULL;
|
|
r.nearParam = g.nearParam = b.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;
|
|
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.nearEdge)
|
|
(*r.nearEdge)->distanceToPerpendicularDistance(r.minDistance, p, r.nearParam);
|
|
if (g.nearEdge)
|
|
(*g.nearEdge)->distanceToPerpendicularDistance(g.minDistance, p, g.nearParam);
|
|
if (b.nearEdge)
|
|
(*b.nearEdge)->distanceToPerpendicularDistance(b.minDistance, p, b.nearParam);
|
|
output(x, row)[0] = float(distanceMapping(r.minDistance.distance));
|
|
output(x, row)[1] = float(distanceMapping(g.minDistance.distance));
|
|
output(x, row)[2] = float(distanceMapping(b.minDistance.distance));
|
|
output(x, row)[3] = float(distanceMapping(minDistance.distance));
|
|
}
|
|
}
|
|
|
|
errorCorrectionConfig.distanceCheckMode = ErrorCorrectionConfig::DO_NOT_CHECK_DISTANCE;
|
|
msdfErrorCorrection(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(false, errorCorrectionConfig));
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef MSDFGEN_USE_FREETYPE
|
|
|
|
#ifndef MSDFGEN_DISABLE_VARIABLE_FONTS
|
|
#endif
|
|
|
|
namespace msdfgen {
|
|
|
|
#define F16DOT16_TO_DOUBLE(x) (1/65536.*double(x))
|
|
#define DOUBLE_TO_F16DOT16(x) FT_Fixed(65536.*x)
|
|
|
|
class FreetypeHandle {
|
|
friend FreetypeHandle *initializeFreetype();
|
|
friend void deinitializeFreetype(FreetypeHandle *library);
|
|
friend FontHandle *loadFont(FreetypeHandle *library, const char *filename);
|
|
friend FontHandle *loadFontData(FreetypeHandle *library, const byte *data, int length);
|
|
#ifndef MSDFGEN_DISABLE_VARIABLE_FONTS
|
|
friend bool setFontVariationAxis(FreetypeHandle *library, FontHandle *font, const char *name, double coordinate);
|
|
friend bool listFontVariationAxes(std::vector<FontVariationAxis> &axes, FreetypeHandle *library, FontHandle *font);
|
|
#endif
|
|
|
|
FT_Library library;
|
|
|
|
};
|
|
|
|
class FontHandle {
|
|
friend FontHandle *adoptFreetypeFont(FT_Face ftFace);
|
|
friend FontHandle *loadFont(FreetypeHandle *library, const char *filename);
|
|
friend FontHandle *loadFontData(FreetypeHandle *library, const byte *data, int length);
|
|
friend void destroyFont(FontHandle *font);
|
|
friend bool getFontMetrics(FontMetrics &metrics, FontHandle *font, FontCoordinateScaling coordinateScaling);
|
|
friend bool getFontWhitespaceWidth(double &spaceAdvance, double &tabAdvance, FontHandle *font, FontCoordinateScaling coordinateScaling);
|
|
friend bool getGlyphCount(unsigned &output, FontHandle *font);
|
|
friend bool getGlyphIndex(GlyphIndex &glyphIndex, FontHandle *font, unicode_t unicode);
|
|
friend bool loadGlyph(Shape &output, FontHandle *font, GlyphIndex glyphIndex, FontCoordinateScaling coordinateScaling, double *outAdvance);
|
|
friend bool loadGlyph(Shape &output, FontHandle *font, unicode_t unicode, FontCoordinateScaling coordinateScaling, double *outAdvance);
|
|
friend bool getKerning(double &output, FontHandle *font, GlyphIndex glyphIndex0, GlyphIndex glyphIndex1, FontCoordinateScaling coordinateScaling);
|
|
friend bool getKerning(double &output, FontHandle *font, unicode_t unicode0, unicode_t unicode1, FontCoordinateScaling coordinateScaling);
|
|
#ifndef MSDFGEN_DISABLE_VARIABLE_FONTS
|
|
friend bool setFontVariationAxis(FreetypeHandle *library, FontHandle *font, const char *name, double coordinate);
|
|
friend bool listFontVariationAxes(std::vector<FontVariationAxis> &axes, FreetypeHandle *library, FontHandle *font);
|
|
#endif
|
|
|
|
FT_Face face;
|
|
bool ownership;
|
|
|
|
};
|
|
|
|
struct FtContext {
|
|
double scale;
|
|
Point2 position;
|
|
Shape *shape;
|
|
Contour *contour;
|
|
};
|
|
|
|
static Point2 ftPoint2(const FT_Vector &vector, double scale) {
|
|
return Point2(scale*vector.x, scale*vector.y);
|
|
}
|
|
|
|
static int ftMoveTo(const FT_Vector *to, void *user) {
|
|
FtContext *context = reinterpret_cast<FtContext *>(user);
|
|
if (!(context->contour && context->contour->edges.empty()))
|
|
context->contour = &context->shape->addContour();
|
|
context->position = ftPoint2(*to, context->scale);
|
|
return 0;
|
|
}
|
|
|
|
static int ftLineTo(const FT_Vector *to, void *user) {
|
|
FtContext *context = reinterpret_cast<FtContext *>(user);
|
|
Point2 endpoint = ftPoint2(*to, context->scale);
|
|
if (endpoint != context->position) {
|
|
context->contour->addEdge(EdgeHolder(context->position, endpoint));
|
|
context->position = endpoint;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ftConicTo(const FT_Vector *control, const FT_Vector *to, void *user) {
|
|
FtContext *context = reinterpret_cast<FtContext *>(user);
|
|
Point2 endpoint = ftPoint2(*to, context->scale);
|
|
if (endpoint != context->position) {
|
|
context->contour->addEdge(EdgeHolder(context->position, ftPoint2(*control, context->scale), endpoint));
|
|
context->position = endpoint;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ftCubicTo(const FT_Vector *control1, const FT_Vector *control2, const FT_Vector *to, void *user) {
|
|
FtContext *context = reinterpret_cast<FtContext *>(user);
|
|
Point2 endpoint = ftPoint2(*to, context->scale);
|
|
if (endpoint != context->position || crossProduct(ftPoint2(*control1, context->scale)-endpoint, ftPoint2(*control2, context->scale)-endpoint)) {
|
|
context->contour->addEdge(EdgeHolder(context->position, ftPoint2(*control1, context->scale), ftPoint2(*control2, context->scale), endpoint));
|
|
context->position = endpoint;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static double getFontCoordinateScale(const FT_Face &face, FontCoordinateScaling coordinateScaling) {
|
|
switch (coordinateScaling) {
|
|
case FONT_SCALING_NONE:
|
|
return 1;
|
|
case FONT_SCALING_EM_NORMALIZED:
|
|
return 1./(face->units_per_EM ? face->units_per_EM : 1);
|
|
case FONT_SCALING_LEGACY:
|
|
return MSDFGEN_LEGACY_FONT_COORDINATE_SCALE;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
GlyphIndex::GlyphIndex(unsigned index) : index(index) { }
|
|
|
|
unsigned GlyphIndex::getIndex() const {
|
|
return index;
|
|
}
|
|
|
|
FreetypeHandle *initializeFreetype() {
|
|
FreetypeHandle *handle = new FreetypeHandle;
|
|
FT_Error error = FT_Init_FreeType(&handle->library);
|
|
if (error) {
|
|
delete handle;
|
|
return NULL;
|
|
}
|
|
return handle;
|
|
}
|
|
|
|
void deinitializeFreetype(FreetypeHandle *library) {
|
|
FT_Done_FreeType(library->library);
|
|
delete library;
|
|
}
|
|
|
|
FontHandle *adoptFreetypeFont(FT_Face ftFace) {
|
|
FontHandle *handle = new FontHandle;
|
|
handle->face = ftFace;
|
|
handle->ownership = false;
|
|
return handle;
|
|
}
|
|
|
|
FT_Error readFreetypeOutline(Shape &output, FT_Outline *outline, double scale) {
|
|
output.contours.clear();
|
|
output.inverseYAxis = false;
|
|
FtContext context = { };
|
|
context.scale = scale;
|
|
context.shape = &output;
|
|
FT_Outline_Funcs ftFunctions;
|
|
ftFunctions.move_to = &ftMoveTo;
|
|
ftFunctions.line_to = &ftLineTo;
|
|
ftFunctions.conic_to = &ftConicTo;
|
|
ftFunctions.cubic_to = &ftCubicTo;
|
|
ftFunctions.shift = 0;
|
|
ftFunctions.delta = 0;
|
|
FT_Error error = FT_Outline_Decompose(outline, &ftFunctions, &context);
|
|
if (!output.contours.empty() && output.contours.back().edges.empty())
|
|
output.contours.pop_back();
|
|
return error;
|
|
}
|
|
|
|
FontHandle *loadFont(FreetypeHandle *library, const char *filename) {
|
|
if (!library)
|
|
return NULL;
|
|
FontHandle *handle = new FontHandle;
|
|
FT_Error error = FT_New_Face(library->library, filename, 0, &handle->face);
|
|
if (error) {
|
|
delete handle;
|
|
return NULL;
|
|
}
|
|
handle->ownership = true;
|
|
return handle;
|
|
}
|
|
|
|
FontHandle *loadFontData(FreetypeHandle *library, const byte *data, int length) {
|
|
if (!library)
|
|
return NULL;
|
|
FontHandle *handle = new FontHandle;
|
|
FT_Error error = FT_New_Memory_Face(library->library, data, length, 0, &handle->face);
|
|
if (error) {
|
|
delete handle;
|
|
return NULL;
|
|
}
|
|
handle->ownership = true;
|
|
return handle;
|
|
}
|
|
|
|
void destroyFont(FontHandle *font) {
|
|
if (font->ownership)
|
|
FT_Done_Face(font->face);
|
|
delete font;
|
|
}
|
|
|
|
bool getFontMetrics(FontMetrics &metrics, FontHandle *font, FontCoordinateScaling coordinateScaling) {
|
|
double scale = getFontCoordinateScale(font->face, coordinateScaling);
|
|
metrics.emSize = scale*font->face->units_per_EM;
|
|
metrics.ascenderY = scale*font->face->ascender;
|
|
metrics.descenderY = scale*font->face->descender;
|
|
metrics.lineHeight = scale*font->face->height;
|
|
metrics.underlineY = scale*font->face->underline_position;
|
|
metrics.underlineThickness = scale*font->face->underline_thickness;
|
|
return true;
|
|
}
|
|
|
|
bool getFontWhitespaceWidth(double &spaceAdvance, double &tabAdvance, FontHandle *font, FontCoordinateScaling coordinateScaling) {
|
|
double scale = getFontCoordinateScale(font->face, coordinateScaling);
|
|
FT_Error error = FT_Load_Char(font->face, ' ', FT_LOAD_NO_SCALE);
|
|
if (error)
|
|
return false;
|
|
spaceAdvance = scale*font->face->glyph->advance.x;
|
|
error = FT_Load_Char(font->face, '\t', FT_LOAD_NO_SCALE);
|
|
if (error)
|
|
return false;
|
|
tabAdvance = scale*font->face->glyph->advance.x;
|
|
return true;
|
|
}
|
|
|
|
bool getGlyphCount(unsigned &output, FontHandle *font) {
|
|
output = (unsigned) font->face->num_glyphs;
|
|
return true;
|
|
}
|
|
|
|
bool getGlyphIndex(GlyphIndex &glyphIndex, FontHandle *font, unicode_t unicode) {
|
|
glyphIndex = GlyphIndex(FT_Get_Char_Index(font->face, unicode));
|
|
return glyphIndex.getIndex() != 0;
|
|
}
|
|
|
|
bool loadGlyph(Shape &output, FontHandle *font, GlyphIndex glyphIndex, FontCoordinateScaling coordinateScaling, double *outAdvance) {
|
|
if (!font)
|
|
return false;
|
|
FT_Error error = FT_Load_Glyph(font->face, glyphIndex.getIndex(), FT_LOAD_NO_SCALE);
|
|
if (error)
|
|
return false;
|
|
double scale = getFontCoordinateScale(font->face, coordinateScaling);
|
|
if (outAdvance)
|
|
*outAdvance = scale*font->face->glyph->advance.x;
|
|
return !readFreetypeOutline(output, &font->face->glyph->outline, scale);
|
|
}
|
|
|
|
bool loadGlyph(Shape &output, FontHandle *font, unicode_t unicode, FontCoordinateScaling coordinateScaling, double *outAdvance) {
|
|
return loadGlyph(output, font, GlyphIndex(FT_Get_Char_Index(font->face, unicode)), coordinateScaling, outAdvance);
|
|
}
|
|
|
|
bool loadGlyph(Shape &output, FontHandle *font, GlyphIndex glyphIndex, double *outAdvance) {
|
|
return loadGlyph(output, font, glyphIndex, FONT_SCALING_LEGACY, outAdvance);
|
|
}
|
|
|
|
bool loadGlyph(Shape &output, FontHandle *font, unicode_t unicode, double *outAdvance) {
|
|
return loadGlyph(output, font, unicode, FONT_SCALING_LEGACY, outAdvance);
|
|
}
|
|
|
|
bool getKerning(double &output, FontHandle *font, GlyphIndex glyphIndex0, GlyphIndex glyphIndex1, FontCoordinateScaling coordinateScaling) {
|
|
FT_Vector kerning;
|
|
if (FT_Get_Kerning(font->face, glyphIndex0.getIndex(), glyphIndex1.getIndex(), FT_KERNING_UNSCALED, &kerning)) {
|
|
output = 0;
|
|
return false;
|
|
}
|
|
output = getFontCoordinateScale(font->face, coordinateScaling)*kerning.x;
|
|
return true;
|
|
}
|
|
|
|
bool getKerning(double &output, FontHandle *font, unicode_t unicode0, unicode_t unicode1, FontCoordinateScaling coordinateScaling) {
|
|
return getKerning(output, font, GlyphIndex(FT_Get_Char_Index(font->face, unicode0)), GlyphIndex(FT_Get_Char_Index(font->face, unicode1)), coordinateScaling);
|
|
}
|
|
|
|
#ifndef MSDFGEN_DISABLE_VARIABLE_FONTS
|
|
|
|
bool setFontVariationAxis(FreetypeHandle *library, FontHandle *font, const char *name, double coordinate) {
|
|
bool success = false;
|
|
if (font->face->face_flags&FT_FACE_FLAG_MULTIPLE_MASTERS) {
|
|
FT_MM_Var *master = NULL;
|
|
if (FT_Get_MM_Var(font->face, &master))
|
|
return false;
|
|
if (master && master->num_axis) {
|
|
std::vector<FT_Fixed> coords(master->num_axis);
|
|
if (!FT_Get_Var_Design_Coordinates(font->face, FT_UInt(coords.size()), &coords[0])) {
|
|
for (FT_UInt i = 0; i < master->num_axis; ++i) {
|
|
if (!strcmp(name, master->axis[i].name)) {
|
|
coords[i] = DOUBLE_TO_F16DOT16(coordinate);
|
|
success = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (FT_Set_Var_Design_Coordinates(font->face, FT_UInt(coords.size()), &coords[0]))
|
|
success = false;
|
|
}
|
|
FT_Done_MM_Var(library->library, master);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool listFontVariationAxes(std::vector<FontVariationAxis> &axes, FreetypeHandle *library, FontHandle *font) {
|
|
if (font->face->face_flags&FT_FACE_FLAG_MULTIPLE_MASTERS) {
|
|
FT_MM_Var *master = NULL;
|
|
if (FT_Get_MM_Var(font->face, &master))
|
|
return false;
|
|
axes.resize(master->num_axis);
|
|
for (FT_UInt i = 0; i < master->num_axis; ++i) {
|
|
FontVariationAxis &axis = axes[i];
|
|
axis.name = master->axis[i].name;
|
|
axis.minValue = F16DOT16_TO_DOUBLE(master->axis[i].minimum);
|
|
axis.maxValue = F16DOT16_TO_DOUBLE(master->axis[i].maximum);
|
|
axis.defaultValue = F16DOT16_TO_DOUBLE(master->axis[i].def);
|
|
}
|
|
FT_Done_MM_Var(library->library, master);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef MSDFGEN_USE_SKIA
|
|
|
|
namespace msdfgen {
|
|
|
|
SkPoint pointToSkiaPoint(Point2 p) {
|
|
return SkPoint::Make((SkScalar) p.x, (SkScalar) p.y);
|
|
}
|
|
|
|
Point2 pointFromSkiaPoint(const SkPoint p) {
|
|
return Point2((double) p.x(), (double) p.y());
|
|
}
|
|
|
|
void shapeToSkiaPath(SkPath &skPath, const Shape &shape) {
|
|
for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) {
|
|
if (!contour->edges.empty()) {
|
|
const EdgeSegment *edge = contour->edges.back();
|
|
skPath.moveTo(pointToSkiaPoint(*edge->controlPoints()));
|
|
for (std::vector<EdgeHolder>::const_iterator nextEdge = contour->edges.begin(); nextEdge != contour->edges.end(); edge = *nextEdge++) {
|
|
const Point2 *p = edge->controlPoints();
|
|
switch (edge->type()) {
|
|
case (int) LinearSegment::EDGE_TYPE:
|
|
skPath.lineTo(pointToSkiaPoint(p[1]));
|
|
break;
|
|
case (int) QuadraticSegment::EDGE_TYPE:
|
|
skPath.quadTo(pointToSkiaPoint(p[1]), pointToSkiaPoint(p[2]));
|
|
break;
|
|
case (int) CubicSegment::EDGE_TYPE:
|
|
skPath.cubicTo(pointToSkiaPoint(p[1]), pointToSkiaPoint(p[2]), pointToSkiaPoint(p[3]));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void shapeFromSkiaPath(Shape &shape, const SkPath &skPath) {
|
|
shape.contours.clear();
|
|
Contour *contour = &shape.addContour();
|
|
SkPath::Iter pathIterator(skPath, true);
|
|
SkPoint edgePoints[4];
|
|
for (SkPath::Verb op; (op = pathIterator.next(edgePoints)) != SkPath::kDone_Verb;) {
|
|
switch (op) {
|
|
case SkPath::kMove_Verb:
|
|
if (!contour->edges.empty())
|
|
contour = &shape.addContour();
|
|
break;
|
|
case SkPath::kLine_Verb:
|
|
contour->addEdge(EdgeHolder(pointFromSkiaPoint(edgePoints[0]), pointFromSkiaPoint(edgePoints[1])));
|
|
break;
|
|
case SkPath::kQuad_Verb:
|
|
contour->addEdge(EdgeHolder(pointFromSkiaPoint(edgePoints[0]), pointFromSkiaPoint(edgePoints[1]), pointFromSkiaPoint(edgePoints[2])));
|
|
break;
|
|
case SkPath::kCubic_Verb:
|
|
contour->addEdge(EdgeHolder(pointFromSkiaPoint(edgePoints[0]), pointFromSkiaPoint(edgePoints[1]), pointFromSkiaPoint(edgePoints[2]), pointFromSkiaPoint(edgePoints[3])));
|
|
break;
|
|
case SkPath::kConic_Verb:
|
|
{
|
|
SkPoint quadPoints[5];
|
|
SkPath::ConvertConicToQuads(edgePoints[0], edgePoints[1], edgePoints[2], pathIterator.conicWeight(), quadPoints, 1);
|
|
contour->addEdge(EdgeHolder(pointFromSkiaPoint(quadPoints[0]), pointFromSkiaPoint(quadPoints[1]), pointFromSkiaPoint(quadPoints[2])));
|
|
contour->addEdge(EdgeHolder(pointFromSkiaPoint(quadPoints[2]), pointFromSkiaPoint(quadPoints[3]), pointFromSkiaPoint(quadPoints[4])));
|
|
}
|
|
break;
|
|
case SkPath::kClose_Verb:
|
|
case SkPath::kDone_Verb:
|
|
break;
|
|
}
|
|
}
|
|
if (contour->edges.empty())
|
|
shape.contours.pop_back();
|
|
}
|
|
|
|
static void pruneCrossedQuadrilaterals(Shape &shape) {
|
|
int n = 0;
|
|
for (int i = 0; i < (int) shape.contours.size(); ++i) {
|
|
Contour &contour = shape.contours[i];
|
|
if (
|
|
contour.edges.size() == 4 &&
|
|
contour.edges[0]->type() == (int) LinearSegment::EDGE_TYPE &&
|
|
contour.edges[1]->type() == (int) LinearSegment::EDGE_TYPE &&
|
|
contour.edges[2]->type() == (int) LinearSegment::EDGE_TYPE &&
|
|
contour.edges[3]->type() == (int) LinearSegment::EDGE_TYPE && (
|
|
sign(crossProduct(contour.edges[0]->direction(1), contour.edges[1]->direction(0)))+
|
|
sign(crossProduct(contour.edges[1]->direction(1), contour.edges[2]->direction(0)))+
|
|
sign(crossProduct(contour.edges[2]->direction(1), contour.edges[3]->direction(0)))+
|
|
sign(crossProduct(contour.edges[3]->direction(1), contour.edges[0]->direction(0)))
|
|
) == 0
|
|
) {
|
|
contour.edges.clear();
|
|
} else {
|
|
if (i != n) {
|
|
#ifdef MSDFGEN_USE_CPP11
|
|
shape.contours[n] = (Contour &&) contour;
|
|
#else
|
|
shape.contours[n] = contour;
|
|
#endif
|
|
}
|
|
++n;
|
|
}
|
|
}
|
|
shape.contours.resize(n);
|
|
}
|
|
|
|
bool resolveShapeGeometry(Shape &shape) {
|
|
SkPath skPath;
|
|
shape.normalize();
|
|
shapeToSkiaPath(skPath, shape);
|
|
if (!Simplify(skPath, &skPath))
|
|
return false;
|
|
// Skia's AsWinding doesn't seem to work for unknown reasons
|
|
shapeFromSkiaPath(shape, skPath);
|
|
// In some rare cases, Skia produces tiny residual crossed quadrilateral contours, which are not valid geometry, so they must be removed.
|
|
pruneCrossedQuadrilaterals(shape);
|
|
shape.orientContours();
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifndef MSDFGEN_DISABLE_SVG
|
|
|
|
#ifdef MSDFGEN_USE_TINYXML2
|
|
#endif
|
|
#ifdef MSDFGEN_USE_DROPXML
|
|
#endif
|
|
|
|
#ifdef MSDFGEN_USE_SKIA
|
|
#endif
|
|
|
|
#define ARC_SEGMENTS_PER_PI 2
|
|
#define ENDPOINT_SNAP_RANGE_PROPORTION (1/16384.)
|
|
|
|
namespace msdfgen {
|
|
|
|
#if defined(_DEBUG) || !NDEBUG
|
|
#define REQUIRE(cond) { if (!(cond)) { fprintf(stderr, "SVG Parse Error (%s:%d): " #cond "\n", __FILE__, __LINE__); return false; } }
|
|
#else
|
|
#define REQUIRE(cond) { if (!(cond)) return false; }
|
|
#endif
|
|
|
|
MSDFGEN_EXT_PUBLIC const int SVG_IMPORT_FAILURE = 0x00;
|
|
MSDFGEN_EXT_PUBLIC const int SVG_IMPORT_SUCCESS_FLAG = 0x01;
|
|
MSDFGEN_EXT_PUBLIC const int SVG_IMPORT_PARTIAL_FAILURE_FLAG = 0x02;
|
|
MSDFGEN_EXT_PUBLIC const int SVG_IMPORT_INCOMPLETE_FLAG = 0x04;
|
|
MSDFGEN_EXT_PUBLIC const int SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG = 0x08;
|
|
MSDFGEN_EXT_PUBLIC const int SVG_IMPORT_TRANSFORMATION_IGNORED_FLAG = 0x10;
|
|
|
|
#define FLAGS_FINAL(flags) (((flags)&(SVG_IMPORT_SUCCESS_FLAG|SVG_IMPORT_INCOMPLETE_FLAG|SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG)) == (SVG_IMPORT_SUCCESS_FLAG|SVG_IMPORT_INCOMPLETE_FLAG|SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG))
|
|
|
|
static void skipExtraChars(const char *&pathDef) {
|
|
while (*pathDef == ',' || *pathDef == ' ' || *pathDef == '\t' || *pathDef == '\r' || *pathDef == '\n')
|
|
++pathDef;
|
|
}
|
|
|
|
static bool readNodeType(char &output, const char *&pathDef) {
|
|
skipExtraChars(pathDef);
|
|
char nodeType = *pathDef;
|
|
if (nodeType && nodeType != '+' && nodeType != '-' && nodeType != '.' && nodeType != ',' && (nodeType < '0' || nodeType > '9')) {
|
|
++pathDef;
|
|
output = nodeType;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool readDouble(double &output, const char *&pathDef) {
|
|
skipExtraChars(pathDef);
|
|
char *end = NULL;
|
|
output = strtod(pathDef, &end);
|
|
if (end > pathDef) {
|
|
pathDef = end;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool readCoord(Point2 &output, const char *&pathDef) {
|
|
return readDouble(output.x, pathDef) && readDouble(output.y, pathDef);
|
|
}
|
|
|
|
static bool readBool(bool &output, const char *&pathDef) {
|
|
skipExtraChars(pathDef);
|
|
char *end = NULL;
|
|
long v = strtol(pathDef, &end, 10);
|
|
if (end > pathDef) {
|
|
pathDef = end;
|
|
output = v != 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static double arcAngle(Vector2 u, Vector2 v) {
|
|
return nonZeroSign(crossProduct(u, v))*acos(clamp(dotProduct(u, v)/(u.length()*v.length()), -1., +1.));
|
|
}
|
|
|
|
static Vector2 rotateVector(Vector2 v, Vector2 direction) {
|
|
return Vector2(direction.x*v.x-direction.y*v.y, direction.y*v.x+direction.x*v.y);
|
|
}
|
|
|
|
static void addArcApproximate(Contour &contour, Point2 startPoint, Point2 endPoint, Vector2 radius, double rotation, bool largeArc, bool sweep) {
|
|
if (endPoint == startPoint)
|
|
return;
|
|
if (radius.x == 0 || radius.y == 0)
|
|
return contour.addEdge(EdgeHolder(startPoint, endPoint));
|
|
|
|
radius.x = fabs(radius.x);
|
|
radius.y = fabs(radius.y);
|
|
Vector2 axis(cos(rotation), sin(rotation));
|
|
|
|
Vector2 rm = rotateVector(.5*(startPoint-endPoint), Vector2(axis.x, -axis.y));
|
|
Vector2 rm2 = rm*rm;
|
|
Vector2 radius2 = radius*radius;
|
|
double radiusGap = rm2.x/radius2.x+rm2.y/radius2.y;
|
|
if (radiusGap > 1) {
|
|
radius *= sqrt(radiusGap);
|
|
radius2 = radius*radius;
|
|
}
|
|
double dq = (radius2.x*rm2.y+radius2.y*rm2.x);
|
|
double pq = radius2.x*radius2.y/dq-1;
|
|
double q = (largeArc == sweep ? -1 : +1)*sqrt(max(pq, 0.));
|
|
Vector2 rc(q*radius.x*rm.y/radius.y, -q*radius.y*rm.x/radius.x);
|
|
Point2 center = .5*(startPoint+endPoint)+rotateVector(rc, axis);
|
|
|
|
double angleStart = arcAngle(Vector2(1, 0), (rm-rc)/radius);
|
|
double angleExtent = arcAngle((rm-rc)/radius, (-rm-rc)/radius);
|
|
if (!sweep && angleExtent > 0)
|
|
angleExtent -= 2*M_PI;
|
|
else if (sweep && angleExtent < 0)
|
|
angleExtent += 2*M_PI;
|
|
|
|
int segments = (int) ceil(ARC_SEGMENTS_PER_PI/M_PI*fabs(angleExtent));
|
|
double angleIncrement = angleExtent/segments;
|
|
double cl = 4/3.*sin(.5*angleIncrement)/(1+cos(.5*angleIncrement));
|
|
|
|
Point2 prevNode = startPoint;
|
|
double angle = angleStart;
|
|
for (int i = 0; i < segments; ++i) {
|
|
Point2 controlPoint[2];
|
|
Vector2 d(cos(angle), sin(angle));
|
|
controlPoint[0] = center+rotateVector(Vector2(d.x-cl*d.y, d.y+cl*d.x)*radius, axis);
|
|
angle += angleIncrement;
|
|
d.set(cos(angle), sin(angle));
|
|
controlPoint[1] = center+rotateVector(Vector2(d.x+cl*d.y, d.y-cl*d.x)*radius, axis);
|
|
Point2 node = i == segments-1 ? endPoint : center+rotateVector(d*radius, axis);
|
|
contour.addEdge(EdgeHolder(prevNode, controlPoint[0], controlPoint[1], node));
|
|
prevNode = node;
|
|
}
|
|
}
|
|
|
|
bool buildShapeFromSvgPath(Shape &shape, const char *pathDef, double endpointSnapRange) {
|
|
char nodeType = '\0';
|
|
char prevNodeType = '\0';
|
|
Point2 prevNode(0, 0);
|
|
bool nodeTypePreread = false;
|
|
while (nodeTypePreread || readNodeType(nodeType, pathDef)) {
|
|
nodeTypePreread = false;
|
|
Contour &contour = shape.addContour();
|
|
bool contourStart = true;
|
|
|
|
Point2 startPoint;
|
|
Point2 controlPoint[2];
|
|
Point2 node;
|
|
|
|
while (*pathDef) {
|
|
switch (nodeType) {
|
|
case 'M': case 'm':
|
|
if (!contourStart) {
|
|
nodeTypePreread = true;
|
|
goto NEXT_CONTOUR;
|
|
}
|
|
REQUIRE(readCoord(node, pathDef));
|
|
if (nodeType == 'm')
|
|
node += prevNode;
|
|
startPoint = node;
|
|
--nodeType; // to 'L' or 'l'
|
|
break;
|
|
case 'Z': case 'z':
|
|
REQUIRE(!contourStart);
|
|
goto NEXT_CONTOUR;
|
|
case 'L': case 'l':
|
|
REQUIRE(readCoord(node, pathDef));
|
|
if (nodeType == 'l')
|
|
node += prevNode;
|
|
contour.addEdge(EdgeHolder(prevNode, node));
|
|
break;
|
|
case 'H': case 'h':
|
|
REQUIRE(readDouble(node.x, pathDef));
|
|
if (nodeType == 'h')
|
|
node.x += prevNode.x;
|
|
contour.addEdge(EdgeHolder(prevNode, node));
|
|
break;
|
|
case 'V': case 'v':
|
|
REQUIRE(readDouble(node.y, pathDef));
|
|
if (nodeType == 'v')
|
|
node.y += prevNode.y;
|
|
contour.addEdge(EdgeHolder(prevNode, node));
|
|
break;
|
|
case 'Q': case 'q':
|
|
REQUIRE(readCoord(controlPoint[0], pathDef));
|
|
REQUIRE(readCoord(node, pathDef));
|
|
if (nodeType == 'q') {
|
|
controlPoint[0] += prevNode;
|
|
node += prevNode;
|
|
}
|
|
contour.addEdge(EdgeHolder(prevNode, controlPoint[0], node));
|
|
break;
|
|
case 'T': case 't':
|
|
if (prevNodeType == 'Q' || prevNodeType == 'q' || prevNodeType == 'T' || prevNodeType == 't')
|
|
controlPoint[0] = node+node-controlPoint[0];
|
|
else
|
|
controlPoint[0] = node;
|
|
REQUIRE(readCoord(node, pathDef));
|
|
if (nodeType == 't')
|
|
node += prevNode;
|
|
contour.addEdge(EdgeHolder(prevNode, controlPoint[0], node));
|
|
break;
|
|
case 'C': case 'c':
|
|
REQUIRE(readCoord(controlPoint[0], pathDef));
|
|
REQUIRE(readCoord(controlPoint[1], pathDef));
|
|
REQUIRE(readCoord(node, pathDef));
|
|
if (nodeType == 'c') {
|
|
controlPoint[0] += prevNode;
|
|
controlPoint[1] += prevNode;
|
|
node += prevNode;
|
|
}
|
|
contour.addEdge(EdgeHolder(prevNode, controlPoint[0], controlPoint[1], node));
|
|
break;
|
|
case 'S': case 's':
|
|
if (prevNodeType == 'C' || prevNodeType == 'c' || prevNodeType == 'S' || prevNodeType == 's')
|
|
controlPoint[0] = node+node-controlPoint[1];
|
|
else
|
|
controlPoint[0] = node;
|
|
REQUIRE(readCoord(controlPoint[1], pathDef));
|
|
REQUIRE(readCoord(node, pathDef));
|
|
if (nodeType == 's') {
|
|
controlPoint[1] += prevNode;
|
|
node += prevNode;
|
|
}
|
|
contour.addEdge(EdgeHolder(prevNode, controlPoint[0], controlPoint[1], node));
|
|
break;
|
|
case 'A': case 'a':
|
|
{
|
|
Vector2 radius;
|
|
double angle;
|
|
bool largeArg;
|
|
bool sweep;
|
|
REQUIRE(readCoord(radius, pathDef));
|
|
REQUIRE(readDouble(angle, pathDef));
|
|
REQUIRE(readBool(largeArg, pathDef));
|
|
REQUIRE(readBool(sweep, pathDef));
|
|
REQUIRE(readCoord(node, pathDef));
|
|
if (nodeType == 'a')
|
|
node += prevNode;
|
|
angle *= M_PI/180.0;
|
|
addArcApproximate(contour, prevNode, node, radius, angle, largeArg, sweep);
|
|
}
|
|
break;
|
|
default:
|
|
REQUIRE(!"Unknown node type");
|
|
}
|
|
contourStart &= nodeType == 'M' || nodeType == 'm';
|
|
prevNode = node;
|
|
prevNodeType = nodeType;
|
|
readNodeType(nodeType, pathDef);
|
|
}
|
|
NEXT_CONTOUR:
|
|
// Fix contour if it isn't properly closed
|
|
if (!contour.edges.empty() && prevNode != startPoint) {
|
|
if ((contour.edges.back()->point(1)-contour.edges[0]->point(0)).length() < endpointSnapRange)
|
|
contour.edges.back()->moveEndPoint(contour.edges[0]->point(0));
|
|
else
|
|
contour.addEdge(EdgeHolder(prevNode, startPoint));
|
|
}
|
|
prevNode = startPoint;
|
|
prevNodeType = '\0';
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#ifdef MSDFGEN_USE_TINYXML2
|
|
|
|
static void findPathByForwardIndex(tinyxml2::XMLElement *&path, int &flags, int &skips, tinyxml2::XMLElement *parent, bool hasTransformation) {
|
|
for (tinyxml2::XMLElement *cur = parent->FirstChildElement(); cur && !FLAGS_FINAL(flags); cur = cur->NextSiblingElement()) {
|
|
if (!strcmp(cur->Name(), "path")) {
|
|
if (!skips--) {
|
|
path = cur;
|
|
flags |= SVG_IMPORT_SUCCESS_FLAG;
|
|
if (hasTransformation || cur->Attribute("transform"))
|
|
flags |= SVG_IMPORT_TRANSFORMATION_IGNORED_FLAG;
|
|
} else if (flags&SVG_IMPORT_SUCCESS_FLAG)
|
|
flags |= SVG_IMPORT_INCOMPLETE_FLAG;
|
|
} else if (!strcmp(cur->Name(), "g"))
|
|
findPathByForwardIndex(path, flags, skips, cur, hasTransformation || cur->Attribute("transform"));
|
|
else if (!strcmp(cur->Name(), "rect") || !strcmp(cur->Name(), "circle") || !strcmp(cur->Name(), "ellipse") || !strcmp(cur->Name(), "polygon"))
|
|
flags |= SVG_IMPORT_INCOMPLETE_FLAG;
|
|
else if (!strcmp(cur->Name(), "mask") || !strcmp(cur->Name(), "use"))
|
|
flags |= SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG;
|
|
}
|
|
}
|
|
|
|
static void findPathByBackwardIndex(tinyxml2::XMLElement *&path, int &flags, int &skips, tinyxml2::XMLElement *parent, bool hasTransformation) {
|
|
for (tinyxml2::XMLElement *cur = parent->LastChildElement(); cur && !FLAGS_FINAL(flags); cur = cur->PreviousSiblingElement()) {
|
|
if (!strcmp(cur->Name(), "path")) {
|
|
if (!skips--) {
|
|
path = cur;
|
|
flags |= SVG_IMPORT_SUCCESS_FLAG;
|
|
if (hasTransformation || cur->Attribute("transform"))
|
|
flags |= SVG_IMPORT_TRANSFORMATION_IGNORED_FLAG;
|
|
} else if (flags&SVG_IMPORT_SUCCESS_FLAG)
|
|
flags |= SVG_IMPORT_INCOMPLETE_FLAG;
|
|
} else if (!strcmp(cur->Name(), "g"))
|
|
findPathByBackwardIndex(path, flags, skips, cur, hasTransformation || cur->Attribute("transform"));
|
|
else if (!strcmp(cur->Name(), "rect") || !strcmp(cur->Name(), "circle") || !strcmp(cur->Name(), "ellipse") || !strcmp(cur->Name(), "polygon"))
|
|
flags |= SVG_IMPORT_INCOMPLETE_FLAG;
|
|
else if (!strcmp(cur->Name(), "mask") || !strcmp(cur->Name(), "use"))
|
|
flags |= SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG;
|
|
}
|
|
}
|
|
|
|
bool loadSvgShape(Shape &output, const char *filename, int pathIndex, Vector2 *dimensions) {
|
|
tinyxml2::XMLDocument doc;
|
|
if (doc.LoadFile(filename))
|
|
return false;
|
|
tinyxml2::XMLElement *root = doc.FirstChildElement("svg");
|
|
if (!root)
|
|
return false;
|
|
|
|
tinyxml2::XMLElement *path = NULL;
|
|
int flags = 0;
|
|
int skippedPaths = abs(pathIndex)-(pathIndex != 0);
|
|
if (pathIndex > 0)
|
|
findPathByForwardIndex(path, flags, skippedPaths, root, false);
|
|
else
|
|
findPathByBackwardIndex(path, flags, skippedPaths, root, false);
|
|
if (!path)
|
|
return false;
|
|
const char *pd = path->Attribute("d");
|
|
if (!pd)
|
|
return false;
|
|
|
|
Vector2 dims(root->DoubleAttribute("width"), root->DoubleAttribute("height"));
|
|
if (const char *viewBox = root->Attribute("viewBox")) {
|
|
double left = 0, top = 0;
|
|
readDouble(left, viewBox) && readDouble(top, viewBox) && readDouble(dims.x, viewBox) && readDouble(dims.y, viewBox);
|
|
}
|
|
if (dimensions)
|
|
*dimensions = dims;
|
|
output.contours.clear();
|
|
output.inverseYAxis = true;
|
|
return buildShapeFromSvgPath(output, pd, ENDPOINT_SNAP_RANGE_PROPORTION*dims.length());
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef MSDFGEN_USE_DROPXML
|
|
|
|
struct StrRange {
|
|
const char *start, *end;
|
|
inline StrRange() : start(), end() { }
|
|
inline StrRange(const char *start, const char *end) : start(start), end(end) { }
|
|
inline std::string str() const { return std::string(start, end); }
|
|
};
|
|
|
|
static bool matchName(const char *start, const char *end, const char *value) {
|
|
for (const char *c = start; c < end; ++c, ++value) {
|
|
if (*c != *value)
|
|
return false;
|
|
}
|
|
return !*value;
|
|
}
|
|
|
|
static std::string xmlDecode(const char *start, const char *end) {
|
|
if (!dropXML::decode(start, end, nullptr, nullptr)) {
|
|
std::string buffer(end-start+1, '\0');
|
|
if (!dropXML::decode(start, end, &buffer[0], &buffer[buffer.size()-1]))
|
|
return std::string();
|
|
if (start == buffer.data()) {
|
|
buffer.resize(end-start, '\0');
|
|
return (std::string &&) buffer;
|
|
}
|
|
}
|
|
return std::string(start, end);
|
|
}
|
|
|
|
static double xmlGetDouble(const char *start, const char *end) {
|
|
double x = 0;
|
|
std::string decodedStr(xmlDecode(start, end));
|
|
const char *strPtr = decodedStr.c_str();
|
|
readDouble(x, strPtr);
|
|
return x;
|
|
}
|
|
|
|
#define SVG_NAME_IS(x) matchName(nameStart, nameEnd, x)
|
|
#define SVG_DEC_VAL() xmlDecode(valueStart, valueEnd)
|
|
#define SVG_DOUBLEVAL() xmlGetDouble(valueStart, valueEnd)
|
|
|
|
static bool readFile(std::vector<char> &output, const char *filename) {
|
|
if (FILE *f = fopen(filename, "rb")) {
|
|
struct FileGuard {
|
|
FILE *f;
|
|
~FileGuard() {
|
|
fclose(f);
|
|
}
|
|
} fileGuard = { f };
|
|
if (fseek(f, 0, SEEK_END))
|
|
return false;
|
|
long size = ftell(f);
|
|
if (size < 0)
|
|
return false;
|
|
output.resize(size);
|
|
if (!size)
|
|
return true;
|
|
if (fseek(f, 0, SEEK_SET))
|
|
return false;
|
|
return fread(&output[0], 1, size, f) == (size_t) size;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class BaseSvgConsumer {
|
|
public:
|
|
inline bool processingInstruction(const char *, const char *) { return true; }
|
|
inline bool doctype(const char *, const char *) { return true; }
|
|
inline bool text(const char *, const char *) { return true; }
|
|
inline bool cdata(const char *, const char *) { return true; }
|
|
};
|
|
|
|
class SvgPathAggregator : public BaseSvgConsumer {
|
|
enum {
|
|
IGNORED,
|
|
SVG,
|
|
G,
|
|
PATH
|
|
} curElement;
|
|
int ignoredDepth;
|
|
|
|
public:
|
|
int flags;
|
|
Vector2 dimensions;
|
|
StrRange viewBox;
|
|
std::vector<StrRange> pathDefs;
|
|
|
|
inline SvgPathAggregator() : curElement(IGNORED), ignoredDepth(0), flags(0) { }
|
|
|
|
inline bool enterElement(const char *nameStart, const char *nameEnd) {
|
|
curElement = IGNORED;
|
|
if (ignoredDepth)
|
|
++ignoredDepth;
|
|
else if (SVG_NAME_IS("svg"))
|
|
curElement = SVG;
|
|
else if (SVG_NAME_IS("g"))
|
|
curElement = G;
|
|
else if (SVG_NAME_IS("path"))
|
|
curElement = PATH;
|
|
else {
|
|
if (SVG_NAME_IS("rect") || SVG_NAME_IS("circle") || SVG_NAME_IS("ellipse") || SVG_NAME_IS("polygon"))
|
|
flags |= SVG_IMPORT_INCOMPLETE_FLAG;
|
|
else if (SVG_NAME_IS("mask") || SVG_NAME_IS("use"))
|
|
flags |= SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG;
|
|
++ignoredDepth;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline bool leaveElement(const char *, const char *) {
|
|
if (ignoredDepth)
|
|
--ignoredDepth;
|
|
return true;
|
|
}
|
|
|
|
inline bool elementAttribute(const char *nameStart, const char *nameEnd, const char *valueStart, const char *valueEnd) {
|
|
switch (curElement) {
|
|
case IGNORED:
|
|
break;
|
|
case SVG:
|
|
if (SVG_NAME_IS("width"))
|
|
dimensions.x = xmlGetDouble(valueStart, valueEnd);
|
|
else if (SVG_NAME_IS("height"))
|
|
dimensions.y = xmlGetDouble(valueStart, valueEnd);
|
|
else if (SVG_NAME_IS("viewBox"))
|
|
viewBox = StrRange(valueStart, valueEnd);
|
|
break;
|
|
case PATH:
|
|
if (SVG_NAME_IS("d"))
|
|
pathDefs.push_back(StrRange(valueStart, valueEnd));
|
|
// fallthrough
|
|
case G:
|
|
if (SVG_NAME_IS("transform"))
|
|
flags |= SVG_IMPORT_TRANSFORMATION_IGNORED_FLAG;
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline bool finishAttributes() { return true; }
|
|
inline bool finish() { return !ignoredDepth; }
|
|
|
|
};
|
|
|
|
bool loadSvgShape(Shape &output, const char *filename, int pathIndex, Vector2 *dimensions) {
|
|
std::vector<char> svgData;
|
|
if (!(readFile(svgData, filename) && !svgData.empty()))
|
|
return false;
|
|
|
|
SvgPathAggregator pathAggregator;
|
|
if (!dropXML::parse(pathAggregator, &svgData[0], &svgData[0]+svgData.size()))
|
|
return false;
|
|
|
|
if (pathIndex <= 0) {
|
|
if (pathIndex == 0)
|
|
pathIndex = -1;
|
|
pathIndex = pathAggregator.pathDefs.size()+pathIndex;
|
|
} else
|
|
--pathIndex;
|
|
if (!(pathIndex > 0 && pathIndex < (int) pathAggregator.pathDefs.size()))
|
|
return false;
|
|
|
|
Vector2 dims(pathAggregator.dimensions);
|
|
if (pathAggregator.viewBox.start < pathAggregator.viewBox.end) {
|
|
std::string viewBoxStr = xmlDecode(pathAggregator.viewBox.start, pathAggregator.viewBox.end);
|
|
const char *viewBoxPtr = viewBoxStr.c_str();
|
|
double left = 0, top = 0;
|
|
readDouble(left, viewBoxPtr) && readDouble(top, viewBoxPtr) && readDouble(dims.x, viewBoxPtr) && readDouble(dims.y, viewBoxPtr);
|
|
}
|
|
if (dimensions)
|
|
*dimensions = dims;
|
|
output.contours.clear();
|
|
output.inverseYAxis = true;
|
|
return buildShapeFromSvgPath(output, xmlDecode(pathAggregator.pathDefs[pathIndex].start, pathAggregator.pathDefs[pathIndex].end).c_str(), ENDPOINT_SNAP_RANGE_PROPORTION*dims.length());
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifndef MSDFGEN_USE_SKIA
|
|
|
|
#ifdef MSDFGEN_USE_TINYXML2
|
|
int loadSvgShape(Shape &output, Shape::Bounds &viewBox, const char *filename) {
|
|
tinyxml2::XMLDocument doc;
|
|
if (doc.LoadFile(filename))
|
|
return SVG_IMPORT_FAILURE;
|
|
tinyxml2::XMLElement *root = doc.FirstChildElement("svg");
|
|
if (!root)
|
|
return SVG_IMPORT_FAILURE;
|
|
|
|
tinyxml2::XMLElement *path = NULL;
|
|
int flags = 0;
|
|
int skippedPaths = 0;
|
|
findPathByBackwardIndex(path, flags, skippedPaths, root, false);
|
|
if (!(path && (flags&SVG_IMPORT_SUCCESS_FLAG)))
|
|
return SVG_IMPORT_FAILURE;
|
|
const char *pd = path->Attribute("d");
|
|
if (!pd)
|
|
return SVG_IMPORT_FAILURE;
|
|
|
|
viewBox.l = 0, viewBox.b = 0;
|
|
Vector2 dims(root->DoubleAttribute("width"), root->DoubleAttribute("height"));
|
|
if (const char *viewBoxStr = root->Attribute("viewBox"))
|
|
readDouble(viewBox.l, viewBoxStr) && readDouble(viewBox.b, viewBoxStr) && readDouble(dims.x, viewBoxStr) && readDouble(dims.y, viewBoxStr);
|
|
viewBox.r = viewBox.l+dims.x;
|
|
viewBox.t = viewBox.b+dims.y;
|
|
output.contours.clear();
|
|
output.inverseYAxis = true;
|
|
if (!buildShapeFromSvgPath(output, pd, ENDPOINT_SNAP_RANGE_PROPORTION*dims.length()))
|
|
return SVG_IMPORT_FAILURE;
|
|
return flags;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MSDFGEN_USE_DROPXML
|
|
int loadSvgShape(Shape &output, Shape::Bounds &viewBox, const char *filename) {
|
|
std::vector<char> svgData;
|
|
if (!(readFile(svgData, filename) && !svgData.empty()))
|
|
return SVG_IMPORT_FAILURE;
|
|
|
|
SvgPathAggregator pathAggregator;
|
|
if (!dropXML::parse(pathAggregator, &svgData[0], &svgData[0]+svgData.size()) || pathAggregator.pathDefs.empty())
|
|
return SVG_IMPORT_FAILURE;
|
|
|
|
viewBox.l = 0, viewBox.b = 0;
|
|
Vector2 dims(pathAggregator.dimensions);
|
|
if (pathAggregator.viewBox.start < pathAggregator.viewBox.end) {
|
|
std::string viewBoxStr = xmlDecode(pathAggregator.viewBox.start, pathAggregator.viewBox.end);
|
|
const char *viewBoxPtr = viewBoxStr.c_str();
|
|
readDouble(viewBox.l, viewBoxPtr) && readDouble(viewBox.b, viewBoxPtr) && readDouble(dims.x, viewBoxPtr) && readDouble(dims.y, viewBoxPtr);
|
|
}
|
|
viewBox.r = viewBox.l+dims.x;
|
|
viewBox.t = viewBox.b+dims.y;
|
|
output.contours.clear();
|
|
output.inverseYAxis = true;
|
|
if (!buildShapeFromSvgPath(output, xmlDecode(pathAggregator.pathDefs.back().start, pathAggregator.pathDefs.back().end).c_str(), ENDPOINT_SNAP_RANGE_PROPORTION*dims.length()))
|
|
return SVG_IMPORT_FAILURE;
|
|
return SVG_IMPORT_SUCCESS_FLAG|pathAggregator.flags;
|
|
}
|
|
#endif
|
|
|
|
#else
|
|
|
|
void shapeFromSkiaPath(Shape &shape, const SkPath &skPath); // defined in resolve-shape-geometry.cpp
|
|
|
|
static bool readTransformationOp(SkScalar dst[6], int &count, const char *&str, const char *name) {
|
|
int nameLen = int(strlen(name));
|
|
if (!memcmp(str, name, nameLen)) {
|
|
const char *curStr = str+nameLen;
|
|
skipExtraChars(curStr);
|
|
if (*curStr == '(') {
|
|
skipExtraChars(++curStr);
|
|
count = 0;
|
|
while (*curStr && *curStr != ')') {
|
|
double x;
|
|
if (!(count < 6 && readDouble(x, curStr)))
|
|
return false;
|
|
dst[count++] = SkScalar(x);
|
|
skipExtraChars(curStr);
|
|
}
|
|
if (*curStr == ')') {
|
|
str = curStr+1;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static SkMatrix parseTransformation(int &flags, const char *str) {
|
|
SkMatrix transformation;
|
|
skipExtraChars(str);
|
|
while (*str) {
|
|
SkScalar values[6];
|
|
int count;
|
|
SkMatrix partial;
|
|
if (readTransformationOp(values, count, str, "matrix") && count == 6) {
|
|
partial.setAll(values[0], values[2], values[4], values[1], values[3], values[5], SkScalar(0), SkScalar(0), SkScalar(1));
|
|
} else if (readTransformationOp(values, count, str, "translate") && (count == 1 || count == 2)) {
|
|
if (count == 1)
|
|
values[1] = SkScalar(0);
|
|
partial.setTranslate(values[0], values[1]);
|
|
} else if (readTransformationOp(values, count, str, "scale") && (count == 1 || count == 2)) {
|
|
if (count == 1)
|
|
values[1] = values[0];
|
|
partial.setScale(values[0], values[1]);
|
|
} else if (readTransformationOp(values, count, str, "rotate") && (count == 1 || count == 3)) {
|
|
if (count == 3)
|
|
partial.setRotate(values[0], values[1], values[2]);
|
|
else
|
|
partial.setRotate(values[0]);
|
|
} else if (readTransformationOp(values, count, str, "skewX") && count == 1) {
|
|
partial.setSkewX(SkScalar(tan(M_PI/180*values[0])));
|
|
} else if (readTransformationOp(values, count, str, "skewY") && count == 1) {
|
|
partial.setSkewY(SkScalar(tan(M_PI/180*values[0])));
|
|
} else {
|
|
flags |= SVG_IMPORT_PARTIAL_FAILURE_FLAG;
|
|
break;
|
|
}
|
|
transformation = transformation*partial;
|
|
skipExtraChars(str);
|
|
}
|
|
return transformation;
|
|
}
|
|
|
|
static SkMatrix combineTransformation(int &flags, const SkMatrix &parentTransformation, const char *transformationString, const char *transformationOriginString) {
|
|
if (transformationString && *transformationString) {
|
|
SkMatrix transformation = parseTransformation(flags, transformationString);
|
|
if (transformationOriginString && *transformationOriginString) {
|
|
Point2 origin;
|
|
if (readCoord(origin, transformationOriginString))
|
|
transformation = SkMatrix::Translate(SkScalar(origin.x), SkScalar(origin.y))*transformation*SkMatrix::Translate(SkScalar(-origin.x), SkScalar(-origin.y));
|
|
else
|
|
flags |= SVG_IMPORT_PARTIAL_FAILURE_FLAG;
|
|
}
|
|
return parentTransformation*transformation;
|
|
}
|
|
return parentTransformation;
|
|
}
|
|
|
|
#ifdef MSDFGEN_USE_TINYXML2
|
|
|
|
static void gatherPaths(SkPath &fullPath, int &flags, tinyxml2::XMLElement *parent, const SkMatrix &transformation) {
|
|
for (tinyxml2::XMLElement *cur = parent->FirstChildElement(); cur && !FLAGS_FINAL(flags); cur = cur->NextSiblingElement()) {
|
|
if (!strcmp(cur->Name(), "g"))
|
|
gatherPaths(fullPath, flags, cur, combineTransformation(flags, transformation, cur->Attribute("transform"), cur->Attribute("transform-origin")));
|
|
else if (!strcmp(cur->Name(), "mask") || !strcmp(cur->Name(), "use"))
|
|
flags |= SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG;
|
|
else {
|
|
SkPath curPath;
|
|
if (!strcmp(cur->Name(), "path")) {
|
|
const char *pd = cur->Attribute("d");
|
|
if (!(pd && SkParsePath::FromSVGString(pd, &curPath))) {
|
|
flags |= SVG_IMPORT_PARTIAL_FAILURE_FLAG;
|
|
continue;
|
|
}
|
|
} else if (!strcmp(cur->Name(), "rect")) {
|
|
SkScalar x = SkScalar(cur->DoubleAttribute("x")), y = SkScalar(cur->DoubleAttribute("y"));
|
|
SkScalar width = SkScalar(cur->DoubleAttribute("width")), height = SkScalar(cur->DoubleAttribute("height"));
|
|
SkScalar rx = SkScalar(cur->DoubleAttribute("rx")), ry = SkScalar(cur->DoubleAttribute("ry"));
|
|
if (!(width && height))
|
|
continue;
|
|
SkRect rect = SkRect::MakeLTRB(x, y, x+width, y+height);
|
|
if (rx || ry) {
|
|
SkScalar radii[] = { rx, ry, rx, ry, rx, ry, rx, ry };
|
|
curPath.addRoundRect(rect, radii);
|
|
} else
|
|
curPath.addRect(rect);
|
|
} else if (!strcmp(cur->Name(), "circle")) {
|
|
SkScalar cx = SkScalar(cur->DoubleAttribute("cx")), cy = SkScalar(cur->DoubleAttribute("cy"));
|
|
SkScalar r = SkScalar(cur->DoubleAttribute("r"));
|
|
if (!r)
|
|
continue;
|
|
curPath.addCircle(cx, cy, r);
|
|
} else if (!strcmp(cur->Name(), "ellipse")) {
|
|
SkScalar cx = SkScalar(cur->DoubleAttribute("cx")), cy = SkScalar(cur->DoubleAttribute("cy"));
|
|
SkScalar rx = SkScalar(cur->DoubleAttribute("rx")), ry = SkScalar(cur->DoubleAttribute("ry"));
|
|
if (!(rx && ry))
|
|
continue;
|
|
curPath.addOval(SkRect::MakeLTRB(cx-rx, cy-ry, cx+rx, cy+ry));
|
|
} else if (!strcmp(cur->Name(), "polygon")) {
|
|
const char *pd = cur->Attribute("points");
|
|
if (!pd) {
|
|
flags |= SVG_IMPORT_PARTIAL_FAILURE_FLAG;
|
|
continue;
|
|
}
|
|
Point2 point;
|
|
if (!readCoord(point, pd))
|
|
continue;
|
|
curPath.moveTo(SkScalar(point.x), SkScalar(point.y));
|
|
if (!readCoord(point, pd))
|
|
continue;
|
|
do {
|
|
curPath.lineTo(SkScalar(point.x), SkScalar(point.y));
|
|
} while (readCoord(point, pd));
|
|
curPath.close();
|
|
} else
|
|
continue;
|
|
const char *fillRule = cur->Attribute("fill-rule");
|
|
if (fillRule && !strcmp(fillRule, "evenodd"))
|
|
curPath.setFillType(SkPathFillType::kEvenOdd);
|
|
curPath.transform(combineTransformation(flags, transformation, cur->Attribute("transform"), cur->Attribute("transform-origin")));
|
|
if (Op(fullPath, curPath, kUnion_SkPathOp, &fullPath))
|
|
flags |= SVG_IMPORT_SUCCESS_FLAG;
|
|
else
|
|
flags |= SVG_IMPORT_PARTIAL_FAILURE_FLAG;
|
|
}
|
|
}
|
|
}
|
|
|
|
int loadSvgShape(Shape &output, Shape::Bounds &viewBox, const char *filename) {
|
|
tinyxml2::XMLDocument doc;
|
|
if (doc.LoadFile(filename))
|
|
return SVG_IMPORT_FAILURE;
|
|
tinyxml2::XMLElement *root = doc.FirstChildElement("svg");
|
|
if (!root)
|
|
return SVG_IMPORT_FAILURE;
|
|
|
|
SkPath fullPath;
|
|
int flags = 0;
|
|
gatherPaths(fullPath, flags, root, SkMatrix());
|
|
if (!((flags&SVG_IMPORT_SUCCESS_FLAG) && Simplify(fullPath, &fullPath)))
|
|
return SVG_IMPORT_FAILURE;
|
|
shapeFromSkiaPath(output, fullPath);
|
|
output.inverseYAxis = true;
|
|
output.orientContours();
|
|
|
|
viewBox.l = 0, viewBox.b = 0;
|
|
Vector2 dims(root->DoubleAttribute("width"), root->DoubleAttribute("height"));
|
|
if (const char *viewBoxStr = root->Attribute("viewBox"))
|
|
readDouble(viewBox.l, viewBoxStr) && readDouble(viewBox.b, viewBoxStr) && readDouble(dims.x, viewBoxStr) && readDouble(dims.y, viewBoxStr);
|
|
viewBox.r = viewBox.l+dims.x;
|
|
viewBox.t = viewBox.b+dims.y;
|
|
return flags;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef MSDFGEN_USE_DROPXML
|
|
|
|
int parseSvgShape(Shape &output, Shape::Bounds &viewBox, const char *svgData, size_t svgLength) {
|
|
|
|
class SvgConsumer : public BaseSvgConsumer {
|
|
enum Element {
|
|
BEGINNING,
|
|
IGNORED,
|
|
SVG,
|
|
G,
|
|
PATH,
|
|
RECT,
|
|
CIRCLE,
|
|
ELLIPSE,
|
|
POLYGON
|
|
} curElement;
|
|
|
|
// Current element attributes
|
|
struct ElementData {
|
|
StrRange transform, transformOrigin;
|
|
Vector2 pos, dims, radius;
|
|
StrRange pathDef;
|
|
bool fillRuleEvenOdd;
|
|
ElementData() : fillRuleEvenOdd(false) { }
|
|
} elem;
|
|
|
|
int ignoredDepth;
|
|
SkMatrix transformation;
|
|
std::stack<SkMatrix> transformationStack;
|
|
|
|
public:
|
|
int flags;
|
|
Vector2 dimensions;
|
|
Shape::Bounds viewBox;
|
|
SkPath fullPath;
|
|
|
|
SvgConsumer() : curElement(BEGINNING), ignoredDepth(0), flags(0), viewBox() { }
|
|
|
|
bool enterElement(const char *nameStart, const char *nameEnd) {
|
|
if (ignoredDepth) {
|
|
++ignoredDepth;
|
|
return true;
|
|
}
|
|
if (curElement == BEGINNING && SVG_NAME_IS("svg"))
|
|
curElement = SVG;
|
|
else if (SVG_NAME_IS("g"))
|
|
curElement = G;
|
|
else if (SVG_NAME_IS("path"))
|
|
curElement = PATH;
|
|
else if (SVG_NAME_IS("rect"))
|
|
curElement = RECT;
|
|
else if (SVG_NAME_IS("circle"))
|
|
curElement = CIRCLE;
|
|
else if (SVG_NAME_IS("ellipse"))
|
|
curElement = ELLIPSE;
|
|
else if (SVG_NAME_IS("polygon"))
|
|
curElement = POLYGON;
|
|
else {
|
|
curElement = IGNORED;
|
|
++ignoredDepth;
|
|
if (SVG_NAME_IS("mask") || SVG_NAME_IS("use"))
|
|
flags |= SVG_IMPORT_UNSUPPORTED_FEATURE_FLAG;
|
|
}
|
|
if (curElement != IGNORED)
|
|
elem = ElementData();
|
|
return true;
|
|
}
|
|
|
|
bool leaveElement(const char *nameStart, const char *nameEnd) {
|
|
if (ignoredDepth) {
|
|
--ignoredDepth;
|
|
return true;
|
|
}
|
|
if (SVG_NAME_IS("g")) {
|
|
if (transformationStack.empty())
|
|
return false;
|
|
transformation = transformationStack.top();
|
|
transformationStack.pop();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool elementAttribute(const char *nameStart, const char *nameEnd, const char *valueStart, const char *valueEnd) {
|
|
switch (curElement) {
|
|
case BEGINNING:
|
|
case IGNORED:
|
|
break;
|
|
case SVG:
|
|
if (SVG_NAME_IS("width"))
|
|
dimensions.x = SVG_DOUBLEVAL();
|
|
else if (SVG_NAME_IS("height"))
|
|
dimensions.y = SVG_DOUBLEVAL();
|
|
else if (SVG_NAME_IS("viewBox")) {
|
|
std::string viewBoxStr(SVG_DEC_VAL());
|
|
const char *strPtr = viewBoxStr.c_str();
|
|
double w = 0, h = 0;
|
|
readDouble(viewBox.l, strPtr) && readDouble(viewBox.b, strPtr) && readDouble(w, strPtr) && readDouble(h, strPtr);
|
|
viewBox.r = viewBox.l+w;
|
|
viewBox.t = viewBox.b+h;
|
|
}
|
|
break;
|
|
case G:
|
|
break;
|
|
case PATH:
|
|
if (SVG_NAME_IS("d"))
|
|
elem.pathDef = StrRange(valueStart, valueEnd);
|
|
break;
|
|
case RECT:
|
|
if (SVG_NAME_IS("x"))
|
|
elem.pos.x = SVG_DOUBLEVAL();
|
|
else if (SVG_NAME_IS("y"))
|
|
elem.pos.y = SVG_DOUBLEVAL();
|
|
else if (SVG_NAME_IS("width"))
|
|
elem.dims.x = SVG_DOUBLEVAL();
|
|
else if (SVG_NAME_IS("height"))
|
|
elem.dims.y = SVG_DOUBLEVAL();
|
|
else if (SVG_NAME_IS("rx"))
|
|
elem.radius.x = SVG_DOUBLEVAL();
|
|
else if (SVG_NAME_IS("ry"))
|
|
elem.radius.y = SVG_DOUBLEVAL();
|
|
break;
|
|
case CIRCLE:
|
|
if (SVG_NAME_IS("cx"))
|
|
elem.pos.x = SVG_DOUBLEVAL();
|
|
else if (SVG_NAME_IS("cy"))
|
|
elem.pos.y = SVG_DOUBLEVAL();
|
|
else if (SVG_NAME_IS("r"))
|
|
elem.radius.x = SVG_DOUBLEVAL();
|
|
break;
|
|
case ELLIPSE:
|
|
if (SVG_NAME_IS("cx"))
|
|
elem.pos.x = SVG_DOUBLEVAL();
|
|
else if (SVG_NAME_IS("cy"))
|
|
elem.pos.y = SVG_DOUBLEVAL();
|
|
else if (SVG_NAME_IS("rx"))
|
|
elem.radius.x = SVG_DOUBLEVAL();
|
|
else if (SVG_NAME_IS("ry"))
|
|
elem.radius.y = SVG_DOUBLEVAL();
|
|
break;
|
|
case POLYGON:
|
|
if (SVG_NAME_IS("points"))
|
|
elem.pathDef = StrRange(valueStart, valueEnd);
|
|
break;
|
|
}
|
|
switch (curElement) {
|
|
case PATH:
|
|
case RECT:
|
|
case CIRCLE:
|
|
case ELLIPSE:
|
|
case POLYGON:
|
|
if (SVG_NAME_IS("fill-rule"))
|
|
elem.fillRuleEvenOdd = SVG_DEC_VAL() == "evenodd";
|
|
// fallthrough
|
|
case G:
|
|
if (SVG_NAME_IS("transform"))
|
|
elem.transform = StrRange(valueStart, valueEnd);
|
|
else if (SVG_NAME_IS("transform-origin"))
|
|
elem.transformOrigin = StrRange(valueStart, valueEnd);
|
|
break;
|
|
default:;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool finishAttributes() {
|
|
switch (curElement) {
|
|
case BEGINNING:
|
|
case IGNORED:
|
|
case SVG:
|
|
break;
|
|
case G:
|
|
transformationStack.push(transformation);
|
|
transformation = combineTransformation(flags, transformation, elem.transform.str().c_str(), elem.transformOrigin.str().c_str());
|
|
break;
|
|
case PATH:
|
|
case RECT:
|
|
case CIRCLE:
|
|
case ELLIPSE:
|
|
case POLYGON:
|
|
{
|
|
SkPath curPath;
|
|
switch (curElement) {
|
|
case PATH:
|
|
if (!SkParsePath::FromSVGString(elem.pathDef.str().c_str(), &curPath)) {
|
|
flags |= SVG_IMPORT_PARTIAL_FAILURE_FLAG;
|
|
return true;
|
|
}
|
|
break;
|
|
case RECT:
|
|
{
|
|
if (!(elem.dims.x && elem.dims.y))
|
|
return true;
|
|
SkRect rect = SkRect::MakeLTRB(elem.pos.x, elem.pos.y, elem.pos.x+elem.dims.x, elem.pos.y+elem.dims.y);
|
|
if (elem.radius.x || elem.radius.y) {
|
|
SkScalar rx = SkScalar(elem.radius.x), ry = SkScalar(elem.radius.y);
|
|
SkScalar radii[] = { rx, ry, rx, ry, rx, ry, rx, ry };
|
|
curPath.addRoundRect(rect, radii);
|
|
} else
|
|
curPath.addRect(rect);
|
|
}
|
|
break;
|
|
case CIRCLE:
|
|
if (!elem.radius.x)
|
|
return true;
|
|
curPath.addCircle(elem.pos.x, elem.pos.y, elem.radius.x);
|
|
break;
|
|
case ELLIPSE:
|
|
if (!(elem.radius.x && elem.radius.y))
|
|
return true;
|
|
curPath.addOval(SkRect::MakeLTRB(elem.pos.x-elem.radius.x, elem.pos.y-elem.radius.y, elem.pos.x+elem.radius.x, elem.pos.y+elem.radius.y));
|
|
break;
|
|
case POLYGON:
|
|
{
|
|
if (elem.pathDef.start == elem.pathDef.end) {
|
|
flags |= SVG_IMPORT_PARTIAL_FAILURE_FLAG;
|
|
return true;
|
|
}
|
|
std::string pdStr = elem.pathDef.str();
|
|
const char *pd = pdStr.c_str();
|
|
Point2 point;
|
|
if (!readCoord(point, pd))
|
|
return true;
|
|
curPath.moveTo(SkScalar(point.x), SkScalar(point.y));
|
|
if (!readCoord(point, pd))
|
|
return true;
|
|
do {
|
|
curPath.lineTo(SkScalar(point.x), SkScalar(point.y));
|
|
} while (readCoord(point, pd));
|
|
curPath.close();
|
|
}
|
|
break;
|
|
default:
|
|
return true;
|
|
}
|
|
if (elem.fillRuleEvenOdd)
|
|
curPath.setFillType(SkPathFillType::kEvenOdd);
|
|
curPath.transform(combineTransformation(flags, transformation, elem.transform.str().c_str(), elem.transformOrigin.str().c_str()));
|
|
if (Op(fullPath, curPath, kUnion_SkPathOp, &fullPath))
|
|
flags |= SVG_IMPORT_SUCCESS_FLAG;
|
|
else
|
|
flags |= SVG_IMPORT_PARTIAL_FAILURE_FLAG;
|
|
}
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool finish() {
|
|
return !ignoredDepth && transformationStack.empty();
|
|
}
|
|
|
|
};
|
|
|
|
SvgConsumer svg;
|
|
if (!(
|
|
dropXML::parse(svg, svgData, svgData+svgLength) &&
|
|
(svg.flags&SVG_IMPORT_SUCCESS_FLAG) &&
|
|
Simplify(svg.fullPath, &svg.fullPath)
|
|
))
|
|
return SVG_IMPORT_FAILURE;
|
|
|
|
shapeFromSkiaPath(output, svg.fullPath);
|
|
output.inverseYAxis = true;
|
|
output.orientContours();
|
|
|
|
viewBox = svg.viewBox;
|
|
if (svg.dimensions.x > 0 && viewBox.r == viewBox.l)
|
|
viewBox.r += svg.dimensions.x;
|
|
if (svg.dimensions.y > 0 && viewBox.t == viewBox.b)
|
|
viewBox.t += svg.dimensions.y;
|
|
return svg.flags;
|
|
}
|
|
|
|
int loadSvgShape(Shape &output, Shape::Bounds &viewBox, const char *filename) {
|
|
std::vector<char> svgData;
|
|
if (!readFile(svgData, filename))
|
|
return SVG_IMPORT_FAILURE;
|
|
return parseSvgShape(output, viewBox, svgData.empty() ? NULL : &svgData[0], svgData.size());
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef MSDFGEN_PARENT_NAMESPACE
|
|
} // namespace MSDFGEN_PARENT_NAMESPACE
|
|
#endif
|
|
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#pragma GCC diagnostic pop
|
|
#elif defined(_MSC_VER)
|
|
#pragma warning(pop)
|
|
#endif
|