Pārlūkot izejas kodu

Asymmetrical range

Chlumsky 1 gadu atpakaļ
vecāks
revīzija
6d252a7dc3

+ 4 - 3
README.md

@@ -128,10 +128,11 @@ int main() {
                 shape.normalize();
                 //                      max. angle
                 edgeColoringSimple(shape, 3.0);
-                //           image width, height
+                //          output width, height
                 Bitmap<float, 3> msdf(32, 32);
-                //                     range, scale, translation
-                generateMSDF(msdf, shape, 4.0, 1.0, Vector2(4.0, 4.0));
+                //                           scale, translation
+                SDFTransformation t(Projection(1.0, Vector2(4.0, 4.0)), Range(4.0));
+                generateMSDF(msdf, shape, t);
                 savePng(msdf, "output.png");
             }
             destroyFont(font);

+ 27 - 0
core/DistanceMapping.cpp

@@ -0,0 +1,27 @@
+
+#include "DistanceMapping.h"
+
+namespace msdfgen {
+
+DistanceMapping DistanceMapping::inverse(Range range) {
+    double rangeWidth = range.upper-range.lower;
+    return DistanceMapping(rangeWidth, range.lower/(rangeWidth ? rangeWidth : 1));
+}
+
+DistanceMapping::DistanceMapping() : scale(1), translate(0) { }
+
+DistanceMapping::DistanceMapping(Range range) : scale(1/(range.upper-range.lower)), translate(-range.lower) { }
+
+double DistanceMapping::operator()(double d) const {
+    return scale*(d+translate);
+}
+
+double DistanceMapping::operator()(Delta d) const {
+    return scale*d.value;
+}
+
+DistanceMapping DistanceMapping::inverse() const {
+    return DistanceMapping(1/scale, -scale*translate);
+}
+
+}

+ 36 - 0
core/DistanceMapping.h

@@ -0,0 +1,36 @@
+
+#pragma once
+
+#include "Range.hpp"
+
+namespace msdfgen {
+
+/// Linear transformation of signed distance values.
+class DistanceMapping {
+
+public:
+    /// Explicitly designates value as distance delta rather than an absolute distance.
+    class Delta {
+    public:
+        double value;
+        inline explicit Delta(double distanceDelta) : value(distanceDelta) { }
+        inline operator double() const { return value; }
+    };
+
+    static DistanceMapping inverse(Range range);
+
+    DistanceMapping();
+    DistanceMapping(Range range);
+    double operator()(double d) const;
+    double operator()(Delta d) const;
+    DistanceMapping inverse() const;
+
+private:
+    double scale;
+    double translate;
+
+    inline DistanceMapping(double scale, double translate) : scale(scale), translate(translate) { }
+
+};
+
+}

+ 16 - 17
core/MSDFErrorCorrection.cpp

@@ -74,7 +74,7 @@ public:
                 // Compute the evaluated distance (interpolated median) before and after error correction, as well as the exact shape distance.
                 float oldPSD = median(oldMSD[0], oldMSD[1], oldMSD[2]);
                 float newPSD = median(newMSD[0], newMSD[1], newMSD[2]);
-                float refPSD = float(parent->invRange*parent->distanceFinder.distance(parent->shapeCoord+tVector*parent->texelSize)+.5);
+                float refPSD = float(parent->distanceMapping(parent->distanceFinder.distance(parent->shapeCoord+tVector*parent->texelSize)));
                 // Compare the differences of the exact distance and the before and after distances.
                 return parent->minImproveRatio*fabsf(newPSD-refPSD) < double(fabsf(oldPSD-refPSD));
             }
@@ -87,7 +87,7 @@ public:
     Point2 shapeCoord, sdfCoord;
     const float *msd;
     bool protectedFlag;
-    inline ShapeDistanceChecker(const BitmapConstRef<float, N> &sdf, const Shape &shape, const Projection &projection, double invRange, double minImproveRatio) : distanceFinder(shape), sdf(sdf), invRange(invRange), minImproveRatio(minImproveRatio) {
+    inline ShapeDistanceChecker(const BitmapConstRef<float, N> &sdf, const Shape &shape, const Projection &projection, DistanceMapping distanceMapping, double minImproveRatio) : distanceFinder(shape), sdf(sdf), distanceMapping(distanceMapping), minImproveRatio(minImproveRatio) {
         texelSize = projection.unprojectVector(Vector2(1));
     }
     inline ArtifactClassifier classifier(const Vector2 &direction, double span) {
@@ -96,15 +96,14 @@ public:
 private:
     ShapeDistanceFinder<ContourCombiner<PerpendicularDistanceSelector> > distanceFinder;
     BitmapConstRef<float, N> sdf;
-    double invRange;
+    DistanceMapping distanceMapping;
     Vector2 texelSize;
     double minImproveRatio;
 };
 
 MSDFErrorCorrection::MSDFErrorCorrection() { }
 
-MSDFErrorCorrection::MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const Projection &projection, double range) : stencil(stencil), projection(projection) {
-    invRange = 1/range;
+MSDFErrorCorrection::MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const SDFTransformation &transformation) : stencil(stencil), transformation(transformation) {
     minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio;
     minImproveRatio = ErrorCorrectionConfig::defaultMinImproveRatio;
     memset(stencil.pixels, 0, sizeof(byte)*stencil.width*stencil.height);
@@ -127,7 +126,7 @@ void MSDFErrorCorrection::protectCorners(const Shape &shape) {
                 // If the color changes from prevEdge to edge, this is a corner.
                 if (!(commonColor&(commonColor-1))) {
                     // Find the four texels that envelop the corner and mark them as protected.
-                    Point2 p = projection.project((*edge)->point(0));
+                    Point2 p = transformation.project((*edge)->point(0));
                     if (shape.inverseYAxis)
                         p.y = stencil.height-p.y;
                     int l = (int) floor(p.x-.5);
@@ -191,7 +190,7 @@ template <int N>
 void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf) {
     float radius;
     // Horizontal texel pairs
-    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(invRange, 0)).length());
+    radius = float(PROTECTION_RADIUS_TOLERANCE*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)), 0)).length());
     for (int y = 0; y < sdf.height; ++y) {
         const float *left = sdf(0, y);
         const float *right = sdf(1, y);
@@ -207,7 +206,7 @@ void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf) {
         }
     }
     // Vertical texel pairs
-    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(0, invRange)).length());
+    radius = float(PROTECTION_RADIUS_TOLERANCE*transformation.unprojectVector(Vector2(0, transformation.distanceMapping(DistanceMapping::Delta(1)))).length());
     for (int y = 0; y < sdf.height-1; ++y) {
         const float *bottom = sdf(0, y);
         const float *top = sdf(0, y+1);
@@ -223,7 +222,7 @@ void MSDFErrorCorrection::protectEdges(const BitmapConstRef<float, N> &sdf) {
         }
     }
     // Diagonal texel pairs
-    radius = float(PROTECTION_RADIUS_TOLERANCE*projection.unprojectVector(Vector2(invRange)).length());
+    radius = float(PROTECTION_RADIUS_TOLERANCE*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)))).length());
     for (int y = 0; y < sdf.height-1; ++y) {
         const float *lb = sdf(0, y);
         const float *rb = sdf(1, y);
@@ -391,9 +390,9 @@ static bool hasDiagonalArtifact(const ArtifactClassifier &artifactClassifier, fl
 template <int N>
 void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf) {
     // Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.
-    double hSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange, 0)).length();
-    double vSpan = minDeviationRatio*projection.unprojectVector(Vector2(0, invRange)).length();
-    double dSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange)).length();
+    double hSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)), 0)).length();
+    double vSpan = minDeviationRatio*transformation.unprojectVector(Vector2(0, transformation.distanceMapping(DistanceMapping::Delta(1)))).length();
+    double dSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)))).length();
     // Inspect all texels.
     for (int y = 0; y < sdf.height; ++y) {
         for (int x = 0; x < sdf.width; ++x) {
@@ -419,14 +418,14 @@ void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf) {
 template <template <typename> class ContourCombiner, int N>
 void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf, const Shape &shape) {
     // Compute the expected deltas between values of horizontally, vertically, and diagonally adjacent texels.
-    double hSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange, 0)).length();
-    double vSpan = minDeviationRatio*projection.unprojectVector(Vector2(0, invRange)).length();
-    double dSpan = minDeviationRatio*projection.unprojectVector(Vector2(invRange)).length();
+    double hSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)), 0)).length();
+    double vSpan = minDeviationRatio*transformation.unprojectVector(Vector2(0, transformation.distanceMapping(DistanceMapping::Delta(1)))).length();
+    double dSpan = minDeviationRatio*transformation.unprojectVector(Vector2(transformation.distanceMapping(DistanceMapping::Delta(1)))).length();
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel
 #endif
     {
-        ShapeDistanceChecker<ContourCombiner, N> shapeDistanceChecker(sdf, shape, projection, invRange, minImproveRatio);
+        ShapeDistanceChecker<ContourCombiner, N> shapeDistanceChecker(sdf, shape, transformation, transformation.distanceMapping, minImproveRatio);
         bool rightToLeft = false;
         // Inspect all texels.
 #ifdef MSDFGEN_USE_OPENMP
@@ -439,7 +438,7 @@ void MSDFErrorCorrection::findErrors(const BitmapConstRef<float, N> &sdf, const
                 if ((*stencil(x, row)&ERROR))
                     continue;
                 const float *c = sdf(x, row);
-                shapeDistanceChecker.shapeCoord = projection.unproject(Point2(x+.5, y+.5));
+                shapeDistanceChecker.shapeCoord = transformation.unproject(Point2(x+.5, y+.5));
                 shapeDistanceChecker.sdfCoord = Point2(x+.5, row+.5);
                 shapeDistanceChecker.msd = c;
                 shapeDistanceChecker.protectedFlag = (*stencil(x, row)&PROTECTED) != 0;

+ 3 - 4
core/MSDFErrorCorrection.h

@@ -1,7 +1,7 @@
 
 #pragma once
 
-#include "Projection.h"
+#include "SDFTransformation.h"
 #include "Shape.h"
 #include "BitmapRef.hpp"
 
@@ -20,7 +20,7 @@ public:
     };
 
     MSDFErrorCorrection();
-    explicit MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const Projection &projection, double range);
+    explicit MSDFErrorCorrection(const BitmapRef<byte, 1> &stencil, const SDFTransformation &transformation);
     /// Sets the minimum ratio between the actual and maximum expected distance delta to be considered an error.
     void setMinDeviationRatio(double minDeviationRatio);
     /// Sets the minimum ratio between the pre-correction distance error and the post-correction distance error.
@@ -46,8 +46,7 @@ public:
 
 private:
     BitmapRef<byte, 1> stencil;
-    Projection projection;
-    double invRange;
+    SDFTransformation transformation;
     double minDeviationRatio;
     double minImproveRatio;
 

+ 46 - 0
core/Range.hpp

@@ -0,0 +1,46 @@
+
+#pragma once
+
+#include "base.h"
+
+namespace msdfgen {
+
+/**
+ * Represents the range between two real values.
+ * For example, the range of representable signed distances.
+ */
+struct Range {
+
+    double lower, upper;
+
+    inline Range(double symmetricalWidth = 0) : lower(-.5*symmetricalWidth), upper(.5*symmetricalWidth) { }
+
+    inline Range(double lowerBound, double upperBound) : lower(lowerBound), upper(upperBound) { }
+
+    inline Range &operator*=(double factor) {
+        lower *= factor;
+        upper *= factor;
+        return *this;
+    }
+
+    inline Range &operator/=(double divisor) {
+        lower /= divisor;
+        upper /= divisor;
+        return *this;
+    }
+
+    inline Range operator*(double factor) const {
+        return Range(lower*factor, upper*factor);
+    }
+
+    inline Range operator/(double divisor) const {
+        return Range(lower/divisor, upper/divisor);
+    }
+
+};
+
+inline Range operator*(double factor, const Range &range) {
+    return Range(factor*range.lower, factor*range.upper);
+}
+
+}

+ 24 - 0
core/SDFTransformation.h

@@ -0,0 +1,24 @@
+
+#pragma once
+
+#include "Projection.h"
+#include "DistanceMapping.h"
+
+namespace msdfgen {
+
+/**
+ * Full signed distance field transformation specifies both spatial transformation (Projection)
+ * as well as distance value transformation (DistanceMapping).
+ */
+class SDFTransformation : public Projection {
+
+public:
+    DistanceMapping distanceMapping;
+
+    inline SDFTransformation() { }
+
+    inline SDFTransformation(const Projection &projection, const DistanceMapping &distanceMapping) : Projection(projection), distanceMapping(distanceMapping) { }
+
+};
+
+}

+ 34 - 16
core/msdf-error-correction.cpp

@@ -10,7 +10,7 @@
 namespace msdfgen {
 
 template <int N>
-static void msdfErrorCorrectionInner(const BitmapRef<float, N> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
+static void msdfErrorCorrectionInner(const BitmapRef<float, N> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
     if (config.errorCorrection.mode == ErrorCorrectionConfig::DISABLED)
         return;
     Bitmap<byte, 1> stencilBuffer;
@@ -19,7 +19,7 @@ static void msdfErrorCorrectionInner(const BitmapRef<float, N> &sdf, const Shape
     BitmapRef<byte, 1> stencil;
     stencil.pixels = config.errorCorrection.buffer ? config.errorCorrection.buffer : (byte *) stencilBuffer;
     stencil.width = sdf.width, stencil.height = sdf.height;
-    MSDFErrorCorrection ec(stencil, projection, range);
+    MSDFErrorCorrection ec(stencil, transformation);
     ec.setMinDeviationRatio(config.errorCorrection.minDeviationRatio);
     ec.setMinImproveRatio(config.errorCorrection.minImproveRatio);
     switch (config.errorCorrection.mode) {
@@ -49,9 +49,9 @@ static void msdfErrorCorrectionInner(const BitmapRef<float, N> &sdf, const Shape
 }
 
 template <int N>
-static void msdfErrorCorrectionShapeless(const BitmapRef<float, N> &sdf, const Projection &projection, double range, double minDeviationRatio, bool protectAll) {
+static void msdfErrorCorrectionShapeless(const BitmapRef<float, N> &sdf, const SDFTransformation &transformation, double minDeviationRatio, bool protectAll) {
     Bitmap<byte, 1> stencilBuffer(sdf.width, sdf.height);
-    MSDFErrorCorrection ec(stencilBuffer, projection, range);
+    MSDFErrorCorrection ec(stencilBuffer, transformation);
     ec.setMinDeviationRatio(minDeviationRatio);
     if (protectAll)
         ec.protectAll();
@@ -59,25 +59,43 @@ static void msdfErrorCorrectionShapeless(const BitmapRef<float, N> &sdf, const P
     ec.apply(sdf);
 }
 
-void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
-    msdfErrorCorrectionInner(sdf, shape, projection, range, config);
+void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
+    msdfErrorCorrectionInner(sdf, shape, transformation, config);
 }
-void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
-    msdfErrorCorrectionInner(sdf, shape, projection, range, config);
+void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
+    msdfErrorCorrectionInner(sdf, shape, transformation, config);
+}
+void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config) {
+    msdfErrorCorrectionInner(sdf, shape, SDFTransformation(projection, range), config);
+}
+void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config) {
+    msdfErrorCorrectionInner(sdf, shape, SDFTransformation(projection, range), config);
 }
 
-void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio) {
-    msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, false);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const SDFTransformation &transformation, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, transformation, minDeviationRatio, false);
+}
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const SDFTransformation &transformation, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, transformation, minDeviationRatio, false);
 }
-void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio) {
-    msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, false);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, Range range, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, SDFTransformation(projection, range), minDeviationRatio, false);
+}
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, Range range, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, SDFTransformation(projection, range), minDeviationRatio, false);
 }
 
-void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio) {
-    msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, true);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const SDFTransformation &transformation, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, transformation, minDeviationRatio, true);
+}
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const SDFTransformation &transformation, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, transformation, minDeviationRatio, true);
+}
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, Range range, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, SDFTransformation(projection, range), minDeviationRatio, true);
 }
-void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio) {
-    msdfErrorCorrectionShapeless(sdf, projection, range, minDeviationRatio, true);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, Range range, double minDeviationRatio) {
+    msdfErrorCorrectionShapeless(sdf, SDFTransformation(projection, range), minDeviationRatio, true);
 }
 
 

+ 14 - 6
core/msdf-error-correction.h

@@ -2,7 +2,9 @@
 #pragma once
 
 #include "Vector2.hpp"
+#include "Range.hpp"
 #include "Projection.h"
+#include "SDFTransformation.h"
 #include "Shape.h"
 #include "BitmapRef.hpp"
 #include "generator-config.h"
@@ -10,16 +12,22 @@
 namespace msdfgen {
 
 /// Predicts potential artifacts caused by the interpolation of the MSDF and corrects them by converting nearby texels to single-channel.
-void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
-void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void msdfErrorCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void msdfErrorCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
 
 /// Applies the simplified error correction to all discontiunous distances (INDISCRIMINATE mode). Does not need shape or translation.
-void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
-void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const SDFTransformation &transformation, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const SDFTransformation &transformation, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, Range range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastDistanceErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, Range range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
 
 /// Applies the simplified error correction to edges only (EDGE_ONLY mode). Does not need shape or translation.
-void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
-void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, double range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const SDFTransformation &transformation, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const SDFTransformation &transformation, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 3> &sdf, const Projection &projection, Range range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
+void msdfFastEdgeErrorCorrection(const BitmapRef<float, 4> &sdf, const Projection &projection, Range range, double minDeviationRatio = ErrorCorrectionConfig::defaultMinDeviationRatio);
 
 /// The original version of the error correction algorithm.
 void msdfErrorCorrection_legacy(const BitmapRef<float, 3> &output, const Vector2 &threshold);

+ 86 - 52
core/msdfgen.cpp

@@ -13,45 +13,45 @@ class DistancePixelConversion;
 
 template <>
 class DistancePixelConversion<double> {
-    double invRange;
+    DistanceMapping mapping;
 public:
     typedef BitmapRef<float, 1> BitmapRefType;
-    inline explicit DistancePixelConversion(double range) : invRange(1/range) { }
+    inline explicit DistancePixelConversion(DistanceMapping mapping) : mapping(mapping) { }
     inline void operator()(float *pixels, double distance) const {
-        *pixels = float(invRange*distance+.5);
+        *pixels = float(mapping(distance));
     }
 };
 
 template <>
 class DistancePixelConversion<MultiDistance> {
-    double invRange;
+    DistanceMapping mapping;
 public:
     typedef BitmapRef<float, 3> BitmapRefType;
-    inline explicit DistancePixelConversion(double range) : invRange(1/range) { }
+    inline explicit DistancePixelConversion(DistanceMapping mapping) : mapping(mapping) { }
     inline void operator()(float *pixels, const MultiDistance &distance) const {
-        pixels[0] = float(invRange*distance.r+.5);
-        pixels[1] = float(invRange*distance.g+.5);
-        pixels[2] = float(invRange*distance.b+.5);
+        pixels[0] = float(mapping(distance.r));
+        pixels[1] = float(mapping(distance.g));
+        pixels[2] = float(mapping(distance.b));
     }
 };
 
 template <>
 class DistancePixelConversion<MultiAndTrueDistance> {
-    double invRange;
+    DistanceMapping mapping;
 public:
     typedef BitmapRef<float, 4> BitmapRefType;
-    inline explicit DistancePixelConversion(double range) : invRange(1/range) { }
+    inline explicit DistancePixelConversion(DistanceMapping mapping) : mapping(mapping) { }
     inline void operator()(float *pixels, const MultiAndTrueDistance &distance) const {
-        pixels[0] = float(invRange*distance.r+.5);
-        pixels[1] = float(invRange*distance.g+.5);
-        pixels[2] = float(invRange*distance.b+.5);
-        pixels[3] = float(invRange*distance.a+.5);
+        pixels[0] = float(mapping(distance.r));
+        pixels[1] = float(mapping(distance.g));
+        pixels[2] = float(mapping(distance.b));
+        pixels[3] = float(mapping(distance.a));
     }
 };
 
 template <class ContourCombiner>
-void generateDistanceField(const typename DistancePixelConversion<typename ContourCombiner::DistanceType>::BitmapRefType &output, const Shape &shape, const Projection &projection, double range) {
-    DistancePixelConversion<typename ContourCombiner::DistanceType> distancePixelConversion(range);
+void generateDistanceField(const typename DistancePixelConversion<typename ContourCombiner::DistanceType>::BitmapRefType &output, const Shape &shape, const SDFTransformation &transformation) {
+    DistancePixelConversion<typename ContourCombiner::DistanceType> distancePixelConversion(transformation.distanceMapping);
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel
 #endif
@@ -65,7 +65,7 @@ void generateDistanceField(const typename DistancePixelConversion<typename Conto
             int row = shape.inverseYAxis ? output.height-y-1 : y;
             for (int col = 0; col < output.width; ++col) {
                 int x = rightToLeft ? output.width-col-1 : col;
-                Point2 p = projection.unproject(Point2(x+.5, y+.5));
+                Point2 p = transformation.unproject(Point2(x+.5, y+.5));
                 typename ContourCombiner::DistanceType distance = distanceFinder.distance(p);
                 distancePixelConversion(output(x, row), distance);
             }
@@ -74,65 +74,96 @@ void generateDistanceField(const typename DistancePixelConversion<typename Conto
     }
 }
 
-void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config) {
+void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const SDFTransformation &transformation, const GeneratorConfig &config) {
     if (config.overlapSupport)
-        generateDistanceField<OverlappingContourCombiner<TrueDistanceSelector> >(output, shape, projection, range);
+        generateDistanceField<OverlappingContourCombiner<TrueDistanceSelector> >(output, shape, transformation);
     else
-        generateDistanceField<SimpleContourCombiner<TrueDistanceSelector> >(output, shape, projection, range);
+        generateDistanceField<SimpleContourCombiner<TrueDistanceSelector> >(output, shape, transformation);
 }
 
-void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config) {
+void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, const SDFTransformation &transformation, const GeneratorConfig &config) {
     if (config.overlapSupport)
-        generateDistanceField<OverlappingContourCombiner<PerpendicularDistanceSelector> >(output, shape, projection, range);
+        generateDistanceField<OverlappingContourCombiner<PerpendicularDistanceSelector> >(output, shape, transformation);
     else
-        generateDistanceField<SimpleContourCombiner<PerpendicularDistanceSelector> >(output, shape, projection, range);
+        generateDistanceField<SimpleContourCombiner<PerpendicularDistanceSelector> >(output, shape, transformation);
 }
 
-void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
     if (config.overlapSupport)
-        generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, projection, range);
+        generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, transformation);
     else
-        generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, projection, range);
-    msdfErrorCorrection(output, shape, projection, range, config);
+        generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, transformation);
+    msdfErrorCorrection(output, shape, transformation, config);
 }
 
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config) {
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config) {
     if (config.overlapSupport)
-        generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range);
+        generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, transformation);
     else
-        generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, projection, range);
-    msdfErrorCorrection(output, shape, projection, range, config);
+        generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, transformation);
+    msdfErrorCorrection(output, shape, transformation, config);
+}
+
+void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config) {
+    if (config.overlapSupport)
+        generateDistanceField<OverlappingContourCombiner<TrueDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+    else
+        generateDistanceField<SimpleContourCombiner<TrueDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+}
+
+void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config) {
+    if (config.overlapSupport)
+        generateDistanceField<OverlappingContourCombiner<PerpendicularDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+    else
+        generateDistanceField<SimpleContourCombiner<PerpendicularDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+}
+
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config) {
+    if (config.overlapSupport)
+        generateDistanceField<OverlappingContourCombiner<MultiDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+    else
+        generateDistanceField<SimpleContourCombiner<MultiDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+    msdfErrorCorrection(output, shape, SDFTransformation(projection, range), config);
+}
+
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config) {
+    if (config.overlapSupport)
+        generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+    else
+        generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, SDFTransformation(projection, range));
+    msdfErrorCorrection(output, shape, SDFTransformation(projection, range), config);
 }
 
 // Legacy API
 
-void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config) {
-    generatePSDF(output, shape, projection, range, config);
+void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config) {
+    generatePSDF(output, shape, SDFTransformation(projection, range), config);
 }
 
-void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
+void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
     generateSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport));
 }
 
-void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
+void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
     generatePSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport));
 }
 
-void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
+void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport) {
     generatePSDF(output, shape, Projection(scale, translate), range, GeneratorConfig(overlapSupport));
 }
 
-void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) {
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) {
     generateMSDF(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(overlapSupport, errorCorrectionConfig));
 }
 
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) {
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig, bool overlapSupport) {
     generateMTSDF(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(overlapSupport, errorCorrectionConfig));
 }
 
 // Legacy version
 
-void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
+void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate) {
+    DistanceMapping distanceMapping(range);
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel for
 #endif
@@ -148,12 +179,13 @@ void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, d
                     if (distance < minDistance)
                         minDistance = distance;
                 }
-            *output(x, row) = float(minDistance.distance/range+.5);
+            *output(x, row) = float(distanceMapping(minDistance.distance));
         }
     }
 }
 
-void generatePSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
+void generatePSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate) {
+    DistanceMapping distanceMapping(range);
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel for
 #endif
@@ -176,16 +208,17 @@ void generatePSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape,
                 }
             if (nearEdge)
                 (*nearEdge)->distanceToPerpendicularDistance(minDistance, p, nearParam);
-            *output(x, row) = float(minDistance.distance/range+.5);
+            *output(x, row) = float(distanceMapping(minDistance.distance));
         }
     }
 }
 
-void generatePseudoSDF_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, Range range, const Vector2 &scale, const Vector2 &translate) {
     generatePSDF_legacy(output, shape, range, scale, translate);
 }
 
-void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) {
+void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) {
+    DistanceMapping distanceMapping(range);
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel for
 #endif
@@ -229,9 +262,9 @@ void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape,
                 (*g.nearEdge)->distanceToPerpendicularDistance(g.minDistance, p, g.nearParam);
             if (b.nearEdge)
                 (*b.nearEdge)->distanceToPerpendicularDistance(b.minDistance, p, b.nearParam);
-            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);
+            output(x, row)[0] = float(distanceMapping(r.minDistance.distance));
+            output(x, row)[1] = float(distanceMapping(g.minDistance.distance));
+            output(x, row)[2] = float(distanceMapping(b.minDistance.distance));
         }
     }
 
@@ -239,7 +272,8 @@ void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape,
     msdfErrorCorrection(output, shape, Projection(scale, translate), range, MSDFGeneratorConfig(false, errorCorrectionConfig));
 }
 
-void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) {
+void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig) {
+    DistanceMapping distanceMapping(range);
 #ifdef MSDFGEN_USE_OPENMP
     #pragma omp parallel for
 #endif
@@ -286,10 +320,10 @@ void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape,
                 (*g.nearEdge)->distanceToPerpendicularDistance(g.minDistance, p, g.nearParam);
             if (b.nearEdge)
                 (*b.nearEdge)->distanceToPerpendicularDistance(b.minDistance, p, b.nearParam);
-            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);
-            output(x, row)[3] = float(minDistance.distance/range+.5);
+            output(x, row)[0] = float(distanceMapping(r.minDistance.distance));
+            output(x, row)[1] = float(distanceMapping(g.minDistance.distance));
+            output(x, row)[2] = float(distanceMapping(b.minDistance.distance));
+            output(x, row)[3] = float(distanceMapping(minDistance.distance));
         }
     }
 

+ 140 - 54
core/render-sdf.cpp

@@ -2,89 +2,175 @@
 #include "render-sdf.h"
 
 #include "arithmetics.hpp"
+#include "DistanceMapping.h"
 #include "pixel-conversion.hpp"
 #include "bitmap-interpolation.hpp"
 
 namespace msdfgen {
 
-static float distVal(float dist, double pxRange, float midValue) {
-    if (!pxRange)
-        return (float) (dist > midValue);
-    return (float) clamp((dist-midValue)*pxRange+.5);
+static float distVal(float dist, DistanceMapping mapping) {
+    return (float) clamp(mapping(dist)+.5);
 }
 
-void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1> &sdf, double pxRange, float midValue) {
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1> &sdf, Range sdfPxRange, float sdThreshold) {
     Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
-    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;
-            interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
-            *output(x, y) = distVal(sd, pxRange, midValue);
+    if (sdfPxRange.lower == sdfPxRange.upper) {
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd;
+                interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
+                *output(x, y) = float(sd >= sdThreshold);
+            }
         }
+    } else {
+        sdfPxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
+        DistanceMapping distanceMapping = DistanceMapping::inverse(sdfPxRange);
+        float sdBias = .5f-sdThreshold;
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd;
+                interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
+                *output(x, y) = distVal(sd+sdBias, distanceMapping);
+            }
+        }
+    }
+
 }
 
-void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, double pxRange, float midValue) {
+void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, Range sdfPxRange, float sdThreshold) {
     Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
-    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;
-            interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
-            float v = distVal(sd, pxRange, midValue);
-            output(x, y)[0] = v;
-            output(x, y)[1] = v;
-            output(x, y)[2] = v;
+    if (sdfPxRange.lower == sdfPxRange.upper) {
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd;
+                interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
+                float v = float(sd >= sdThreshold);
+                output(x, y)[0] = v;
+                output(x, y)[1] = v;
+                output(x, y)[2] = v;
+            }
         }
+    } else {
+        sdfPxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
+        DistanceMapping distanceMapping = DistanceMapping::inverse(sdfPxRange);
+        float sdBias = .5f-sdThreshold;
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd;
+                interpolate(&sd, sdf, scale*Point2(x+.5, y+.5));
+                float v = distVal(sd+sdBias, distanceMapping);
+                output(x, y)[0] = v;
+                output(x, y)[1] = v;
+                output(x, y)[2] = v;
+            }
+        }
+    }
 }
 
-void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, double pxRange, float midValue) {
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, Range sdfPxRange, float sdThreshold) {
     Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
-    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];
-            interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
-            *output(x, y) = distVal(median(sd[0], sd[1], sd[2]), pxRange, midValue);
+    if (sdfPxRange.lower == sdfPxRange.upper) {
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[3];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                *output(x, y) = float(median(sd[0], sd[1], sd[2]) >= sdThreshold);
+            }
+        }
+    } else {
+        sdfPxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
+        DistanceMapping distanceMapping = DistanceMapping::inverse(sdfPxRange);
+        float sdBias = .5f-sdThreshold;
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[3];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                *output(x, y) = distVal(median(sd[0], sd[1], sd[2])+sdBias, distanceMapping);
+            }
         }
+    }
 }
 
-void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &sdf, double pxRange, float midValue) {
+void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &sdf, Range sdfPxRange, float sdThreshold) {
     Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
-    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];
-            interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
-            output(x, y)[0] = distVal(sd[0], pxRange, midValue);
-            output(x, y)[1] = distVal(sd[1], pxRange, midValue);
-            output(x, y)[2] = distVal(sd[2], pxRange, midValue);
+    if (sdfPxRange.lower == sdfPxRange.upper) {
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[3];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                output(x, y)[0] = float(sd[0] >= sdThreshold);
+                output(x, y)[1] = float(sd[1] >= sdThreshold);
+                output(x, y)[2] = float(sd[2] >= sdThreshold);
+            }
         }
+    } else {
+        sdfPxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
+        DistanceMapping distanceMapping = DistanceMapping::inverse(sdfPxRange);
+        float sdBias = .5f-sdThreshold;
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[3];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                output(x, y)[0] = distVal(sd[0]+sdBias, distanceMapping);
+                output(x, y)[1] = distVal(sd[1]+sdBias, distanceMapping);
+                output(x, y)[2] = distVal(sd[2]+sdBias, distanceMapping);
+            }
+        }
+    }
 }
 
-void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &sdf, double pxRange, float midValue) {
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &sdf, Range sdfPxRange, float sdThreshold) {
     Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
-    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[4];
-            interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
-            *output(x, y) = distVal(median(sd[0], sd[1], sd[2]), pxRange, midValue);
+    if (sdfPxRange.lower == sdfPxRange.upper) {
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[4];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                *output(x, y) = float(median(sd[0], sd[1], sd[2]) >= sdThreshold);
+            }
+        }
+    } else {
+        sdfPxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
+        DistanceMapping distanceMapping = DistanceMapping::inverse(sdfPxRange);
+        float sdBias = .5f-sdThreshold;
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[4];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                *output(x, y) = distVal(median(sd[0], sd[1], sd[2])+sdBias, distanceMapping);
+            }
         }
+    }
 }
 
-void renderSDF(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &sdf, double pxRange, float midValue) {
+void renderSDF(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &sdf, Range sdfPxRange, float sdThreshold) {
     Vector2 scale((double) sdf.width/output.width, (double) sdf.height/output.height);
-    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[4];
-            interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
-            output(x, y)[0] = distVal(sd[0], pxRange, midValue);
-            output(x, y)[1] = distVal(sd[1], pxRange, midValue);
-            output(x, y)[2] = distVal(sd[2], pxRange, midValue);
-            output(x, y)[3] = distVal(sd[3], pxRange, midValue);
+    if (sdfPxRange.lower == sdfPxRange.upper) {
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[4];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                output(x, y)[0] = float(sd[0] >= sdThreshold);
+                output(x, y)[1] = float(sd[1] >= sdThreshold);
+                output(x, y)[2] = float(sd[2] >= sdThreshold);
+                output(x, y)[3] = float(sd[3] >= sdThreshold);
+            }
+        }
+    } else {
+        sdfPxRange *= (double) (output.width+output.height)/(sdf.width+sdf.height);
+        DistanceMapping distanceMapping = DistanceMapping::inverse(sdfPxRange);
+        float sdBias = .5f-sdThreshold;
+        for (int y = 0; y < output.height; ++y) {
+            for (int x = 0; x < output.width; ++x) {
+                float sd[4];
+                interpolate(sd, sdf, scale*Point2(x+.5, y+.5));
+                output(x, y)[0] = distVal(sd[0]+sdBias, distanceMapping);
+                output(x, y)[1] = distVal(sd[1]+sdBias, distanceMapping);
+                output(x, y)[2] = distVal(sd[2]+sdBias, distanceMapping);
+                output(x, y)[3] = distVal(sd[3]+sdBias, distanceMapping);
+            }
         }
+    }
 }
 
 void simulate8bit(const BitmapRef<float, 1> &bitmap) {

+ 7 - 6
core/render-sdf.h

@@ -2,17 +2,18 @@
 #pragma once
 
 #include "Vector2.hpp"
+#include "Range.hpp"
 #include "BitmapRef.hpp"
 
 namespace msdfgen {
 
 /// Reconstructs the shape's appearance into output from the distance field sdf.
-void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1> &sdf, double pxRange = 0, float midValue = .5f);
-void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, double pxRange = 0, float midValue = .5f);
-void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, double pxRange = 0, float midValue = .5f);
-void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &sdf, double pxRange = 0, float midValue = .5f);
-void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &sdf, double pxRange = 0, float midValue = .5f);
-void renderSDF(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &sdf, double pxRange = 0, float midValue = .5f);
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 1> &sdf, Range sdfPxRange = 0, float sdThreshold = .5f);
+void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 1> &sdf, Range sdfPxRange = 0, float sdThreshold = .5f);
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 3> &sdf, Range sdfPxRange = 0, float sdThreshold = .5f);
+void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3> &sdf, Range sdfPxRange = 0, float sdThreshold = .5f);
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &sdf, Range sdfPxRange = 0, float sdThreshold = .5f);
+void renderSDF(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &sdf, Range sdfPxRange = 0, float sdThreshold = .5f);
 
 /// Snaps the values of the floating-point bitmaps into one of the 256 values representable in a standard 8-bit bitmap.
 void simulate8bit(const BitmapRef<float, 1> &bitmap);

+ 66 - 55
main.cpp

@@ -388,6 +388,10 @@ static const char *const helpText =
     "OPTIONS\n"
     "  -angle <angle>\n"
         "\tSpecifies the minimum angle between adjacent edges to be considered a corner. Append D for degrees.\n"
+    "  -apxrange <outermost distance> <innermost distance>\n"
+        "\tSpecifies the outermost (negative) and innermost representable distance in pixels.\n"
+    "  -arange <outermost distance> <innermost distance>\n"
+        "\tSpecifies the outermost (negative) and innermost representable distance in shape units.\n"
     "  -ascale <x scale> <y scale>\n"
         "\tSets the scale used to convert shape units to pixels asymmetrically.\n"
     "  -autoframe\n"
@@ -396,8 +400,6 @@ static const char *const helpText =
         "\tSelects the strategy of the edge coloring heuristic.\n"
     "  -dimensions <width> <height>\n"
         "\tSets the dimensions of the output image.\n"
-    "  -distanceshift <shift>\n"
-        "\tShifts all normalized distances in the output distance field by this value.\n"
     "  -edgecolors <sequence>\n"
         "\tOverrides automatic edge coloring with the specified color sequence.\n"
     "  -errorcorrection <mode>\n"
@@ -555,8 +557,8 @@ int main(int argc, const char *const *argv) {
         RANGE_UNIT,
         RANGE_PX
     } rangeMode = RANGE_PX;
-    double range = 1;
-    double pxRange = 2;
+    Range range(1);
+    Range pxRange(2);
     Vector2 translate;
     Vector2 scale = 1;
     bool scaleSpecified = false;
@@ -740,18 +742,42 @@ int main(int argc, const char *const *argv) {
         }
         ARG_CASE("-range" ARG_CASE_OR "-unitrange", 1) {
             double r;
-            if (!(parseDouble(r, argv[argPos++]) && r > 0))
-                ABORT("Invalid range argument. Use -range <range> with a positive real number.");
+            if (!parseDouble(r, argv[argPos++]))
+                ABORT("Invalid range argument. Use -range <range> with a real number.");
+            if (r == 0)
+                ABORT("Range must be non-zero.");
             rangeMode = RANGE_UNIT;
-            range = r;
+            range = Range(r);
             continue;
         }
         ARG_CASE("-pxrange", 1) {
             double r;
-            if (!(parseDouble(r, argv[argPos++]) && r > 0))
-                ABORT("Invalid range argument. Use -pxrange <range> with a positive real number.");
+            if (!parseDouble(r, argv[argPos++]))
+                ABORT("Invalid range argument. Use -pxrange <range> with a real number.");
+            if (r == 0)
+                ABORT("Range must be non-zero.");
             rangeMode = RANGE_PX;
-            pxRange = r;
+            pxRange = Range(r);
+            continue;
+        }
+        ARG_CASE("-arange" ARG_CASE_OR "-aunitrange", 2) {
+            double r0, r1;
+            if (!(parseDouble(r0, argv[argPos++]) && parseDouble(r1, argv[argPos++])))
+                ABORT("Invalid range arguments. Use -arange <minimum> <maximum> with two real numbers.");
+            if (r0 == r1)
+                ABORT("Range must be non-empty.");
+            rangeMode = RANGE_UNIT;
+            range = Range(r0, r1);
+            continue;
+        }
+        ARG_CASE("-apxrange", 2) {
+            double r0, r1;
+            if (!(parseDouble(r0, argv[argPos++]) && parseDouble(r1, argv[argPos++])))
+                ABORT("Invalid range arguments. Use -apxrange <minimum> <maximum> with two real numbers.");
+            if (r0 == r1)
+                ABORT("Range must be non-empty.");
+            rangeMode = RANGE_PX;
+            pxRange = Range(r0, r1);
             continue;
         }
         ARG_CASE("-scale", 1) {
@@ -1039,16 +1065,22 @@ int main(int argc, const char *const *argv) {
     if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS)
         bounds = shape.getBounds();
 
+    if (outputDistanceShift) {
+        Range &rangeRef = rangeMode == RANGE_PX ? pxRange : range;
+        double rangeShift = -outputDistanceShift*(rangeRef.upper-rangeRef.lower);
+        rangeRef.lower += rangeShift;
+        rangeRef.upper += rangeShift;
+    }
+
     // Auto-frame
     if (autoFrame) {
         double l = bounds.l, b = bounds.b, r = bounds.r, t = bounds.t;
         Vector2 frame(width, height);
-        double m = .5+(double) outputDistanceShift;
         if (!scaleSpecified) {
             if (rangeMode == RANGE_UNIT)
-                l -= m*range, b -= m*range, r += m*range, t += m*range;
+                l += range.lower, b += range.lower, r -= range.lower, t -= range.lower;
             else
-                frame -= 2*m*pxRange;
+                frame += 2*pxRange.lower;
         }
         if (l >= r || b >= t)
             l = 0, b = 0, r = 1, t = 1;
@@ -1067,7 +1099,7 @@ int main(int argc, const char *const *argv) {
             }
         }
         if (rangeMode == RANGE_PX && !scaleSpecified)
-            translate += m*pxRange/scale;
+            translate -= pxRange.lower/scale;
     }
 
     if (rangeMode == RANGE_PX)
@@ -1094,13 +1126,13 @@ int main(int argc, const char *const *argv) {
             fprintf(out, "translate = %.17g, %.17g\n", translate.x, translate.y);
         }
         if (rangeMode == RANGE_PX)
-            fprintf(out, "range = %.17g\n", range);
+            fprintf(out, "range %.17g to %.17g\n", range.lower, range.upper);
         if (mode == METRICS && outputSpecified)
             fclose(out);
     }
 
     // Compute output
-    Projection projection(scale, translate);
+    SDFTransformation transformation(Projection(scale, translate), range);
     Bitmap<float, 1> sdf;
     Bitmap<float, 3> msdf;
     Bitmap<float, 4> mtsdf;
@@ -1125,7 +1157,7 @@ int main(int argc, const char *const *argv) {
             if (legacyMode)
                 generateSDF_legacy(sdf, shape, range, scale, translate);
             else
-                generateSDF(sdf, shape, projection, range, generatorConfig);
+                generateSDF(sdf, shape, transformation, generatorConfig);
             break;
         }
         case PERPENDICULAR: {
@@ -1133,7 +1165,7 @@ int main(int argc, const char *const *argv) {
             if (legacyMode)
                 generatePSDF_legacy(sdf, shape, range, scale, translate);
             else
-                generatePSDF(sdf, shape, projection, range, generatorConfig);
+                generatePSDF(sdf, shape, transformation, generatorConfig);
             break;
         }
         case MULTI: {
@@ -1145,7 +1177,7 @@ int main(int argc, const char *const *argv) {
             if (legacyMode)
                 generateMSDF_legacy(msdf, shape, range, scale, translate, generatorConfig.errorCorrection);
             else
-                generateMSDF(msdf, shape, projection, range, generatorConfig);
+                generateMSDF(msdf, shape, transformation, generatorConfig);
             break;
         }
         case MULTI_AND_TRUE: {
@@ -1157,7 +1189,7 @@ int main(int argc, const char *const *argv) {
             if (legacyMode)
                 generateMTSDF_legacy(mtsdf, shape, range, scale, translate, generatorConfig.errorCorrection);
             else
-                generateMTSDF(mtsdf, shape, projection, range, generatorConfig);
+                generateMTSDF(mtsdf, shape, transformation, generatorConfig);
             break;
         }
         default:;
@@ -1188,39 +1220,18 @@ int main(int argc, const char *const *argv) {
         switch (mode) {
             case SINGLE:
             case PERPENDICULAR:
-                distanceSignCorrection(sdf, shape, projection, fillRule);
-                break;
-            case MULTI:
-                distanceSignCorrection(msdf, shape, projection, fillRule);
-                msdfErrorCorrection(msdf, shape, projection, range, postErrorCorrectionConfig);
-                break;
-            case MULTI_AND_TRUE:
-                distanceSignCorrection(mtsdf, shape, projection, fillRule);
-                msdfErrorCorrection(msdf, shape, projection, range, postErrorCorrectionConfig);
-                break;
-            default:;
-        }
-    }
-    if (outputDistanceShift) {
-        float *pixel = NULL, *pixelsEnd = NULL;
-        switch (mode) {
-            case SINGLE:
-            case PERPENDICULAR:
-                pixel = (float *) sdf;
-                pixelsEnd = pixel+1*sdf.width()*sdf.height();
+                distanceSignCorrection(sdf, shape, transformation, fillRule);
                 break;
             case MULTI:
-                pixel = (float *) msdf;
-                pixelsEnd = pixel+3*msdf.width()*msdf.height();
+                distanceSignCorrection(msdf, shape, transformation, fillRule);
+                msdfErrorCorrection(msdf, shape, transformation, postErrorCorrectionConfig);
                 break;
             case MULTI_AND_TRUE:
-                pixel = (float *) mtsdf;
-                pixelsEnd = pixel+4*mtsdf.width()*mtsdf.height();
+                distanceSignCorrection(mtsdf, shape, transformation, fillRule);
+                msdfErrorCorrection(msdf, shape, transformation, postErrorCorrectionConfig);
                 break;
             default:;
         }
-        while (pixel < pixelsEnd)
-            *pixel++ += outputDistanceShift;
     }
 
     // Save output
@@ -1243,18 +1254,18 @@ int main(int argc, const char *const *argv) {
             if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
                 simulate8bit(sdf);
             if (estimateError) {
-                double sdfError = estimateSDFError(sdf, shape, projection, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
+                double sdfError = estimateSDFError(sdf, shape, transformation, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
                 printf("SDF error ~ %e\n", sdfError);
             }
             if (testRenderMulti) {
                 Bitmap<float, 3> render(testWidthM, testHeightM);
-                renderSDF(render, sdf, avgScale*range, .5f+outputDistanceShift);
+                renderSDF(render, sdf, avgScale*range);
                 if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRenderMulti))
                     fputs("Failed to write test render file.\n", stderr);
             }
             if (testRender) {
                 Bitmap<float, 1> render(testWidth, testHeight);
-                renderSDF(render, sdf, avgScale*range, .5f+outputDistanceShift);
+                renderSDF(render, sdf, avgScale*range);
                 if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRender))
                     fputs("Failed to write test render file.\n", stderr);
             }
@@ -1267,18 +1278,18 @@ int main(int argc, const char *const *argv) {
             if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
                 simulate8bit(msdf);
             if (estimateError) {
-                double sdfError = estimateSDFError(msdf, shape, projection, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
+                double sdfError = estimateSDFError(msdf, shape, transformation, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
                 printf("SDF error ~ %e\n", sdfError);
             }
             if (testRenderMulti) {
                 Bitmap<float, 3> render(testWidthM, testHeightM);
-                renderSDF(render, msdf, avgScale*range, .5f+outputDistanceShift);
+                renderSDF(render, msdf, avgScale*range);
                 if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRenderMulti))
                     fputs("Failed to write test render file.\n", stderr);
             }
             if (testRender) {
                 Bitmap<float, 1> render(testWidth, testHeight);
-                renderSDF(render, msdf, avgScale*range, .5f+outputDistanceShift);
+                renderSDF(render, msdf, avgScale*range);
                 if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRender))
                     fputs("Failed to write test render file.\n", stderr);
             }
@@ -1291,18 +1302,18 @@ int main(int argc, const char *const *argv) {
             if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
                 simulate8bit(mtsdf);
             if (estimateError) {
-                double sdfError = estimateSDFError(mtsdf, shape, projection, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
+                double sdfError = estimateSDFError(mtsdf, shape, transformation, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
                 printf("SDF error ~ %e\n", sdfError);
             }
             if (testRenderMulti) {
                 Bitmap<float, 4> render(testWidthM, testHeightM);
-                renderSDF(render, mtsdf, avgScale*range, .5f+outputDistanceShift);
+                renderSDF(render, mtsdf, avgScale*range);
                 if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRenderMulti))
                     fputs("Failed to write test render file.\n", stderr);
             }
             if (testRender) {
                 Bitmap<float, 1> render(testWidth, testHeight);
-                renderSDF(render, mtsdf, avgScale*range, .5f+outputDistanceShift);
+                renderSDF(render, mtsdf, avgScale*range);
                 if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRender))
                     fputs("Failed to write test render file.\n", stderr);
             }

+ 22 - 15
msdfgen.h

@@ -18,7 +18,10 @@
 #include "core/base.h"
 #include "core/arithmetics.hpp"
 #include "core/Vector2.hpp"
+#include "core/Range.hpp"
 #include "core/Projection.h"
+#include "core/DistanceMapping.h"
+#include "core/SDFTransformation.h"
 #include "core/Scanline.h"
 #include "core/Shape.h"
 #include "core/BitmapRef.hpp"
@@ -38,31 +41,35 @@
 namespace msdfgen {
 
 /// Generates a conventional single-channel signed distance field.
-void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig());
+void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const SDFTransformation &transformation, const GeneratorConfig &config = GeneratorConfig());
 
 /// Generates a single-channel signed perpendicular distance field.
-void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig());
+void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, const SDFTransformation &transformation, const GeneratorConfig &config = GeneratorConfig());
 
 /// Generates a multi-channel signed distance field. Edge colors must be assigned first! (See edgeColoringSimple)
-void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
 
 /// Generates a multi-channel signed distance field with true distance in the alpha channel. Edge colors must be assigned first.
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, double range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const SDFTransformation &transformation, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
 
 // Old version of the function API's kept for backwards compatibility
-void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, double range, const GeneratorConfig &config = GeneratorConfig());
+void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config = GeneratorConfig());
+void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config = GeneratorConfig());
+void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, const Projection &projection, Range range, const GeneratorConfig &config = GeneratorConfig());
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, const Projection &projection, Range range, const MSDFGeneratorConfig &config = MSDFGeneratorConfig());
 
-void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
-void generatePSDF(const BitmapRef<float, 1> &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);
-void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig(), bool overlapSupport = true);
-void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig(), bool overlapSupport = true);
+void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
+void generatePSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
+void generatePseudoSDF(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, bool overlapSupport = true);
+void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig(), bool overlapSupport = true);
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, const ErrorCorrectionConfig &errorCorrectionConfig = ErrorCorrectionConfig(), bool overlapSupport = true);
 
 // Original simpler versions of the previous functions, which work well under normal circumstances, but cannot deal with overlapping contours.
-void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate);
-void generatePSDF_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, ErrorCorrectionConfig errorCorrectionConfig = ErrorCorrectionConfig());
-void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig = ErrorCorrectionConfig());
+void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate);
+void generatePSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate);
+void generatePseudoSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate);
+void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig = ErrorCorrectionConfig());
+void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, Range range, const Vector2 &scale, const Vector2 &translate, ErrorCorrectionConfig errorCorrectionConfig = ErrorCorrectionConfig());
 
 }