Browse Source

Ink trap edge coloring heuristic added

Viktor Chlumský 5 years ago
parent
commit
e88da21071
3 changed files with 146 additions and 6 deletions
  1. 121 0
      core/edge-coloring.cpp
  2. 8 0
      core/edge-coloring.h
  3. 17 6
      main.cpp

+ 121 - 0
core/edge-coloring.cpp

@@ -7,6 +7,17 @@ static bool isCorner(const Vector2 &aDir, const Vector2 &bDir, double crossThres
     return dotProduct(aDir, bDir) <= 0 || fabs(crossProduct(aDir, bDir)) > crossThreshold;
     return dotProduct(aDir, bDir) <= 0 || fabs(crossProduct(aDir, bDir)) > crossThreshold;
 }
 }
 
 
+static double estimateEdgeLength(const EdgeSegment *edge) {
+    double len = 0;
+    Point2 prev = edge->point(0);
+    for (int i = 1; i <= MSDFGEN_EDGE_LENGTH_PRECISION; ++i) {
+        Point2 cur = edge->point(1./MSDFGEN_EDGE_LENGTH_PRECISION*i);
+        len += (cur-prev).length();
+        prev = cur;
+    }
+    return len;
+}
+
 static void switchColor(EdgeColor &color, unsigned long long &seed, EdgeColor banned = BLACK) {
 static void switchColor(EdgeColor &color, unsigned long long &seed, EdgeColor banned = BLACK) {
     EdgeColor combined = EdgeColor(color&banned);
     EdgeColor combined = EdgeColor(color&banned);
     if (combined == RED || combined == GREEN || combined == BLUE) {
     if (combined == RED || combined == GREEN || combined == BLUE) {
@@ -94,4 +105,114 @@ void edgeColoringSimple(Shape &shape, double angleThreshold, unsigned long long
     }
     }
 }
 }
 
 
+struct EdgeColoringInkTrapCorner {
+    int index;
+    double prevEdgeLengthEstimate;
+    bool minor;
+    EdgeColor color;
+};
+
+void edgeColoringInkTrap(Shape &shape, double angleThreshold, unsigned long long seed) {
+    typedef EdgeColoringInkTrapCorner Corner;
+    double crossThreshold = sin(angleThreshold);
+    std::vector<Corner> corners;
+    for (std::vector<Contour>::iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) {
+        // Identify corners
+        double splineLength = 0;
+        corners.clear();
+        if (!contour->edges.empty()) {
+            Vector2 prevDirection = contour->edges.back()->direction(1);
+            int index = 0;
+            for (std::vector<EdgeHolder>::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge, ++index) {
+                if (isCorner(prevDirection.normalize(), (*edge)->direction(0).normalize(), crossThreshold)) {
+                    Corner corner = { index, splineLength };
+                    corners.push_back(corner);
+                    splineLength = 0;
+                }
+                splineLength += estimateEdgeLength(*edge);
+                prevDirection = (*edge)->direction(1);
+            }
+        }
+
+        // Smooth contour
+        if (corners.empty())
+            for (std::vector<EdgeHolder>::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge)
+                (*edge)->color = WHITE;
+        // "Teardrop" case
+        else if (corners.size() == 1) {
+            EdgeColor colors[3] = { WHITE, WHITE };
+            switchColor(colors[0], seed);
+            switchColor(colors[2] = colors[0], seed);
+            int corner = corners[0].index;
+            if (contour->edges.size() >= 3) {
+                int m = (int) contour->edges.size();
+                for (int i = 0; i < m; ++i)
+                    contour->edges[(corner+i)%m]->color = (colors+1)[int(3+2.875*i/(m-1)-1.4375+.5)-3];
+            } else if (contour->edges.size() >= 1) {
+                // Less than three edge segments for three colors => edges must be split
+                EdgeSegment *parts[7] = { };
+                contour->edges[0]->splitInThirds(parts[0+3*corner], parts[1+3*corner], parts[2+3*corner]);
+                if (contour->edges.size() >= 2) {
+                    contour->edges[1]->splitInThirds(parts[3-3*corner], parts[4-3*corner], parts[5-3*corner]);
+                    parts[0]->color = parts[1]->color = colors[0];
+                    parts[2]->color = parts[3]->color = colors[1];
+                    parts[4]->color = parts[5]->color = colors[2];
+                } else {
+                    parts[0]->color = colors[0];
+                    parts[1]->color = colors[1];
+                    parts[2]->color = colors[2];
+                }
+                contour->edges.clear();
+                for (int i = 0; parts[i]; ++i)
+                    contour->edges.push_back(EdgeHolder(parts[i]));
+            }
+        }
+        // Multiple corners
+        else {
+            int cornerCount = (int) corners.size();
+            int majorCornerCount = cornerCount;
+            if (cornerCount > 3) {
+                corners.begin()->prevEdgeLengthEstimate += splineLength;
+                for (int i = 0; i < cornerCount; ++i) {
+                    if (
+                        corners[i].prevEdgeLengthEstimate > corners[(i+1)%cornerCount].prevEdgeLengthEstimate &&
+                        corners[(i+1)%cornerCount].prevEdgeLengthEstimate < corners[(i+2)%cornerCount].prevEdgeLengthEstimate
+                    ) {
+                        corners[i].minor = true;
+                        --majorCornerCount;
+                    }
+                }
+            }
+            EdgeColor color = WHITE;
+            EdgeColor initialColor = BLACK;
+            for (int i = 0; i < cornerCount; ++i) {
+                if (!corners[i].minor) {
+                    --majorCornerCount;
+                    switchColor(color, seed, EdgeColor(!majorCornerCount*initialColor));
+                    corners[i].color = color;
+                    if (!initialColor)
+                        initialColor = color;
+                }
+            }
+            for (int i = 0; i < cornerCount; ++i) {
+                if (corners[i].minor) {
+                    EdgeColor nextColor = corners[(i+1)%cornerCount].color;
+                    corners[i].color = EdgeColor((color&nextColor)^WHITE);
+                } else
+                    color = corners[i].color;
+            }
+            int spline = 0;
+            int start = corners[0].index;
+            color = corners[0].color;
+            int m = (int) contour->edges.size();
+            for (int i = 0; i < m; ++i) {
+                int index = (start+i)%m;
+                if (spline+1 < cornerCount && corners[spline+1].index == index)
+                    color = corners[++spline].color;
+                contour->edges[index]->color = color;
+            }
+        }
+    }
+}
+
 }
 }

+ 8 - 0
core/edge-coloring.h

@@ -3,6 +3,8 @@
 
 
 #include "Shape.h"
 #include "Shape.h"
 
 
+#define MSDFGEN_EDGE_LENGTH_PRECISION 4
+
 namespace msdfgen {
 namespace msdfgen {
 
 
 /** Assigns colors to edges of the shape in accordance to the multi-channel distance field technique.
 /** Assigns colors to edges of the shape in accordance to the multi-channel distance field technique.
@@ -12,4 +14,10 @@ namespace msdfgen {
  */
  */
 void edgeColoringSimple(Shape &shape, double angleThreshold, unsigned long long seed = 0);
 void edgeColoringSimple(Shape &shape, double angleThreshold, unsigned long long seed = 0);
 
 
+/** The alternative "ink trap" coloring strategy is designed for better results with typefaces
+ *  that use ink traps as a design feature. It guarantees that even if all edges that are shorter than
+ *  both their neighboring edges are removed, the coloring remains consistent with the established rules.
+ */
+void edgeColoringInkTrap(Shape &shape, double angleThreshold, unsigned long long seed = 0);
+
 }
 }

+ 17 - 6
main.cpp

@@ -290,6 +290,8 @@ static const char *helpText =
         "\tSets the scale used to convert shape units to pixels asymmetrically.\n"
         "\tSets the scale used to convert shape units to pixels asymmetrically.\n"
     "  -autoframe\n"
     "  -autoframe\n"
         "\tAutomatically scales (unless specified) and translates the shape to fit.\n"
         "\tAutomatically scales (unless specified) and translates the shape to fit.\n"
+    "  -coloringstrategy <simple / inktrap>\n"
+        "\tSelects the strategy of the edge coloring heuristic.\n"
     "  -edgecolors <sequence>\n"
     "  -edgecolors <sequence>\n"
         "\tOverrides automatic edge coloring with the specified color sequence.\n"
         "\tOverrides automatic edge coloring with the specified color sequence.\n"
     "  -errorcorrection <threshold>\n"
     "  -errorcorrection <threshold>\n"
@@ -400,6 +402,7 @@ int main(int argc, const char * const *argv) {
         GUESS
         GUESS
     } orientation = KEEP;
     } orientation = KEEP;
     unsigned long long coloringSeed = 0;
     unsigned long long coloringSeed = 0;
+    void (*edgeColoring)(Shape &, double, unsigned long long) = edgeColoringSimple;
 
 
     int argPos = 1;
     int argPos = 1;
     bool suggestHelp = false;
     bool suggestHelp = false;
@@ -575,14 +578,22 @@ int main(int argc, const char * const *argv) {
             argPos += 2;
             argPos += 2;
             continue;
             continue;
         }
         }
+        ARG_CASE("-coloringstrategy", 1) {
+            if (!strcmp(argv[argPos+1], "simple")) edgeColoring = edgeColoringSimple;
+            else if (!strcmp(argv[argPos+1], "inktrap")) edgeColoring = edgeColoringInkTrap;
+            else
+                puts("Unknown coloring strategy specified.");
+            argPos += 2;
+            continue;
+        }
         ARG_CASE("-edgecolors", 1) {
         ARG_CASE("-edgecolors", 1) {
-            static const char *allowed = " ?,cmyCMY";
+            static const char *allowed = " ?,cmwyCMWY";
             for (int i = 0; argv[argPos+1][i]; ++i) {
             for (int i = 0; argv[argPos+1][i]; ++i) {
                 for (int j = 0; allowed[j]; ++j)
                 for (int j = 0; allowed[j]; ++j)
                     if (argv[argPos+1][i] == allowed[j])
                     if (argv[argPos+1][i] == allowed[j])
-                        goto ROLL_ARG;
-                ABORT("Invalid edge coloring sequence. Use -assign <color sequence> with only the colors C, M, and Y. Separate contours by commas and use ? to keep the default assigment for a contour.");
-            ROLL_ARG:;
+                        goto EDGE_COLOR_VERIFIED;
+                ABORT("Invalid edge coloring sequence. Use -edgecolors <color sequence> with only the colors C, M, Y, and W. Separate contours by commas and use ? to keep the default assigment for a contour.");
+            EDGE_COLOR_VERIFIED:;
             }
             }
             edgeAssignment = argv[argPos+1];
             edgeAssignment = argv[argPos+1];
             argPos += 2;
             argPos += 2;
@@ -807,7 +818,7 @@ int main(int argc, const char * const *argv) {
         }
         }
         case MULTI: {
         case MULTI: {
             if (!skipColoring)
             if (!skipColoring)
-                edgeColoringSimple(shape, angleThreshold, coloringSeed);
+                edgeColoring(shape, angleThreshold, coloringSeed);
             if (edgeAssignment)
             if (edgeAssignment)
                 parseColoring(shape, edgeAssignment);
                 parseColoring(shape, edgeAssignment);
             msdf = Bitmap<float, 3>(width, height);
             msdf = Bitmap<float, 3>(width, height);
@@ -819,7 +830,7 @@ int main(int argc, const char * const *argv) {
         }
         }
         case MULTI_AND_TRUE: {
         case MULTI_AND_TRUE: {
             if (!skipColoring)
             if (!skipColoring)
-                edgeColoringSimple(shape, angleThreshold, coloringSeed);
+                edgeColoring(shape, angleThreshold, coloringSeed);
             if (edgeAssignment)
             if (edgeAssignment)
                 parseColoring(shape, edgeAssignment);
                 parseColoring(shape, edgeAssignment);
             mtsdf = Bitmap<float, 4>(width, height);
             mtsdf = Bitmap<float, 4>(width, height);