mirror of https://github.com/Chlumsky/msdfgen.git
Added SDF fill error estimation, miter bounds computation
This commit is contained in:
parent
f0b8f556d2
commit
86acea7835
|
|
@ -301,6 +301,7 @@
|
|||
<ClInclude Include="core\EdgeColor.h" />
|
||||
<ClInclude Include="core\EdgeHolder.h" />
|
||||
<ClInclude Include="core\equation-solver.h" />
|
||||
<ClInclude Include="core\estimate-sdf-error.h" />
|
||||
<ClInclude Include="core\pixel-conversion.hpp" />
|
||||
<ClInclude Include="core\rasterization.h" />
|
||||
<ClInclude Include="core\render-sdf.h" />
|
||||
|
|
@ -326,6 +327,7 @@
|
|||
<ClCompile Include="core\edge-selectors.cpp" />
|
||||
<ClCompile Include="core\EdgeHolder.cpp" />
|
||||
<ClCompile Include="core\equation-solver.cpp" />
|
||||
<ClCompile Include="core\estimate-sdf-error.cpp" />
|
||||
<ClCompile Include="core\rasterization.cpp" />
|
||||
<ClCompile Include="core\render-sdf.cpp" />
|
||||
<ClCompile Include="core\save-bmp.cpp" />
|
||||
|
|
|
|||
|
|
@ -102,6 +102,9 @@
|
|||
<ClInclude Include="core\save-tiff.h">
|
||||
<Filter>Core</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="core\estimate-sdf-error.h">
|
||||
<Filter>Core</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
|
|
@ -173,6 +176,9 @@
|
|||
<ClCompile Include="core\save-tiff.cpp">
|
||||
<Filter>Core</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="core\estimate-sdf-error.cpp">
|
||||
<Filter>Core</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Msdfgen.rc">
|
||||
|
|
|
|||
|
|
@ -21,7 +21,14 @@ void Contour::addEdge(EdgeHolder &&edge) {
|
|||
|
||||
EdgeHolder & Contour::addEdge() {
|
||||
edges.resize(edges.size()+1);
|
||||
return edges[edges.size()-1];
|
||||
return edges.back();
|
||||
}
|
||||
|
||||
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 Contour::bounds(double &l, double &b, double &r, double &t) const {
|
||||
|
|
@ -29,6 +36,22 @@ void Contour::bounds(double &l, double &b, double &r, double &t) const {
|
|||
(*edge)->bounds(l, b, r, t);
|
||||
}
|
||||
|
||||
void Contour::miterBounds(double &l, double &b, double &r, double &t, double border, double miterLimit) 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);
|
||||
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);
|
||||
pointBounds(miter, l, b, r, t);
|
||||
prevDir = (*edge)->direction(1).normalize(true);
|
||||
}
|
||||
}
|
||||
|
||||
int Contour::winding() const {
|
||||
if (edges.empty())
|
||||
return 0;
|
||||
|
|
@ -45,7 +68,7 @@ int Contour::winding() const {
|
|||
total += shoelace(c, d);
|
||||
total += shoelace(d, a);
|
||||
} else {
|
||||
Point2 prev = edges[edges.size()-1]->point(0);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ public:
|
|||
EdgeHolder & addEdge();
|
||||
/// Adjusts the bounding box to fit the contour.
|
||||
void bounds(double &l, double &b, double &r, double &t) const;
|
||||
/// Adjusts the bounding box to fit the contour border's mitered corners.
|
||||
void miterBounds(double &l, double &b, double &r, double &t, double border, double miterLimit) const;
|
||||
/// Computes the winding of the contour. Returns 1 if positive, -1 if negative.
|
||||
int winding() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,57 @@ 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() {
|
||||
|
|
@ -67,4 +118,8 @@ int Scanline::sumIntersections(double x) const {
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool Scanline::filled(double x, FillRule fillRule) const {
|
||||
return interpretFillRule(sumIntersections(x), fillRule);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,18 +5,31 @@
|
|||
|
||||
namespace msdfgen {
|
||||
|
||||
/// Fill rule dictates how intersection total is interpreted during rasterization.
|
||||
enum FillRule {
|
||||
FILL_NONZERO,
|
||||
FILL_ODD, // "even-odd"
|
||||
FILL_POSITIVE,
|
||||
FILL_NEGATIVE
|
||||
};
|
||||
|
||||
/// Resolves the number of intersection into a binary fill value based on fill rule.
|
||||
bool interpretFillRule(int intersections, FillRule fillRule);
|
||||
|
||||
/// Represents a horizontal scanline intersecting a shape.
|
||||
class Scanline {
|
||||
|
||||
public:
|
||||
/// An intersection with the scanline.
|
||||
struct Intersection {
|
||||
/// X coordinate
|
||||
/// X coordinate.
|
||||
double x;
|
||||
/// Normalized Y direction of the oriented edge at the point of intersection
|
||||
/// Normalized Y direction of the oriented edge at the point of intersection.
|
||||
int direction;
|
||||
};
|
||||
|
||||
static double overlap(const Scanline &a, const Scanline &b, double xFrom, double xTo, FillRule fillRule);
|
||||
|
||||
Scanline();
|
||||
/// Populates the intersection list.
|
||||
void setIntersections(const std::vector<Intersection> &intersections);
|
||||
|
|
@ -27,6 +40,8 @@ public:
|
|||
int countIntersections(double x) const;
|
||||
/// Returns the total sign of intersections left of x.
|
||||
int sumIntersections(double x) const;
|
||||
/// Decides whether the scanline is filled at x based on fill rule.
|
||||
bool filled(double x, FillRule fillRule) const;
|
||||
|
||||
private:
|
||||
std::vector<Intersection> intersections;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ void Shape::addContour(Contour &&contour) {
|
|||
|
||||
Contour & Shape::addContour() {
|
||||
contours.resize(contours.size()+1);
|
||||
return contours[contours.size()-1];
|
||||
return contours.back();
|
||||
}
|
||||
|
||||
bool Shape::validate() const {
|
||||
|
|
@ -53,6 +53,11 @@ void Shape::bounds(double &l, double &b, double &r, double &t) const {
|
|||
contour->bounds(l, b, r, t);
|
||||
}
|
||||
|
||||
void Shape::miterBounds(double &l, double &b, double &r, double &t, double border, double miterLimit) const {
|
||||
for (std::vector<Contour>::const_iterator contour = contours.begin(); contour != contours.end(); ++contour)
|
||||
contour->miterBounds(l, b, r, t, border, miterLimit);
|
||||
}
|
||||
|
||||
void Shape::scanline(Scanline &line, double y) const {
|
||||
std::vector<Scanline::Intersection> intersections;
|
||||
double x[3];
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ public:
|
|||
bool validate() const;
|
||||
/// Adjusts the bounding box to fit the shape.
|
||||
void bounds(double &l, double &b, double &r, double &t) const;
|
||||
/// Adjusts the bounding box to fit the shape border's mitered corners.
|
||||
void miterBounds(double &l, double &b, double &r, double &t, double border, double miterLimit) const;
|
||||
/// Outputs the scanline that intersects the shape at y.
|
||||
void scanline(Scanline &line, double y) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,168 @@
|
|||
|
||||
#include "estimate-sdf-error.h"
|
||||
|
||||
#include <cmath>
|
||||
#include "arithmetics.hpp"
|
||||
|
||||
namespace msdfgen {
|
||||
|
||||
void scanlineSDF(Scanline &line, const BitmapConstRef<float, 1> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) {
|
||||
if (!(sdf.width > 0 && sdf.height > 0))
|
||||
return line.setIntersections(std::vector<Scanline::Intersection>());
|
||||
double pixelY = clamp(scale.x*(y+translate.y)-.5, double(sdf.height-1));
|
||||
if (inverseYAxis)
|
||||
pixelY = sdf.height-1-pixelY;
|
||||
int b = (int) floor(pixelY);
|
||||
int t = b+1;
|
||||
double bt = pixelY-b;
|
||||
if (t >= sdf.height) {
|
||||
b = sdf.height-1;
|
||||
t = sdf.height-1;
|
||||
bt = 1;
|
||||
}
|
||||
bool inside = false;
|
||||
std::vector<Scanline::Intersection> intersections;
|
||||
float lv, rv = mix(*sdf(0, b), *sdf(0, t), bt);
|
||||
if ((inside = rv > .5f)) {
|
||||
Scanline::Intersection intersection = { -1e240, 1 };
|
||||
intersections.push_back(intersection);
|
||||
}
|
||||
for (int l = 0, r = 1; r < sdf.width; ++l, ++r) {
|
||||
lv = rv;
|
||||
rv = mix(*sdf(r, b), *sdf(r, t), bt);
|
||||
if (lv != rv) {
|
||||
double lr = double(.5f-lv)/double(rv-lv);
|
||||
if (lr >= 0 && lr <= 1) {
|
||||
Scanline::Intersection intersection = { (l+lr+.5)/scale.x-translate.x, sign(rv-lv) };
|
||||
intersections.push_back(intersection);
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef MSDFGEN_USE_CPP11
|
||||
line.setIntersections((std::vector<Scanline::Intersection> &&) intersections);
|
||||
#else
|
||||
line.setIntersections(intersections);
|
||||
#endif
|
||||
}
|
||||
|
||||
void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) {
|
||||
if (!(sdf.width > 0 && sdf.height > 0))
|
||||
return line.setIntersections(std::vector<Scanline::Intersection>());
|
||||
double pixelY = clamp(scale.x*(y+translate.y)-.5, double(sdf.height-1));
|
||||
if (inverseYAxis)
|
||||
pixelY = sdf.height-1-pixelY;
|
||||
int b = (int) floor(pixelY);
|
||||
int t = b+1;
|
||||
double bt = pixelY-b;
|
||||
if (t >= sdf.height) {
|
||||
b = sdf.height-1;
|
||||
t = sdf.height-1;
|
||||
bt = 1;
|
||||
}
|
||||
bool inside = false;
|
||||
std::vector<Scanline::Intersection> intersections;
|
||||
float lv[3], rv[3];
|
||||
rv[0] = mix(sdf(0, b)[0], sdf(0, t)[0], bt);
|
||||
rv[1] = mix(sdf(0, b)[1], sdf(0, t)[1], bt);
|
||||
rv[2] = mix(sdf(0, b)[2], sdf(0, t)[2], bt);
|
||||
if ((inside = median(rv[0], rv[1], rv[2]) > .5f)) {
|
||||
Scanline::Intersection intersection = { -1e240, 1 };
|
||||
intersections.push_back(intersection);
|
||||
}
|
||||
for (int l = 0, r = 1; r < sdf.width; ++l, ++r) {
|
||||
lv[0] = rv[0], lv[1] = rv[1], lv[2] = rv[2];
|
||||
rv[0] = mix(sdf(r, b)[0], sdf(r, t)[0], bt);
|
||||
rv[1] = mix(sdf(r, b)[1], sdf(r, t)[1], bt);
|
||||
rv[2] = mix(sdf(r, b)[2], sdf(r, t)[2], bt);
|
||||
Scanline::Intersection newIntersections[4];
|
||||
int newIntersectionCount = 0;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (lv[i] != rv[i]) {
|
||||
double lr = double(.5f-lv[i])/double(rv[i]-lv[i]);
|
||||
if (lr >= 0 && lr <= 1) {
|
||||
float v[3] = {
|
||||
mix(lv[0], rv[0], lr),
|
||||
mix(lv[1], rv[1], lr),
|
||||
mix(lv[2], rv[2], lr)
|
||||
};
|
||||
if (median(v[0], v[1], v[2]) == v[i]) {
|
||||
newIntersections[newIntersectionCount].x = (l+lr+.5)/scale.x-translate.x;
|
||||
newIntersections[newIntersectionCount].direction = sign(rv[i]-lv[i]);
|
||||
++newIntersectionCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sort new intersections
|
||||
if (newIntersectionCount >= 2) {
|
||||
if (newIntersections[0].x > newIntersections[1].x)
|
||||
newIntersections[3] = newIntersections[0], newIntersections[0] = newIntersections[1], newIntersections[1] = newIntersections[3];
|
||||
if (newIntersectionCount >= 3 && newIntersections[1].x > newIntersections[2].x) {
|
||||
newIntersections[3] = newIntersections[1], newIntersections[1] = newIntersections[2], newIntersections[2] = newIntersections[3];
|
||||
if (newIntersections[0].x > newIntersections[1].x)
|
||||
newIntersections[3] = newIntersections[0], newIntersections[0] = newIntersections[1], newIntersections[1] = newIntersections[3];
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < newIntersectionCount; ++i) {
|
||||
if ((newIntersections[i].direction > 0) == !inside) {
|
||||
intersections.push_back(newIntersections[i]);
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
// Consistency check
|
||||
float rvScalar = median(rv[0], rv[1], rv[2]);
|
||||
if ((rvScalar > .5f) != inside && rvScalar != .5f && !intersections.empty()) {
|
||||
intersections.pop_back();
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
#ifdef MSDFGEN_USE_CPP11
|
||||
line.setIntersections((std::vector<Scanline::Intersection> &&) intersections);
|
||||
#else
|
||||
line.setIntersections(intersections);
|
||||
#endif
|
||||
}
|
||||
|
||||
double estimateSDFError(const BitmapConstRef<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) {
|
||||
if (sdf.width <= 1 || sdf.height <= 1 || scanlinesPerRow < 1)
|
||||
return 0;
|
||||
double subRowSize = 1./scanlinesPerRow;
|
||||
double xFrom = .5/scale.x-translate.x;
|
||||
double xTo = (sdf.width-.5)/scale.x-translate.x;
|
||||
double overlapFactor = 1/(xTo-xFrom);
|
||||
double error = 0;
|
||||
Scanline refScanline, sdfScanline;
|
||||
for (int row = 0; row < sdf.height-1; ++row) {
|
||||
for (int subRow = 0; subRow < scanlinesPerRow; ++subRow) {
|
||||
double bt = (subRow+.5)*subRowSize;
|
||||
double y = (row+bt+.5)/scale.y-translate.y;
|
||||
shape.scanline(refScanline, y);
|
||||
scanlineSDF(sdfScanline, sdf, scale, translate, shape.inverseYAxis, y);
|
||||
error += 1-overlapFactor*Scanline::overlap(refScanline, sdfScanline, xFrom, xTo, fillRule);
|
||||
}
|
||||
}
|
||||
return error/((sdf.height-1)*scanlinesPerRow);
|
||||
}
|
||||
|
||||
double estimateSDFError(const BitmapConstRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) {
|
||||
if (sdf.width <= 1 || sdf.height <= 1 || scanlinesPerRow < 1)
|
||||
return 0;
|
||||
double subRowSize = 1./scanlinesPerRow;
|
||||
double xFrom = .5/scale.x-translate.x;
|
||||
double xTo = (sdf.width-.5)/scale.x-translate.x;
|
||||
double overlapFactor = 1/(xTo-xFrom);
|
||||
double error = 0;
|
||||
Scanline refScanline, sdfScanline;
|
||||
for (int row = 0; row < sdf.height-1; ++row) {
|
||||
for (int subRow = 0; subRow < scanlinesPerRow; ++subRow) {
|
||||
double bt = (subRow+.5)*subRowSize;
|
||||
double y = (row+bt+.5)/scale.y-translate.y;
|
||||
shape.scanline(refScanline, y);
|
||||
scanlineSDF(sdfScanline, sdf, scale, translate, shape.inverseYAxis, y);
|
||||
error += 1-overlapFactor*Scanline::overlap(refScanline, sdfScanline, xFrom, xTo, fillRule);
|
||||
}
|
||||
}
|
||||
return error/((sdf.height-1)*scanlinesPerRow);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Vector2.h"
|
||||
#include "Scanline.h"
|
||||
#include "Shape.h"
|
||||
#include "BitmapRef.hpp"
|
||||
|
||||
namespace msdfgen {
|
||||
|
||||
/// Analytically constructs a scanline at y evaluating fill by linear interpolation of the SDF.
|
||||
void scanlineSDF(Scanline &line, const BitmapConstRef<float, 1> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y);
|
||||
void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y);
|
||||
|
||||
/// Estimates the portion of the area that will be filled incorrectly when rendering using the SDF.
|
||||
double estimateSDFError(const BitmapConstRef<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO);
|
||||
double estimateSDFError(const BitmapConstRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO);
|
||||
|
||||
}
|
||||
|
|
@ -3,24 +3,9 @@
|
|||
|
||||
#include <vector>
|
||||
#include "arithmetics.hpp"
|
||||
#include "Scanline.h"
|
||||
|
||||
namespace msdfgen {
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
void rasterize(const BitmapRef<float, 1> &output, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
|
||||
Point2 p;
|
||||
Scanline scanline;
|
||||
|
|
@ -30,8 +15,7 @@ void rasterize(const BitmapRef<float, 1> &output, const Shape &shape, const Vect
|
|||
shape.scanline(scanline, p.y);
|
||||
for (int x = 0; x < output.width; ++x) {
|
||||
p.x = (x+.5)/scale.x-translate.x;
|
||||
int intersections = scanline.sumIntersections(p.x);
|
||||
bool fill = interpretFillRule(intersections, fillRule);
|
||||
bool fill = scanline.filled(p.x, fillRule);
|
||||
*output(x, row) = (float) fill;
|
||||
}
|
||||
}
|
||||
|
|
@ -46,8 +30,7 @@ void distanceSignCorrection(const BitmapRef<float, 1> &sdf, const Shape &shape,
|
|||
shape.scanline(scanline, p.y);
|
||||
for (int x = 0; x < sdf.width; ++x) {
|
||||
p.x = (x+.5)/scale.x-translate.x;
|
||||
int intersections = scanline.sumIntersections(p.x);
|
||||
bool fill = interpretFillRule(intersections, fillRule);
|
||||
bool fill = scanline.filled(p.x, fillRule);
|
||||
float &sd = *sdf(x, row);
|
||||
if ((sd > .5f) != fill)
|
||||
sd = 1.f-sd;
|
||||
|
|
@ -71,8 +54,7 @@ void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape,
|
|||
shape.scanline(scanline, p.y);
|
||||
for (int x = 0; x < w; ++x) {
|
||||
p.x = (x+.5)/scale.x-translate.x;
|
||||
int intersections = scanline.sumIntersections(p.x);
|
||||
bool fill = interpretFillRule(intersections, fillRule);
|
||||
bool fill = scanline.filled(p.x, fillRule);
|
||||
float *msd = sdf(x, row);
|
||||
float sd = median(msd[0], msd[1], msd[2]);
|
||||
if (sd == .5f)
|
||||
|
|
|
|||
|
|
@ -2,19 +2,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "Vector2.h"
|
||||
#include "Scanline.h"
|
||||
#include "Shape.h"
|
||||
#include "BitmapRef.hpp"
|
||||
|
||||
namespace msdfgen {
|
||||
|
||||
/// Fill rule dictates how intersection total is interpreted during rasterization.
|
||||
enum FillRule {
|
||||
FILL_NONZERO,
|
||||
FILL_ODD, // "even-odd"
|
||||
FILL_POSITIVE,
|
||||
FILL_NEGATIVE
|
||||
};
|
||||
|
||||
/// Rasterizes the shape into a monochrome bitmap.
|
||||
void rasterize(const BitmapRef<float, 1> &output, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
|
||||
/// Fixes the sign of the input signed distance field, so that it matches the shape's rasterized fill.
|
||||
|
|
|
|||
|
|
@ -251,8 +251,8 @@ static bool buildFromPath(Shape &shape, const char *pathDef, double size) {
|
|||
NEXT_CONTOUR:
|
||||
// Fix contour if it isn't properly closed
|
||||
if (!contour.edges.empty() && prevNode != startPoint) {
|
||||
if ((contour.edges[contour.edges.size()-1]->point(1)-contour.edges[0]->point(0)).length() < ENDPOINT_SNAP_RANGE_PROPORTION*size)
|
||||
contour.edges[contour.edges.size()-1]->moveEndPoint(contour.edges[0]->point(0));
|
||||
if ((contour.edges.back()->point(1)-contour.edges[0]->point(0)).length() < ENDPOINT_SNAP_RANGE_PROPORTION*size)
|
||||
contour.edges.back()->moveEndPoint(contour.edges[0]->point(0));
|
||||
else
|
||||
contour.addEdge(new LinearSegment(prevNode, startPoint));
|
||||
}
|
||||
|
|
|
|||
27
main.cpp
27
main.cpp
|
|
@ -21,6 +21,7 @@
|
|||
#endif
|
||||
|
||||
#define LARGE_VALUE 1e240
|
||||
#define SDF_ERROR_ESTIMATE_PRECISION 19
|
||||
|
||||
using namespace msdfgen;
|
||||
|
||||
|
|
@ -36,6 +37,10 @@ enum Format {
|
|||
BINART_FLOAT_BE
|
||||
};
|
||||
|
||||
static bool is8bitFormat(Format format) {
|
||||
return format == PNG || format == BMP || format == TEXT || format == BINARY;
|
||||
}
|
||||
|
||||
static char toupper(char c) {
|
||||
return c >= 'a' && c <= 'z' ? c-'a'+'A' : c;
|
||||
}
|
||||
|
|
@ -213,7 +218,7 @@ static const char * writeOutput(const BitmapConstRef<float, N> &bitmap, const ch
|
|||
switch (format) {
|
||||
case PNG: return savePng(bitmap, filename) ? NULL : "Failed to write output PNG image.";
|
||||
case BMP: return saveBmp(bitmap, filename) ? NULL : "Failed to write output BMP image.";
|
||||
case TIFF: return saveTiff(bitmap, filename) ? NULL : "Failed to write output BMP image.";
|
||||
case TIFF: return saveTiff(bitmap, filename) ? NULL : "Failed to write output TIFF image.";
|
||||
case TEXT: case TEXT_FLOAT: {
|
||||
FILE *file = fopen(filename, "w");
|
||||
if (!file) return "Failed to write output text file.";
|
||||
|
|
@ -288,6 +293,8 @@ static const char *helpText =
|
|||
"\tOverrides automatic edge coloring with the specified color sequence.\n"
|
||||
" -errorcorrection <threshold>\n"
|
||||
"\tChanges the threshold used to detect and correct potential artifacts. 0 disables error correction.\n"
|
||||
" -estimateerror\n"
|
||||
"\tComputes and prints the distance field's estimated fill error to the standard output.\n"
|
||||
" -exportshape <filename.txt>\n"
|
||||
"\tSaves the shape description into a text file that can be edited and loaded using -shapedesc.\n"
|
||||
" -fillrule <nonzero / evenodd / positive / negative>\n"
|
||||
|
|
@ -383,6 +390,7 @@ int main(int argc, const char * const *argv) {
|
|||
const char *edgeAssignment = NULL;
|
||||
bool yFlip = false;
|
||||
bool printMetrics = false;
|
||||
bool estimateError = false;
|
||||
bool skipColoring = false;
|
||||
enum {
|
||||
KEEP,
|
||||
|
|
@ -610,6 +618,11 @@ int main(int argc, const char * const *argv) {
|
|||
argPos += 1;
|
||||
continue;
|
||||
}
|
||||
ARG_CASE("-estimateerror", 0) {
|
||||
estimateError = true;
|
||||
argPos += 1;
|
||||
continue;
|
||||
}
|
||||
ARG_CASE("-keeporder", 0) {
|
||||
orientation = KEEP;
|
||||
argPos += 1;
|
||||
|
|
@ -857,8 +870,12 @@ int main(int argc, const char * const *argv) {
|
|||
error = writeOutput<1>(sdf, output, format);
|
||||
if (error)
|
||||
ABORT(error);
|
||||
if (testRenderMulti || testRender)
|
||||
if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
|
||||
simulate8bit(sdf);
|
||||
if (estimateError) {
|
||||
double sdfError = estimateSDFError(sdf, shape, scale, translate, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
|
||||
printf("SDF error ~ %e\n", sdfError);
|
||||
}
|
||||
if (testRenderMulti) {
|
||||
Bitmap<float, 3> render(testWidthM, testHeightM);
|
||||
renderSDF(render, sdf, avgScale*range);
|
||||
|
|
@ -876,8 +893,12 @@ int main(int argc, const char * const *argv) {
|
|||
error = writeOutput<3>(msdf, output, format);
|
||||
if (error)
|
||||
ABORT(error);
|
||||
if (testRenderMulti || testRender)
|
||||
if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
|
||||
simulate8bit(msdf);
|
||||
if (estimateError) {
|
||||
double sdfError = estimateSDFError(msdf, shape, scale, translate, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
|
||||
printf("SDF error ~ %e\n", sdfError);
|
||||
}
|
||||
if (testRenderMulti) {
|
||||
Bitmap<float, 3> render(testWidthM, testHeightM);
|
||||
renderSDF(render, msdf, avgScale*range);
|
||||
|
|
|
|||
|
|
@ -17,12 +17,15 @@
|
|||
|
||||
#include "core/arithmetics.hpp"
|
||||
#include "core/Vector2.h"
|
||||
#include "core/Scanline.h"
|
||||
#include "core/Shape.h"
|
||||
#include "core/BitmapRef.hpp"
|
||||
#include "core/Bitmap.h"
|
||||
#include "core/pixel-conversion.hpp"
|
||||
#include "core/edge-coloring.h"
|
||||
#include "core/render-sdf.h"
|
||||
#include "core/rasterization.h"
|
||||
#include "core/estimate-sdf-error.h"
|
||||
#include "core/save-bmp.h"
|
||||
#include "core/save-tiff.h"
|
||||
#include "core/shape-description.h"
|
||||
|
|
|
|||
Loading…
Reference in New Issue