Bitmap refactor

This commit is contained in:
Chlumsky 2019-04-20 22:05:54 +02:00
parent fe19381bcc
commit 997e42f734
23 changed files with 498 additions and 352 deletions

View File

@ -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.

View File

@ -291,6 +291,8 @@
<ItemGroup>
<ClInclude Include="core\arithmetics.hpp" />
<ClInclude Include="core\Bitmap.h" />
<ClInclude Include="core\Bitmap.hpp" />
<ClInclude Include="core\BitmapRef.hpp" />
<ClInclude Include="core\contour-combiners.h" />
<ClInclude Include="core\Contour.h" />
<ClInclude Include="core\edge-coloring.h" />
@ -299,6 +301,7 @@
<ClInclude Include="core\EdgeColor.h" />
<ClInclude Include="core\EdgeHolder.h" />
<ClInclude Include="core\equation-solver.h" />
<ClInclude Include="core\pixel-conversion.hpp" />
<ClInclude Include="core\rasterization.h" />
<ClInclude Include="core\render-sdf.h" />
<ClInclude Include="core\save-bmp.h" />
@ -315,7 +318,6 @@
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="core\Bitmap.cpp" />
<ClCompile Include="core\contour-combiners.cpp" />
<ClCompile Include="core\Contour.cpp" />
<ClCompile Include="core\edge-coloring.cpp" />

View File

@ -90,14 +90,20 @@
<ClInclude Include="core\rasterization.h">
<Filter>Core</Filter>
</ClInclude>
<ClInclude Include="core\BitmapRef.hpp">
<Filter>Core</Filter>
</ClInclude>
<ClInclude Include="core\Bitmap.hpp">
<Filter>Core</Filter>
</ClInclude>
<ClInclude Include="core\pixel-conversion.hpp">
<Filter>Core</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
<Filter>Standalone</Filter>
</ClCompile>
<ClCompile Include="core\Bitmap.cpp">
<Filter>Core</Filter>
</ClCompile>
<ClCompile Include="core\Contour.cpp">
<Filter>Core</Filter>
</ClCompile>

View File

@ -1,77 +0,0 @@
#include "Bitmap.h"
#include <cstring>
namespace msdfgen {
template <typename T>
Bitmap<T>::Bitmap() : content(NULL), w(0), h(0) { }
template <typename T>
Bitmap<T>::Bitmap(int width, int height) : w(width), h(height) {
content = new T[w*h];
}
template <typename T>
Bitmap<T>::Bitmap(const Bitmap<T> &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 <typename T>
Bitmap<T>::Bitmap(Bitmap<T> &&orig) : content(orig.content), w(orig.w), h(orig.h) {
orig.content = NULL;
}
#endif
template <typename T>
Bitmap<T>::~Bitmap() {
delete [] content;
}
template <typename T>
Bitmap<T> & Bitmap<T>::operator=(const Bitmap<T> &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 <typename T>
Bitmap<T> & Bitmap<T>::operator=(Bitmap<T> &&orig) {
delete [] content;
content = orig.content;
w = orig.w, h = orig.h;
orig.content = NULL;
return *this;
}
#endif
template <typename T>
int Bitmap<T>::width() const {
return w;
}
template <typename T>
int Bitmap<T>::height() const {
return h;
}
template <typename T>
T & Bitmap<T>::operator()(int x, int y) {
return content[y*w+x];
}
template <typename T>
const T & Bitmap<T>::operator()(int x, int y) const {
return content[y*w+x];
}
template class Bitmap<float>;
template class Bitmap<FloatRGB>;
}

View File

@ -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 <typename T>
/// A 2D image bitmap with N channels of type T. Pixel memory is managed by the class.
template <typename T, int N = 1>
class Bitmap {
public:
Bitmap();
Bitmap(int width, int height);
Bitmap(const Bitmap<T> &orig);
Bitmap(const BitmapConstRef<T, N> &orig);
Bitmap(const Bitmap<T, N> &orig);
#ifdef MSDFGEN_USE_CPP11
Bitmap(Bitmap<T> &&orig);
Bitmap(Bitmap<T, N> &&orig);
#endif
~Bitmap();
Bitmap<T> & operator=(const Bitmap<T> &orig);
Bitmap<T, N> & operator=(const BitmapConstRef<T, N> &orig);
Bitmap<T, N> & operator=(const Bitmap<T, N> &orig);
#ifdef MSDFGEN_USE_CPP11
Bitmap<T> & operator=(Bitmap<T> &&orig);
Bitmap<T, N> & operator=(Bitmap<T, N> &&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<T, N>();
operator BitmapConstRef<T, N>() const;
private:
T *content;
T *pixels;
int w, h;
};
}
#include "Bitmap.hpp"

117
core/Bitmap.hpp Normal file
View File

@ -0,0 +1,117 @@
#include "Bitmap.h"
#include <cstdlib>
#include <cstring>
namespace msdfgen {
template <typename T, int N>
Bitmap<T, N>::Bitmap() : pixels(NULL), w(0), h(0) { }
template <typename T, int N>
Bitmap<T, N>::Bitmap(int width, int height) : w(width), h(height) {
pixels = new T[N*w*h];
}
template <typename T, int N>
Bitmap<T, N>::Bitmap(const BitmapConstRef<T, N> &orig) : w(orig.width), h(orig.height) {
pixels = new T[N*w*h];
memcpy(pixels, orig.pixels, sizeof(T)*N*w*h);
}
template <typename T, int N>
Bitmap<T, N>::Bitmap(const Bitmap<T, N> &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 <typename T, int N>
Bitmap<T, N>::Bitmap(Bitmap<T, N> &&orig) : pixels(orig.pixels), w(orig.w), h(orig.h) {
orig.pixels = NULL;
orig.w = 0, orig.h = 0;
}
#endif
template <typename T, int N>
Bitmap<T, N>::~Bitmap() {
delete [] pixels;
}
template <typename T, int N>
Bitmap<T, N> & Bitmap<T, N>::operator=(const BitmapConstRef<T, N> &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 <typename T, int N>
Bitmap<T, N> & Bitmap<T, N>::operator=(const Bitmap<T, N> &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 <typename T, int N>
Bitmap<T, N> & Bitmap<T, N>::operator=(Bitmap<T, N> &&orig) {
if (this != &orig) {
delete [] pixels;
pixels = orig.pixels;
w = orig.w, h = orig.h;
orig.pixels = NULL;
}
return *this;
}
#endif
template <typename T, int N>
int Bitmap<T, N>::width() const {
return w;
}
template <typename T, int N>
int Bitmap<T, N>::height() const {
return h;
}
template <typename T, int N>
T * Bitmap<T, N>::operator()(int x, int y) {
return pixels+N*(w*y+x);
}
template <typename T, int N>
const T * Bitmap<T, N>::operator()(int x, int y) const {
return pixels+N*(w*y+x);
}
template <typename T, int N>
Bitmap<T, N>::operator T *() {
return pixels;
}
template <typename T, int N>
Bitmap<T, N>::operator const T *() const {
return pixels;
}
template <typename T, int N>
Bitmap<T, N>::operator BitmapRef<T, N>() {
return BitmapRef<T, N>(pixels, w, h);
}
template <typename T, int N>
Bitmap<T, N>::operator BitmapConstRef<T, N>() const {
return BitmapConstRef<T, N>(pixels, w, h);
}
}

43
core/BitmapRef.hpp Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <cstdlib>
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 <typename T, int N = 1>
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 <typename T, int N = 1>
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<T, N> &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);
}
};
}

View File

@ -32,7 +32,7 @@ void SimpleContourCombiner<EdgeSelector>::reset(const Point2 &p) {
}
template <class EdgeSelector>
void SimpleContourCombiner<EdgeSelector>::setContourEdge(int i, const EdgeSelector &edgeSelector) {
void SimpleContourCombiner<EdgeSelector>::setContourEdgeSelection(int i, const EdgeSelector &edgeSelector) {
shapeEdgeSelector.merge(edgeSelector);
}
@ -61,7 +61,7 @@ void OverlappingContourCombiner<EdgeSelector>::reset(const Point2 &p) {
}
template <class EdgeSelector>
void OverlappingContourCombiner<EdgeSelector>::setContourEdge(int i, const EdgeSelector &edgeSelector) {
void OverlappingContourCombiner<EdgeSelector>::setContourEdgeSelection(int i, const EdgeSelector &edgeSelector) {
DistanceType edgeDistance = edgeSelector.distance();
edgeSelectors[i] = edgeSelector;
shapeEdgeSelector.merge(edgeSelector);

View File

@ -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:

View File

@ -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 &param) 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 &param) 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 &param) const {
@ -161,15 +166,15 @@ SignedDistance CubicSegment::signedDistance(Point2 origin, double &param) 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 &param) 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;
}

View File

@ -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);

View File

@ -13,29 +13,25 @@ class DistancePixelConversion;
template <>
class DistancePixelConversion<double> {
public:
typedef float PixelType;
inline static PixelType convert(double distance, double range) {
return PixelType(distance/range+.5);
typedef BitmapRef<float, 1> BitmapRefType;
inline static void convert(float *pixels, double distance, double range) {
*pixels = float(distance/range+.5);
}
};
template <>
class DistancePixelConversion<MultiDistance> {
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<float, 3> 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 <class ContourCombiner>
void generateDistanceField(Bitmap<typename DistancePixelConversion<typename ContourCombiner::DistanceType>::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<typename ContourCombiner::DistanceType>::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<typename DistancePixelConversion<typename Cont
#ifdef MSDFGEN_USE_OPENMP
#pragma omp for
#endif
for (int y = 0; y < h; ++y) {
int row = shape.inverseYAxis ? h-y-1 : y;
for (int y = 0; y < output.height; ++y) {
int row = shape.inverseYAxis ? output.height-y-1 : y;
p.y = (y+.5)/scale.y-translate.y;
for (int x = 0; x < w; ++x) {
for (int x = 0; x < output.width; ++x) {
p.x = (x+.5)/scale.x-translate.x;
contourCombiner.reset(p);
@ -66,32 +62,32 @@ void generateDistanceField(Bitmap<typename DistancePixelConversion<typename Cont
curEdge = nextEdge;
}
contourCombiner.setContourEdge(int(contour-shape.contours.begin()), edgeSelector);
contourCombiner.setContourEdgeSelection(int(contour-shape.contours.begin()), edgeSelector);
}
}
typename ContourCombiner::DistanceType distance = contourCombiner.distance();
output(x, row) = DistancePixelConversion<typename ContourCombiner::DistanceType>::convert(distance, range);
DistancePixelConversion<typename ContourCombiner::DistanceType>::convert(output(x, row), distance, range);
}
}
}
}
void generateSDF(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
if (overlapSupport)
generateDistanceField<OverlappingContourCombiner<TrueDistanceSelector> >(output, shape, range, scale, translate);
else
generateDistanceField<SimpleContourCombiner<TrueDistanceSelector> >(output, shape, range, scale, translate);
}
void generatePseudoSDF(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
if (overlapSupport)
generateDistanceField<OverlappingContourCombiner<PseudoDistanceSelector> >(output, shape, range, scale, translate);
else
generateDistanceField<SimpleContourCombiner<PseudoDistanceSelector> >(output, shape, range, scale, translate);
}
void generateMSDF(Bitmap<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold, bool overlapSupport) {
void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold, bool overlapSupport) {
if (overlapSupport)
generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, range, scale, translate);
else
@ -100,10 +96,10 @@ void generateMSDF(Bitmap<FloatRGB> &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<FloatRGB> &output, const Vector2 &threshold) {
void msdfErrorCorrection(const BitmapRef<float, 3> &output, const Vector2 &threshold) {
std::vector<std::pair<int, int> > 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<FloatRGB> &output, const Vector2 &threshold) {
clashes.push_back(std::make_pair(x, y));
}
for (std::vector<std::pair<int, int> >::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<FloatRGB> &output, const Vector2 &threshold) {
clashes.push_back(std::make_pair(x, y));
}
for (std::vector<std::pair<int, int> >::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<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
int w = output.width(), h = output.height();
void generateSDF_legacy(const BitmapRef<float, 1> &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<float> &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<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
int w = output.width(), h = output.height();
void generatePseudoSDF_legacy(const BitmapRef<float, 1> &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<float> &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<FloatRGB> &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<float, 3> &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<FloatRGB> &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);
}
}

18
core/pixel-conversion.hpp Normal file
View File

@ -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);
}
}

View File

@ -21,44 +21,42 @@ static bool interpretFillRule(int intersections, FillRule fillRule) {
return false;
}
void rasterize(Bitmap<float> &output, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
int w = output.width(), h = output.height();
void rasterize(const BitmapRef<float, 1> &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<float> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
int w = sdf.width(), h = sdf.height();
void distanceSignCorrection(const BitmapRef<float, 1> &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<FloatRGB> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
int w = sdf.width(), h = sdf.height();
void distanceSignCorrection(const BitmapRef<float, 3> &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<FloatRGB> &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<FloatRGB> &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;

View File

@ -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<float> &output, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
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.
void distanceSignCorrection(Bitmap<float> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(Bitmap<FloatRGB> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(const BitmapRef<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
}

View File

@ -2,106 +2,87 @@
#include "render-sdf.h"
#include "arithmetics.hpp"
#include "pixel-conversion.hpp"
namespace msdfgen {
template <typename S>
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 <typename T>
static T sample(const Bitmap<T> &bitmap, Point2 pos) {
int w = bitmap.width(), h = bitmap.height();
double x = pos.x*w-.5;
double y = pos.y*h-.5;
template <typename T, int N>
static void sample(T *output, const BitmapConstRef<T, N> &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<float> &output, const Bitmap<float> &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<float, 1> &output, const BitmapConstRef<float, 1> &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<FloatRGB> &output, const Bitmap<float> &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<float, 3> &output, const BitmapConstRef<float, 1> &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<float> &output, const Bitmap<FloatRGB> &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<float, 1> &output, const BitmapConstRef<float, 3> &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<FloatRGB> &output, const Bitmap<FloatRGB> &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<float, 3> &output, const BitmapConstRef<float, 3> &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<float> &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<float, 1> &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<FloatRGB> &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<float, 3> &bitmap) {
const float *end = bitmap.pixels+3*bitmap.width*bitmap.height;
for (float *p = bitmap.pixels; p < end; ++p)
*p = pixelByteToFloat(pixelFloatToByte(*p));
}
}

View File

@ -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<float> &output, const Bitmap<float> &sdf, double pxRange = 0);
void renderSDF(Bitmap<FloatRGB> &output, const Bitmap<float> &sdf, double pxRange = 0);
void renderSDF(Bitmap<float> &output, const Bitmap<FloatRGB> &sdf, double pxRange = 0);
void renderSDF(Bitmap<FloatRGB> &output, const Bitmap<FloatRGB> &sdf, double pxRange = 0);
void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1> &sdf, double pxRange = 0);
void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, double pxRange = 0);
void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, double pxRange = 0);
void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &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<float> &bitmap);
void simulate8bit(Bitmap<FloatRGB> &bitmap);
void simulate8bit(const BitmapRef<float, 1> &bitmap);
void simulate8bit(const BitmapRef<float, 3> &bitmap);
}

View File

@ -1,8 +1,8 @@
#include "save-bmp.h"
#define _CRT_SECURE_NO_WARNINGS
#include "save-bmp.h"
#include <cstdio>
#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<float> &bitmap, const char *filename) {
bool saveBmp(const BitmapConstRef<byte, 1> &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<FloatRGB> &bitmap, const char *filename) {
bool saveBmp(const BitmapConstRef<byte, 3> &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<float, 1> &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<float, 3> &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);

View File

@ -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<float> &bitmap, const char *filename);
bool saveBmp(const Bitmap<FloatRGB> &bitmap, const char *filename);
bool saveBmp(const BitmapConstRef<byte, 1> &bitmap, const char *filename);
bool saveBmp(const BitmapConstRef<byte, 3> &bitmap, const char *filename);
bool saveBmp(const BitmapConstRef<float, 1> &bitmap, const char *filename);
bool saveBmp(const BitmapConstRef<float, 3> &bitmap, const char *filename);
}

View File

@ -1,30 +1,38 @@
#include "save-png.h"
#include "../core/arithmetics.hpp"
#include <lodepng.h>
#include "../core/pixel-conversion.hpp"
namespace msdfgen {
bool savePng(const Bitmap<float> &bitmap, const char *filename) {
std::vector<unsigned char> pixels(bitmap.width()*bitmap.height());
std::vector<unsigned char>::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<byte, 1> &bitmap, const char *filename) {
return !lodepng::encode(filename, bitmap.pixels, bitmap.width, bitmap.height, LCT_GREY);
}
bool savePng(const Bitmap<FloatRGB> &bitmap, const char *filename) {
std::vector<unsigned char> pixels(3*bitmap.width()*bitmap.height());
std::vector<unsigned char>::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<byte, 3> &bitmap, const char *filename) {
return !lodepng::encode(filename, bitmap.pixels, bitmap.width, bitmap.height, LCT_RGB);
}
bool savePng(const BitmapConstRef<float, 1> &bitmap, const char *filename) {
std::vector<byte> pixels(bitmap.width*bitmap.height);
std::vector<byte>::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<float, 3> &bitmap, const char *filename) {
std::vector<byte> pixels(3*bitmap.width*bitmap.height);
std::vector<byte>::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);
}
}

View File

@ -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<float> &bitmap, const char *filename);
bool savePng(const Bitmap<FloatRGB> &bitmap, const char *filename);
bool savePng(const BitmapConstRef<byte, 1> &bitmap, const char *filename);
bool savePng(const BitmapConstRef<byte, 3> &bitmap, const char *filename);
bool savePng(const BitmapConstRef<float, 1> &bitmap, const char *filename);
bool savePng(const BitmapConstRef<float, 3> &bitmap, const char *filename);
}

View File

@ -131,19 +131,11 @@ static void parseColoring(Shape &shape, const char *edgeAssignment) {
}
}
static void invertColor(Bitmap<float> &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<FloatRGB> &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 <int N>
static void invertColor(const BitmapRef<float, N> &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 <typename T>
static const char * writeOutput(const Bitmap<T> &bitmap, const char *filename, Format format) {
template <int N>
static const char * writeOutput(const BitmapConstRef<float, N> &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<T> &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<const float *>(&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<const float *>(&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<T> &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<const float *>(&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<const float *>(&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<const float *>(&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<T> &bitmap, const char *filename, F
}
} else {
if (format == AUTO || format == TEXT)
writeTextBitmap(stdout, reinterpret_cast<const float *>(&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<const float *>(&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<float> sdf;
Bitmap<FloatRGB> msdf;
Bitmap<float, 1> sdf;
Bitmap<float, 3> msdf;
switch (mode) {
case SINGLE: {
sdf = Bitmap<float>(width, height);
sdf = Bitmap<float, 1>(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<float>(width, height);
sdf = Bitmap<float, 1>(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<FloatRGB>(width, height);
msdf = Bitmap<float, 3>(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<FloatRGB> render(testWidthM, testHeightM);
Bitmap<float, 3> render(testWidthM, testHeightM);
renderSDF(render, sdf, avgScale*range);
if (!savePng(render, testRenderMulti))
puts("Failed to write test render file.");
}
if (testRender) {
Bitmap<float> render(testWidth, testHeight);
Bitmap<float, 1> 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<FloatRGB> render(testWidthM, testHeightM);
Bitmap<float, 3> render(testWidthM, testHeightM);
renderSDF(render, msdf, avgScale*range);
if (!savePng(render, testRenderMulti))
puts("Failed to write test render file.");
}
if (testRender) {
Bitmap<float> render(testWidth, testHeight);
Bitmap<float, 1> render(testWidth, testHeight);
renderSDF(render, msdf, avgScale*range);
if (!savePng(render, testRender))
ABORT("Failed to write test render file.");

View File

@ -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<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
void generateSDF(const BitmapRef<float, 1> &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<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
void generatePseudoSDF(const BitmapRef<float, 1> &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<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.001, bool overlapSupport = true);
void generateMSDF(const BitmapRef<float, 3> &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<FloatRGB> &output, const Vector2 &threshold);
void msdfErrorCorrection(const BitmapRef<float, 3> &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<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
void generatePseudoSDF_legacy(Bitmap<float> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
void generateMSDF_legacy(Bitmap<FloatRGB> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.001);
void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = 1.001);
}