From 997e42f7340cb927ab76d3493b8dc18e0e3ffeb5 Mon Sep 17 00:00:00 2001 From: Chlumsky Date: Sat, 20 Apr 2019 22:05:54 +0200 Subject: [PATCH] Bitmap refactor --- CHANGELOG.md | 2 +- Msdfgen.vcxproj | 4 +- Msdfgen.vcxproj.filters | 12 +++- core/Bitmap.cpp | 77 ----------------------- core/Bitmap.h | 33 +++++----- core/Bitmap.hpp | 117 +++++++++++++++++++++++++++++++++++ core/BitmapRef.hpp | 43 +++++++++++++ core/contour-combiners.cpp | 4 +- core/contour-combiners.h | 4 +- core/edge-segments.cpp | 31 ++++++---- core/edge-selectors.cpp | 2 +- core/msdfgen.cpp | 97 ++++++++++++++--------------- core/pixel-conversion.hpp | 18 ++++++ core/rasterization.cpp | 44 +++++++------- core/rasterization.h | 8 +-- core/render-sdf.cpp | 121 ++++++++++++++++--------------------- core/render-sdf.h | 14 ++--- core/save-bmp.cpp | 84 +++++++++++++++++++------ core/save-bmp.h | 8 ++- ext/save-png.cpp | 42 +++++++------ ext/save-png.h | 8 ++- main.cpp | 62 +++++++++---------- msdfgen.h | 15 ++--- 23 files changed, 498 insertions(+), 352 deletions(-) delete mode 100644 core/Bitmap.cpp create mode 100644 core/Bitmap.hpp create mode 100644 core/BitmapRef.hpp create mode 100644 core/pixel-conversion.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index a427adb..065fca4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -## Version 1.6 (2019-04-06) +## Version 1.6 (2019-04-08) - Core algorithm rewritten to split up advanced edge selection logic into modular template arguments. - Pseudo-distance evaluation reworked to eliminate discontinuities at the midpoint between edges. diff --git a/Msdfgen.vcxproj b/Msdfgen.vcxproj index b4a20f9..7c9e65a 100644 --- a/Msdfgen.vcxproj +++ b/Msdfgen.vcxproj @@ -291,6 +291,8 @@ + + @@ -299,6 +301,7 @@ + @@ -315,7 +318,6 @@ - diff --git a/Msdfgen.vcxproj.filters b/Msdfgen.vcxproj.filters index 9008ea4..86d5a29 100644 --- a/Msdfgen.vcxproj.filters +++ b/Msdfgen.vcxproj.filters @@ -90,14 +90,20 @@ Core + + Core + + + Core + + + Core + Standalone - - Core - Core diff --git a/core/Bitmap.cpp b/core/Bitmap.cpp deleted file mode 100644 index 645aebc..0000000 --- a/core/Bitmap.cpp +++ /dev/null @@ -1,77 +0,0 @@ - -#include "Bitmap.h" - -#include - -namespace msdfgen { - -template -Bitmap::Bitmap() : content(NULL), w(0), h(0) { } - -template -Bitmap::Bitmap(int width, int height) : w(width), h(height) { - content = new T[w*h]; -} - -template -Bitmap::Bitmap(const Bitmap &orig) : w(orig.w), h(orig.h) { - content = new T[w*h]; - memcpy(content, orig.content, w*h*sizeof(T)); -} - -#ifdef MSDFGEN_USE_CPP11 -template -Bitmap::Bitmap(Bitmap &&orig) : content(orig.content), w(orig.w), h(orig.h) { - orig.content = NULL; -} -#endif - -template -Bitmap::~Bitmap() { - delete [] content; -} - -template -Bitmap & Bitmap::operator=(const Bitmap &orig) { - delete [] content; - w = orig.w, h = orig.h; - content = new T[w*h]; - memcpy(content, orig.content, w*h*sizeof(T)); - return *this; -} - -#ifdef MSDFGEN_USE_CPP11 -template -Bitmap & Bitmap::operator=(Bitmap &&orig) { - delete [] content; - content = orig.content; - w = orig.w, h = orig.h; - orig.content = NULL; - return *this; -} -#endif - -template -int Bitmap::width() const { - return w; -} - -template -int Bitmap::height() const { - return h; -} - -template -T & Bitmap::operator()(int x, int y) { - return content[y*w+x]; -} - -template -const T & Bitmap::operator()(int x, int y) const { - return content[y*w+x]; -} - -template class Bitmap; -template class Bitmap; - -} diff --git a/core/Bitmap.h b/core/Bitmap.h index 6ae84ed..3ad23a8 100644 --- a/core/Bitmap.h +++ b/core/Bitmap.h @@ -1,40 +1,45 @@ #pragma once +#include "BitmapRef.hpp" + namespace msdfgen { -/// A floating-point RGB pixel. -struct FloatRGB { - float r, g, b; -}; - -/// A 2D image bitmap. -template +/// A 2D image bitmap with N channels of type T. Pixel memory is managed by the class. +template class Bitmap { public: Bitmap(); Bitmap(int width, int height); - Bitmap(const Bitmap &orig); + Bitmap(const BitmapConstRef &orig); + Bitmap(const Bitmap &orig); #ifdef MSDFGEN_USE_CPP11 - Bitmap(Bitmap &&orig); + Bitmap(Bitmap &&orig); #endif ~Bitmap(); - Bitmap & operator=(const Bitmap &orig); + Bitmap & operator=(const BitmapConstRef &orig); + Bitmap & operator=(const Bitmap &orig); #ifdef MSDFGEN_USE_CPP11 - Bitmap & operator=(Bitmap &&orig); + Bitmap & operator=(Bitmap &&orig); #endif /// Bitmap width in pixels. int width() const; /// Bitmap height in pixels. int height() const; - T & operator()(int x, int y); - const T & operator()(int x, int y) const; + T * operator()(int x, int y); + const T * operator()(int x, int y) const; + explicit operator T *(); + explicit operator const T *() const; + operator BitmapRef(); + operator BitmapConstRef() const; private: - T *content; + T *pixels; int w, h; }; } + +#include "Bitmap.hpp" diff --git a/core/Bitmap.hpp b/core/Bitmap.hpp new file mode 100644 index 0000000..cb16cac --- /dev/null +++ b/core/Bitmap.hpp @@ -0,0 +1,117 @@ + +#include "Bitmap.h" + +#include +#include + +namespace msdfgen { + +template +Bitmap::Bitmap() : pixels(NULL), w(0), h(0) { } + +template +Bitmap::Bitmap(int width, int height) : w(width), h(height) { + pixels = new T[N*w*h]; +} + +template +Bitmap::Bitmap(const BitmapConstRef &orig) : w(orig.width), h(orig.height) { + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); +} + +template +Bitmap::Bitmap(const Bitmap &orig) : w(orig.w), h(orig.h) { + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); +} + +#ifdef MSDFGEN_USE_CPP11 +template +Bitmap::Bitmap(Bitmap &&orig) : pixels(orig.pixels), w(orig.w), h(orig.h) { + orig.pixels = NULL; + orig.w = 0, orig.h = 0; +} +#endif + +template +Bitmap::~Bitmap() { + delete [] pixels; +} + +template +Bitmap & Bitmap::operator=(const BitmapConstRef &orig) { + if (pixels != orig.pixels) { + delete [] pixels; + w = orig.width, h = orig.height; + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); + } + return *this; +} + +template +Bitmap & Bitmap::operator=(const Bitmap &orig) { + if (this != &orig) { + delete [] pixels; + w = orig.w, h = orig.h; + pixels = new T[N*w*h]; + memcpy(pixels, orig.pixels, sizeof(T)*N*w*h); + } + return *this; +} + +#ifdef MSDFGEN_USE_CPP11 +template +Bitmap & Bitmap::operator=(Bitmap &&orig) { + if (this != &orig) { + delete [] pixels; + pixels = orig.pixels; + w = orig.w, h = orig.h; + orig.pixels = NULL; + } + return *this; +} +#endif + +template +int Bitmap::width() const { + return w; +} + +template +int Bitmap::height() const { + return h; +} + +template +T * Bitmap::operator()(int x, int y) { + return pixels+N*(w*y+x); +} + +template +const T * Bitmap::operator()(int x, int y) const { + return pixels+N*(w*y+x); +} + +template +Bitmap::operator T *() { + return pixels; +} + +template +Bitmap::operator const T *() const { + return pixels; +} + +template +Bitmap::operator BitmapRef() { + return BitmapRef(pixels, w, h); +} + +template +Bitmap::operator BitmapConstRef() const { + return BitmapConstRef(pixels, w, h); +} + +} diff --git a/core/BitmapRef.hpp b/core/BitmapRef.hpp new file mode 100644 index 0000000..6f9620d --- /dev/null +++ b/core/BitmapRef.hpp @@ -0,0 +1,43 @@ + +#pragma once + +#include + +namespace msdfgen { + +typedef unsigned char byte; + +/// Reference to a 2D image bitmap or a buffer acting as one. Pixel storage not owned or managed by the object. +template +struct BitmapRef { + + T *pixels; + int width, height; + + inline BitmapRef() : pixels(NULL), width(0), height(0) { } + inline BitmapRef(T *pixels, int width, int height) : pixels(pixels), width(width), height(height) { } + + inline T * operator()(int x, int y) const { + return pixels+N*(width*y+x); + } + +}; + +/// Constant reference to a 2D image bitmap or a buffer acting as one. Pixel storage not owned or managed by the object. +template +struct BitmapConstRef { + + const T *pixels; + int width, height; + + inline BitmapConstRef() : pixels(NULL), width(0), height(0) { } + inline BitmapConstRef(const T *pixels, int width, int height) : pixels(pixels), width(width), height(height) { } + inline BitmapConstRef(const BitmapRef &orig) : pixels(orig.pixels), width(orig.width), height(orig.height) { } + + inline const T * operator()(int x, int y) const { + return pixels+N*(width*y+x); + } + +}; + +} diff --git a/core/contour-combiners.cpp b/core/contour-combiners.cpp index 61b5dfb..e52f159 100644 --- a/core/contour-combiners.cpp +++ b/core/contour-combiners.cpp @@ -32,7 +32,7 @@ void SimpleContourCombiner::reset(const Point2 &p) { } template -void SimpleContourCombiner::setContourEdge(int i, const EdgeSelector &edgeSelector) { +void SimpleContourCombiner::setContourEdgeSelection(int i, const EdgeSelector &edgeSelector) { shapeEdgeSelector.merge(edgeSelector); } @@ -61,7 +61,7 @@ void OverlappingContourCombiner::reset(const Point2 &p) { } template -void OverlappingContourCombiner::setContourEdge(int i, const EdgeSelector &edgeSelector) { +void OverlappingContourCombiner::setContourEdgeSelection(int i, const EdgeSelector &edgeSelector) { DistanceType edgeDistance = edgeSelector.distance(); edgeSelectors[i] = edgeSelector; shapeEdgeSelector.merge(edgeSelector); diff --git a/core/contour-combiners.h b/core/contour-combiners.h index c057968..d8ddbcf 100644 --- a/core/contour-combiners.h +++ b/core/contour-combiners.h @@ -16,7 +16,7 @@ public: explicit SimpleContourCombiner(const Shape &shape); void reset(const Point2 &p); - void setContourEdge(int i, const EdgeSelector &edgeSelector); + void setContourEdgeSelection(int i, const EdgeSelector &edgeSelector); DistanceType distance() const; private: @@ -34,7 +34,7 @@ public: explicit OverlappingContourCombiner(const Shape &shape); void reset(const Point2 &p); - void setContourEdge(int i, const EdgeSelector &edgeSelector); + void setContourEdgeSelection(int i, const EdgeSelector &edgeSelector); DistanceType distance() const; private: diff --git a/core/edge-segments.cpp b/core/edge-segments.cpp index 1f8bbef..4c369bf 100644 --- a/core/edge-segments.cpp +++ b/core/edge-segments.cpp @@ -82,7 +82,10 @@ Vector2 LinearSegment::direction(double param) const { } Vector2 QuadraticSegment::direction(double param) const { - return mix(p[1]-p[0], p[2]-p[1], param); + 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 { @@ -119,19 +122,21 @@ SignedDistance QuadraticSegment::signedDistance(Point2 origin, double ¶m) co double t[3]; int solutions = solveCubic(t, a, b, c, d); - double minDistance = nonZeroSign(crossProduct(ab, qa))*qa.length(); // distance from A - param = -dotProduct(qa, ab)/dotProduct(ab, ab); + Vector2 epDir = direction(0); + double minDistance = nonZeroSign(crossProduct(epDir, qa))*qa.length(); // distance from A + param = -dotProduct(qa, epDir)/dotProduct(epDir, epDir); { - double distance = nonZeroSign(crossProduct(p[2]-p[1], p[2]-origin))*(p[2]-origin).length(); // distance from B + epDir = direction(1); + double distance = nonZeroSign(crossProduct(epDir, p[2]-origin))*(p[2]-origin).length(); // distance from B if (fabs(distance) < fabs(minDistance)) { minDistance = distance; - param = dotProduct(origin-p[1], p[2]-p[1])/dotProduct(p[2]-p[1], p[2]-p[1]); + param = dotProduct(origin-p[1], epDir)/dotProduct(epDir, epDir); } } for (int i = 0; i < solutions; ++i) { if (t[i] > 0 && t[i] < 1) { - Point2 endpoint = p[0]+2*t[i]*ab+t[i]*t[i]*br; - double distance = nonZeroSign(crossProduct(p[2]-p[0], endpoint-origin))*(endpoint-origin).length(); + Point2 qe = p[0]+2*t[i]*ab+t[i]*t[i]*br-origin; + double distance = nonZeroSign(crossProduct(p[2]-p[0], qe))*qe.length(); if (fabs(distance) <= fabs(minDistance)) { minDistance = distance; param = t[i]; @@ -142,9 +147,9 @@ SignedDistance QuadraticSegment::signedDistance(Point2 origin, double ¶m) co if (param >= 0 && param <= 1) return SignedDistance(minDistance, 0); if (param < .5) - return SignedDistance(minDistance, fabs(dotProduct(ab.normalize(), qa.normalize()))); + return SignedDistance(minDistance, fabs(dotProduct(direction(0).normalize(), qa.normalize()))); else - return SignedDistance(minDistance, fabs(dotProduct((p[2]-p[1]).normalize(), (p[2]-origin).normalize()))); + return SignedDistance(minDistance, fabs(dotProduct(direction(1).normalize(), (p[2]-origin).normalize()))); } SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const { @@ -161,15 +166,15 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const double distance = nonZeroSign(crossProduct(epDir, p[3]-origin))*(p[3]-origin).length(); // distance from B if (fabs(distance) < fabs(minDistance)) { minDistance = distance; - param = dotProduct(origin+epDir-p[3], epDir)/dotProduct(epDir, epDir); + 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; for (int step = 0;; ++step) { - Vector2 qpt = point(t)-origin; - double distance = nonZeroSign(crossProduct(direction(t), qpt))*qpt.length(); + Vector2 qe = p[0]+3*t*ab+3*t*t*br+t*t*t*as-origin; // do not simplify with qa !!! + double distance = nonZeroSign(crossProduct(direction(t), qe))*qe.length(); if (fabs(distance) < fabs(minDistance)) { minDistance = distance; param = t; @@ -179,7 +184,7 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double ¶m) const // Improve t Vector2 d1 = 3*as*t*t+6*br*t+3*ab; Vector2 d2 = 6*as*t+6*br; - t -= dotProduct(qpt, d1)/(dotProduct(d1, d1)+dotProduct(qpt, d2)); + t -= dotProduct(qe, d1)/(dotProduct(d1, d1)+dotProduct(qe, d2)); if (t < 0 || t > 1) break; } diff --git a/core/edge-selectors.cpp b/core/edge-selectors.cpp index c1679dd..1df94d6 100644 --- a/core/edge-selectors.cpp +++ b/core/edge-selectors.cpp @@ -103,7 +103,7 @@ void MultiDistanceSelector::addEdge(const EdgeSegment *prevEdge, const EdgeSegme g.addEdgeTrueDistance(edge, distance, param); if (edge->color&BLUE) b.addEdgeTrueDistance(edge, distance, param); - if (PseudoDistanceSelector::pointFacingEdge(prevEdge, edge, nextEdge, p, param)) { + if (PseudoDistanceSelectorBase::pointFacingEdge(prevEdge, edge, nextEdge, p, param)) { edge->distanceToPseudoDistance(distance, p, param); if (edge->color&RED) r.addEdgePseudoDistance(distance); diff --git a/core/msdfgen.cpp b/core/msdfgen.cpp index 708e1b7..2dc095c 100644 --- a/core/msdfgen.cpp +++ b/core/msdfgen.cpp @@ -13,29 +13,25 @@ class DistancePixelConversion; template <> class DistancePixelConversion { public: - typedef float PixelType; - inline static PixelType convert(double distance, double range) { - return PixelType(distance/range+.5); + typedef BitmapRef BitmapRefType; + inline static void convert(float *pixels, double distance, double range) { + *pixels = float(distance/range+.5); } }; template <> class DistancePixelConversion { public: - typedef FloatRGB PixelType; - inline static PixelType convert(const MultiDistance &distance, double range) { - PixelType pixel; - pixel.r = float(distance.r/range+.5); - pixel.g = float(distance.g/range+.5); - pixel.b = float(distance.b/range+.5); - return pixel; + typedef BitmapRef BitmapRefType; + inline static void convert(float *pixels, const MultiDistance &distance, double range) { + pixels[0] = float(distance.r/range+.5); + pixels[1] = float(distance.g/range+.5); + pixels[2] = float(distance.b/range+.5); } }; template -void generateDistanceField(Bitmap::PixelType> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) { - int w = output.width(), h = output.height(); - +void generateDistanceField(const typename DistancePixelConversion::BitmapRefType &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) { #ifdef MSDFGEN_USE_OPENMP #pragma omp parallel #endif @@ -45,10 +41,10 @@ void generateDistanceField(Bitmap::convert(distance, range); + DistancePixelConversion::convert(output(x, row), distance, range); } } } } -void generateSDF(Bitmap &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) { +void generateSDF(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) { if (overlapSupport) generateDistanceField >(output, shape, range, scale, translate); else generateDistanceField >(output, shape, range, scale, translate); } -void generatePseudoSDF(Bitmap &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) { +void generatePseudoSDF(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) { if (overlapSupport) generateDistanceField >(output, shape, range, scale, translate); else generateDistanceField >(output, shape, range, scale, translate); } -void generateMSDF(Bitmap &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold, bool overlapSupport) { +void generateMSDF(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold, bool overlapSupport) { if (overlapSupport) generateDistanceField >(output, shape, range, scale, translate); else @@ -100,10 +96,10 @@ void generateMSDF(Bitmap &output, const Shape &shape, double range, co msdfErrorCorrection(output, edgeThreshold/(scale*range)); } -inline static bool detectClash(const FloatRGB &a, const FloatRGB &b, double threshold) { +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.r, a1 = a.g, a2 = a.b; - float b0 = b.r, b1 = b.g, b2 = b.b; + 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; @@ -122,9 +118,9 @@ inline static bool detectClash(const FloatRGB &a, const FloatRGB &b, double thre fabsf(a2-.5f) >= fabsf(b2-.5f); // Out of the pair, only flag the pixel farther from a shape edge } -void msdfErrorCorrection(Bitmap &output, const Vector2 &threshold) { +void msdfErrorCorrection(const BitmapRef &output, const Vector2 &threshold) { std::vector > clashes; - int w = output.width(), h = output.height(); + int w = output.width, h = output.height; for (int y = 0; y < h; ++y) for (int x = 0; x < w; ++x) { if ( @@ -136,9 +132,9 @@ void msdfErrorCorrection(Bitmap &output, const Vector2 &threshold) { clashes.push_back(std::make_pair(x, y)); } for (std::vector >::const_iterator clash = clashes.begin(); clash != clashes.end(); ++clash) { - FloatRGB &pixel = output(clash->first, clash->second); - float med = median(pixel.r, pixel.g, pixel.b); - pixel.r = med, pixel.g = med, pixel.b = med; + 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(); @@ -153,23 +149,22 @@ void msdfErrorCorrection(Bitmap &output, const Vector2 &threshold) { clashes.push_back(std::make_pair(x, y)); } for (std::vector >::const_iterator clash = clashes.begin(); clash != clashes.end(); ++clash) { - FloatRGB &pixel = output(clash->first, clash->second); - float med = median(pixel.r, pixel.g, pixel.b); - pixel.r = med, pixel.g = med, pixel.b = med; + 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 } // Legacy version -void generateSDF_legacy(Bitmap &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) { - int w = output.width(), h = output.height(); +void generateSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) { #ifdef MSDFGEN_USE_OPENMP #pragma omp parallel for #endif - for (int y = 0; y < h; ++y) { - int row = shape.inverseYAxis ? h-y-1 : y; - for (int x = 0; x < w; ++x) { + 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; @@ -179,19 +174,18 @@ void generateSDF_legacy(Bitmap &output, const Shape &shape, double range, if (distance < minDistance) minDistance = distance; } - output(x, row) = float(minDistance.distance/range+.5); + *output(x, row) = float(minDistance.distance/range+.5); } } } -void generatePseudoSDF_legacy(Bitmap &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) { - int w = output.width(), h = output.height(); +void generatePseudoSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) { #ifdef MSDFGEN_USE_OPENMP #pragma omp parallel for #endif - for (int y = 0; y < h; ++y) { - int row = shape.inverseYAxis ? h-y-1 : y; - for (int x = 0; x < w; ++x) { + 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; @@ -208,19 +202,18 @@ void generatePseudoSDF_legacy(Bitmap &output, const Shape &shape, double } if (nearEdge) (*nearEdge)->distanceToPseudoDistance(minDistance, p, nearParam); - output(x, row) = float(minDistance.distance/range+.5); + *output(x, row) = float(minDistance.distance/range+.5); } } } -void generateMSDF_legacy(Bitmap &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold) { - int w = output.width(), h = output.height(); +void generateMSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold) { #ifdef MSDFGEN_USE_OPENMP #pragma omp parallel for #endif - for (int y = 0; y < h; ++y) { - int row = shape.inverseYAxis ? h-y-1 : y; - for (int x = 0; x < w; ++x) { + 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 { @@ -258,9 +251,9 @@ void generateMSDF_legacy(Bitmap &output, const Shape &shape, double ra (*g.nearEdge)->distanceToPseudoDistance(g.minDistance, p, g.nearParam); if (b.nearEdge) (*b.nearEdge)->distanceToPseudoDistance(b.minDistance, p, b.nearParam); - output(x, row).r = float(r.minDistance.distance/range+.5); - output(x, row).g = float(g.minDistance.distance/range+.5); - output(x, row).b = float(b.minDistance.distance/range+.5); + output(x, row)[0] = float(r.minDistance.distance/range+.5); + output(x, row)[1] = float(g.minDistance.distance/range+.5); + output(x, row)[2] = float(b.minDistance.distance/range+.5); } } diff --git a/core/pixel-conversion.hpp b/core/pixel-conversion.hpp new file mode 100644 index 0000000..7e9b6d0 --- /dev/null +++ b/core/pixel-conversion.hpp @@ -0,0 +1,18 @@ + +#pragma once + +#include "arithmetics.hpp" + +namespace msdfgen { + +typedef unsigned char byte; + +inline byte pixelFloatToByte(float x) { + return byte(clamp(256.f*x, 255.f)); +} + +inline float pixelByteToFloat(byte x) { + return 1.f/255.f*float(x); +} + +} diff --git a/core/rasterization.cpp b/core/rasterization.cpp index ac0a4cc..6fc4284 100644 --- a/core/rasterization.cpp +++ b/core/rasterization.cpp @@ -21,44 +21,42 @@ static bool interpretFillRule(int intersections, FillRule fillRule) { return false; } -void rasterize(Bitmap &output, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { - int w = output.width(), h = output.height(); +void rasterize(const BitmapRef &output, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { Point2 p; Scanline scanline; - for (int y = 0; y < h; ++y) { - int row = shape.inverseYAxis ? h-y-1 : y; + for (int y = 0; y < output.height; ++y) { + int row = shape.inverseYAxis ? output.height-y-1 : y; p.y = (y+.5)/scale.y-translate.y; shape.scanline(scanline, p.y); - for (int x = 0; x < w; ++x) { + 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); - output(x, row) = (float) fill; + *output(x, row) = (float) fill; } } } -void distanceSignCorrection(Bitmap &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { - int w = sdf.width(), h = sdf.height(); +void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { Point2 p; Scanline scanline; - for (int y = 0; y < h; ++y) { - int row = shape.inverseYAxis ? h-y-1 : y; + for (int y = 0; y < sdf.height; ++y) { + int row = shape.inverseYAxis ? sdf.height-y-1 : y; p.y = (y+.5)/scale.y-translate.y; shape.scanline(scanline, p.y); - for (int x = 0; x < w; ++x) { + 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); - float &sd = sdf(x, row); + float &sd = *sdf(x, row); if ((sd > .5f) != fill) sd = 1.f-sd; } } } -void distanceSignCorrection(Bitmap &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { - int w = sdf.width(), h = sdf.height(); +void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) { + int w = sdf.width, h = sdf.height; if (!(w*h)) return; Point2 p; @@ -75,14 +73,14 @@ void distanceSignCorrection(Bitmap &sdf, const Shape &shape, const Vec p.x = (x+.5)/scale.x-translate.x; int intersections = scanline.sumIntersections(p.x); bool fill = interpretFillRule(intersections, fillRule); - FloatRGB &msd = sdf(x, row); - float sd = median(msd.r, msd.g, msd.b); + float *msd = sdf(x, row); + float sd = median(msd[0], msd[1], msd[2]); if (sd == .5f) ambiguous = true; else if ((sd > .5f) != fill) { - msd.r = 1.f-msd.r; - msd.g = 1.f-msd.g; - msd.b = 1.f-msd.b; + msd[0] = 1.f-msd[0]; + msd[1] = 1.f-msd[1]; + msd[2] = 1.f-msd[2]; *match = -1; } else *match = 1; @@ -102,10 +100,10 @@ void distanceSignCorrection(Bitmap &sdf, const Shape &shape, const Vec if (y > 0) neighborMatch += *(match-w); if (y < h-1) neighborMatch += *(match+w); if (neighborMatch < 0) { - FloatRGB &msd = sdf(x, row); - msd.r = 1.f-msd.r; - msd.g = 1.f-msd.g; - msd.b = 1.f-msd.b; + float *msd = sdf(x, row); + msd[0] = 1.f-msd[0]; + msd[1] = 1.f-msd[1]; + msd[2] = 1.f-msd[2]; } } ++match; diff --git a/core/rasterization.h b/core/rasterization.h index 10e8f41..91bf02c 100644 --- a/core/rasterization.h +++ b/core/rasterization.h @@ -3,7 +3,7 @@ #include "Vector2.h" #include "Shape.h" -#include "Bitmap.h" +#include "BitmapRef.hpp" namespace msdfgen { @@ -16,9 +16,9 @@ enum FillRule { }; /// Rasterizes the shape into a monochrome bitmap. -void rasterize(Bitmap &output, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO); +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. -void distanceSignCorrection(Bitmap &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO); -void distanceSignCorrection(Bitmap &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO); +void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO); +void distanceSignCorrection(const BitmapRef &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO); } diff --git a/core/render-sdf.cpp b/core/render-sdf.cpp index 7a9cc12..223d8cf 100644 --- a/core/render-sdf.cpp +++ b/core/render-sdf.cpp @@ -2,106 +2,87 @@ #include "render-sdf.h" #include "arithmetics.hpp" +#include "pixel-conversion.hpp" namespace msdfgen { -template -inline FloatRGB mix(FloatRGB a, FloatRGB b, S weight) { - FloatRGB output = { - mix(a.r, b.r, weight), - mix(a.g, b.g, weight), - mix(a.b, b.b, weight) - }; - return output; -} - -template -static T sample(const Bitmap &bitmap, Point2 pos) { - int w = bitmap.width(), h = bitmap.height(); - double x = pos.x*w-.5; - double y = pos.y*h-.5; +template +static void sample(T *output, const BitmapConstRef &bitmap, Point2 pos) { + double x = pos.x*bitmap.width-.5; + double y = pos.y*bitmap.height-.5; int l = (int) floor(x); int b = (int) floor(y); int r = l+1; int t = b+1; double lr = x-l; double bt = y-b; - l = clamp(l, w-1), r = clamp(r, w-1); - b = clamp(b, h-1), t = clamp(t, h-1); - return mix(mix(bitmap(l, b), bitmap(r, b), lr), mix(bitmap(l, t), bitmap(r, t), lr), bt); + l = clamp(l, bitmap.width-1), r = clamp(r, bitmap.width-1); + b = clamp(b, bitmap.height-1), t = clamp(t, bitmap.height-1); + for (int i = 0; i < N; ++i) + output[i] = mix(mix(bitmap(l, b)[i], bitmap(r, b)[i], lr), mix(bitmap(l, t)[i], bitmap(r, t)[i], lr), bt); } static float distVal(float dist, double pxRange) { if (!pxRange) - return dist > .5f; + return (float) (dist > .5f); return (float) clamp((dist-.5f)*pxRange+.5); } -void renderSDF(Bitmap &output, const Bitmap &sdf, double pxRange) { - int w = output.width(), h = output.height(); - pxRange *= (double) (w+h)/(sdf.width()+sdf.height()); - for (int y = 0; y < h; ++y) - for (int x = 0; x < w; ++x) { - float s = sample(sdf, Point2((x+.5)/w, (y+.5)/h)); - output(x, y) = distVal(s, pxRange); +void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange) { + pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height); + for (int y = 0; y < output.height; ++y) + for (int x = 0; x < output.width; ++x) { + float sd; + sample(&sd, sdf, Point2((x+.5)/output.width, (y+.5)/output.height)); + *output(x, y) = distVal(sd, pxRange); } } -void renderSDF(Bitmap &output, const Bitmap &sdf, double pxRange) { - int w = output.width(), h = output.height(); - pxRange *= (double) (w+h)/(sdf.width()+sdf.height()); - for (int y = 0; y < h; ++y) - for (int x = 0; x < w; ++x) { - float s = sample(sdf, Point2((x+.5)/w, (y+.5)/h)); - float v = distVal(s, pxRange); - output(x, y).r = v; - output(x, y).g = v; - output(x, y).b = v; +void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange) { + pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height); + for (int y = 0; y < output.height; ++y) + for (int x = 0; x < output.width; ++x) { + float sd; + sample(&sd, sdf, Point2((x+.5)/output.width, (y+.5)/output.height)); + float v = distVal(sd, pxRange); + output(x, y)[0] = v; + output(x, y)[1] = v; + output(x, y)[2] = v; } } -void renderSDF(Bitmap &output, const Bitmap &sdf, double pxRange) { - int w = output.width(), h = output.height(); - pxRange *= (double) (w+h)/(sdf.width()+sdf.height()); - for (int y = 0; y < h; ++y) - for (int x = 0; x < w; ++x) { - FloatRGB s = sample(sdf, Point2((x+.5)/w, (y+.5)/h)); - output(x, y) = distVal(median(s.r, s.g, s.b), pxRange); +void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange) { + pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height); + for (int y = 0; y < output.height; ++y) + for (int x = 0; x < output.width; ++x) { + float sd[3]; + sample(sd, sdf, Point2((x+.5)/output.width, (y+.5)/output.height)); + *output(x, y) = distVal(median(sd[0], sd[1], sd[2]), pxRange); } } -void renderSDF(Bitmap &output, const Bitmap &sdf, double pxRange) { - int w = output.width(), h = output.height(); - pxRange *= (double) (w+h)/(sdf.width()+sdf.height()); - for (int y = 0; y < h; ++y) - for (int x = 0; x < w; ++x) { - FloatRGB s = sample(sdf, Point2((x+.5)/w, (y+.5)/h)); - output(x, y).r = distVal(s.r, pxRange); - output(x, y).g = distVal(s.g, pxRange); - output(x, y).b = distVal(s.b, pxRange); +void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange) { + pxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height); + for (int y = 0; y < output.height; ++y) + for (int x = 0; x < output.width; ++x) { + float sd[3]; + sample(sd, sdf, Point2((x+.5)/output.width, (y+.5)/output.height)); + output(x, y)[0] = distVal(sd[0], pxRange); + output(x, y)[1] = distVal(sd[1], pxRange); + output(x, y)[2] = distVal(sd[2], pxRange); } } -void simulate8bit(Bitmap &bitmap) { - int w = bitmap.width(), h = bitmap.height(); - for (int y = 0; y < h; ++y) - for (int x = 0; x < w; ++x) { - unsigned char v = clamp(int(bitmap(x, y)*0x100), 0xff); - bitmap(x, y) = v/255.f; - } +void simulate8bit(const BitmapRef &bitmap) { + const float *end = bitmap.pixels+1*bitmap.width*bitmap.height; + for (float *p = bitmap.pixels; p < end; ++p) + *p = pixelByteToFloat(pixelFloatToByte(*p)); } -void simulate8bit(Bitmap &bitmap) { - int w = bitmap.width(), h = bitmap.height(); - for (int y = 0; y < h; ++y) - for (int x = 0; x < w; ++x) { - unsigned char r = clamp(int(bitmap(x, y).r*0x100), 0xff); - unsigned char g = clamp(int(bitmap(x, y).g*0x100), 0xff); - unsigned char b = clamp(int(bitmap(x, y).b*0x100), 0xff); - bitmap(x, y).r = r/255.f; - bitmap(x, y).g = g/255.f; - bitmap(x, y).b = b/255.f; - } +void simulate8bit(const BitmapRef &bitmap) { + const float *end = bitmap.pixels+3*bitmap.width*bitmap.height; + for (float *p = bitmap.pixels; p < end; ++p) + *p = pixelByteToFloat(pixelFloatToByte(*p)); } } diff --git a/core/render-sdf.h b/core/render-sdf.h index c63186d..a388e31 100644 --- a/core/render-sdf.h +++ b/core/render-sdf.h @@ -2,18 +2,18 @@ #pragma once #include "Vector2.h" -#include "Bitmap.h" +#include "BitmapRef.hpp" namespace msdfgen { /// Reconstructs the shape's appearance into output from the distance field sdf. -void renderSDF(Bitmap &output, const Bitmap &sdf, double pxRange = 0); -void renderSDF(Bitmap &output, const Bitmap &sdf, double pxRange = 0); -void renderSDF(Bitmap &output, const Bitmap &sdf, double pxRange = 0); -void renderSDF(Bitmap &output, const Bitmap &sdf, double pxRange = 0); +void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange = 0); +void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange = 0); +void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange = 0); +void renderSDF(const BitmapRef &output, const BitmapConstRef &sdf, double pxRange = 0); /// Snaps the values of the floating-point bitmaps into one of the 256 values representable in a standard 8-bit bitmap. -void simulate8bit(Bitmap &bitmap); -void simulate8bit(Bitmap &bitmap); +void simulate8bit(const BitmapRef &bitmap); +void simulate8bit(const BitmapRef &bitmap); } diff --git a/core/save-bmp.cpp b/core/save-bmp.cpp index 6ff8fba..5ba8920 100644 --- a/core/save-bmp.cpp +++ b/core/save-bmp.cpp @@ -1,8 +1,8 @@ -#include "save-bmp.h" - #define _CRT_SECURE_NO_WARNINGS +#include "save-bmp.h" + #include #ifdef MSDFGEN_USE_CPP11 @@ -14,7 +14,7 @@ typedef unsigned char uint8_t; #endif -#include "arithmetics.hpp" +#include "pixel-conversion.hpp" namespace msdfgen { @@ -60,47 +60,97 @@ static bool writeBmpHeader(FILE *file, int width, int height, int &paddedWidth) return true; } -bool saveBmp(const Bitmap &bitmap, const char *filename) { +bool saveBmp(const BitmapConstRef &bitmap, const char *filename) { FILE *file = fopen(filename, "wb"); if (!file) return false; int paddedWidth; - writeBmpHeader(file, bitmap.width(), bitmap.height(), paddedWidth); + writeBmpHeader(file, bitmap.width, bitmap.height, paddedWidth); const uint8_t padding[4] = { }; - for (int y = 0; y < bitmap.height(); ++y) { - for (int x = 0; x < bitmap.width(); ++x) { - uint8_t px = (uint8_t) clamp(int(bitmap(x, y)*0x100), 0xff); + int padLength = paddedWidth-3*bitmap.width; + for (int y = 0; y < bitmap.height; ++y) { + for (int x = 0; x < bitmap.width; ++x) { + uint8_t px = (uint8_t) *bitmap(x, y); fwrite(&px, sizeof(uint8_t), 1, file); fwrite(&px, sizeof(uint8_t), 1, file); fwrite(&px, sizeof(uint8_t), 1, file); } - fwrite(padding, 1, paddedWidth-3*bitmap.width(), file); + fwrite(padding, 1, padLength, file); } return !fclose(file); } -bool saveBmp(const Bitmap &bitmap, const char *filename) { +bool saveBmp(const BitmapConstRef &bitmap, const char *filename) { FILE *file = fopen(filename, "wb"); if (!file) return false; int paddedWidth; - writeBmpHeader(file, bitmap.width(), bitmap.height(), paddedWidth); + writeBmpHeader(file, bitmap.width, bitmap.height, paddedWidth); const uint8_t padding[4] = { }; - for (int y = 0; y < bitmap.height(); ++y) { - for (int x = 0; x < bitmap.width(); ++x) { + int padLength = paddedWidth-3*bitmap.width; + for (int y = 0; y < bitmap.height; ++y) { + for (int x = 0; x < bitmap.width; ++x) { uint8_t bgr[3] = { - (uint8_t) clamp(int(bitmap(x, y).b*0x100), 0xff), - (uint8_t) clamp(int(bitmap(x, y).g*0x100), 0xff), - (uint8_t) clamp(int(bitmap(x, y).r*0x100), 0xff) + (uint8_t) bitmap(x, y)[2], + (uint8_t) bitmap(x, y)[1], + (uint8_t) bitmap(x, y)[0] }; fwrite(bgr, sizeof(uint8_t), 3, file); } - fwrite(padding, 1, paddedWidth-3*bitmap.width(), file); + fwrite(padding, 1, padLength, file); + } + + return !fclose(file); +} + +bool saveBmp(const BitmapConstRef &bitmap, const char *filename) { + FILE *file = fopen(filename, "wb"); + if (!file) + return false; + + int paddedWidth; + writeBmpHeader(file, bitmap.width, bitmap.height, paddedWidth); + + const uint8_t padding[4] = { }; + int padLength = paddedWidth-3*bitmap.width; + for (int y = 0; y < bitmap.height; ++y) { + for (int x = 0; x < bitmap.width; ++x) { + uint8_t px = (uint8_t) pixelFloatToByte(*bitmap(x, y)); + fwrite(&px, sizeof(uint8_t), 1, file); + fwrite(&px, sizeof(uint8_t), 1, file); + fwrite(&px, sizeof(uint8_t), 1, file); + } + fwrite(padding, 1, padLength, file); + } + + return !fclose(file); +} + +bool saveBmp(const BitmapConstRef &bitmap, const char *filename) { + FILE *file = fopen(filename, "wb"); + if (!file) + return false; + + int paddedWidth; + writeBmpHeader(file, bitmap.width, bitmap.height, paddedWidth); + + const uint8_t padding[4] = { }; + int padLength = paddedWidth-3*bitmap.width; + for (int y = 0; y < bitmap.height; ++y) { + for (int x = 0; x < bitmap.width; ++x) { + uint8_t bgr[3] = { + (uint8_t) pixelFloatToByte(bitmap(x, y)[2]), + (uint8_t) pixelFloatToByte(bitmap(x, y)[1]), + (uint8_t) pixelFloatToByte(bitmap(x, y)[0]) + }; + fwrite(bgr, sizeof(uint8_t), 3, file); + } + fwrite(padding, 1, padLength, file); } return !fclose(file); diff --git a/core/save-bmp.h b/core/save-bmp.h index 9642f2d..4fbe703 100644 --- a/core/save-bmp.h +++ b/core/save-bmp.h @@ -1,12 +1,14 @@ #pragma once -#include "Bitmap.h" +#include "BitmapRef.hpp" namespace msdfgen { /// Saves the bitmap as a BMP file. -bool saveBmp(const Bitmap &bitmap, const char *filename); -bool saveBmp(const Bitmap &bitmap, const char *filename); +bool saveBmp(const BitmapConstRef &bitmap, const char *filename); +bool saveBmp(const BitmapConstRef &bitmap, const char *filename); +bool saveBmp(const BitmapConstRef &bitmap, const char *filename); +bool saveBmp(const BitmapConstRef &bitmap, const char *filename); } diff --git a/ext/save-png.cpp b/ext/save-png.cpp index 06dbdd6..6eb2f55 100644 --- a/ext/save-png.cpp +++ b/ext/save-png.cpp @@ -1,30 +1,38 @@ #include "save-png.h" -#include "../core/arithmetics.hpp" #include +#include "../core/pixel-conversion.hpp" namespace msdfgen { -bool savePng(const Bitmap &bitmap, const char *filename) { - std::vector pixels(bitmap.width()*bitmap.height()); - std::vector::iterator it = pixels.begin(); - for (int y = bitmap.height()-1; y >= 0; --y) - for (int x = 0; x < bitmap.width(); ++x) - *it++ = clamp(int(bitmap(x, y)*0x100), 0xff); - return !lodepng::encode(filename, pixels, bitmap.width(), bitmap.height(), LCT_GREY); +bool savePng(const BitmapConstRef &bitmap, const char *filename) { + return !lodepng::encode(filename, bitmap.pixels, bitmap.width, bitmap.height, LCT_GREY); } -bool savePng(const Bitmap &bitmap, const char *filename) { - std::vector pixels(3*bitmap.width()*bitmap.height()); - std::vector::iterator it = pixels.begin(); - for (int y = bitmap.height()-1; y >= 0; --y) - for (int x = 0; x < bitmap.width(); ++x) { - *it++ = clamp(int(bitmap(x, y).r*0x100), 0xff); - *it++ = clamp(int(bitmap(x, y).g*0x100), 0xff); - *it++ = clamp(int(bitmap(x, y).b*0x100), 0xff); +bool savePng(const BitmapConstRef &bitmap, const char *filename) { + return !lodepng::encode(filename, bitmap.pixels, bitmap.width, bitmap.height, LCT_RGB); +} + +bool savePng(const BitmapConstRef &bitmap, const char *filename) { + std::vector pixels(bitmap.width*bitmap.height); + std::vector::iterator it = pixels.begin(); + for (int y = bitmap.height-1; y >= 0; --y) + for (int x = 0; x < bitmap.width; ++x) + *it++ = pixelFloatToByte(*bitmap(x, y)); + return !lodepng::encode(filename, pixels, bitmap.width, bitmap.height, LCT_GREY); +} + +bool savePng(const BitmapConstRef &bitmap, const char *filename) { + std::vector pixels(3*bitmap.width*bitmap.height); + std::vector::iterator it = pixels.begin(); + for (int y = bitmap.height-1; y >= 0; --y) + for (int x = 0; x < bitmap.width; ++x) { + *it++ = pixelFloatToByte(bitmap(x, y)[0]); + *it++ = pixelFloatToByte(bitmap(x, y)[1]); + *it++ = pixelFloatToByte(bitmap(x, y)[2]); } - return !lodepng::encode(filename, pixels, bitmap.width(), bitmap.height(), LCT_RGB); + return !lodepng::encode(filename, pixels, bitmap.width, bitmap.height, LCT_RGB); } } diff --git a/ext/save-png.h b/ext/save-png.h index e1103ca..c0c2743 100644 --- a/ext/save-png.h +++ b/ext/save-png.h @@ -1,12 +1,14 @@ #pragma once -#include "../core/Bitmap.h" +#include "../core/BitmapRef.hpp" namespace msdfgen { /// Saves the bitmap as a PNG file. -bool savePng(const Bitmap &bitmap, const char *filename); -bool savePng(const Bitmap &bitmap, const char *filename); +bool savePng(const BitmapConstRef &bitmap, const char *filename); +bool savePng(const BitmapConstRef &bitmap, const char *filename); +bool savePng(const BitmapConstRef &bitmap, const char *filename); +bool savePng(const BitmapConstRef &bitmap, const char *filename); } diff --git a/main.cpp b/main.cpp index 7c750dd..5c4da3c 100644 --- a/main.cpp +++ b/main.cpp @@ -131,19 +131,11 @@ static void parseColoring(Shape &shape, const char *edgeAssignment) { } } -static void invertColor(Bitmap &bitmap) { - for (int y = 0; y < bitmap.height(); ++y) - for (int x = 0; x < bitmap.width(); ++x) - bitmap(x, y) = 1.f-bitmap(x, y); -} - -static void invertColor(Bitmap &bitmap) { - for (int y = 0; y < bitmap.height(); ++y) - for (int x = 0; x < bitmap.width(); ++x) { - bitmap(x, y).r = 1.f-bitmap(x, y).r; - bitmap(x, y).g = 1.f-bitmap(x, y).g; - bitmap(x, y).b = 1.f-bitmap(x, y).b; - } +template +static void invertColor(const BitmapRef &bitmap) { + const float *end = bitmap.pixels+N*bitmap.width*bitmap.height; + for (float *p = bitmap.pixels; p < end; ++p) + *p = 1.f-*p; } static bool writeTextBitmap(FILE *file, const float *values, int cols, int rows) { @@ -205,8 +197,8 @@ static bool cmpExtension(const char *path, const char *ext) { return true; } -template -static const char * writeOutput(const Bitmap &bitmap, const char *filename, Format format) { +template +static const char * writeOutput(const BitmapConstRef &bitmap, const char *filename, Format format) { if (filename) { if (format == AUTO) { if (cmpExtension(filename, ".png")) format = PNG; @@ -223,9 +215,9 @@ static const char * writeOutput(const Bitmap &bitmap, const char *filename, F FILE *file = fopen(filename, "w"); if (!file) return "Failed to write output text file."; if (format == TEXT) - writeTextBitmap(file, reinterpret_cast(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width(), bitmap.height()); + writeTextBitmap(file, bitmap.pixels, N*bitmap.width, bitmap.height); else if (format == TEXT_FLOAT) - writeTextBitmapFloat(file, reinterpret_cast(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width(), bitmap.height()); + writeTextBitmapFloat(file, bitmap.pixels, N*bitmap.width, bitmap.height); fclose(file); return NULL; } @@ -233,11 +225,11 @@ static const char * writeOutput(const Bitmap &bitmap, const char *filename, F FILE *file = fopen(filename, "wb"); if (!file) return "Failed to write output binary file."; if (format == BINARY) - writeBinBitmap(file, reinterpret_cast(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width()*bitmap.height()); + writeBinBitmap(file, bitmap.pixels, N*bitmap.width*bitmap.height); else if (format == BINARY_FLOAT) - writeBinBitmapFloat(file, reinterpret_cast(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width()*bitmap.height()); + writeBinBitmapFloat(file, bitmap.pixels, N*bitmap.width*bitmap.height); else if (format == BINART_FLOAT_BE) - writeBinBitmapFloatBE(file, reinterpret_cast(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width()*bitmap.height()); + writeBinBitmapFloatBE(file, bitmap.pixels, N*bitmap.width*bitmap.height); fclose(file); return NULL; } @@ -245,9 +237,9 @@ static const char * writeOutput(const Bitmap &bitmap, const char *filename, F } } else { if (format == AUTO || format == TEXT) - writeTextBitmap(stdout, reinterpret_cast(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width(), bitmap.height()); + writeTextBitmap(stdout, bitmap.pixels, N*bitmap.width, bitmap.height); else if (format == TEXT_FLOAT) - writeTextBitmapFloat(stdout, reinterpret_cast(&bitmap(0, 0)), sizeof(T)/sizeof(float)*bitmap.width(), bitmap.height()); + writeTextBitmapFloat(stdout, bitmap.pixels, N*bitmap.width, bitmap.height); else return "Unsupported format for standard output."; } @@ -771,11 +763,11 @@ int main(int argc, const char * const *argv) { } // Compute output - Bitmap sdf; - Bitmap msdf; + Bitmap sdf; + Bitmap msdf; switch (mode) { case SINGLE: { - sdf = Bitmap(width, height); + sdf = Bitmap(width, height); if (legacyMode) generateSDF_legacy(sdf, shape, range, scale, translate); else @@ -783,7 +775,7 @@ int main(int argc, const char * const *argv) { break; } case PSEUDO: { - sdf = Bitmap(width, height); + sdf = Bitmap(width, height); if (legacyMode) generatePseudoSDF_legacy(sdf, shape, range, scale, translate); else @@ -795,7 +787,7 @@ int main(int argc, const char * const *argv) { edgeColoringSimple(shape, angleThreshold, coloringSeed); if (edgeAssignment) parseColoring(shape, edgeAssignment); - msdf = Bitmap(width, height); + msdf = Bitmap(width, height); if (legacyMode) generateMSDF_legacy(msdf, shape, range, scale, translate, scanlinePass ? 0 : edgeThreshold); else @@ -822,10 +814,10 @@ int main(int argc, const char * const *argv) { switch (mode) { case SINGLE: case PSEUDO: - invertColor(sdf); + invertColor<1>(sdf); break; case MULTI: - invertColor(msdf); + invertColor<3>(msdf); break; default:; } @@ -858,38 +850,38 @@ int main(int argc, const char * const *argv) { switch (mode) { case SINGLE: case PSEUDO: - error = writeOutput(sdf, output, format); + error = writeOutput<1>(sdf, output, format); if (error) ABORT(error); if (testRenderMulti || testRender) simulate8bit(sdf); if (testRenderMulti) { - Bitmap render(testWidthM, testHeightM); + Bitmap render(testWidthM, testHeightM); renderSDF(render, sdf, avgScale*range); if (!savePng(render, testRenderMulti)) puts("Failed to write test render file."); } if (testRender) { - Bitmap render(testWidth, testHeight); + Bitmap render(testWidth, testHeight); renderSDF(render, sdf, avgScale*range); if (!savePng(render, testRender)) puts("Failed to write test render file."); } break; case MULTI: - error = writeOutput(msdf, output, format); + error = writeOutput<3>(msdf, output, format); if (error) ABORT(error); if (testRenderMulti || testRender) simulate8bit(msdf); if (testRenderMulti) { - Bitmap render(testWidthM, testHeightM); + Bitmap render(testWidthM, testHeightM); renderSDF(render, msdf, avgScale*range); if (!savePng(render, testRenderMulti)) puts("Failed to write test render file."); } if (testRender) { - Bitmap render(testWidth, testHeight); + Bitmap render(testWidth, testHeight); renderSDF(render, msdf, avgScale*range); if (!savePng(render, testRender)) ABORT("Failed to write test render file."); diff --git a/msdfgen.h b/msdfgen.h index 891d5b7..f41fc16 100644 --- a/msdfgen.h +++ b/msdfgen.h @@ -18,6 +18,7 @@ #include "core/arithmetics.hpp" #include "core/Vector2.h" #include "core/Shape.h" +#include "core/BitmapRef.hpp" #include "core/Bitmap.h" #include "core/edge-coloring.h" #include "core/render-sdf.h" @@ -30,20 +31,20 @@ namespace msdfgen { /// Generates a conventional single-channel signed distance field. -void generateSDF(Bitmap &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true); +void generateSDF(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true); /// Generates a single-channel signed pseudo-distance field. -void generatePseudoSDF(Bitmap &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true); +void generatePseudoSDF(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true); /// Generates a multi-channel signed distance field. Edge colors must be assigned first! (See edgeColoringSimple) -void generateMSDF(Bitmap &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.001, bool overlapSupport = true); +void generateMSDF(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.001, bool overlapSupport = true); /// Resolves multi-channel signed distance field values that may cause interpolation artifacts. (Already called by generateMSDF) -void msdfErrorCorrection(Bitmap &output, const Vector2 &threshold); +void msdfErrorCorrection(const BitmapRef &output, const Vector2 &threshold); // Original simpler versions of the previous functions, which work well under normal circumstances, but cannot deal with overlapping contours. -void generateSDF_legacy(Bitmap &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate); -void generatePseudoSDF_legacy(Bitmap &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate); -void generateMSDF_legacy(Bitmap &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.001); +void generateSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate); +void generatePseudoSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate); +void generateMSDF_legacy(const BitmapRef &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.001); }