Browse Source

Added MTSDF mode

Viktor Chlumský 5 years ago
parent
commit
e93c8d988c

+ 2 - 1
README.md

@@ -43,9 +43,10 @@ msdfgen.exe <mode> <input> <options>
 where only the input specification is required.
 
 Mode can be one of:
- - **sdf** &ndash; generates a conventional monochrome signed distance field.
+ - **sdf** &ndash; generates a conventional monochrome (true) signed distance field.
  - **psdf** &ndash; generates a monochrome signed pseudo-distance field.
  - **msdf** (default) &ndash; generates a multi-channel signed distance field using my new method.
+ - **mtsdf** &ndash; generates a combined multi-channel and true signed distance field in the alpha channel.
 
 The input can be specified as one of:
  - **-font \<filename.ttf\> \<character code\>** &ndash; to load a glyph from a font file.

+ 2 - 0
core/contour-combiners.cpp

@@ -44,6 +44,7 @@ typename SimpleContourCombiner<EdgeSelector>::DistanceType SimpleContourCombiner
 template class SimpleContourCombiner<TrueDistanceSelector>;
 template class SimpleContourCombiner<PseudoDistanceSelector>;
 template class SimpleContourCombiner<MultiDistanceSelector>;
+template class SimpleContourCombiner<MultiAndTrueDistanceSelector>;
 
 template <class EdgeSelector>
 OverlappingContourCombiner<EdgeSelector>::OverlappingContourCombiner(const Shape &shape) {
@@ -118,5 +119,6 @@ typename OverlappingContourCombiner<EdgeSelector>::DistanceType OverlappingConto
 template class OverlappingContourCombiner<TrueDistanceSelector>;
 template class OverlappingContourCombiner<PseudoDistanceSelector>;
 template class OverlappingContourCombiner<MultiDistanceSelector>;
+template class OverlappingContourCombiner<MultiAndTrueDistanceSelector>;
 
 }

+ 25 - 0
core/edge-selectors.cpp

@@ -76,6 +76,10 @@ double PseudoDistanceSelectorBase::computeDistance(const Point2 &p) const {
     return minDistance;
 }
 
+SignedDistance PseudoDistanceSelectorBase::trueDistance() const {
+    return minTrueDistance;
+}
+
 PseudoDistanceSelector::PseudoDistanceSelector(const Point2 &p) : p(p) { }
 
 void PseudoDistanceSelector::addEdge(const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge) {
@@ -128,4 +132,25 @@ MultiDistanceSelector::DistanceType MultiDistanceSelector::distance() const {
     return multiDistance;
 }
 
+SignedDistance MultiDistanceSelector::trueDistance() const {
+    SignedDistance distance = r.trueDistance();
+    if (g.trueDistance() < distance)
+        distance = g.trueDistance();
+    if (b.trueDistance() < distance)
+        distance = b.trueDistance();
+    return distance;
+}
+
+MultiAndTrueDistanceSelector::MultiAndTrueDistanceSelector(const Point2 &p) : MultiDistanceSelector(p) { }
+
+MultiAndTrueDistanceSelector::DistanceType MultiAndTrueDistanceSelector::distance() const {
+    MultiDistance multiDistance = MultiDistanceSelector::distance();
+    MultiAndTrueDistance mtd;
+    mtd.r = multiDistance.r;
+    mtd.g = multiDistance.g;
+    mtd.b = multiDistance.b;
+    mtd.a = trueDistance().distance;
+    return mtd;
+}
+
 }

+ 16 - 0
core/edge-selectors.h

@@ -10,6 +10,9 @@ namespace msdfgen {
 struct MultiDistance {
     double r, g, b;
 };
+struct MultiAndTrueDistance : MultiDistance {
+    double a;
+};
 
 /// Selects the nearest edge by its true distance.
 class TrueDistanceSelector {
@@ -38,6 +41,7 @@ public:
     void addEdgePseudoDistance(const SignedDistance &distance);
     void merge(const PseudoDistanceSelectorBase &other);
     double computeDistance(const Point2 &p) const;
+    SignedDistance trueDistance() const;
 
 private:
     SignedDistance minTrueDistance;
@@ -73,6 +77,7 @@ public:
     void addEdge(const EdgeSegment *prevEdge, const EdgeSegment *edge, const EdgeSegment *nextEdge);
     void merge(const MultiDistanceSelector &other);
     DistanceType distance() const;
+    SignedDistance trueDistance() const;
 
 private:
     Point2 p;
@@ -80,4 +85,15 @@ private:
 
 };
 
+/// Selects the nearest edge for each of the three color channels by its pseudo-distance and by true distance for the alpha channel.
+class MultiAndTrueDistanceSelector : public MultiDistanceSelector {
+
+public:
+    typedef MultiAndTrueDistance DistanceType;
+
+    explicit MultiAndTrueDistanceSelector(const Point2 &p = Point2());
+    DistanceType distance() const;
+
+};
+
 }

+ 18 - 20
core/estimate-sdf-error.cpp

@@ -45,7 +45,8 @@ void scanlineSDF(Scanline &line, const BitmapConstRef<float, 1> &sdf, const Vect
 #endif
 }
 
-void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) {
+template <int N>
+void scanlineMSDF(Scanline &line, const BitmapConstRef<float, N> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) {
     if (!(sdf.width > 0 && sdf.height > 0))
         return line.setIntersections(std::vector<Scanline::Intersection>());
     double pixelY = clamp(scale.x*(y+translate.y)-.5, double(sdf.height-1));
@@ -123,7 +124,15 @@ void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Vect
 #endif
 }
 
-double estimateSDFError(const BitmapConstRef<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) {
+void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) {
+    scanlineMSDF(line, sdf, scale, translate, inverseYAxis, y);
+}
+void scanlineSDF(Scanline &line, const BitmapConstRef<float, 4> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y) {
+    scanlineMSDF(line, sdf, scale, translate, inverseYAxis, y);
+}
+
+template <int N>
+double estimateSDFErrorInner(const BitmapConstRef<float, N> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) {
     if (sdf.width <= 1 || sdf.height <= 1 || scanlinesPerRow < 1)
         return 0;
     double subRowSize = 1./scanlinesPerRow;
@@ -144,25 +153,14 @@ double estimateSDFError(const BitmapConstRef<float, 1> &sdf, const Shape &shape,
     return error/((sdf.height-1)*scanlinesPerRow);
 }
 
+double estimateSDFError(const BitmapConstRef<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) {
+    return estimateSDFErrorInner(sdf, shape, scale, translate, scanlinesPerRow, fillRule);
+}
 double estimateSDFError(const BitmapConstRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) {
-    if (sdf.width <= 1 || sdf.height <= 1 || scanlinesPerRow < 1)
-        return 0;
-    double subRowSize = 1./scanlinesPerRow;
-    double xFrom = .5/scale.x-translate.x;
-    double xTo = (sdf.width-.5)/scale.x-translate.x;
-    double overlapFactor = 1/(xTo-xFrom);
-    double error = 0;
-    Scanline refScanline, sdfScanline;
-    for (int row = 0; row < sdf.height-1; ++row) {
-        for (int subRow = 0; subRow < scanlinesPerRow; ++subRow) {
-            double bt = (subRow+.5)*subRowSize;
-            double y = (row+bt+.5)/scale.y-translate.y;
-            shape.scanline(refScanline, y);
-            scanlineSDF(sdfScanline, sdf, scale, translate, shape.inverseYAxis, y);
-            error += 1-overlapFactor*Scanline::overlap(refScanline, sdfScanline, xFrom, xTo, fillRule);
-        }
-    }
-    return error/((sdf.height-1)*scanlinesPerRow);
+    return estimateSDFErrorInner(sdf, shape, scale, translate, scanlinesPerRow, fillRule);
+}
+double estimateSDFError(const BitmapConstRef<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule) {
+    return estimateSDFErrorInner(sdf, shape, scale, translate, scanlinesPerRow, fillRule);
 }
 
 }

+ 2 - 0
core/estimate-sdf-error.h

@@ -11,9 +11,11 @@ namespace msdfgen {
 /// Analytically constructs a scanline at y evaluating fill by linear interpolation of the SDF.
 void scanlineSDF(Scanline &line, const BitmapConstRef<float, 1> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y);
 void scanlineSDF(Scanline &line, const BitmapConstRef<float, 3> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y);
+void scanlineSDF(Scanline &line, const BitmapConstRef<float, 4> &sdf, const Vector2 &scale, const Vector2 &translate, bool inverseYAxis, double y);
 
 /// Estimates the portion of the area that will be filled incorrectly when rendering using the SDF.
 double estimateSDFError(const BitmapConstRef<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO);
 double estimateSDFError(const BitmapConstRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO);
+double estimateSDFError(const BitmapConstRef<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, int scanlinesPerRow, FillRule fillRule = FILL_NONZERO);
 
 }

+ 88 - 1
core/msdfgen.cpp

@@ -30,6 +30,18 @@ public:
     }
 };
 
+template <>
+class DistancePixelConversion<MultiAndTrueDistance> {
+public:
+    typedef BitmapRef<float, 4> BitmapRefType;
+    inline static void convert(float *pixels, const MultiAndTrueDistance &distance, double range) {
+        pixels[0] = float(distance.r/range+.5);
+        pixels[1] = float(distance.g/range+.5);
+        pixels[2] = float(distance.b/range+.5);
+        pixels[3] = float(distance.a/range+.5);
+    }
+};
+
 template <class ContourCombiner>
 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
@@ -96,6 +108,15 @@ void generateMSDF(const BitmapRef<float, 3> &output, const Shape &shape, double
         msdfErrorCorrection(output, edgeThreshold/(scale*range));
 }
 
+void generateMTSDF(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold, bool overlapSupport) {
+    if (overlapSupport)
+        generateDistanceField<OverlappingContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, range, scale, translate);
+    else
+        generateDistanceField<SimpleContourCombiner<MultiAndTrueDistanceSelector> >(output, shape, range, scale, translate);
+    if (edgeThreshold > 0)
+        msdfErrorCorrection(output, edgeThreshold/(scale*range));
+}
+
 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[0], a1 = a[1], a2 = a[2];
@@ -118,7 +139,8 @@ inline static bool detectClash(const float *a, const float *b, double threshold)
         fabsf(a2-.5f) >= fabsf(b2-.5f); // Out of the pair, only flag the pixel farther from a shape edge
 }
 
-void msdfErrorCorrection(const BitmapRef<float, 3> &output, const Vector2 &threshold) {
+template <int N>
+void msdfErrorCorrectionInner(const BitmapRef<float, N> &output, const Vector2 &threshold) {
     std::vector<std::pair<int, int> > clashes;
     int w = output.width, h = output.height;
     for (int y = 0; y < h; ++y)
@@ -156,6 +178,13 @@ void msdfErrorCorrection(const BitmapRef<float, 3> &output, const Vector2 &thres
 #endif
 }
 
+void msdfErrorCorrection(const BitmapRef<float, 3> &output, const Vector2 &threshold) {
+    msdfErrorCorrectionInner(output, threshold);
+}
+void msdfErrorCorrection(const BitmapRef<float, 4> &output, const Vector2 &threshold) {
+    msdfErrorCorrectionInner(output, threshold);
+}
+
 // Legacy version
 
 void generateSDF_legacy(const BitmapRef<float, 1> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate) {
@@ -261,4 +290,62 @@ void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape,
         msdfErrorCorrection(output, edgeThreshold/(scale*range));
 }
 
+void generateMTSDF_legacy(const BitmapRef<float, 4> &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 < 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;
+            struct {
+                SignedDistance minDistance;
+                const EdgeHolder *nearEdge;
+                double nearParam;
+            } r, g, b;
+            r.nearEdge = g.nearEdge = b.nearEdge = NULL;
+            r.nearParam = g.nearParam = b.nearParam = 0;
+
+            for (std::vector<Contour>::const_iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
+                for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) {
+                    double param;
+                    SignedDistance distance = (*edge)->signedDistance(p, param);
+                    if (distance < minDistance)
+                        minDistance = distance;
+                    if ((*edge)->color&RED && distance < r.minDistance) {
+                        r.minDistance = distance;
+                        r.nearEdge = &*edge;
+                        r.nearParam = param;
+                    }
+                    if ((*edge)->color&GREEN && distance < g.minDistance) {
+                        g.minDistance = distance;
+                        g.nearEdge = &*edge;
+                        g.nearParam = param;
+                    }
+                    if ((*edge)->color&BLUE && distance < b.minDistance) {
+                        b.minDistance = distance;
+                        b.nearEdge = &*edge;
+                        b.nearParam = param;
+                    }
+                }
+
+            if (r.nearEdge)
+                (*r.nearEdge)->distanceToPseudoDistance(r.minDistance, p, r.nearParam);
+            if (g.nearEdge)
+                (*g.nearEdge)->distanceToPseudoDistance(g.minDistance, p, g.nearParam);
+            if (b.nearEdge)
+                (*b.nearEdge)->distanceToPseudoDistance(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);
+        }
+    }
+
+    if (edgeThreshold > 0)
+        msdfErrorCorrection(output, edgeThreshold/(scale*range));
+}
+
 }

+ 12 - 1
core/rasterization.cpp

@@ -38,7 +38,8 @@ void distanceSignCorrection(const BitmapRef<float, 1> &sdf, const Shape &shape,
     }
 }
 
-void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
+template <int N>
+static void multiDistanceSignCorrection(const BitmapRef<float, N> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
     int w = sdf.width, h = sdf.height;
     if (!(w*h))
         return;
@@ -66,6 +67,8 @@ void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape,
                 *match = -1;
             } else
                 *match = 1;
+            if (N >= 4 && (msd[3] > .5f) != fill)
+                msd[3] = 1.f-msd[3];
             ++match;
         }
     }
@@ -94,4 +97,12 @@ void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape,
     }
 }
 
+void distanceSignCorrection(const BitmapRef<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
+    multiDistanceSignCorrection(sdf, shape, scale, translate, fillRule);
+}
+
+void distanceSignCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
+    multiDistanceSignCorrection(sdf, shape, scale, translate, fillRule);
+}
+
 }

+ 1 - 0
core/rasterization.h

@@ -13,5 +13,6 @@ void rasterize(const BitmapRef<float, 1> &output, const Shape &shape, const Vect
 /// Fixes the sign of the input signed distance field, so that it matches the shape's rasterized fill.
 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);
+void distanceSignCorrection(const BitmapRef<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
 
 }

+ 29 - 0
core/render-sdf.cpp

@@ -73,6 +73,29 @@ void renderSDF(const BitmapRef<float, 3> &output, const BitmapConstRef<float, 3>
         }
 }
 
+void renderSDF(const BitmapRef<float, 1> &output, const BitmapConstRef<float, 4> &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[4];
+            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(const BitmapRef<float, 4> &output, const BitmapConstRef<float, 4> &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[4];
+            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);
+            output(x, y)[3] = distVal(sd[3], pxRange);
+        }
+}
+
 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)
@@ -85,4 +108,10 @@ void simulate8bit(const BitmapRef<float, 3> &bitmap) {
         *p = pixelByteToFloat(pixelFloatToByte(*p));
 }
 
+void simulate8bit(const BitmapRef<float, 4> &bitmap) {
+    const float *end = bitmap.pixels+4*bitmap.width*bitmap.height;
+    for (float *p = bitmap.pixels; p < end; ++p)
+        *p = pixelByteToFloat(pixelFloatToByte(*p));
+}
+
 }

+ 3 - 0
core/render-sdf.h

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

+ 10 - 0
core/save-bmp.cpp

@@ -108,6 +108,11 @@ bool saveBmp(const BitmapConstRef<byte, 3> &bitmap, const char *filename) {
     return !fclose(file);
 }
 
+bool saveBmp(const BitmapConstRef<byte, 4> &bitmap, const char *filename) {
+    // RGBA not supported by the BMP format
+    return false;
+}
+
 bool saveBmp(const BitmapConstRef<float, 1> &bitmap, const char *filename) {
     FILE *file = fopen(filename, "wb");
     if (!file)
@@ -156,4 +161,9 @@ bool saveBmp(const BitmapConstRef<float, 3> &bitmap, const char *filename) {
     return !fclose(file);
 }
 
+bool saveBmp(const BitmapConstRef<float, 4> &bitmap, const char *filename) {
+    // RGBA not supported by the BMP format
+    return false;
+}
+
 }

+ 2 - 0
core/save-bmp.h

@@ -8,7 +8,9 @@ namespace msdfgen {
 /// Saves the bitmap as a BMP file.
 bool saveBmp(const BitmapConstRef<byte, 1> &bitmap, const char *filename);
 bool saveBmp(const BitmapConstRef<byte, 3> &bitmap, const char *filename);
+bool saveBmp(const BitmapConstRef<byte, 4> &bitmap, const char *filename);
 bool saveBmp(const BitmapConstRef<float, 1> &bitmap, const char *filename);
 bool saveBmp(const BitmapConstRef<float, 3> &bitmap, const char *filename);
+bool saveBmp(const BitmapConstRef<float, 4> &bitmap, const char *filename);
 
 }

+ 39 - 41
core/save-tiff.cpp

@@ -20,6 +20,11 @@ template <typename T>
 static bool writeValue(FILE *file, T value) {
     return fwrite(&value, sizeof(T), 1, file) == 1;
 }
+template <typename T>
+static void writeValueRepeated(FILE *file, T value, int times) {
+    for (int i = 0; i < times; ++i)
+        writeValue(file, value);
+}
 
 static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
     #ifdef __BIG_ENDIAN__
@@ -47,8 +52,8 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
     writeValue<uint16_t>(file, 0x0102u);
     writeValue<uint16_t>(file, 0x0003u);
     writeValue<uint32_t>(file, channels);
-    if (channels == 3)
-        writeValue<uint32_t>(file, 0x00c2u); // Offset of 32, 32, 32
+    if (channels > 1)
+        writeValue<uint32_t>(file, 0x00c2u); // Offset of 32, 32, ...
     else {
         writeValue<uint16_t>(file, 32);
         writeValue<uint16_t>(file, 0);
@@ -63,13 +68,13 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
     writeValue<uint16_t>(file, 0x0106u);
     writeValue<uint16_t>(file, 0x0003u);
     writeValue<uint32_t>(file, 1);
-    writeValue<uint16_t>(file, channels == 3 ? 2 : 1);
+    writeValue<uint16_t>(file, channels >= 3 ? 2 : 1);
     writeValue<uint16_t>(file, 0);
     // StripOffsets
     writeValue<uint16_t>(file, 0x0111u);
     writeValue<uint16_t>(file, 0x0004u);
     writeValue<uint32_t>(file, 1);
-    writeValue<uint32_t>(file, channels == 3 ? 0x00f6u : 0x00d2u); // Offset of pixel data
+    writeValue<uint32_t>(file, 0x00d2u+(channels > 1)*channels*12); // Offset of pixel data
     // SamplesPerPixel
     writeValue<uint16_t>(file, 0x0115u);
     writeValue<uint16_t>(file, 0x0003u);
@@ -90,12 +95,12 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
     writeValue<uint16_t>(file, 0x011au);
     writeValue<uint16_t>(file, 0x0005u);
     writeValue<uint32_t>(file, 1);
-    writeValue<uint32_t>(file, channels == 3 ? 0x00c8u : 0x00c2u); // Offset of 300, 1
+    writeValue<uint32_t>(file, 0x00c2u+(channels > 1)*channels*2); // Offset of 300, 1
     // YResolution
     writeValue<uint16_t>(file, 0x011bu);
     writeValue<uint16_t>(file, 0x0005u);
     writeValue<uint32_t>(file, 1);
-    writeValue<uint32_t>(file, channels == 3 ? 0x00d0u : 0x00cau); // Offset of 300, 1
+    writeValue<uint32_t>(file, 0x00cau+(channels > 1)*channels*2); // Offset of 300, 1
     // ResolutionUnit
     writeValue<uint16_t>(file, 0x0128u);
     writeValue<uint16_t>(file, 0x0003u);
@@ -106,8 +111,8 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
     writeValue<uint16_t>(file, 0x0153u);
     writeValue<uint16_t>(file, 0x0003u);
     writeValue<uint32_t>(file, channels);
-    if (channels == 3)
-        writeValue<uint32_t>(file, 0x00d8u); // Offset of 3, 3, 3
+    if (channels > 1)
+        writeValue<uint32_t>(file, 0x00d2u+channels*2); // Offset of 3, 3, ...
     else {
         writeValue<uint16_t>(file, 3);
         writeValue<uint16_t>(file, 0);
@@ -116,46 +121,38 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
     writeValue<uint16_t>(file, 0x0154u);
     writeValue<uint16_t>(file, 0x000bu);
     writeValue<uint32_t>(file, channels);
-    if (channels == 3)
-        writeValue<uint32_t>(file, 0x00deu); // Offset of 0.f, 0.f, 0.f
+    if (channels > 1)
+        writeValue<uint32_t>(file, 0x00d2u+channels*4); // Offset of 0.f, 0.f, ...
     else
         writeValue<float>(file, 0.f);
     // SMaxSampleValue
     writeValue<uint16_t>(file, 0x0155u);
     writeValue<uint16_t>(file, 0x000bu);
     writeValue<uint32_t>(file, channels);
-    if (channels == 3)
-        writeValue<uint32_t>(file, 0x00eau); // Offset of 1.f, 1.f, 1.f
+    if (channels > 1)
+        writeValue<uint32_t>(file, 0x00d2u+channels*8); // Offset of 1.f, 1.f, ...
     else
         writeValue<float>(file, 1.f);
     // Offset = 0x00be
 
     writeValue<uint32_t>(file, 0);
 
-    if (channels == 3) {
+    if (channels > 1) {
         // 0x00c2 BitsPerSample data
-        writeValue<uint16_t>(file, 32);
-        writeValue<uint16_t>(file, 32);
-        writeValue<uint16_t>(file, 32);
-        // 0x00c8 XResolution data
+        writeValueRepeated<uint16_t>(file, 32, channels);
+        // 0x00c2 + 2*N XResolution data
         writeValue<uint32_t>(file, 300);
         writeValue<uint32_t>(file, 1);
-        // 0x00d0 YResolution data
+        // 0x00ca + 2*N YResolution data
         writeValue<uint32_t>(file, 300);
         writeValue<uint32_t>(file, 1);
-        // 0x00d8 SampleFormat data
-        writeValue<uint16_t>(file, 3);
-        writeValue<uint16_t>(file, 3);
-        writeValue<uint16_t>(file, 3);
-        // 0x00de SMinSampleValue data
-        writeValue<float>(file, 0.f);
-        writeValue<float>(file, 0.f);
-        writeValue<float>(file, 0.f);
-        // 0x00ea SMaxSampleValue data
-        writeValue<float>(file, 1.f);
-        writeValue<float>(file, 1.f);
-        writeValue<float>(file, 1.f);
-        // Offset = 0x00f6
+        // 0x00d2 + 2*N SampleFormat data
+        writeValueRepeated<uint16_t>(file, 3, channels);
+        // 0x00d2 + 4*N SMinSampleValue data
+        writeValueRepeated<float>(file, 0.f, channels);
+        // 0x00d2 + 8*N SMaxSampleValue data
+        writeValueRepeated<float>(file, 1.f, channels);
+        // Offset = 0x00d2 + 12*N
     } else {
         // 0x00c2 XResolution data
         writeValue<uint32_t>(file, 300);
@@ -169,24 +166,25 @@ static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
     return true;
 }
 
-bool saveTiff(const BitmapConstRef<float, 1> &bitmap, const char *filename) {
+template <int N>
+bool saveTiffFloat(const BitmapConstRef<float, N> &bitmap, const char *filename) {
     FILE *file = fopen(filename, "wb");
     if (!file)
         return false;
-    writeTiffHeader(file, bitmap.width, bitmap.height, 1);
+    writeTiffHeader(file, bitmap.width, bitmap.height, N);
     for (int y = bitmap.height-1; y >= 0; --y)
-        fwrite(bitmap(0, y), sizeof(float), bitmap.width, file);
+        fwrite(bitmap(0, y), sizeof(float), N*bitmap.width, file);
     return !fclose(file);
 }
 
+bool saveTiff(const BitmapConstRef<float, 1> &bitmap, const char *filename) {
+    return saveTiffFloat(bitmap, filename);
+}
 bool saveTiff(const BitmapConstRef<float, 3> &bitmap, const char *filename) {
-    FILE *file = fopen(filename, "wb");
-    if (!file)
-        return false;
-    writeTiffHeader(file, bitmap.width, bitmap.height, 3);
-    for (int y = bitmap.height-1; y >= 0; --y)
-        fwrite(bitmap(0, y), sizeof(float), 3*bitmap.width, file);
-    return !fclose(file);
+    return saveTiffFloat(bitmap, filename);
+}
+bool saveTiff(const BitmapConstRef<float, 4> &bitmap, const char *filename) {
+    return saveTiffFloat(bitmap, filename);
 }
 
 }

+ 1 - 0
core/save-tiff.h

@@ -8,5 +8,6 @@ namespace msdfgen {
 /// Saves the bitmap as an uncompressed floating-point TIFF file.
 bool saveTiff(const BitmapConstRef<float, 1> &bitmap, const char *filename);
 bool saveTiff(const BitmapConstRef<float, 3> &bitmap, const char *filename);
+bool saveTiff(const BitmapConstRef<float, 4> &bitmap, const char *filename);
 
 }

+ 20 - 0
ext/save-png.cpp

@@ -21,6 +21,13 @@ bool savePng(const BitmapConstRef<byte, 3> &bitmap, const char *filename) {
     return !lodepng::encode(filename, pixels, bitmap.width, bitmap.height, LCT_RGB);
 }
 
+bool savePng(const BitmapConstRef<byte, 4> &bitmap, const char *filename) {
+    std::vector<byte> pixels(4*bitmap.width*bitmap.height);
+    for (int y = 0; y < bitmap.height; ++y)
+        memcpy(&pixels[4*bitmap.width*y], bitmap(0, bitmap.height-y-1), 4*bitmap.width);
+    return !lodepng::encode(filename, pixels, bitmap.width, bitmap.height, LCT_RGBA);
+}
+
 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();
@@ -42,4 +49,17 @@ bool savePng(const BitmapConstRef<float, 3> &bitmap, const char *filename) {
     return !lodepng::encode(filename, pixels, bitmap.width, bitmap.height, LCT_RGB);
 }
 
+bool savePng(const BitmapConstRef<float, 4> &bitmap, const char *filename) {
+    std::vector<byte> pixels(4*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]);
+            *it++ = pixelFloatToByte(bitmap(x, y)[3]);
+        }
+    return !lodepng::encode(filename, pixels, bitmap.width, bitmap.height, LCT_RGBA);
+}
+
 }

+ 2 - 0
ext/save-png.h

@@ -8,7 +8,9 @@ namespace msdfgen {
 /// Saves the bitmap as a PNG file.
 bool savePng(const BitmapConstRef<byte, 1> &bitmap, const char *filename);
 bool savePng(const BitmapConstRef<byte, 3> &bitmap, const char *filename);
+bool savePng(const BitmapConstRef<byte, 4> &bitmap, const char *filename);
 bool savePng(const BitmapConstRef<float, 1> &bitmap, const char *filename);
 bool savePng(const BitmapConstRef<float, 3> &bitmap, const char *filename);
+bool savePng(const BitmapConstRef<float, 4> &bitmap, const char *filename);
 
 }

+ 51 - 2
main.cpp

@@ -265,9 +265,10 @@ static const char *helpText =
         " <mode> <input specification> <options>\n"
     "\n"
     "MODES\n"
-    "  sdf - Generate conventional monochrome signed distance field.\n"
+    "  sdf - Generate conventional monochrome (true) signed distance field.\n"
     "  psdf - Generate monochrome signed pseudo-distance field.\n"
     "  msdf - Generate multi-channel signed distance field. This is used by default if no mode is specified.\n"
+    "  mtsdf - Generate combined multi-channel and true signed distance field in the alpha channel.\n"
     "  metrics - Report shape metrics only.\n"
     "\n"
     "INPUT SPECIFICATION\n"
@@ -355,6 +356,7 @@ int main(int argc, const char * const *argv) {
         SINGLE,
         PSEUDO,
         MULTI,
+        MULTI_AND_TRUE,
         METRICS
     } mode = MULTI;
     bool legacyMode = false;
@@ -385,7 +387,7 @@ int main(int argc, const char * const *argv) {
     Vector2 scale = 1;
     bool scaleSpecified = false;
     double angleThreshold = 3;
-    double edgeThreshold = 1.001;
+    double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD;
     bool defEdgeAssignment = true;
     const char *edgeAssignment = NULL;
     bool yFlip = false;
@@ -410,6 +412,7 @@ int main(int argc, const char * const *argv) {
         ARG_MODE("sdf", SINGLE)
         ARG_MODE("psdf", PSEUDO)
         ARG_MODE("msdf", MULTI)
+        ARG_MODE("mtsdf", MULTI_AND_TRUE)
         ARG_MODE("metrics", METRICS)
 
         ARG_CASE("-svg", 1) {
@@ -658,6 +661,8 @@ int main(int argc, const char * const *argv) {
     double glyphAdvance = 0;
     if (!inputType || !input)
         ABORT("No input specified! Use either -svg <file.svg> or -font <file.ttf/otf> <character code>, or see -help.");
+    if (mode == MULTI_AND_TRUE && (format == BMP || format == AUTO && output && cmpExtension(output, ".bmp")))
+        ABORT("Incompatible image format. A BMP file cannot contain alpha channel, which is required in mtsdf mode.");
     Shape shape;
     switch (inputType) {
         case SVG: {
@@ -782,6 +787,7 @@ int main(int argc, const char * const *argv) {
     // Compute output
     Bitmap<float, 1> sdf;
     Bitmap<float, 3> msdf;
+    Bitmap<float, 4> mtsdf;
     switch (mode) {
         case SINGLE: {
             sdf = Bitmap<float, 1>(width, height);
@@ -811,6 +817,18 @@ int main(int argc, const char * const *argv) {
                 generateMSDF(msdf, shape, range, scale, translate, scanlinePass ? 0 : edgeThreshold, overlapSupport);
             break;
         }
+        case MULTI_AND_TRUE: {
+            if (!skipColoring)
+                edgeColoringSimple(shape, angleThreshold, coloringSeed);
+            if (edgeAssignment)
+                parseColoring(shape, edgeAssignment);
+            mtsdf = Bitmap<float, 4>(width, height);
+            if (legacyMode)
+                generateMTSDF_legacy(mtsdf, shape, range, scale, translate, scanlinePass ? 0 : edgeThreshold);
+            else
+                generateMTSDF(mtsdf, shape, range, scale, translate, scanlinePass ? 0 : edgeThreshold, overlapSupport);
+            break;
+        }
         default:;
     }
 
@@ -836,6 +854,9 @@ int main(int argc, const char * const *argv) {
             case MULTI:
                 invertColor<3>(msdf);
                 break;
+            case MULTI_AND_TRUE:
+                invertColor<4>(mtsdf);
+                break;
             default:;
         }
     }
@@ -850,6 +871,11 @@ int main(int argc, const char * const *argv) {
                 if (edgeThreshold > 0)
                     msdfErrorCorrection(msdf, edgeThreshold/(scale*range));
                 break;
+            case MULTI_AND_TRUE:
+                distanceSignCorrection(mtsdf, shape, scale, translate, fillRule);
+                if (edgeThreshold > 0)
+                    msdfErrorCorrection(mtsdf, edgeThreshold/(scale*range));
+                break;
             default:;
         }
     }
@@ -912,6 +938,29 @@ int main(int argc, const char * const *argv) {
                     ABORT("Failed to write test render file.");
             }
             break;
+        case MULTI_AND_TRUE:
+            error = writeOutput<4>(mtsdf, output, format);
+            if (error)
+                ABORT(error);
+            if (is8bitFormat(format) && (testRenderMulti || testRender || estimateError))
+                simulate8bit(mtsdf);
+            if (estimateError) {
+                double sdfError = estimateSDFError(mtsdf, shape, scale, translate, SDF_ERROR_ESTIMATE_PRECISION, fillRule);
+                printf("SDF error ~ %e\n", sdfError);
+            }
+            if (testRenderMulti) {
+                Bitmap<float, 4> render(testWidthM, testHeightM);
+                renderSDF(render, mtsdf, avgScale*range);
+                if (!savePng(render, testRenderMulti))
+                    puts("Failed to write test render file.");
+            }
+            if (testRender) {
+                Bitmap<float, 1> render(testWidth, testHeight);
+                renderSDF(render, mtsdf, avgScale*range);
+                if (!savePng(render, testRender))
+                    ABORT("Failed to write test render file.");
+            }
+            break;
         default:;
     }
 

+ 8 - 2
msdfgen.h

@@ -31,6 +31,7 @@
 #include "core/shape-description.h"
 
 #define MSDFGEN_VERSION "1.6"
+#define MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD 1.001
 
 namespace msdfgen {
 
@@ -41,14 +42,19 @@ void generateSDF(const BitmapRef<float, 1> &output, const Shape &shape, double r
 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(const BitmapRef<float, 3> &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 = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD, bool overlapSupport = true);
+
+/// 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, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD, bool overlapSupport = true);
 
 /// Resolves multi-channel signed distance field values that may cause interpolation artifacts. (Already called by generateMSDF)
 void msdfErrorCorrection(const BitmapRef<float, 3> &output, const Vector2 &threshold);
+void msdfErrorCorrection(const BitmapRef<float, 4> &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(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);
+void generateMSDF_legacy(const BitmapRef<float, 3> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD);
+void generateMTSDF_legacy(const BitmapRef<float, 4> &output, const Shape &shape, double range, const Vector2 &scale, const Vector2 &translate, double edgeThreshold = MSDFGEN_DEFAULT_ERROR_CORRECTION_THRESHOLD);
 
 }