From acb01df098048ac13e2b3c6e287f95d9cd77151b Mon Sep 17 00:00:00 2001 From: Chlumsky Date: Fri, 28 May 2021 19:26:41 +0200 Subject: [PATCH] Edge coloring by edge to edge distance - experimental version --- core/edge-coloring.cpp | 281 +++++++++++++++++++++++++++++++++++++++++ core/edge-coloring.h | 6 + main.cpp | 3 +- 3 files changed, 289 insertions(+), 1 deletion(-) diff --git a/core/edge-coloring.cpp b/core/edge-coloring.cpp index 307a3d4..370f9aa 100644 --- a/core/edge-coloring.cpp +++ b/core/edge-coloring.cpp @@ -1,6 +1,12 @@ #include "edge-coloring.h" +#include +#include +#include +#include +#include "arithmetics.hpp" + namespace msdfgen { static bool isCorner(const Vector2 &aDir, const Vector2 &bDir, double crossThreshold) { @@ -215,4 +221,279 @@ void edgeColoringInkTrap(Shape &shape, double angleThreshold, unsigned long long } } +// EDGE COLORING BY DISTANCE - EXPERIMENTAL IMPLEMENTATION - WORK IN PROGRESS +#define MAX_RECOLOR_STEPS 16 +#define EDGE_DISTANCE_PRECISION 16 + +static double edgeToEdgeDistance(const EdgeSegment &a, const EdgeSegment &b, int precision) { + if (a.point(0) == b.point(0) || a.point(0) == b.point(1) || a.point(1) == b.point(0) || a.point(1) == b.point(1)) + return 0; + double iFac = 1./precision; + double minDistance = (b.point(0)-a.point(0)).length(); + for (int i = 0; i <= precision; ++i) { + double t = iFac*i; + double d = fabs(a.signedDistance(b.point(t), t).distance); + minDistance = min(minDistance, d); + } + for (int i = 0; i <= precision; ++i) { + double t = iFac*i; + double d = fabs(b.signedDistance(a.point(t), t).distance); + minDistance = min(minDistance, d); + } + return minDistance; +} + +static double splineToSplineDistance(EdgeSegment * const *edgeSegments, int aStart, int aEnd, int bStart, int bEnd, int precision) { + double minDistance = fabs(SignedDistance::INFINITE.distance); + for (int ai = aStart; ai < aEnd; ++ai) + for (int bi = bStart; bi < bEnd && minDistance; ++bi) { + double d = edgeToEdgeDistance(*edgeSegments[ai], *edgeSegments[bi], precision); + minDistance = min(minDistance, d); + } + return minDistance; +} + +static void colorSecondDegreeGraph(int *coloring, const int * const *edgeMatrix, int vertexCount, unsigned long long seed) { + for (int i = 0; i < vertexCount; ++i) { + int possibleColors = 7; + for (int j = 0; j < i; ++j) { + if (edgeMatrix[i][j]) + possibleColors &= ~(1<>= 1; + break; + case 4: + color = 2; + break; + case 5: + color = ((int) seed+1&1)<<1; + seed >>= 1; + break; + case 6: + color = ((int) seed&1)+1; + seed >>= 1; + break; + case 7: + color = int((seed+i)%3); + seed /= 3; + break; + } + coloring[i] = color; + } +} + +static int vertexPossibleColors(const int *coloring, const int *edgeVector, int vertexCount) { + int usedColors = 0; + for (int i = 0; i < vertexCount; ++i) + if (edgeVector[i]) + usedColors |= 1< &uncolored, int *coloring, const int * const *edgeMatrix, int vertex, int vertexCount) { + for (int i = vertex+1; i < vertexCount; ++i) { + if (edgeMatrix[vertex][i] && coloring[i] == coloring[vertex]) { + coloring[i] = -1; + uncolored.push(i); + } + } + for (int i = 0; i < vertex; ++i) { + if (edgeMatrix[vertex][i] && coloring[i] == coloring[vertex]) { + coloring[i] = -1; + uncolored.push(i); + } + } +} + +static bool tryAddEdge(int *coloring, int * const *edgeMatrix, int vertexCount, int vertexA, int vertexB, int *coloringBuffer) { + static const int FIRST_POSSIBLE_COLOR[8] = { -1, 0, 1, 0, 2, 2, 1, 0 }; + edgeMatrix[vertexA][vertexB] = 1; + edgeMatrix[vertexB][vertexA] = 1; + if (coloring[vertexA] != coloring[vertexB]) + return true; + int bPossibleColors = vertexPossibleColors(coloring, edgeMatrix[vertexB], vertexCount); + if (bPossibleColors) { + coloring[vertexB] = FIRST_POSSIBLE_COLOR[bPossibleColors]; + return true; + } + memcpy(coloringBuffer, coloring, sizeof(int)*vertexCount); + std::queue uncolored; + { + int *coloring = coloringBuffer; + coloring[vertexB] = FIRST_POSSIBLE_COLOR[7&~(1<(a)-**reinterpret_cast(b)); +} + +void edgeColoringByDistance(Shape &shape, double angleThreshold, unsigned long long seed) { + + std::vector edgeSegments; + std::vector splineStarts; + + double crossThreshold = sin(angleThreshold); + std::vector corners; + for (std::vector::iterator contour = shape.contours.begin(); contour != shape.contours.end(); ++contour) + if (!contour->edges.empty()) { + // Identify corners + corners.clear(); + Vector2 prevDirection = contour->edges.back()->direction(1); + int index = 0; + for (std::vector::const_iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge, ++index) { + if (isCorner(prevDirection.normalize(), (*edge)->direction(0).normalize(), crossThreshold)) + corners.push_back(index); + prevDirection = (*edge)->direction(1); + } + + splineStarts.push_back((int) edgeSegments.size()); + // Smooth contour + if (corners.empty()) + for (std::vector::iterator edge = contour->edges.begin(); edge != contour->edges.end(); ++edge) + edgeSegments.push_back(&**edge); + // "Teardrop" case + else if (corners.size() == 1) { + int corner = corners[0]; + if (contour->edges.size() >= 3) { + int m = (int) contour->edges.size(); + for (int i = 0; i < m; ++i) { + if (i == m/2) + splineStarts.push_back((int) edgeSegments.size()); + if (int(3+2.875*i/(m-1)-1.4375+.5)-3) + edgeSegments.push_back(&*contour->edges[(corner+i)%m]); + else + contour->edges[(corner+i)%m]->color = WHITE; + } + } 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]); + edgeSegments.push_back(parts[0]); + edgeSegments.push_back(parts[1]); + parts[2]->color = parts[3]->color = WHITE; + splineStarts.push_back((int) edgeSegments.size()); + edgeSegments.push_back(parts[4]); + edgeSegments.push_back(parts[5]); + } else { + edgeSegments.push_back(parts[0]); + parts[1]->color = WHITE; + splineStarts.push_back((int) edgeSegments.size()); + edgeSegments.push_back(parts[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 spline = 0; + int start = corners[0]; + 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) { + splineStarts.push_back((int) edgeSegments.size()); + ++spline; + } + edgeSegments.push_back(&*contour->edges[index]); + } + } + } + splineStarts.push_back((int) edgeSegments.size()); + + int segmentCount = (int) edgeSegments.size(); + int splineCount = (int) splineStarts.size()-1; + if (!splineCount) + return; + + std::vector distanceMatrixStorage(splineCount*splineCount); + std::vector distanceMatrix(splineCount); + for (int i = 0; i < splineCount; ++i) + distanceMatrix[i] = &distanceMatrixStorage[i*splineCount]; + const double *distanceMatrixBase = &distanceMatrixStorage[0]; + + for (int i = 0; i < splineCount; ++i) { + distanceMatrix[i][i] = -1; + for (int j = i+1; j < splineCount; ++j) { + double dist = splineToSplineDistance(&edgeSegments[0], splineStarts[i], splineStarts[i+1], splineStarts[j], splineStarts[j+1], EDGE_DISTANCE_PRECISION); + distanceMatrix[i][j] = dist; + distanceMatrix[j][i] = dist; + } + } + + std::vector graphEdgeDistances; + graphEdgeDistances.reserve(splineCount*(splineCount-1)/2); + for (int i = 0; i < splineCount; ++i) + for (int j = i+1; j < splineCount; ++j) + graphEdgeDistances.push_back(&distanceMatrix[i][j]); + int graphEdgeCount = (int) graphEdgeDistances.size(); + if (!graphEdgeDistances.empty()) + qsort(&graphEdgeDistances[0], graphEdgeDistances.size(), sizeof(const double *), &cmpDoublePtr); + + std::vector edgeMatrixStorage(splineCount*splineCount); + std::vector edgeMatrix(splineCount); + for (int i = 0; i < splineCount; ++i) + edgeMatrix[i] = &edgeMatrixStorage[i*splineCount]; + int nextEdge = 0; + for (; nextEdge < graphEdgeCount && !*graphEdgeDistances[nextEdge]; ++nextEdge) { + int elem = graphEdgeDistances[nextEdge]-distanceMatrixBase; + int row = elem/splineCount; + int col = elem%splineCount; + edgeMatrix[row][col] = 1; + edgeMatrix[col][row] = 1; + } + + std::vector coloring(2*splineCount); + colorSecondDegreeGraph(&coloring[0], &edgeMatrix[0], splineCount, seed); + for (; nextEdge < graphEdgeCount; ++nextEdge) { + int elem = graphEdgeDistances[nextEdge]-distanceMatrixBase; + tryAddEdge(&coloring[0], &edgeMatrix[0], splineCount, elem/splineCount, elem%splineCount, &coloring[splineCount]); + } + + const EdgeColor colors[3] = { YELLOW, CYAN, MAGENTA }; + int spline = -1; + for (int i = 0; i < segmentCount; ++i) { + if (splineStarts[spline+1] == i) + ++spline; + edgeSegments[i]->color = colors[coloring[spline]]; + } +} + } diff --git a/core/edge-coloring.h b/core/edge-coloring.h index 8f1a006..ffd5e6d 100644 --- a/core/edge-coloring.h +++ b/core/edge-coloring.h @@ -20,4 +20,10 @@ void edgeColoringSimple(Shape &shape, double angleThreshold, unsigned long long */ void edgeColoringInkTrap(Shape &shape, double angleThreshold, unsigned long long seed = 0); +/** The alternative coloring by distance tries to use different colors for edges that are close together. + * This should theoretically be the best strategy on average. However, since it needs to compute the distance + * between all pairs of edges, and perform a graph optimization task, it is much slower than the rest. + */ +void edgeColoringByDistance(Shape &shape, double angleThreshold, unsigned long long seed = 0); + } diff --git a/main.cpp b/main.cpp index afc57ba..7fcbf30 100644 --- a/main.cpp +++ b/main.cpp @@ -304,7 +304,7 @@ static const char *helpText = "\tSets the scale used to convert shape units to pixels asymmetrically.\n" " -autoframe\n" "\tAutomatically scales (unless specified) and translates the shape to fit.\n" - " -coloringstrategy \n" + " -coloringstrategy \n" "\tSelects the strategy of the edge coloring heuristic.\n" " -distanceshift \n" "\tShifts all normalized distances in the output distance field by this value.\n" @@ -715,6 +715,7 @@ int main(int argc, const char * const *argv) { ARG_CASE("-coloringstrategy", 1) { if (!strcmp(argv[argPos+1], "simple")) edgeColoring = edgeColoringSimple; else if (!strcmp(argv[argPos+1], "inktrap")) edgeColoring = edgeColoringInkTrap; + else if (!strcmp(argv[argPos+1], "distance")) edgeColoring = edgeColoringByDistance; else puts("Unknown coloring strategy specified."); argPos += 2;