Ver código fonte

Bitmap refactor

Chlumsky 6 anos atrás
pai
commit
997e42f734

+ 1 - 1
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.

+ 3 - 1
Msdfgen.vcxproj

@@ -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" />

+ 9 - 3
Msdfgen.vcxproj.filters

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

+ 0 - 77
core/Bitmap.cpp

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

+ 19 - 14
core/Bitmap.h

@@ -1,40 +1,45 @@
 
 #pragma once
 
-namespace msdfgen {
+#include "BitmapRef.hpp"
 
-/// A floating-point RGB pixel.
-struct FloatRGB {
-    float r, g, b;
-};
+namespace msdfgen {
 
-/// 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 - 0
core/Bitmap.hpp

@@ -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 - 0
core/BitmapRef.hpp

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

+ 2 - 2
core/contour-combiners.cpp

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

+ 2 - 2
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:

+ 18 - 13
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 &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;
         }

+ 1 - 1
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);

+ 45 - 52
core/msdfgen.cpp

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

+ 21 - 23
core/rasterization.cpp

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

+ 4 - 4
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<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);
 
 }

+ 51 - 70
core/render-sdf.cpp

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

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

+ 67 - 17
core/save-bmp.cpp

@@ -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);
+
+    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) *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<byte, 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) 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, 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);
+    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) 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, 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<float, 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) 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, paddedWidth-3*bitmap.width(), file);
+        fwrite(padding, 1, padLength, file);
     }
 
     return !fclose(file);

+ 5 - 3
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<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);
 
 }

+ 25 - 17
ext/save-png.cpp

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

+ 5 - 3
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<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);
 
 }

+ 27 - 35
main.cpp

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

+ 8 - 7
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<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);
 
 }