diff --git a/Msdfgen.vcxproj b/Msdfgen.vcxproj index 63f58ee..7308dd9 100644 --- a/Msdfgen.vcxproj +++ b/Msdfgen.vcxproj @@ -301,6 +301,7 @@ + @@ -326,6 +327,7 @@ + diff --git a/Msdfgen.vcxproj.filters b/Msdfgen.vcxproj.filters index fe3e3bf..5c1fd80 100644 --- a/Msdfgen.vcxproj.filters +++ b/Msdfgen.vcxproj.filters @@ -102,6 +102,9 @@ Core + + Core + @@ -173,6 +176,9 @@ Core + + Core + diff --git a/core/Contour.cpp b/core/Contour.cpp index 87b755a..38a0e0a 100644 --- a/core/Contour.cpp +++ b/core/Contour.cpp @@ -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::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::const_iterator edge = edges.begin(); edge != edges.end(); ++edge) { Point2 cur = (*edge)->point(0); total += shoelace(prev, cur); diff --git a/core/Contour.h b/core/Contour.h index 7284d2d..5d818fd 100644 --- a/core/Contour.h +++ b/core/Contour.h @@ -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; diff --git a/core/Scanline.cpp b/core/Scanline.cpp index ad80202..556ef0a 100644 --- a/core/Scanline.cpp +++ b/core/Scanline.cpp @@ -10,6 +10,57 @@ static int compareIntersections(const void *a, const void *b) { return sign(reinterpret_cast(a)->x-reinterpret_cast(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); +} + } diff --git a/core/Scanline.h b/core/Scanline.h index 1da2e67..9c8f340 100644 --- a/core/Scanline.h +++ b/core/Scanline.h @@ -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 &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 intersections; diff --git a/core/Shape.cpp b/core/Shape.cpp index 6d41b10..6bd2ba8 100644 --- a/core/Shape.cpp +++ b/core/Shape.cpp @@ -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::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 intersections; double x[3]; diff --git a/core/Shape.h b/core/Shape.h index 4834b9e..1806e7a 100644 --- a/core/Shape.h +++ b/core/Shape.h @@ -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; diff --git a/core/estimate-sdf-error.cpp b/core/estimate-sdf-error.cpp new file mode 100644 index 0000000..d09c283 --- /dev/null +++ b/core/estimate-sdf-error.cpp @@ -0,0 +1,168 @@ + +#include "estimate-sdf-error.h" + +#include +#include "arithmetics.hpp" + +namespace msdfgen { + +void scanlineSDF(Scanline &line, const BitmapConstRef &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) { + if (!(sdf.width > 0 && sdf.height > 0)) + return line.setIntersections(std::vector()); + 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 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 &&) intersections); +#else + line.setIntersections(intersections); +#endif +} + +void scanlineSDF(Scanline &line, const BitmapConstRef &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) { + if (!(sdf.width > 0 && sdf.height > 0)) + return line.setIntersections(std::vector()); + 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 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 &&) intersections); +#else + line.setIntersections(intersections); +#endif +} + +double estimateSDFError(const BitmapConstRef &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 &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); +} + +} diff --git a/core/estimate-sdf-error.h b/core/estimate-sdf-error.h new file mode 100644 index 0000000..671154f --- /dev/null +++ b/core/estimate-sdf-error.h @@ -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 &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y); +void scanlineSDF(Scanline &line, const BitmapConstRef &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 &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO); +double estimateSDFError(const BitmapConstRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO); + +} diff --git a/core/rasterization.cpp b/core/rasterization.cpp index 6fc4284..4aad1da 100644 --- a/core/rasterization.cpp +++ b/core/rasterization.cpp @@ -3,24 +3,9 @@ #include #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 &output, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { Point2 p; Scanline scanline; @@ -30,8 +15,7 @@ void rasterize(const BitmapRef &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 &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 &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) diff --git a/core/rasterization.h b/core/rasterization.h index 91bf02c..3ce2d5a 100644 --- a/core/rasterization.h +++ b/core/rasterization.h @@ -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 &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. diff --git a/ext/import-svg.cpp b/ext/import-svg.cpp index dd3462f..95c865e 100644 --- a/ext/import-svg.cpp +++ b/ext/import-svg.cpp @@ -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)); } diff --git a/main.cpp b/main.cpp index 9f2aeed..432ed45 100644 --- a/main.cpp +++ b/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 &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 \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 \n" "\tSaves the shape description into a text file that can be edited and loaded using -shapedesc.\n" " -fillrule \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 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 render(testWidthM, testHeightM); renderSDF(render, msdf, avgScale*range); diff --git a/msdfgen.h b/msdfgen.h index 8f201f8..b6a097d 100644 --- a/msdfgen.h +++ b/msdfgen.h @@ -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"