Browse Source

Fixed asymmetrical range interaction with scanline and winding reversal, default format in non-PNG build changed to BMP

Chlumsky 2 weeks ago
parent
commit
b25851c859
3 changed files with 81 additions and 70 deletions
  1. 32 18
      core/rasterization.cpp
  2. 7 4
      core/rasterization.h
  3. 42 48
      main.cpp

+ 32 - 18
core/rasterization.cpp

@@ -16,26 +16,28 @@ void rasterize(BitmapSection<float, 1> output, const Shape &shape, const Project
     }
 }
 
-void distanceSignCorrection(BitmapSection<float, 1> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
+void distanceSignCorrection(BitmapSection<float, 1> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue, FillRule fillRule) {
     sdf.reorient(shape.getYAxisOrientation());
+    float doubleSdfZeroValue = sdfZeroValue+sdfZeroValue;
     Scanline scanline;
     for (int y = 0; y < sdf.height; ++y) {
         shape.scanline(scanline, projection.unprojectY(y+.5));
         for (int x = 0; x < sdf.width; ++x) {
             bool fill = scanline.filled(projection.unprojectX(x+.5), fillRule);
             float &sd = *sdf(x, y);
-            if ((sd > .5f) != fill)
-                sd = 1.f-sd;
+            if ((sd > sdfZeroValue) != fill)
+                sd = doubleSdfZeroValue-sd;
         }
     }
 }
 
 template <int N>
-static void multiDistanceSignCorrection(BitmapSection<float, N> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
+static void multiDistanceSignCorrection(BitmapSection<float, N> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue, FillRule fillRule) {
     int w = sdf.width, h = sdf.height;
     if (!(w && h))
         return;
     sdf.reorient(shape.getYAxisOrientation());
+    float doubleSdfZeroValue = sdfZeroValue+sdfZeroValue;
     Scanline scanline;
     bool ambiguous = false;
     std::vector<char> matchMap;
@@ -47,17 +49,17 @@ static void multiDistanceSignCorrection(BitmapSection<float, N> sdf, const Shape
             bool fill = scanline.filled(projection.unprojectX(x+.5), fillRule);
             float *msd = sdf(x, y);
             float sd = median(msd[0], msd[1], msd[2]);
-            if (sd == .5f)
+            if (sd == sdfZeroValue)
                 ambiguous = true;
-            else if ((sd > .5f) != fill) {
-                msd[0] = 1.f-msd[0];
-                msd[1] = 1.f-msd[1];
-                msd[2] = 1.f-msd[2];
+            else if ((sd > sdfZeroValue) != fill) {
+                msd[0] = doubleSdfZeroValue-msd[0];
+                msd[1] = doubleSdfZeroValue-msd[1];
+                msd[2] = doubleSdfZeroValue-msd[2];
                 *match = -1;
             } else
                 *match = 1;
-            if (N >= 4 && (msd[3] > .5f) != fill)
-                msd[3] = 1.f-msd[3];
+            if (N >= 4 && (msd[3] > sdfZeroValue) != fill)
+                msd[3] = doubleSdfZeroValue-msd[3];
             ++match;
         }
     }
@@ -74,9 +76,9 @@ static void multiDistanceSignCorrection(BitmapSection<float, N> sdf, const Shape
                     if (y < h-1) neighborMatch += *(match+w);
                     if (neighborMatch < 0) {
                         float *msd = sdf(x, y);
-                        msd[0] = 1.f-msd[0];
-                        msd[1] = 1.f-msd[1];
-                        msd[2] = 1.f-msd[2];
+                        msd[0] = doubleSdfZeroValue-msd[0];
+                        msd[1] = doubleSdfZeroValue-msd[1];
+                        msd[2] = doubleSdfZeroValue-msd[2];
                     }
                 }
                 ++match;
@@ -85,12 +87,12 @@ static void multiDistanceSignCorrection(BitmapSection<float, N> sdf, const Shape
     }
 }
 
-void distanceSignCorrection(BitmapSection<float, 3> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
-    multiDistanceSignCorrection(sdf, shape, projection, fillRule);
+void distanceSignCorrection(BitmapSection<float, 3> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue, FillRule fillRule) {
+    multiDistanceSignCorrection(sdf, shape, projection, sdfZeroValue, fillRule);
 }
 
-void distanceSignCorrection(BitmapSection<float, 4> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
-    multiDistanceSignCorrection(sdf, shape, projection, fillRule);
+void distanceSignCorrection(BitmapSection<float, 4> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue, FillRule fillRule) {
+    multiDistanceSignCorrection(sdf, shape, projection, sdfZeroValue, fillRule);
 }
 
 // Legacy API
@@ -99,6 +101,18 @@ void rasterize(const BitmapSection<float, 1> &output, const Shape &shape, const
     rasterize(output, shape, Projection(scale, translate), fillRule);
 }
 
+void distanceSignCorrection(BitmapSection<float, 1> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
+    distanceSignCorrection(sdf, shape, projection, .5f, fillRule);
+}
+
+void distanceSignCorrection(BitmapSection<float, 3> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
+    distanceSignCorrection(sdf, shape, projection, .5f, fillRule);
+}
+
+void distanceSignCorrection(BitmapSection<float, 4> sdf, const Shape &shape, const Projection &projection, FillRule fillRule) {
+    distanceSignCorrection(sdf, shape, projection, .5f, fillRule);
+}
+
 void distanceSignCorrection(const BitmapSection<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule) {
     distanceSignCorrection(sdf, shape, Projection(scale, translate), fillRule);
 }

+ 7 - 4
core/rasterization.h

@@ -12,12 +12,15 @@ namespace msdfgen {
 /// Rasterizes the shape into a monochrome bitmap.
 void rasterize(BitmapSection<float, 1> output, const Shape &shape, const Projection &projection, FillRule fillRule = FILL_NONZERO);
 /// Fixes the sign of the input signed distance field, so that it matches the shape's rasterized fill.
-void distanceSignCorrection(BitmapSection<float, 1> sdf, const Shape &shape, const Projection &projection, FillRule fillRule = FILL_NONZERO);
-void distanceSignCorrection(BitmapSection<float, 3> sdf, const Shape &shape, const Projection &projection, FillRule fillRule = FILL_NONZERO);
-void distanceSignCorrection(BitmapSection<float, 4> sdf, const Shape &shape, const Projection &projection, FillRule fillRule = FILL_NONZERO);
+void distanceSignCorrection(BitmapSection<float, 1> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue = .5f, FillRule fillRule = FILL_NONZERO);
+void distanceSignCorrection(BitmapSection<float, 3> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue = .5f, FillRule fillRule = FILL_NONZERO);
+void distanceSignCorrection(BitmapSection<float, 4> sdf, const Shape &shape, const Projection &projection, float sdfZeroValue = .5f, FillRule fillRule = FILL_NONZERO);
 
-// Old version of the function API's kept for backwards compatibility
+// Old versions of the function API's kept for backwards compatibility
 void rasterize(const BitmapSection<float, 1> &output, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
+void distanceSignCorrection(BitmapSection<float, 1> sdf, const Shape &shape, const Projection &projection, FillRule fillRule);
+void distanceSignCorrection(BitmapSection<float, 3> sdf, const Shape &shape, const Projection &projection, FillRule fillRule);
+void distanceSignCorrection(BitmapSection<float, 4> sdf, const Shape &shape, const Projection &projection, FillRule fillRule);
 void distanceSignCorrection(const BitmapSection<float, 1> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
 void distanceSignCorrection(const BitmapSection<float, 3> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);
 void distanceSignCorrection(const BitmapSection<float, 4> &sdf, const Shape &shape, const Vector2 &scale, const Vector2 &translate, FillRule fillRule = FILL_NONZERO);

+ 42 - 48
main.cpp

@@ -35,8 +35,8 @@
 #define DEFAULT_IMAGE_EXTENSION "png"
 #define SAVE_DEFAULT_IMAGE_FORMAT savePng
 #else
-#define DEFAULT_IMAGE_EXTENSION "tiff"
-#define SAVE_DEFAULT_IMAGE_FORMAT saveTiff
+#define DEFAULT_IMAGE_EXTENSION "bmp"
+#define SAVE_DEFAULT_IMAGE_FORMAT saveBmp
 #endif
 
 using namespace msdfgen;
@@ -193,15 +193,6 @@ static FontHandle *loadVarFont(FreetypeHandle *library, const char *filename) {
 #endif
 #endif
 
-template <int N>
-static void invertColor(const BitmapSection<float, N> &bitmap) {
-    for (int y = 0; y < bitmap.height; ++y) {
-        float *p = bitmap(0, y);
-        for (const float *end = p+N*bitmap.width; p < end; ++p)
-            *p = 1.f-*p;
-    }
-}
-
 static bool writeTextBitmap(FILE *file, const float *values, int cols, int rows, int rowStride) {
     for (int row = 0; row < rows; ++row) {
         const float *cur = values;
@@ -452,7 +443,7 @@ static const char *const helpText =
     "  -format <bmp / tiff / rgba / fl32 / text / textfloat / bin / binfloat / binfloatbe>\n"
 #endif
         "\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
-    "  -guessorder\n"
+    "  -guesswinding\n"
         "\tAttempts to detect if shape contours have the wrong winding and generates the SDF with the right one.\n"
     "  -help\n"
         "\tDisplays this help.\n"
@@ -483,7 +474,7 @@ static const char *const helpText =
         "\tSets the width of the range between the lowest and highest signed distance in pixels.\n"
     "  -range <range>\n"
         "\tSets the width of the range between the lowest and highest signed distance in shape units.\n"
-    "  -reverseorder\n"
+    "  -reversewinding\n"
         "\tGenerates the distance field as if the shape's vertices were in reverse order.\n"
     "  -scale <scale>\n"
         "\tSets the scale used to convert shape units to pixels.\n"
@@ -499,10 +490,10 @@ static const char *const helpText =
 #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG)
         "\tRenders an image preview using the generated distance field and saves it as a PNG file.\n"
 #else
-        "\tRenders an image preview using the generated distance field and saves it as a TIFF file.\n"
+        "\tRenders an image preview using the generated distance field and saves it as a BMP file.\n"
 #endif
     "  -testrendermulti <filename." DEFAULT_IMAGE_EXTENSION "> <width> <height>\n"
-        "\tRenders an image preview without flattening the color channels.\n"
+        "\tRenders an image preview without resolving the color channels.\n"
     "  -translate <x> <y>\n"
         "\tSets the translation of the shape in shape units.\n"
     "  -version\n"
@@ -510,7 +501,7 @@ static const char *const helpText =
     "  -windingpreprocess\n"
         "\tAttempts to fix only the contour windings assuming no self-intersections and even-odd fill rule.\n"
     "  -yflip\n"
-        "\tInverts the Y axis in the output distance field. The default order is bottom to top.\n"
+        "\tInverts the Y-axis in the output distance field. The default orientation is upward.\n"
     "\n";
 
 static const char *errorCorrectionHelpText =
@@ -612,7 +603,7 @@ int main(int argc, const char *const *argv) {
         KEEP,
         REVERSE,
         GUESS
-    } orientation = KEEP;
+    } winding = KEEP;
     unsigned long long coloringSeed = 0;
     void (*edgeColoring)(Shape &, double, unsigned long long) = &edgeColoringSimple;
     bool explicitErrorCorrectionMode = false;
@@ -982,16 +973,16 @@ int main(int argc, const char *const *argv) {
             estimateError = true;
             continue;
         }
-        ARG_CASE("-keeporder", 0) {
-            orientation = KEEP;
+        ARG_CASE("-keepwinding" ARG_CASE_OR "-keeporder", 0) {
+            winding = KEEP;
             continue;
         }
-        ARG_CASE("-reverseorder", 0) {
-            orientation = REVERSE;
+        ARG_CASE("-reversewinding" ARG_CASE_OR "-reverseorder", 0) {
+            winding = REVERSE;
             continue;
         }
-        ARG_CASE("-guessorder", 0) {
-            orientation = GUESS;
+        ARG_CASE("-guesswinding" ARG_CASE_OR "-guessorder", 0) {
+            winding = GUESS;
             continue;
         }
         ARG_CASE("-seed", 1) {
@@ -1137,9 +1128,20 @@ int main(int argc, const char *const *argv) {
 
     double avgScale = .5*(scale.x+scale.y);
     Shape::Bounds bounds = { };
-    if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS || svgExport)
+    if (autoFrame || mode == METRICS || printMetrics || winding == GUESS || svgExport)
         bounds = shape.getBounds();
 
+    if (winding == GUESS) {
+        // Get sign of signed distance outside bounds
+        Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
+        double distance = SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
+        winding = distance <= 0 ? KEEP : REVERSE;
+    }
+    if (winding == REVERSE) {
+        for (std::vector<Contour>::iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour)
+            contour->reverse();
+    }
+
     if (outputDistanceShift) {
         Range &rangeRef = rangeMode == RANGE_PX ? pxRange : range;
         double rangeShift = -outputDistanceShift*(rangeRef.upper-rangeRef.lower);
@@ -1276,39 +1278,19 @@ int main(int argc, const char *const *argv) {
         default:;
     }
 
-    if (orientation == GUESS) {
-        // Get sign of signed distance outside bounds
-        Point2 p(bounds.l-(bounds.r-bounds.l)-1, bounds.b-(bounds.t-bounds.b)-1);
-        double distance = SimpleTrueShapeDistanceFinder::oneShotDistance(shape, p);
-        orientation = distance <= 0 ? KEEP : REVERSE;
-    }
-    if (orientation == REVERSE) {
-        switch (mode) {
-            case SINGLE:
-            case PERPENDICULAR:
-                invertColor<1>(sdf);
-                break;
-            case MULTI:
-                invertColor<3>(msdf);
-                break;
-            case MULTI_AND_TRUE:
-                invertColor<4>(mtsdf);
-                break;
-            default:;
-        }
-    }
     if (scanlinePass) {
+        float sdfZeroValue = range.lower != range.upper ? float(range.lower/(range.lower-range.upper)) : .5f;
         switch (mode) {
             case SINGLE:
             case PERPENDICULAR:
-                distanceSignCorrection(sdf, shape, transformation, fillRule);
+                distanceSignCorrection(sdf, shape, transformation, sdfZeroValue, fillRule);
                 break;
             case MULTI:
-                distanceSignCorrection(msdf, shape, transformation, fillRule);
+                distanceSignCorrection(msdf, shape, transformation, sdfZeroValue, fillRule);
                 msdfErrorCorrection(msdf, shape, transformation, postErrorCorrectionConfig);
                 break;
             case MULTI_AND_TRUE:
-                distanceSignCorrection(mtsdf, shape, transformation, fillRule);
+                distanceSignCorrection(mtsdf, shape, transformation, sdfZeroValue, fillRule);
                 msdfErrorCorrection(mtsdf, shape, transformation, postErrorCorrectionConfig);
                 break;
             default:;
@@ -1344,12 +1326,16 @@ int main(int argc, const char *const *argv) {
             if (testRenderMulti) {
                 Bitmap<float, 3> render(testWidthM, testHeightM);
                 renderSDF(render, sdf, avgScale*range);
+                if (!cmpExtension(testRenderMulti, "." DEFAULT_IMAGE_EXTENSION))
+                    fputs("Warning: -testrendermulti specified with an extension other than ." DEFAULT_IMAGE_EXTENSION " but will be saved in that format anyway.\n", stderr);
                 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);
+                if (!cmpExtension(testRender, "." DEFAULT_IMAGE_EXTENSION))
+                    fputs("Warning: -testrender specified with an extension other than ." DEFAULT_IMAGE_EXTENSION " but will be saved in that format anyway.\n", stderr);
                 if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRender))
                     fputs("Failed to write test render file.\n", stderr);
             }
@@ -1368,12 +1354,16 @@ int main(int argc, const char *const *argv) {
             if (testRenderMulti) {
                 Bitmap<float, 3> render(testWidthM, testHeightM);
                 renderSDF(render, msdf, avgScale*range);
+                if (!cmpExtension(testRenderMulti, "." DEFAULT_IMAGE_EXTENSION))
+                    fputs("Warning: -testrendermulti specified with an extension other than ." DEFAULT_IMAGE_EXTENSION " but will be saved in that format anyway.\n", stderr);
                 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);
+                if (!cmpExtension(testRender, "." DEFAULT_IMAGE_EXTENSION))
+                    fputs("Warning: -testrender specified with an extension other than ." DEFAULT_IMAGE_EXTENSION " but will be saved in that format anyway.\n", stderr);
                 if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRender))
                     fputs("Failed to write test render file.\n", stderr);
             }
@@ -1392,12 +1382,16 @@ int main(int argc, const char *const *argv) {
             if (testRenderMulti) {
                 Bitmap<float, 4> render(testWidthM, testHeightM);
                 renderSDF(render, mtsdf, avgScale*range);
+                if (!cmpExtension(testRenderMulti, "." DEFAULT_IMAGE_EXTENSION))
+                    fputs("Warning: -testrendermulti specified with an extension other than ." DEFAULT_IMAGE_EXTENSION " but will be saved in that format anyway.\n", stderr);
                 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);
+                if (!cmpExtension(testRender, "." DEFAULT_IMAGE_EXTENSION))
+                    fputs("Warning: -testrender specified with an extension other than ." DEFAULT_IMAGE_EXTENSION " but will be saved in that format anyway.\n", stderr);
                 if (!SAVE_DEFAULT_IMAGE_FORMAT(render, testRender))
                     fputs("Failed to write test render file.\n", stderr);
             }