| |
| |
| #include "toonz/tcenterlinevectorizer.h" |
| #include "tsystem.h" |
| #include "tstopwatch.h" |
| #include "tpalette.h" |
| |
| |
| |
| #include "trastercm.h" |
| #include "ttoonzimage.h" |
| |
| #include "tregion.h" |
| #include "tstroke.h" |
| |
| #include "trasterimage.h" |
| |
| #define SEARCH_WINDOW 3 |
| #define JUNCTION_THICKNESS_RATIO 0.7 |
| #define JOIN_LIMIT 0.8 |
| #define THICKNESS_LIMIT 0.2 |
| #define THICKNESS_LIMIT_2 0.04 |
| |
| #undef DEBUG |
| |
| |
| struct ControlPoint { |
| TStroke *m_stroke; |
| int m_index; |
| ControlPoint(TStroke *stroke, int index) |
| : m_stroke(stroke), m_index(index) |
| { |
| } |
| TPointD getPoint() const |
| { |
| return m_stroke->getControlPoint(m_index); |
| } |
| void setPoint(const TPointD &p) |
| { |
| TThickPoint point = m_stroke->getControlPoint(m_index); |
| point.x = p.x; |
| point.y = p.y; |
| m_stroke->setControlPoint(m_index, point); |
| } |
| }; |
| |
| void renormalizeImage(TVectorImage *vi) |
| { |
| int i, j; |
| int n = vi->getStrokeCount(); |
| std::vector<ControlPoint> points; |
| points.reserve(n * 2); |
| for (i = 0; i < n; i++) { |
| TStroke *stroke = vi->getStroke(i); |
| int m = stroke->getControlPointCount(); |
| if (m > 0) { |
| if (m == 1) |
| points.push_back(ControlPoint(stroke, 0)); |
| else { |
| points.push_back(ControlPoint(stroke, 0)); |
| points.push_back(ControlPoint(stroke, m - 1)); |
| } |
| } |
| } |
| int count = points.size(); |
| for (i = 0; i < count; i++) { |
| ControlPoint &pi = points[i]; |
| TPointD posi = pi.getPoint(); |
| TPointD center = posi; |
| std::vector<int> neighbours; |
| neighbours.push_back(i); |
| for (j = i + 1; j < count; j++) { |
| TPointD posj = points[j].getPoint(); |
| double d = tdistance(posj, posi); |
| if (d < 0.01) { |
| |
| |
| |
| |
| neighbours.push_back(j); |
| center += posj; |
| } |
| } |
| int m = neighbours.size(); |
| if (m == 1) |
| continue; |
| center = center * (1.0 / m); |
| for (j = 0; j < m; j++) |
| points[neighbours[j]].setPoint(center); |
| } |
| } |
| |
| |
| |
| class Node; |
| |
| class DataPixel |
| { |
| public: |
| TPoint m_pos; |
| int m_value; |
| bool m_ink; |
| |
| Node *m_node; |
| DataPixel() : m_value(0), m_ink(false), m_node(0) {} |
| }; |
| |
| |
| #ifdef WIN32 |
| template class DV_EXPORT_API TSmartPointerT<TRasterT<DataPixel>>; |
| #endif |
| typedef TRasterPT<DataPixel> DataRasterP; |
| |
| |
| |
| class Junction; |
| |
| class Node |
| { |
| public: |
| Node *m_other; |
| DataPixel *m_pixel; |
| Node *m_prev, *m_next; |
| Junction *m_junction; |
| #ifdef DEBUG |
| bool m_flag; |
| #endif |
| bool m_visited; |
| Node() |
| : m_pixel(0), |
| m_prev(0), |
| m_next(0), |
| m_junction(0), |
| #ifdef DEBUG |
| m_flag(false), |
| #endif |
| m_visited(false) |
| { |
| } |
| }; |
| |
| |
| |
| class ProtoStroke; |
| |
| class Junction |
| { |
| public: |
| TThickPoint m_center; |
| std::deque<Node *> m_nodes; |
| int m_junctionOrder; |
| std::vector<ProtoStroke *> m_protoStrokes; |
| bool m_locked; |
| Junction() |
| : m_center(), m_nodes(), m_junctionOrder(0), m_protoStrokes(), m_locked(false) {} |
| bool isConvex(); |
| }; |
| |
| |
| |
| class ProtoStroke |
| { |
| public: |
| TPointD m_startDirection, m_endDirection; |
| Junction *m_startJunction, *m_endJunction; |
| std::deque<TThickPoint> m_points; |
| ProtoStroke() |
| : m_points(), m_startDirection(), m_endDirection(), m_startJunction(0), m_endJunction(0) {} |
| ProtoStroke(std::deque<TThickPoint>::iterator it_b, |
| std::deque<TThickPoint>::iterator it_e) |
| : m_points(it_b, it_e), m_startDirection(), m_endDirection(), m_startJunction(0), m_endJunction(0) {} |
| }; |
| |
| class ProtoRegion |
| { |
| public: |
| TPointD m_center; |
| bool m_isConvex; |
| std::vector<TThickPoint> m_points; |
| ProtoRegion(bool isConvex) |
| : m_points(), m_isConvex(isConvex), m_center() {} |
| }; |
| |
| class JunctionMerge |
| { |
| public: |
| Junction *m_junction; |
| Node *m_startNode, *m_endNode; |
| bool m_isNext; |
| JunctionMerge(Junction *junction) |
| : m_junction(junction), m_startNode(0), m_endNode(0), m_isNext(true) {} |
| JunctionMerge(Junction *junction, Node *startNode, Node *endNode, bool |
| isNext) |
| : m_junction(junction), m_startNode(startNode), m_endNode(endNode), m_isNext(isNext) {} |
| bool operator==(const JunctionMerge &op2) |
| { |
| return (m_junction == op2.m_junction); |
| } |
| }; |
| |
| class JunctionLink |
| { |
| public: |
| Junction *m_first; |
| Junction *m_second; |
| int m_order; |
| JunctionLink(Junction *first, Junction *second) |
| : m_first(first), m_second(second), m_order(0) {} |
| bool operator==(const JunctionLink &op2) const |
| { |
| return (m_first == op2.m_first && m_second == op2.m_second) || (m_first == |
| op2.m_second && |
| m_second == op2.m_first); |
| } |
| }; |
| |
| class CenterLineVectorizer |
| { |
| TPalette *m_palette; |
| |
| public: |
| |
| |
| TRasterP m_src; |
| |
| VectorizerConfiguration m_configuration; |
| |
| #ifdef DEBUG |
| TRaster32P m_output; |
| #endif |
| DataRasterP m_dataRaster; |
| vector<pair<int, DataRasterP>> m_dataRasterArray; |
| TVectorImageP m_vimage; |
| #ifdef DEBUG |
| TVectorImageP m_rimage; |
| #endif |
| |
| #ifdef DEBUG |
| vector<vector<TThickPoint>> m_outlines; |
| vector<TPointD> m_unvisitedNodes; |
| vector<vector<Node *>> m_junctionPolygons; |
| #endif |
| |
| vector<Node *> m_nodes; |
| vector<Junction *> m_junctions; |
| vector<JunctionLink> m_links; |
| list<ProtoStroke> m_protoStrokes; |
| list<ProtoRegion> m_protoRegions; |
| |
| |
| Node *findOtherSide(Node *node); |
| bool testOtherSide(Node *na1, Node *nb1, double &startDist2); |
| |
| TPointD computeCenter(Node *na, Node *nb, double &r); |
| |
| bool isHole(Node *startNode); |
| void traceLine(DataPixel *pix); |
| |
| TPoint computeGradient(DataPixel *pix) |
| { |
| assert(m_dataRaster); |
| const int wrap = m_dataRaster->getWrap(); |
| |
| TPoint g(0, 0); |
| int n, s, w, e, nw, sw, ne, se; |
| |
| w = pix[-1].m_value; |
| nw = pix[-1 + wrap].m_value; |
| sw = pix[-1 - wrap].m_value; |
| |
| e = pix[+1].m_value; |
| ne = pix[+1 + wrap].m_value; |
| se = pix[+1 - wrap].m_value; |
| |
| n = pix[+wrap].m_value; |
| s = pix[-wrap].m_value; |
| |
| g.y = -sw + ne - se + nw + 2 * (n - s); |
| g.x = -sw + ne + se - nw + 2 * (e - w); |
| return g; |
| } |
| |
| CenterLineVectorizer(const VectorizerConfiguration &configuration, TPalette *palette) |
| : m_configuration(configuration), m_palette(palette) {} |
| |
| ~CenterLineVectorizer(); |
| |
| void clearNodes(); |
| void createNodes(); |
| |
| void clearJunctions(); |
| |
| void makeOutputRaster(); |
| void makeDataRaster(const TRasterP &src); |
| |
| DataPixel *findUnvisitedInkPixel(); |
| |
| bool linkNextProtoStroke( |
| ProtoStroke *const dstProtoStroke, |
| Junction *const currJunction, |
| const int k); |
| void joinProtoStrokes(ProtoStroke *const dstProtoStroke); |
| |
| void resolveUnvisitedNode(Node *node); |
| Junction *mergeJunctions(const std::list<JunctionMerge> &junctions); |
| void joinJunctions(); |
| void createJunctionPolygon(Junction *junction); |
| void handleLinks(); |
| void handleJunctions(); |
| |
| void createStrokes(); |
| void createRegions(); |
| |
| void vectorize(); |
| void init(); |
| |
| |
| Node *createNode(DataPixel *pix); |
| |
| void link(DataPixel *pix, DataPixel *from, DataPixel *to); |
| |
| |
| |
| |
| |
| |
| |
| |
| private: |
| |
| CenterLineVectorizer(const CenterLineVectorizer &); |
| CenterLineVectorizer &operator=(const CenterLineVectorizer &); |
| }; |
| |
| |
| |
| class CompareJunctionNodes |
| { |
| TPointD m_center; |
| |
| public: |
| CompareJunctionNodes(TPointD center) |
| : m_center(center) {} |
| |
| bool operator()(const Node *n1, const Node *n2) |
| { |
| TPointD p1 = convert(n1->m_pixel->m_pos) - m_center; |
| TPointD p2 = convert(n2->m_pixel->m_pos) - m_center; |
| |
| double alpha1, alpha2; |
| |
| if (p1.x > 0) |
| alpha1 = -p1.y / sqrt(norm2(p1)); |
| else if (p1.x < 0) |
| alpha1 = 2 + p1.y / sqrt(norm2(p1)); |
| else |
| { |
| if (p1.y > 0) |
| alpha1 = -1; |
| else if (p1.y < 0) |
| alpha1 = 1; |
| else |
| assert(true); |
| } |
| |
| if (p2.x > 0) |
| alpha2 = -p2.y / sqrt(norm2(p2)); |
| else if (p2.x < 0) |
| alpha2 = 2 + p2.y / sqrt(norm2(p2)); |
| else |
| { |
| if (p2.y > 0) |
| alpha2 = -1; |
| else if (p2.y < 0) |
| alpha2 = 1; |
| else |
| assert(true); |
| } |
| |
| if (alpha2 - alpha1 > 0) |
| return true; |
| else if (alpha2 - alpha1 < 0) |
| return false; |
| else |
| return false; |
| } |
| }; |
| |
| |
| |
| |
| inline bool collinear(const TPointD &a, const TPointD &b, const TPointD &c) |
| { |
| double area = (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y); |
| return area == 0; |
| } |
| |
| |
| |
| inline bool right(const TPointD &a, const TPointD &b, const TPointD &c) |
| { |
| double area = (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y); |
| return area < 0; |
| } |
| |
| |
| |
| enum RectIntersectionResult { |
| parallel, |
| coincident, |
| intersected |
| }; |
| |
| RectIntersectionResult rectsIntersect(const TPointD &a1, const TPointD &a2, |
| const TPointD &b1, const TPointD &b2, |
| TPointD &intersection) |
| { |
| double ma, mb, ca, cb; |
| |
| if (a2.x == a1.x && b2.x == b1.x) { |
| if (collinear(a1, a2, b1) && |
| collinear(a1, a2, b2)) |
| return coincident; |
| else |
| return parallel; |
| } else if (a1.x == a2.x) { |
| mb = (b2.y - b1.y) / (b2.x - b1.x); |
| cb = b1.y - mb * b1.x; |
| intersection.x = a1.x; |
| intersection.y = mb * intersection.x + cb; |
| return intersected; |
| } else if (b1.x == b2.x) { |
| ma = (a2.y - a1.y) / (a2.x - a1.x); |
| ca = a1.y - ma * a1.x; |
| intersection.x = b1.x; |
| intersection.y = ma * intersection.x + ca; |
| return intersected; |
| } else { |
| ma = (a2.y - a1.y) / (a2.x - a1.x); |
| mb = (b2.y - b1.y) / (b2.x - b1.x); |
| |
| if (ma == mb) { |
| if (collinear(a1, a2, b1) && |
| collinear(a1, a2, b2)) |
| return coincident; |
| else |
| return parallel; |
| } |
| |
| ca = a1.y - ma * a1.x; |
| cb = b1.y - mb * b1.x; |
| |
| intersection.x = (ca - cb) / (mb - ma); |
| intersection.y = ma * intersection.x + ca; |
| return intersected; |
| } |
| } |
| |
| |
| |
| |
| bool Junction::isConvex() |
| { |
| int nonConvexAngles = 0; |
| assert(m_nodes.size() > 2); |
| std::deque<Node *>::iterator it = m_nodes.begin(); |
| std::deque<Node *>::iterator it_e = m_nodes.end() - 2; |
| for (; it != it_e; ++it) { |
| if (right( |
| convert((*(it))->m_pixel->m_pos), |
| convert((*(it + 1))->m_pixel->m_pos), |
| convert((*(it + 2))->m_pixel->m_pos))) |
| nonConvexAngles++; |
| if (nonConvexAngles > 1) |
| return false; |
| } |
| if (right( |
| convert((*(it))->m_pixel->m_pos), |
| convert((*(it + 1))->m_pixel->m_pos), |
| convert((*(m_nodes.begin()))->m_pixel->m_pos))) |
| nonConvexAngles++; |
| if (nonConvexAngles > 1) |
| return false; |
| return true; |
| } |
| |
| |
| |
| |
| CenterLineVectorizer::~CenterLineVectorizer() |
| { |
| clearNodes(); |
| clearJunctions(); |
| |
| #ifdef DEBUG |
| m_outlines.clear(); |
| m_unvisitedNodes.clear(); |
| m_junctionPolygons.clear(); |
| #endif |
| |
| m_links.clear(); |
| m_protoStrokes.clear(); |
| m_protoRegions.clear(); |
| |
| } |
| |
| |
| |
| void CenterLineVectorizer::clearNodes() |
| { |
| for (int i = 0; i < (int)m_nodes.size(); i++) |
| delete m_nodes[i]; |
| m_nodes.clear(); |
| } |
| |
| |
| |
| void CenterLineVectorizer::clearJunctions() |
| { |
| for (int i = 0; i < (int)m_junctions.size(); i++) |
| delete m_junctions[i]; |
| m_junctions.clear(); |
| } |
| |
| |
| |
| void CenterLineVectorizer::link( |
| DataPixel *pix, |
| DataPixel *srcPix, |
| DataPixel *dstPix) |
| { |
| Node *srcNode = 0, *dstNode = 0, *node = 0; |
| Node *tmp; |
| for (tmp = pix->m_node; tmp; tmp = tmp->m_other) { |
| if (tmp->m_pixel == 0) |
| continue; |
| if (tmp->m_prev && tmp->m_prev->m_pixel == srcPix) { |
| assert(srcNode == 0); |
| if (node) { |
| assert(node->m_next->m_pixel == dstPix); |
| assert(node->m_prev == 0); |
| node->m_prev = tmp->m_prev; |
| tmp->m_prev->m_next = node; |
| tmp->m_next = tmp->m_prev = 0; |
| tmp->m_pixel = 0; |
| return; |
| } |
| assert(tmp->m_next == 0); |
| srcNode = tmp->m_prev; |
| node = tmp; |
| } |
| if (tmp->m_next && tmp->m_next->m_pixel == dstPix) { |
| assert(dstNode == 0); |
| if (node) { |
| assert(node->m_prev->m_pixel == srcPix); |
| assert(node->m_next == 0); |
| node->m_next = tmp->m_next; |
| tmp->m_next->m_prev = node; |
| tmp->m_next = tmp->m_prev = 0; |
| tmp->m_pixel = 0; |
| return; |
| } |
| assert(tmp->m_prev == 0); |
| dstNode = tmp->m_next; |
| node = tmp; |
| } |
| } |
| if (!node) |
| node = createNode(pix); |
| if (!srcNode) |
| srcNode = createNode(srcPix); |
| if (!dstNode) |
| dstNode = createNode(dstPix); |
| |
| if (!node->m_next) { |
| node->m_next = dstNode; |
| assert(dstNode->m_prev == 0); |
| dstNode->m_prev = node; |
| } |
| if (!node->m_prev) { |
| node->m_prev = srcNode; |
| assert(srcNode->m_next == 0); |
| srcNode->m_next = node; |
| } |
| |
| assert(node->m_next == dstNode); |
| assert(node->m_prev == srcNode); |
| assert(dstNode->m_prev == node); |
| assert(srcNode->m_next == node); |
| } |
| |
| |
| |
| inline int colorDistance2(const TPixel32 &c0, const TPixel32 &c1) |
| { |
| return ((c0.r - c1.r) * (c0.r - c1.r) + (c0.g - c1.g) * (c0.g - c1.g) + (c0.b - c1.b) * (c0.b - c1.b)); |
| } |
| |
| |
| #define MAX_TOLERANCE 20 |
| |
| #include "tcolorstyles.h" |
| |
| void CenterLineVectorizer::makeDataRaster(const TRasterP &src) |
| { |
| m_vimage = new TVectorImage(); |
| if (!src) |
| return; |
| m_src = src; |
| |
| clearNodes(); |
| clearJunctions(); |
| |
| int ii, x, y = 0; |
| TRaster32P srcRGBM = (TRaster32P)m_src; |
| TRasterCM32P srcCM = (TRasterCM32P)m_src; |
| TRasterGR8P srcGR = (TRasterGR8P)m_src; |
| |
| DataRasterP dataRaster(m_src->getSize().lx + 2, m_src->getSize().ly + 2); |
| int ly = dataRaster->getLy(); |
| int lx = dataRaster->getLx(); |
| int wrap = dataRaster->getWrap(); |
| DataPixel *dataPix0 = dataRaster->pixels(0); |
| DataPixel *dataPix1 = dataRaster->pixels(0) + m_src->getLx() + 1; |
| for (y = 0; y < ly; y++, dataPix0 += wrap, dataPix1 += wrap) { |
| dataPix0->m_pos.x = 0; |
| dataPix1->m_pos.x = lx - 1; |
| dataPix0->m_pos.y = dataPix1->m_pos.y = y; |
| dataPix0->m_value = dataPix1->m_value = 0; |
| dataPix0->m_ink = dataPix1->m_ink = false; |
| dataPix0->m_node = dataPix1->m_node = 0; |
| } |
| dataPix0 = dataRaster->pixels(0); |
| dataPix1 = dataRaster->pixels(ly - 1); |
| for (x = 0; x < lx; x++, dataPix0++, dataPix1++) { |
| dataPix0->m_pos.x = dataPix1->m_pos.x = x; |
| dataPix0->m_pos.y = 0; |
| dataPix1->m_pos.y = ly - 1; |
| dataPix0->m_value = dataPix1->m_value = 0; |
| dataPix0->m_ink = dataPix1->m_ink = false; |
| dataPix0->m_node = dataPix1->m_node = 0; |
| } |
| |
| if (srcRGBM) { |
| assert(m_palette); |
| int inkId = m_palette->addStyle(m_configuration.m_inkColor); |
| |
| m_dataRasterArray.push_back(pair<int, DataRasterP>(inkId, dataRaster)); |
| int maxDistance2 = m_configuration.m_threshold * m_configuration.m_threshold; |
| |
| for (y = 0; y < m_src->getLy(); y++) { |
| TPixel32 *inPix = srcRGBM->pixels(y); |
| TPixel32 *inEndPix = inPix + srcRGBM->getLx(); |
| DataPixel *dataPix = dataRaster->pixels(y + 1) + 1; |
| x = 0; |
| while (inPix < inEndPix) { |
| *dataPix = DataPixel(); |
| int distance2 = colorDistance2(m_configuration.m_inkColor, *inPix); |
| |
| |
| if (y == 0 || y == m_src->getLy() - 1 || x == 0 || x == m_src->getLx() - 1) { |
| dataPix->m_value = 255; |
| dataPix->m_ink = false; |
| } else { |
| dataPix->m_value = (inPix->r + 2 * inPix->g + inPix->b) >> 2; |
| dataPix->m_ink = (distance2 < maxDistance2); |
| } |
| dataPix->m_pos.x = x++; |
| dataPix->m_pos.y = y; |
| dataPix->m_node = 0; |
| |
| inPix++; |
| dataPix++; |
| } |
| } |
| } else if (srcGR) { |
| assert(m_palette); |
| int inkId = m_palette->addStyle(m_configuration.m_inkColor); |
| |
| m_dataRasterArray.push_back(pair<int, DataRasterP>(inkId, dataRaster)); |
| int threshold = m_configuration.m_threshold; |
| |
| for (y = 0; y < m_src->getLy(); y++) { |
| TPixelGR8 *inPix = srcGR->pixels(y); |
| TPixelGR8 *inEndPix = inPix + srcGR->getLx(); |
| DataPixel *dataPix = dataRaster->pixels(y + 1) + 1; |
| x = 0; |
| while (inPix < inEndPix) { |
| *dataPix = DataPixel(); |
| |
| |
| |
| if (y == 0 || y == m_src->getLy() - 1 || x == 0 || x == m_src->getLx() - 1) { |
| dataPix->m_value = 255; |
| dataPix->m_ink = false; |
| } else { |
| dataPix->m_value = inPix->value; |
| dataPix->m_ink = (inPix->value < threshold); |
| } |
| dataPix->m_pos.x = x++; |
| dataPix->m_pos.y = y; |
| dataPix->m_node = 0; |
| |
| inPix++; |
| dataPix++; |
| } |
| } |
| } |
| |
| else if (srcCM) { |
| int currInk, nextInk = 0; |
| do { |
| int threshold = m_configuration.m_threshold; |
| currInk = nextInk; |
| nextInk = 0; |
| m_dataRasterArray.push_back(pair<int, DataRasterP>(currInk, dataRaster)); |
| |
| |
| for (y = 0; y < srcCM->getLy(); y++) { |
| TPixelCM32 *inPix = srcCM->pixels(y); |
| TPixelCM32 *inEndPix = inPix + m_src->getLx(); |
| DataPixel *dataPix = dataRaster->pixels(y + 1) + 1; |
| x = 0; |
| while (inPix < inEndPix) { |
| *dataPix = DataPixel(); |
| int value = inPix->getTone(); |
| if (value < 255 && !m_configuration.m_ignoreInkColors) { |
| int ink = inPix->getInk(); |
| if (currInk == 0) { |
| currInk = ink; |
| m_dataRasterArray.back().first = ink; |
| } else if (ink != currInk) { |
| value = 255; |
| if (nextInk == 0) { |
| for (ii = 0; ii < (int)m_dataRasterArray.size() - 1; ii++) |
| if (m_dataRasterArray[ii].first == ink) |
| break; |
| if (ii == (int)m_dataRasterArray.size() - 1) |
| nextInk = ink; |
| } |
| } |
| } |
| |
| dataPix->m_pos.x = x++; |
| dataPix->m_pos.y = y; |
| dataPix->m_value = value; |
| dataPix->m_ink = (value < threshold); |
| dataPix->m_node = 0; |
| |
| inPix++; |
| dataPix++; |
| } |
| } |
| } while (nextInk != 0); |
| if (m_configuration.m_ignoreInkColors) { |
| assert(m_dataRasterArray.size() == 1); |
| m_dataRasterArray.back().first = 1; |
| } |
| } else |
| assert(false); |
| } |
| |
| void CenterLineVectorizer::init() |
| { |
| int y; |
| |
| |
| |
| |
| |
| |
| |
| DataRasterP dataRaster = m_dataRaster; |
| const int wrap = dataRaster->getWrap(); |
| const int delta[] = {-wrap - 1, -wrap, -wrap + 1, 1, wrap + 1, wrap, wrap - 1, -1}; |
| |
| for (y = 1; y < dataRaster->getLy() - 1; y++) { |
| DataPixel *pix = dataRaster->pixels(y); |
| DataPixel *endPix = pix + dataRaster->getLx() - 1; |
| pix++; |
| for (pix++; pix < endPix; ++pix) { |
| if ((pix->m_ink == false) || |
| (pix[-wrap].m_ink && pix[wrap].m_ink && |
| pix[-1].m_ink && pix[1].m_ink)) |
| continue; |
| int i; |
| for (i = 0; i < 8; i++) |
| if (pix[delta[i]].m_ink && pix[delta[(i + 1) & 0x7]].m_ink == false) |
| break; |
| int start = i; |
| if (i == 8) |
| continue; |
| for (;;) { |
| int j = (i + 1) & 0x7; |
| assert(i < 8 && pix[delta[i]].m_ink); |
| assert(j < 8 && pix[delta[j]].m_ink == false); |
| do |
| j = (j + 1) & 0x7; |
| while (pix[delta[j]].m_ink == false); |
| assert(j < 8 && pix[delta[j]].m_ink); |
| if (((i + 2) & 0x7) != j || (i & 1) == 0) { |
| |
| link(pix, pix + delta[i], pix + delta[j]); |
| } |
| i = j; |
| assert(i < 8); |
| while (pix[delta[(i + 1) & 0x7]].m_ink) |
| i = (i + 1) & 0x7; |
| assert(i < 8 && pix[delta[i]].m_ink); |
| assert(pix[delta[(i + 1) & 0x7]].m_ink == false); |
| if (i == start) |
| break; |
| } |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| TPointD CenterLineVectorizer::computeCenter(Node *na, Node *nb, double &r) |
| { |
| TPointD pa = convert(na->m_pixel->m_pos); |
| TPointD pb = convert(nb->m_pixel->m_pos); |
| r = THICKNESS_LIMIT; |
| if (pa == pb) |
| return pa; |
| |
| TPointD center((pa + pb) * 0.5); |
| r = norm(pa - pb) * 0.5; |
| return center; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| |
| |
| double computeDistance2(Node *na, Node *nb) |
| { |
| assert(na->m_pixel); |
| assert(nb->m_pixel); |
| TPointD d = convert(na->m_pixel->m_pos - nb->m_pixel->m_pos); |
| return d * d; |
| } |
| |
| |
| |
| Node *CenterLineVectorizer::findOtherSide(Node *node) |
| { |
| DataPixel *pix = node->m_pixel; |
| |
| TPoint dir = -computeGradient(pix); |
| if (dir == TPoint(0, 0)) |
| return 0; |
| TPoint d1(tsign(dir.x), 0), d2(0, tsign(dir.y)); |
| int num = abs(dir.y), den = abs(dir.x); |
| if (num > den) { |
| tswap(d1, d2); |
| tswap(num, den); |
| } |
| TPoint pos = pix->m_pos; |
| int i; |
| for (i = 0;; i++) { |
| TPoint q(pos.x + d1.x * i + d2.x * num * i / den, pos.y + d1.y * i + |
| d2.y * num * i / den); |
| |
| DataPixel *nextPix = m_dataRaster->pixels(q.y) + q.x; |
| if (nextPix->m_ink == false) |
| break; |
| pix = nextPix; |
| } |
| assert(pix); |
| if (!pix->m_node) { |
| const int wrap = m_dataRaster->getWrap(); |
| if (pix[-1].m_node) |
| pix--; |
| else if (pix[1].m_node) |
| pix++; |
| else if (pix[wrap].m_node) |
| pix += wrap; |
| else if (pix[-wrap].m_node) |
| pix -= wrap; |
| else { |
| assert(0); |
| } |
| } |
| if (!pix->m_node) |
| return 0; |
| Node *q = pix->m_node; |
| while (q->m_pixel == 0 && q->m_other) |
| q = q->m_other; |
| assert(q && q->m_pixel == pix); |
| |
| for (i = 0; i < 5; i++) { |
| if (!q->m_prev) |
| break; |
| q = q->m_prev; |
| } |
| |
| Node *best = q; |
| double bestDist2 = computeDistance2(q, node); |
| for (i = 0; i < 10; i++) { |
| q = q->m_next; |
| if (!q) |
| break; |
| double dist2 = computeDistance2(q, node); |
| if (dist2 < bestDist2) { |
| bestDist2 = dist2; |
| best = q; |
| } |
| } |
| |
| return best; |
| } |
| |
| |
| |
| TPointD findDirection(Node *na, Node *nb, int skip) |
| { |
| for (int i = 0; i < skip; i++) { |
| na = na->m_next; |
| nb = nb->m_prev; |
| } |
| TPointD p0 = 0.5 * (convert(na->m_pixel->m_pos) + convert(nb->m_pixel->m_pos)); |
| na = na->m_next; |
| nb = nb->m_prev; |
| TPointD p1 = 0.5 * (convert(na->m_pixel->m_pos) + convert(nb->m_pixel->m_pos)); |
| na = na->m_next; |
| nb = nb->m_prev; |
| TPointD p2 = 0.5 * (convert(na->m_pixel->m_pos) + convert(nb->m_pixel->m_pos)); |
| TPointD dir = 0.25 * (3 * (p1 - p0) + (p2 - p1)); |
| double dirn = norm(dir); |
| if (dirn > 0) |
| return dir * (1 / dirn); |
| else |
| return dir; |
| } |
| |
| |
| |
| void setEndpointsDirections(ProtoStroke &protoStroke) |
| { |
| assert(!protoStroke.m_points.empty()); |
| int size = protoStroke.m_points.size(); |
| double startThickness = 0, endThickness = 0; |
| if (size == 1) |
| return; |
| if (size < 5) { |
| protoStroke.m_startDirection = protoStroke.m_points[0] - |
| protoStroke.m_points[1]; |
| protoStroke.m_endDirection = protoStroke.m_points[size - 1] - |
| protoStroke.m_points[size - 2]; |
| } else if (size < 10) { |
| assert(protoStroke.m_points.front() != protoStroke.m_points.back()); |
| protoStroke.m_startDirection = protoStroke.m_points[0] - |
| protoStroke.m_points[2]; |
| protoStroke.m_endDirection = protoStroke.m_points[size - 1] - |
| protoStroke.m_points[size - 3]; |
| startThickness = 0.5 * (protoStroke.m_points[1].thick + |
| protoStroke.m_points[2].thick); |
| endThickness = 0.5 * (protoStroke.m_points[size - 2].thick + |
| protoStroke.m_points[size - 3].thick); |
| } else { |
| protoStroke.m_startDirection = 4 * protoStroke.m_points[0]; |
| protoStroke.m_endDirection = 4 * protoStroke.m_points[size - 1]; |
| |
| for (int i = 0; i < 4; ++i) { |
| protoStroke.m_startDirection -= protoStroke.m_points[3 + i]; |
| protoStroke.m_endDirection -= protoStroke.m_points[size - 4 - i]; |
| startThickness += protoStroke.m_points[i].thick; |
| endThickness += protoStroke.m_points[size - i - 1].thick; |
| } |
| protoStroke.m_startDirection = protoStroke.m_startDirection * (1 / 4.0); |
| protoStroke.m_endDirection = protoStroke.m_endDirection * (1 / 4.0); |
| startThickness *= (1 / 4.0); |
| endThickness *= (1 / 4.0); |
| } |
| |
| double startNorm = norm(protoStroke.m_startDirection); |
| if (startNorm) |
| protoStroke.m_startDirection = protoStroke.m_startDirection * |
| (1 / startNorm); |
| |
| double endNorm = norm(protoStroke.m_endDirection); |
| if (endNorm) |
| protoStroke.m_endDirection = protoStroke.m_endDirection * (1 / endNorm); |
| |
| if (startThickness != 0) |
| protoStroke.m_points[0].thick = protoStroke.m_points[1].thick = |
| protoStroke.m_points[2].thick = startThickness; |
| if (endThickness != 0) |
| protoStroke.m_points[size - 1].thick = protoStroke.m_points[size - 2].thick = |
| protoStroke.m_points[size - 3].thick = endThickness; |
| } |
| |
| |
| |
| bool CenterLineVectorizer::testOtherSide(Node *na1, Node *nb1, double |
| &startDist2) |
| { |
| if (na1->m_pixel->m_pos == nb1->m_pixel->m_pos || na1->m_next == nb1 || na1->m_prev == nb1 || na1->m_next->m_next == nb1 || na1->m_prev->m_prev == nb1) |
| return false; |
| |
| Node *na2 = na1->m_next; |
| for (int i = 1; i < 3; ++i) { |
| na2 = na2->m_next; |
| Node *nb2 = findOtherSide(na2); |
| if (nb2 == 0 && i == 3) |
| return true; |
| if (nb2 == 0) |
| continue; |
| |
| double newStartDist2 = computeDistance2(na2, nb2); |
| if (newStartDist2 > startDist2) |
| startDist2 = newStartDist2; |
| for (int j = 0; j < 3 * i + 1; ++j) { |
| if (nb2 == nb1) |
| return true; |
| nb2 = nb2->m_next; |
| } |
| } |
| return false; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| bool isJunction(const Node *na, const Node *nb) |
| { |
| TPointD pa = convert(na->m_pixel->m_pos); |
| TPointD pb = convert(nb->m_pixel->m_pos); |
| |
| Node *na0 = na->m_prev; |
| Node *na1 = na->m_next; |
| Node *nb0 = nb->m_next; |
| Node *nb1 = nb->m_prev; |
| |
| TPointD da = convert(na1->m_pixel->m_pos) - pa; |
| TPointD db = convert(nb1->m_pixel->m_pos) - pb; |
| |
| TPointD da0 = pa - convert(na0->m_pixel->m_pos); |
| TPointD db0 = pb - convert(nb0->m_pixel->m_pos); |
| |
| if ((da0.x * da.x + da0.y * da.y) / (norm(da0) * norm(da)) <= 0) { |
| return true; |
| } |
| |
| if ((db0.x * db.x + db0.y * db.y) / (norm(db0) * norm(db)) <= 0) { |
| return true; |
| } |
| |
| if (right(TPointD(0, 0), da, db)) { |
| if ((da.x * db.x + da.y * db.y) / (norm(da) * norm(db)) <= 0) { |
| return true; |
| } |
| } else { |
| if ((da.x * db.x + da.y * db.y) / (norm(da) * norm(db)) <= -0.5) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| |
| void CenterLineVectorizer::traceLine(DataPixel *pix) |
| { |
| Junction *startJunction = 0; |
| Junction *endJunction = 0; |
| assert(m_dataRaster); |
| const int wrap = m_dataRaster->getWrap(); |
| |
| if (!pix->m_ink) |
| return; |
| while (pix->m_node == 0) |
| pix -= wrap; |
| |
| Node *node = pix->m_node; |
| while (node && node->m_pixel == 0 && node->m_other) |
| node = node->m_other; |
| assert(node && node->m_pixel == pix); |
| |
| Node *naa = node; |
| |
| bool isSamePixel; |
| if (node->m_other && node->m_other->m_pixel && (node->m_pixel->m_pos == |
| node->m_other->m_pixel->m_pos)) { |
| node = node->m_other; |
| isSamePixel = true; |
| } else { |
| node = findOtherSide(node); |
| isSamePixel = false; |
| } |
| if (node == 0) |
| return; |
| Node *nbb = node; |
| |
| if (naa->m_visited || nbb->m_visited) |
| return; |
| |
| double startDist2 = computeDistance2(naa, nbb); |
| if (startDist2 > 2000) |
| return; |
| if (startDist2 < THICKNESS_LIMIT_2) |
| startDist2 = THICKNESS_LIMIT_2; |
| |
| |
| if (!isSamePixel && !testOtherSide(naa, nbb, startDist2)) |
| return; |
| |
| double minDist2 = 0; |
| double maxDist2 = startDist2 + 3.0; |
| if (startDist2 > 36) { |
| minDist2 = startDist2 / 2; |
| maxDist2 = startDist2 + 0.25 * startDist2; |
| } |
| if (naa->m_next == nbb || naa->m_prev == nbb) |
| return; |
| |
| naa->m_visited = nbb->m_visited = true; |
| std::deque<TThickPoint> dpoints; |
| |
| for (int k = 0; k < 2; k++) { |
| Node *na = naa; |
| Node *nb = nbb; |
| |
| for (;;) { |
| TPoint &pa = na->m_pixel->m_pos; |
| TPoint &pb = nb->m_pixel->m_pos; |
| TPoint ba = pb - pa; |
| |
| Node *na1 = na->m_next; |
| Node *nb1 = nb->m_prev; |
| |
| |
| TPoint da = na1->m_pixel->m_pos - pb; |
| TPoint db = nb1->m_pixel->m_pos - pa; |
| |
| if (na1->m_junction || nb1->m_junction) { |
| na1->m_visited = true; |
| nb1->m_visited = true; |
| TPointD p1; |
| if (na1->m_junction && nb1->m_junction) { |
| |
| } else { |
| if (na1->m_junction) { |
| p1 = convert(nb1->m_pixel->m_pos); |
| nb1->m_junction = na1->m_junction; |
| na1->m_junction->m_nodes.push_front(nb1); |
| } else { |
| p1 = convert(na1->m_pixel->m_pos); |
| na1->m_junction = nb1->m_junction; |
| nb1->m_junction->m_nodes.push_back(na1); |
| } |
| } |
| double r; |
| TPointD center; |
| TThickPoint point; |
| |
| center = computeCenter(na, nb, r); |
| if (r < THICKNESS_LIMIT) |
| r = THICKNESS_LIMIT; |
| point = TThickPoint(center, r); |
| if (k == 0) |
| dpoints.push_front(point); |
| else |
| dpoints.push_back(point); |
| |
| center = computeCenter(na1, nb1, r); |
| if (r < THICKNESS_LIMIT) |
| r = THICKNESS_LIMIT; |
| point = TThickPoint(center, r); |
| if (k == 0) |
| dpoints.push_front(point); |
| else |
| dpoints.push_back(point); |
| |
| if (k == 0) |
| startJunction = na1->m_junction; |
| else |
| endJunction = na1->m_junction; |
| |
| break; |
| } |
| |
| |
| if (norm2(db) >= norm2(da)) { |
| if (na1->m_visited) |
| break; |
| |
| na = na1; |
| na->m_visited = true; |
| #ifdef DEBUG |
| getOutPix(na->m_pixel) = TPixel32::Red; |
| #endif |
| } else { |
| if (nb1->m_visited) |
| break; |
| |
| nb = nb1; |
| nb->m_visited = true; |
| #ifdef DEBUG |
| getOutPix(nb->m_pixel) = TPixel32::Green; |
| #endif |
| } |
| double distance2 = computeDistance2(na, nb); |
| if (distance2 < 0.25) |
| distance2 = 0.25; |
| |
| double r; |
| TPointD center = computeCenter(na, nb, r); |
| if (r < THICKNESS_LIMIT) |
| r = THICKNESS_LIMIT; |
| TThickPoint point(center, r); |
| TPointI pos = convert(center); |
| |
| |
| |
| |
| if (distance2 > maxDist2 || distance2 < minDist2 || na->m_next == nb || na->m_prev == nb |
| |
| |
| ) { |
| if (na->m_next == nb || na->m_prev == nb || na->m_next->m_next == nb || |
| na->m_prev->m_prev == nb) { |
| if (k == 0) { |
| if (!dpoints.empty()) |
| dpoints.pop_front(); |
| if (!dpoints.empty()) |
| dpoints.pop_front(); |
| } else { |
| if (!dpoints.empty()) |
| dpoints.pop_back(); |
| if (!dpoints.empty()) |
| dpoints.pop_back(); |
| } |
| } |
| |
| |
| na->m_junction = nb->m_junction = new Junction(); |
| na->m_junction->m_nodes.push_back(nb); |
| nb->m_junction->m_nodes.push_back(na); |
| m_junctions.push_back(na->m_junction); |
| |
| if (k == 0) |
| startJunction = na->m_junction; |
| else |
| endJunction = na->m_junction; |
| break; |
| } |
| if (k == 0) |
| dpoints.push_front(point); |
| else |
| dpoints.push_back(point); |
| } |
| |
| tswap(naa, nbb); |
| } |
| if (dpoints.size() == 0) |
| naa->m_visited = nbb->m_visited = false; |
| else { |
| m_protoStrokes.push_back(ProtoStroke(dpoints.begin(), dpoints.end())); |
| setEndpointsDirections(m_protoStrokes.back()); |
| if (startJunction) { |
| m_protoStrokes.back().m_startJunction = startJunction; |
| startJunction->m_protoStrokes.push_back(&m_protoStrokes.back()); |
| } |
| if (endJunction) { |
| m_protoStrokes.back().m_endJunction = endJunction; |
| endJunction->m_protoStrokes.push_back(&m_protoStrokes.back()); |
| } |
| } |
| } |
| |
| |
| |
| Node *CenterLineVectorizer::createNode(DataPixel *pix) |
| { |
| Node *node = new Node(); |
| node->m_pixel = pix; |
| node->m_other = pix->m_node; |
| pix->m_node = node; |
| m_nodes.push_back(node); |
| return node; |
| } |
| |
| |
| |
| void CenterLineVectorizer::createNodes() |
| { |
| } |
| |
| |
| |
| #ifdef DEBUG |
| void CenterLineVectorizer::outputNodes(Node *node) |
| { |
| std::vector<TThickPoint> points; |
| Node *start = node, *curr = node; |
| int i = 0; |
| do { |
| TThickPoint point(convert(curr->m_pixel->m_pos), 0); |
| points.push_back(point); |
| |
| curr->m_flag = true; |
| assert(curr->m_next); |
| curr = curr->m_next; |
| i++; |
| } while (curr != start); |
| TThickPoint point(convert(curr->m_pixel->m_pos), 0); |
| points.push_back(point); |
| m_outlines.push_back(points); |
| } |
| #endif |
| |
| |
| |
| bool CenterLineVectorizer::linkNextProtoStroke( |
| ProtoStroke *const dstProtoStroke, |
| Junction *const currJunction, |
| int k) |
| { |
| assert(dstProtoStroke); |
| assert(currJunction); |
| |
| return false; |
| if (k == 0 && dstProtoStroke->m_startJunction->m_locked || |
| k == 1 && dstProtoStroke->m_endJunction->m_locked) |
| return false; |
| |
| ProtoStroke *srcProtoStroke = 0; |
| Junction *nextJunction = 0; |
| |
| std::vector<ProtoStroke *>::iterator it; |
| std::vector<ProtoStroke *>::iterator it_e = |
| currJunction->m_protoStrokes.end(); |
| std::vector<ProtoStroke *>::iterator it_candidate = it_e; |
| double candidateProbability = JOIN_LIMIT; |
| int candidate_k; |
| for (it = currJunction->m_protoStrokes.begin(); it != it_e; it++) { |
| |
| if (*it == dstProtoStroke) { |
| it = currJunction->m_protoStrokes.erase(it); |
| it_e = currJunction->m_protoStrokes.end(); |
| if (candidateProbability == JOIN_LIMIT) |
| it_candidate = it_e; |
| if (it == it_e) |
| break; |
| } |
| double probability; |
| int next_k; |
| if (((*it))->m_startJunction == currJunction) |
| next_k = 0; |
| else if ((*it)->m_endJunction == currJunction) |
| next_k = 1; |
| else |
| assert(false); |
| |
| if (k == 0) { |
| if (next_k == 0) |
| probability = 1 - norm2(dstProtoStroke->m_startDirection + |
| (*it)->m_startDirection); |
| else |
| probability = 1 - norm2(dstProtoStroke->m_startDirection + |
| (*it)->m_endDirection); |
| } else { |
| if (next_k == 0) |
| probability = 1 - norm2(dstProtoStroke->m_endDirection + |
| (*it)->m_startDirection); |
| else |
| probability = 1 - norm2(dstProtoStroke->m_endDirection + |
| (*it)->m_endDirection); |
| } |
| |
| if (probability > candidateProbability || |
| |
| (*it)->m_points.size() < 3) { |
| it_candidate = it; |
| candidateProbability = probability; |
| candidate_k = next_k; |
| } |
| } |
| |
| if (it_candidate == it_e) |
| return false; |
| srcProtoStroke = *it_candidate; |
| assert(srcProtoStroke); |
| |
| if ((*it_candidate)->m_startJunction == (*it_candidate)->m_endJunction) |
| return false; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (candidate_k == 0) |
| nextJunction = (*it_candidate)->m_endJunction; |
| else |
| nextJunction = (*it_candidate)->m_startJunction; |
| |
| if (k == 0) { |
| if (candidate_k == 0) { |
| dstProtoStroke->m_points.insert( |
| dstProtoStroke->m_points.begin(), |
| srcProtoStroke->m_points.rbegin(), |
| srcProtoStroke->m_points.rend()); |
| } else { |
| dstProtoStroke->m_points.insert( |
| dstProtoStroke->m_points.begin(), |
| srcProtoStroke->m_points.begin(), |
| srcProtoStroke->m_points.end()); |
| } |
| } else { |
| if (candidate_k == 0) { |
| dstProtoStroke->m_points.insert( |
| dstProtoStroke->m_points.end(), |
| srcProtoStroke->m_points.begin(), |
| srcProtoStroke->m_points.end()); |
| } else { |
| dstProtoStroke->m_points.insert( |
| dstProtoStroke->m_points.end(), |
| srcProtoStroke->m_points.rbegin(), |
| srcProtoStroke->m_points.rend()); |
| } |
| } |
| |
| srcProtoStroke->m_points.clear(); |
| srcProtoStroke->m_startJunction = 0; |
| srcProtoStroke->m_endJunction = 0; |
| |
| |
| currJunction->m_protoStrokes.erase(it_candidate); |
| |
| |
| if (nextJunction) { |
| it = std::find( |
| nextJunction->m_protoStrokes.begin(), |
| nextJunction->m_protoStrokes.end(), |
| srcProtoStroke); |
| assert(it != nextJunction->m_protoStrokes.end()); |
| nextJunction->m_protoStrokes.erase(it); |
| nextJunction->m_protoStrokes.push_back(dstProtoStroke); |
| } |
| |
| if (k == 0) { |
| dstProtoStroke->m_startJunction = nextJunction; |
| |
| if (candidate_k == 0) |
| dstProtoStroke->m_startDirection = srcProtoStroke->m_endDirection; |
| else |
| dstProtoStroke->m_startDirection = srcProtoStroke->m_startDirection; |
| } else { |
| dstProtoStroke->m_endJunction = nextJunction; |
| |
| if (candidate_k == 0) |
| dstProtoStroke->m_endDirection = srcProtoStroke->m_endDirection; |
| else |
| dstProtoStroke->m_endDirection = srcProtoStroke->m_startDirection; |
| } |
| if (!nextJunction) |
| return false; |
| return true; |
| } |
| |
| |
| |
| void CenterLineVectorizer::joinProtoStrokes(ProtoStroke *const |
| dstProtoStroke) |
| { |
| |
| for (int k = 0; k < 2; k++) { |
| for (;;) { |
| Junction *currJunction; |
| if (k == 0 && !dstProtoStroke->m_startJunction) |
| break; |
| else if (k == 0) |
| currJunction = dstProtoStroke->m_startJunction; |
| else if (!dstProtoStroke->m_endJunction) |
| break; |
| else |
| currJunction = dstProtoStroke->m_endJunction; |
| |
| if (!linkNextProtoStroke(dstProtoStroke, currJunction, k)) |
| break; |
| } |
| } |
| std::vector<ProtoStroke *>::iterator it; |
| if (dstProtoStroke->m_startJunction) { |
| it = std::find( |
| dstProtoStroke->m_startJunction->m_protoStrokes.begin(), |
| dstProtoStroke->m_startJunction->m_protoStrokes.end(), |
| dstProtoStroke); |
| if (it != dstProtoStroke->m_startJunction->m_protoStrokes.end()) |
| dstProtoStroke->m_startJunction->m_protoStrokes.erase(it); |
| dstProtoStroke->m_startJunction = 0; |
| } |
| if (dstProtoStroke->m_endJunction) { |
| it = std::find( |
| dstProtoStroke->m_endJunction->m_protoStrokes.begin(), |
| dstProtoStroke->m_endJunction->m_protoStrokes.end(), |
| dstProtoStroke); |
| if (it != dstProtoStroke->m_endJunction->m_protoStrokes.end()) |
| dstProtoStroke->m_endJunction->m_protoStrokes.erase(it); |
| dstProtoStroke->m_endJunction = 0; |
| } |
| } |
| |
| |
| |
| bool y_intersect(const double y0, const TPointD p1, const TPointD p2, |
| double &x0) |
| { |
| if ((y0 < p1.y && y0 < p2.y) || |
| (y0 > p1.y && y0 > p2.y)) |
| return false; |
| |
| x0 = (p1.x * (p2.y - y0) + p2.x * (y0 - p1.y)) / (p2.y - p1.y); |
| return true; |
| } |
| |
| bool isContained(TThickPoint point, std::deque<Node *> &polygon) |
| { |
| std::deque<Node *>::iterator it_b = polygon.begin(); |
| std::deque<Node *>::iterator it_e = polygon.end(); |
| std::deque<Node *>::iterator it; |
| |
| int intersectionsNumber = 0; |
| |
| for (it = it_b; it < it_e; ++it) { |
| double x; |
| TPointD p1, p2; |
| |
| p1 = convert((*it)->m_pixel->m_pos); |
| if ((it + 1) == it_e) |
| p2 = convert((*it_b)->m_pixel->m_pos); |
| else |
| p2 = convert((*(it + 1))->m_pixel->m_pos); |
| |
| if (point.y == p1.y || point.y == p2.y || p1.y == p2.y) |
| continue; |
| if (y_intersect(point.y, p1, p2, x) && x >= point.x) |
| ++intersectionsNumber; |
| } |
| |
| return (intersectionsNumber % 2 == 0) ? false : true; |
| } |
| |
| |
| |
| Junction *CenterLineVectorizer::mergeJunctions(const std::list<JunctionMerge> &junctions) |
| { |
| Junction *newJunction = new Junction(); |
| |
| std::list<JunctionMerge>::const_iterator it_junction; |
| std::deque<Node *>::iterator it_startNode, it_endNode; |
| |
| for (it_junction = junctions.begin(); it_junction != junctions.end(); |
| ++it_junction) { |
| newJunction->m_center += it_junction->m_junction->m_center; |
| |
| std::deque<Node *>::iterator it_node; |
| if (it_junction->m_startNode == 0 && it_junction->m_endNode == 0) { |
| for (it_node = it_junction->m_junction->m_nodes.begin(); |
| it_node != it_junction->m_junction->m_nodes.end(); ++it_node) { |
| (*it_node)->m_junction = newJunction; |
| newJunction->m_nodes.push_back(*it_node); |
| } |
| } else { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| it_startNode = std::find(newJunction->m_nodes.begin(), |
| newJunction->m_nodes.end(), |
| it_junction->m_startNode); |
| assert(it_startNode != newJunction->m_nodes.end()); |
| it_endNode = std::find(it_junction->m_junction->m_nodes.begin(), |
| it_junction->m_junction->m_nodes.end(), |
| it_junction->m_endNode); |
| assert(it_endNode != it_junction->m_junction->m_nodes.end()); |
| |
| for (it_node = it_junction->m_junction->m_nodes.begin(); |
| it_node != it_junction->m_junction->m_nodes.end(); ++it_node) { |
| (*it_node)->m_junction = newJunction; |
| } |
| |
| if (it_junction->m_isNext) { |
| ++it_startNode; |
| newJunction->m_nodes.insert(it_startNode, |
| it_junction->m_junction->m_nodes.begin(), it_endNode); |
| it_startNode = std::find(newJunction->m_nodes.begin(), |
| newJunction->m_nodes.end(), |
| it_junction->m_startNode); |
| assert(it_startNode != newJunction->m_nodes.end()); |
| ++it_startNode; |
| newJunction->m_nodes.insert(it_startNode, it_endNode, |
| it_junction->m_junction->m_nodes.end()); |
| } else { |
| ++it_endNode; |
| newJunction->m_nodes.insert(it_startNode, |
| it_junction->m_junction->m_nodes.begin(), it_endNode); |
| it_startNode = std::find(newJunction->m_nodes.begin(), |
| newJunction->m_nodes.end(), |
| it_junction->m_startNode); |
| assert(it_startNode != newJunction->m_nodes.end()); |
| newJunction->m_nodes.insert(it_startNode, it_endNode, |
| it_junction->m_junction->m_nodes.end()); |
| } |
| } |
| |
| std::vector<ProtoStroke *>::iterator it_protoStroke; |
| for (it_protoStroke = it_junction->m_junction->m_protoStrokes.begin(); |
| it_protoStroke != it_junction->m_junction->m_protoStrokes.end(); |
| ++it_protoStroke) { |
| if ((*it_protoStroke)->m_startJunction == it_junction->m_junction) { |
| (*it_protoStroke)->m_startJunction = newJunction; |
| newJunction->m_protoStrokes.push_back(*it_protoStroke); |
| } |
| if ((*it_protoStroke)->m_endJunction == it_junction->m_junction) { |
| (*it_protoStroke)->m_endJunction = newJunction; |
| newJunction->m_protoStrokes.push_back(*it_protoStroke); |
| } |
| } |
| |
| std::vector<JunctionLink>::iterator it_links; |
| for (it_links = m_links.begin(); it_links != m_links.end(); ++it_links) { |
| if (it_links->m_first == it_junction->m_junction) |
| it_links->m_first = |
| newJunction; |
| if (it_links->m_second == it_junction->m_junction) |
| it_links->m_second = |
| newJunction; |
| } |
| |
| |
| std::vector<Junction *>::iterator it; |
| it = std::find( |
| m_junctions.begin(), |
| m_junctions.end(), |
| it_junction->m_junction); |
| assert(it != m_junctions.end()); |
| delete it_junction->m_junction; |
| m_junctions.erase(it); |
| } |
| double size = junctions.size(); |
| newJunction->m_center = newJunction->m_center * (1 / size); |
| |
| std::vector<ProtoStroke *>::iterator it_protoStroke; |
| for (it_protoStroke = newJunction->m_protoStrokes.begin(); |
| it_protoStroke != newJunction->m_protoStrokes.end(); |
| ++it_protoStroke) { |
| if ((*it_protoStroke)->m_points.size() < 6 && |
| (*it_protoStroke)->m_startJunction == newJunction && |
| (*it_protoStroke)->m_endJunction == newJunction) { |
| (*it_protoStroke)->m_points.clear(); |
| (*it_protoStroke)->m_startJunction = (*it_protoStroke)->m_endJunction = |
| 0; |
| |
| ProtoStroke *protoStroke = *it_protoStroke; |
| for (;;) { |
| std::vector<ProtoStroke *>::iterator it; |
| it = std::find(newJunction->m_protoStrokes.begin(), |
| newJunction->m_protoStrokes.end(), |
| protoStroke); |
| if (it == newJunction->m_protoStrokes.end()) |
| break; |
| newJunction->m_protoStrokes.erase(it); |
| } |
| if (newJunction->m_protoStrokes.empty()) |
| break; |
| it_protoStroke = newJunction->m_protoStrokes.begin(); |
| } |
| } |
| |
| return newJunction; |
| } |
| |
| |
| |
| void CenterLineVectorizer::joinJunctions() |
| { |
| for (int i = 0; i < (int)m_junctions.size(); i++) { |
| Junction *currJunction = m_junctions[i]; |
| |
| |
| |
| std::list<JunctionMerge> joinJunctions; |
| joinJunctions.push_back(JunctionMerge(currJunction)); |
| |
| |
| std::deque<Node *>::iterator it; |
| for (it = currJunction->m_nodes.begin(); it != currJunction->m_nodes.end(); |
| it++) { |
| Node *currNode = *it; |
| Node *prevNode = *it; |
| Node *nextNode = *it; |
| std::vector<JunctionLink>::iterator it_link; |
| int j; |
| for (j = 0; true; ++j) { |
| prevNode = prevNode->m_prev; |
| prevNode->m_visited = true; |
| if (prevNode->m_junction != 0 && prevNode->m_junction != currJunction) { |
| |
| it_link = std::find(m_links.begin(), m_links.end(), |
| JunctionLink(currJunction, prevNode->m_junction)); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (it_link == m_links.end() && |
| std::find(joinJunctions.begin(), joinJunctions.end(), |
| JunctionMerge(prevNode->m_junction)) == joinJunctions.end()) { |
| joinJunctions.push_back(JunctionMerge(prevNode->m_junction, currNode, |
| prevNode, false)); |
| } |
| } |
| if (prevNode->m_visited) |
| break; |
| } |
| for (j = 0; true; ++j) { |
| nextNode = nextNode->m_next; |
| prevNode->m_visited = true; |
| if (nextNode->m_junction != 0 && nextNode->m_junction != currJunction) { |
| |
| it_link = std::find(m_links.begin(), m_links.end(), |
| JunctionLink(currJunction, nextNode->m_junction)); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (it_link == m_links.end() && |
| std::find(joinJunctions.begin(), joinJunctions.end(), |
| JunctionMerge(nextNode->m_junction)) == joinJunctions.end()) { |
| joinJunctions.push_back(JunctionMerge(nextNode->m_junction, currNode, |
| nextNode, true)); |
| } |
| } |
| if (nextNode->m_visited) |
| break; |
| } |
| } |
| if (joinJunctions.size() > 1) { |
| |
| |
| |
| |
| |
| |
| |
| |
| Junction *newJunction = mergeJunctions(joinJunctions); |
| assert(newJunction); |
| m_junctions.push_back(newJunction); |
| } |
| joinJunctions.clear(); |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void CenterLineVectorizer::createJunctionPolygon(Junction *junction) |
| { |
| |
| |
| |
| std::deque<Node *>::iterator it, it_temp; |
| for (it = junction->m_nodes.begin(); it != junction->m_nodes.end(); it++) { |
| int step = 0; |
| |
| Node *currNode; |
| Node *otherNode; |
| |
| if (it + 1 == junction->m_nodes.end()) |
| otherNode = *junction->m_nodes.begin(); |
| else |
| otherNode = *(it + 1); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| currNode = (*it)->m_next; |
| if (!currNode->m_visited) { |
| step = 0; |
| while (!currNode->m_visited && currNode->m_junction != junction) { |
| it = junction->m_nodes.insert(it + 1, currNode); |
| currNode->m_visited = true; |
| currNode = currNode->m_next; |
| ++step; |
| } |
| if (currNode->m_junction != junction) |
| for (int i = 0; i < step; ++i) { |
| currNode = currNode->m_prev; |
| currNode->m_visited = false; |
| it = junction->m_nodes.erase(it); |
| --it; |
| } |
| else { |
| |
| if (currNode != otherNode) { |
| Node tempNode = *otherNode; |
| *otherNode = *currNode; |
| *currNode = tempNode; |
| |
| |
| |
| |
| |
| |
| } |
| continue; |
| } |
| } |
| } |
| } |
| |
| |
| |
| void CenterLineVectorizer::handleLinks() |
| { |
| std::vector<JunctionLink>::iterator it_links; |
| for (it_links = m_links.begin(); it_links != m_links.end(); ++it_links) { |
| ProtoStroke linkingProtoStroke; |
| |
| |
| TThickPoint p1 = it_links->m_first->m_center; |
| TThickPoint p2 = it_links->m_second->m_center - p1; |
| linkingProtoStroke.m_points.push_back(TThickPoint(p1, |
| JUNCTION_THICKNESS_RATIO * p1.thick)); |
| linkingProtoStroke.m_points.push_back(TThickPoint(p1 + 0.5 * p2, |
| JUNCTION_THICKNESS_RATIO * (p1.thick + 0.5 * p2.thick))); |
| linkingProtoStroke.m_points.push_back(TThickPoint(p1 + p2, |
| JUNCTION_THICKNESS_RATIO * (p1.thick + p2.thick))); |
| |
| |
| setEndpointsDirections(linkingProtoStroke); |
| |
| |
| |
| |
| m_protoStrokes.push_back(linkingProtoStroke); |
| |
| |
| } |
| } |
| |
| |
| |
| bool isFalseBranch(int k, ProtoStroke *protoStroke, int recursionOrder, |
| TThickPoint &edge) |
| { |
| if (recursionOrder > 3) |
| return false; |
| ++recursionOrder; |
| if (k == 0) { |
| if (protoStroke->m_points.front().thick > 3.0) |
| return false; |
| if (protoStroke->m_points.size() <= 20) { |
| if (protoStroke->m_endJunction) { |
| if (protoStroke->m_endJunction->m_protoStrokes.size() == 1) { |
| edge = protoStroke->m_points.back(); |
| protoStroke->m_endJunction->m_protoStrokes.clear(); |
| protoStroke->m_points.clear(); |
| protoStroke->m_startJunction = 0; |
| protoStroke->m_endJunction = 0; |
| return true; |
| } else if (protoStroke->m_endJunction->m_protoStrokes.size() == 2) { |
| ProtoStroke *nextProtoStroke; |
| int next_k; |
| |
| if (protoStroke->m_endJunction->m_protoStrokes[0] == protoStroke) |
| nextProtoStroke = protoStroke->m_endJunction->m_protoStrokes[1]; |
| else if (protoStroke->m_endJunction->m_protoStrokes[1] == protoStroke) |
| nextProtoStroke = protoStroke->m_endJunction->m_protoStrokes[0]; |
| if (nextProtoStroke->m_startJunction == protoStroke->m_endJunction) |
| next_k = 0; |
| else if (nextProtoStroke->m_endJunction == protoStroke->m_endJunction) |
| next_k = 1; |
| |
| if (isFalseBranch(next_k, nextProtoStroke, recursionOrder, edge)) { |
| protoStroke->m_endJunction->m_protoStrokes.clear(); |
| protoStroke->m_points.clear(); |
| protoStroke->m_startJunction = 0; |
| protoStroke->m_endJunction = 0; |
| return true; |
| } else |
| return false; |
| } else |
| return false; |
| } |
| edge = protoStroke->m_points.back(); |
| protoStroke->m_points.clear(); |
| protoStroke->m_startJunction = 0; |
| protoStroke->m_endJunction = 0; |
| return true; |
| } |
| } else if (k == 1) { |
| if (protoStroke->m_points.back().thick > 3.0) |
| return false; |
| if (protoStroke->m_points.size() <= 20) { |
| if (protoStroke->m_startJunction) { |
| if (protoStroke->m_startJunction->m_protoStrokes.size() == 1) { |
| edge = protoStroke->m_points.front(); |
| protoStroke->m_startJunction->m_protoStrokes.clear(); |
| protoStroke->m_points.clear(); |
| protoStroke->m_startJunction = 0; |
| protoStroke->m_endJunction = 0; |
| return true; |
| } else if (protoStroke->m_startJunction->m_protoStrokes.size() == 2) { |
| ProtoStroke *nextProtoStroke; |
| int next_k; |
| |
| if (protoStroke->m_startJunction->m_protoStrokes[0] == protoStroke) |
| nextProtoStroke = protoStroke->m_startJunction->m_protoStrokes[1]; |
| else if (protoStroke->m_startJunction->m_protoStrokes[1] == protoStroke) |
| nextProtoStroke = protoStroke->m_startJunction->m_protoStrokes[0]; |
| if (nextProtoStroke->m_startJunction == protoStroke->m_startJunction) |
| next_k = 0; |
| else if (nextProtoStroke->m_endJunction == protoStroke->m_startJunction) |
| next_k = 1; |
| |
| if (isFalseBranch(next_k, nextProtoStroke, recursionOrder, edge)) { |
| protoStroke->m_startJunction->m_protoStrokes.clear(); |
| protoStroke->m_points.clear(); |
| protoStroke->m_startJunction = 0; |
| protoStroke->m_endJunction = 0; |
| return true; |
| } else |
| return false; |
| } else |
| return false; |
| } |
| edge = protoStroke->m_points.front(); |
| protoStroke->m_points.clear(); |
| protoStroke->m_startJunction = 0; |
| protoStroke->m_endJunction = 0; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| |
| void CenterLineVectorizer::handleJunctions() |
| { |
| std::vector<Junction *>::iterator it_junction = m_junctions.begin(); |
| for (it_junction = m_junctions.begin(); it_junction < m_junctions.end(); |
| it_junction++) { |
| Junction *currJunction = *it_junction; |
| |
| if (currJunction->m_protoStrokes.empty()) { |
| std::deque<Node *>::iterator it_node; |
| for (it_node = currJunction->m_nodes.begin(); |
| it_node != currJunction->m_nodes.end(); ++it_node) { |
| (*it_node)->m_junction = 0; |
| (*it_node)->m_visited = false; |
| } |
| delete currJunction; |
| it_junction = m_junctions.erase(it_junction); |
| --it_junction; |
| continue; |
| } |
| |
| std::vector<ProtoStroke *>::iterator it; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| double size = currJunction->m_protoStrokes.size(); |
| for (it = currJunction->m_protoStrokes.begin(); |
| it != currJunction->m_protoStrokes.end(); it++) { |
| if (((*it))->m_startJunction == currJunction && (*it)->m_endJunction == currJunction) { |
| currJunction->m_center += 0.5 * (*it)->m_points.front(); |
| currJunction->m_center += 0.5 * (*it)->m_points.back(); |
| } else if (((*it))->m_startJunction == currJunction) { |
| currJunction->m_center += (*it)->m_points.front(); |
| } else if ((*it)->m_endJunction == currJunction) { |
| currJunction->m_center += (*it)->m_points.back(); |
| } |
| } |
| currJunction->m_center = currJunction->m_center * (1 / size); |
| } |
| |
| #ifdef DEBUG |
| int j = 0; |
| for (; j < (int)m_nodes.size(); j++) { |
| Node *node = m_nodes[j]; |
| if (!node->m_visited && node->m_pixel) { |
| m_unvisitedNodes.push_back(convert(node->m_pixel->m_pos)); |
| |
| } |
| } |
| |
| for (j = 0; j < (int)m_nodes.size(); j++) { |
| Node *node = m_nodes[j]; |
| |
| if (node->m_pixel == 0 || node->m_flag) |
| continue; |
| outputNodes(node); |
| } |
| #endif |
| |
| joinJunctions(); |
| |
| for (it_junction = m_junctions.begin(); it_junction < m_junctions.end(); |
| it_junction++) { |
| Junction *currJunction = *it_junction; |
| if (currJunction->m_center.thick < 2 * THICKNESS_LIMIT) |
| currJunction->m_center.thick = 2 * THICKNESS_LIMIT; |
| |
| std::vector<ProtoStroke *>::iterator it, it_next; |
| |
| createJunctionPolygon(currJunction); |
| |
| currJunction->m_junctionOrder = currJunction->m_protoStrokes.size(); |
| |
| if (currJunction->m_protoStrokes.size() == 3) { |
| std::vector<ProtoStroke *>::iterator it_e = |
| currJunction->m_protoStrokes.end(); |
| std::vector<ProtoStroke *>::iterator it_candidate1 = it_e, it_candidate2 = |
| it_e; |
| int candidate1_k, candidate2_k; |
| double candidateProbability = JOIN_LIMIT; |
| int k, next_k; |
| for (it = currJunction->m_protoStrokes.begin(); it != it_e; it++) { |
| if (((*it))->m_startJunction == currJunction) |
| k = 0; |
| else if ((*it)->m_endJunction == currJunction) |
| k = 1; |
| else |
| assert(false); |
| |
| if (isFalseBranch(k, *it, 0, currJunction->m_center)) { |
| currJunction->m_protoStrokes.erase(it); |
| currJunction->m_locked = true; |
| break; |
| } |
| |
| it_next = it; |
| it_next++; |
| for (; it_next != it_e; it_next++) { |
| double probability; |
| if (((*it_next))->m_startJunction == currJunction) |
| next_k = 0; |
| else if ((*it_next)->m_endJunction == currJunction) |
| next_k = 1; |
| else |
| assert(false); |
| |
| if (k == 0) { |
| if (next_k == 0) |
| probability = 1 - norm2((*it)->m_startDirection + |
| (*it_next)->m_startDirection); |
| else |
| probability = 1 - norm2((*it)->m_startDirection + |
| (*it_next)->m_endDirection); |
| } else { |
| if (next_k == 0) |
| probability = 1 - norm2((*it)->m_endDirection + |
| (*it_next)->m_startDirection); |
| else |
| probability = 1 - norm2((*it)->m_endDirection + |
| (*it_next)->m_endDirection); |
| } |
| if (probability > candidateProbability || |
| (*it)->m_points.size() < 3) { |
| candidateProbability = probability; |
| it_candidate1 = it; |
| it_candidate2 = it_next; |
| candidate1_k = k; |
| candidate2_k = next_k; |
| } |
| } |
| } |
| if (currJunction->m_protoStrokes.size() == 3 && it_candidate1 != it_e && |
| it_candidate2 != it_e) { |
| TThickPoint p1 = (candidate1_k == 0) ? (*it_candidate1)->m_points.front() |
| : (*it_candidate1)->m_points.back(); |
| TThickPoint p2 = (candidate2_k == 0) ? (*it_candidate2)->m_points.front() |
| : (*it_candidate2)->m_points.back(); |
| currJunction->m_center = 0.5 * (p1 + p2); |
| } |
| } |
| if (false || currJunction->m_protoStrokes.size() == 2) { |
| TPointD a1, a2, b1, b2; |
| |
| if (currJunction->m_protoStrokes[0]->m_startJunction == currJunction) { |
| a1 = currJunction->m_protoStrokes[0]->m_points.front(); |
| a2 = currJunction->m_protoStrokes[0]->m_startDirection - a1; |
| } else { |
| assert(currJunction->m_protoStrokes[0]->m_endJunction == currJunction); |
| a1 = currJunction->m_protoStrokes[0]->m_points.back(); |
| a2 = currJunction->m_protoStrokes[0]->m_endDirection - a1; |
| } |
| |
| if (currJunction->m_protoStrokes[1]->m_startJunction == currJunction) { |
| b1 = currJunction->m_protoStrokes[1]->m_points.front(); |
| b2 = currJunction->m_protoStrokes[1]->m_startDirection - b1; |
| } else { |
| assert(currJunction->m_protoStrokes[1]->m_endJunction == currJunction); |
| b1 = currJunction->m_protoStrokes[1]->m_points.back(); |
| b2 = currJunction->m_protoStrokes[1]->m_endDirection - b1; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| if (true || currJunction->m_nodes.size() <= 12 || |
| currJunction->isConvex()) { |
| TThickPoint junctionPoint(currJunction->m_center); |
| for (it = currJunction->m_protoStrokes.begin(); |
| it != currJunction->m_protoStrokes.end(); it++) { |
| assert((*it)->m_startJunction == currJunction || (*it)->m_endJunction == currJunction); |
| if ((*it)->m_startJunction == currJunction) { |
| TPointD deltaPoint = (*it)->m_points.front() - junctionPoint; |
| double deltaThick = (*it)->m_points.front().thick - |
| junctionPoint.thick; |
| if (norm2(deltaPoint) > 0.2) { |
| if (norm2(deltaPoint) > 2) { |
| (*it)->m_points.push_front( |
| TThickPoint(junctionPoint + 0.7 * deltaPoint, junctionPoint.thick + |
| 0.7 * deltaThick)); |
| (*it)->m_points.push_front( |
| TThickPoint(junctionPoint + 0.4 * deltaPoint, junctionPoint.thick + |
| 0.4 * deltaThick)); |
| } |
| (*it)->m_points.push_front( |
| TThickPoint(junctionPoint + 0.0 * deltaPoint, junctionPoint.thick + |
| 0.0 * deltaThick)); |
| } |
| } |
| if ((*it)->m_endJunction == currJunction) { |
| TPointD deltaPoint = (*it)->m_points.back() - junctionPoint; |
| double deltaThick = (*it)->m_points.back().thick - junctionPoint.thick; |
| if (norm2(deltaPoint) > 0.2) { |
| if (norm2(deltaPoint) > 2) { |
| (*it)->m_points.push_back( |
| TThickPoint(junctionPoint + 0.7 * deltaPoint, junctionPoint.thick + |
| 0.7 * deltaThick)); |
| (*it)->m_points.push_back( |
| TThickPoint(junctionPoint + 0.4 * deltaPoint, junctionPoint.thick + |
| 0.4 * deltaThick)); |
| } |
| (*it)->m_points.push_back( |
| TThickPoint(junctionPoint + 0.0 * deltaPoint, junctionPoint.thick + |
| 0.0 * deltaThick)); |
| } |
| } |
| } |
| } else { |
| ProtoRegion region(currJunction->isConvex()); |
| region.m_center = currJunction->m_center; |
| |
| std::deque<Node *>::iterator it_nodes; |
| for (it_nodes = currJunction->m_nodes.begin(); |
| it_nodes != currJunction->m_nodes.end(); it_nodes++) |
| region.m_points.push_back( |
| TThickPoint(convert((*it_nodes)->m_pixel->m_pos), 0.0)); |
| |
| region.m_points.push_back( |
| TThickPoint(convert((*currJunction->m_nodes.begin())->m_pixel->m_pos), 0.0)); |
| |
| m_protoRegions.push_back(region); |
| |
| for (it = currJunction->m_protoStrokes.begin(); |
| it != currJunction->m_protoStrokes.end(); it++) { |
| if ((*it)->m_startJunction == currJunction && (*it)->m_endJunction == currJunction) { |
| if ((*it)->m_points.size() < 10) |
| (*it)->m_points.clear(); |
| } |
| |
| if ((*it)->m_startJunction == currJunction) { |
| (*it)->m_startJunction = 0; |
| } |
| if ((*it)->m_endJunction == currJunction) { |
| (*it)->m_endJunction = 0; |
| } |
| } |
| currJunction->m_protoStrokes.clear(); |
| } |
| } |
| |
| |
| } |
| |
| |
| |
| void CenterLineVectorizer::createStrokes() |
| { |
| std::list<ProtoStroke>::iterator it_centerlines = m_protoStrokes.begin(); |
| for (; it_centerlines != m_protoStrokes.end(); it_centerlines++) { |
| if (!it_centerlines->m_points.empty()) { |
| joinProtoStrokes(&(*it_centerlines)); |
| if (it_centerlines->m_points.size() <= 1) |
| continue; |
| |
| std::vector<TThickPoint> points; |
| std::deque<TThickPoint>::iterator it; |
| |
| if (m_configuration.m_smoothness > 0) |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| std::deque<TThickPoint> &points = it_centerlines->m_points; |
| it = points.begin(); |
| for (;;) { |
| if (it == points.end()) |
| break; |
| ++it; |
| int maxD = std::distance(it, points.end()); |
| int d = tmin(maxD, m_configuration.m_smoothness); |
| if (d == 0) |
| break; |
| it = points.erase(it, it + d); |
| } |
| } |
| it = it_centerlines->m_points.begin(); |
| points.push_back(*it); |
| assert(it->thick >= THICKNESS_LIMIT); |
| TThickPoint old = *it; |
| for (it++; it != it_centerlines->m_points.end(); ++it) { |
| assert(it->thick >= THICKNESS_LIMIT); |
| TThickPoint point(0.5 * (*it + old), 0.5 * (it->thick + old.thick)); |
| points.push_back(point); |
| old = *it; |
| } |
| points.push_back(it_centerlines->m_points.back()); |
| |
| int n = points.size(); |
| if (n > 5) { |
| double *rr = new double[n]; |
| int i; |
| for (i = 0; i < n; i++) |
| rr[i] = points[i].thick; |
| for (i = 2; i < n - 2; i++) |
| points[i].thick = 0.8 * (1 / 5.0) * |
| ( +rr[i - 2] + rr[i - 1] + rr[i] + rr[i + 1] + rr[i + 2] ); |
| points[0].thick = points[1].thick = points[2].thick; |
| points[n - 1].thick = points[n - 2].thick = points[n - 3].thick; |
| delete[] rr; |
| } |
| TStroke *stroke = TStroke::interpolate(points, m_configuration.m_interpolationError); |
| stroke->setStyle(m_configuration.m_strokeStyleId); |
| m_vimage->addStroke(stroke); |
| } |
| } |
| } |
| |
| |
| |
| void CenterLineVectorizer::createRegions() |
| { |
| std::list<ProtoRegion>::iterator it_regions; |
| for (it_regions = m_protoRegions.begin(); it_regions != m_protoRegions.end(); it_regions++) { |
| assert(it_regions->m_points.size() > 4); |
| TStroke *stroke = TStroke::interpolate(it_regions->m_points, 0.1); |
| stroke->setSelfLoop(); |
| stroke->setStyle(m_configuration.m_strokeStyleId); |
| m_vimage->addStroke(stroke); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| } |
| |
| |
| |
| void CenterLineVectorizer::vectorize() |
| { |
| int j = 0; |
| |
| for (j = 0; j < (int)m_nodes.size(); j++) { |
| Node *node = m_nodes[j]; |
| if (node->m_pixel == 0 || node->m_visited) |
| continue; |
| traceLine(node->m_pixel); |
| } |
| |
| handleJunctions(); |
| createStrokes(); |
| createRegions(); |
| } |
| |
| |
| |
| #ifdef DEBUG |
| void printTime(TStopWatch &sw, string name) |
| { |
| ostrstream ss; |
| ss << name << " : "; |
| sw.print(ss); |
| ss << '\n' << '\0'; |
| string s = ss.str(); |
| ss.freeze(false); |
| TSystem::outputDebug(s); |
| } |
| #endif |
| |
| |
| |
| #ifdef DEBUG |
| void CenterLineVectorizer::outputInks() |
| { |
| for (int i = 0; i < m_dataRaster->getLx() * m_dataRaster->getLy(); i++) { |
| DataPixel *pix = m_dataRaster->pixels() + i; |
| if (pix->m_ink) |
| getOutPix(pix) = TPixel32::Black; |
| else |
| getOutPix(pix) = TPixel32::White; |
| } |
| } |
| #endif |
| |
| |
| |
| TVectorImageP centerlineVectorize(const TImageP &image, const VectorizerConfiguration &c, TPalette *palette) |
| { |
| CenterLineVectorizer vectorizer(c, palette); |
| |
| TRasterImageP ri = image; |
| TToonzImageP vi = image; |
| if (ri) |
| vectorizer.makeDataRaster(ri->getRaster()); |
| else |
| vectorizer.makeDataRaster(vi->getRaster()); |
| for (int i = 0; i < (int)vectorizer.m_dataRasterArray.size(); i++) { |
| vectorizer.m_dataRaster = vectorizer.m_dataRasterArray[i].second; |
| vectorizer.m_configuration.m_strokeStyleId = vectorizer.m_dataRasterArray[i].first; |
| vectorizer.init(); |
| vectorizer.vectorize(); |
| } |
| |
| renormalizeImage(vectorizer.m_vimage.getPointer()); |
| |
| return vectorizer.m_vimage; |
| } |
| |
| |
| |
| class OutlineVectorizer : public CenterLineVectorizer |
| { |
| |
| public: |
| list<vector<TThickPoint>> m_protoOutlines; |
| |
| void traceOutline(Node *initialNode); |
| |
| OutlineVectorizer(const VectorizerConfiguration &configuration, TPalette *palette) : CenterLineVectorizer(configuration, palette) {} |
| ~OutlineVectorizer(); |
| |
| void createOutlineStrokes(); |
| |
| private: |
| |
| OutlineVectorizer(const OutlineVectorizer &); |
| OutlineVectorizer &operator=(const OutlineVectorizer &); |
| }; |
| |
| |
| |
| OutlineVectorizer::~OutlineVectorizer() |
| { |
| m_protoOutlines.clear(); |
| } |
| |
| |
| |
| void OutlineVectorizer::traceOutline(Node *initialNode) |
| { |
| Node *startNode = initialNode; |
| Node *node; |
| do { |
| if (!startNode) |
| break; |
| node = findOtherSide(startNode); |
| if (!node) |
| break; |
| |
| double startDist2 = computeDistance2(startNode, node); |
| if (startDist2 > 0.1) |
| break; |
| |
| startNode = startNode->m_next; |
| |
| } while (startNode != initialNode); |
| |
| if (!startNode) |
| return; |
| node = startNode; |
| std::vector<TThickPoint> points; |
| do { |
| node = node->m_next; |
| if (!node) |
| break; |
| node->m_visited = true; |
| points.push_back(TThickPoint(convert(node->m_pixel->m_pos), 0)); |
| } while (node != startNode); |
| m_protoOutlines.push_back(points); |
| } |
| |
| |
| |
| void OutlineVectorizer::createOutlineStrokes() |
| { |
| m_vimage->enableRegionComputing(true, false); |
| int j; |
| |
| for (j = 0; j < (int)m_nodes.size(); j++) { |
| Node *node = m_nodes[j]; |
| if (node->m_pixel == 0 || node->m_visited) |
| continue; |
| traceOutline(node); |
| } |
| |
| #ifdef DEBUG |
| for (j = 0; j < (int)m_nodes.size(); j++) { |
| Node *node = m_nodes[j]; |
| |
| if (node->m_pixel == 0 || node->m_flag) |
| continue; |
| outputNodes(node); |
| } |
| #endif |
| |
| std::list<std::vector<TThickPoint>>::iterator it_outlines = m_protoOutlines.begin(); |
| for (; it_outlines != m_protoOutlines.end(); it_outlines++) { |
| if (it_outlines->size() > 3) { |
| std::vector<TThickPoint> points; |
| std::vector<TThickPoint>::iterator it; |
| |
| if (it_outlines->size() > 10) { |
| it = it_outlines->begin() + 1; |
| for (;;) { |
| if (it >= it_outlines->end() - (m_configuration.m_smoothness + 1)) |
| break; |
| for (j = 0; j < m_configuration.m_smoothness; j++) |
| it = it_outlines->erase(it); |
| ++it; |
| } |
| } |
| points.push_back(it_outlines->front()); |
| it = it_outlines->begin(); |
| TThickPoint old = *it; |
| ++it; |
| for (; it != it_outlines->end(); ++it) { |
| TThickPoint point((1 / 2.0) * (*it + old)); |
| points.push_back(point); |
| old = *it; |
| } |
| points.push_back(it_outlines->back()); |
| points.push_back(it_outlines->front()); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| TStroke *stroke = TStroke::interpolate(points, m_configuration.m_interpolationError); |
| stroke->setStyle(m_configuration.m_strokeStyleId); |
| stroke->setSelfLoop(); |
| m_vimage->addStroke(stroke); |
| |
| |
| |
| |
| |
| } |
| } |
| |
| |
| |
| |
| } |
| |
| |
| |
| TVectorImageP outlineVectorize(const TImageP &image, const VectorizerConfiguration &configuration, TPalette *palette) |
| { |
| TVectorImageP out; |
| |
| OutlineVectorizer vectorizer(configuration, palette); |
| |
| TRasterImageP ri = image; |
| TToonzImageP vi = image; |
| if (ri) |
| vectorizer.makeDataRaster(ri->getRaster()); |
| else |
| vectorizer.makeDataRaster(vi->getRaster()); |
| int layersCount = vectorizer.m_dataRasterArray.size(); |
| if (layersCount > 1) { |
| out = new TVectorImage(); |
| out->setPalette(palette); |
| } |
| for (int i = 0; i < (int)layersCount; i++) { |
| vectorizer.m_dataRaster = vectorizer.m_dataRasterArray[i].second; |
| vectorizer.m_configuration.m_strokeStyleId = vectorizer.m_dataRasterArray[i].first; |
| vectorizer.m_protoOutlines.clear(); |
| vectorizer.init(); |
| vectorizer.createOutlineStrokes(); |
| renormalizeImage(vectorizer.m_vimage.getPointer()); |
| vectorizer.m_vimage->setPalette(palette); |
| if (layersCount > 1) |
| out->mergeImage(vectorizer.m_vimage, TAffine()); |
| |
| if (i != (int)layersCount - 1) |
| vectorizer.m_vimage = new TVectorImage(); |
| } |
| |
| return (layersCount == 1) ? vectorizer.m_vimage : out; |
| } |
| |
| |
| void applyFillColors(TRegion *r, const TRasterP &ras, TPalette *palette, const TPoint &offset, bool leaveUnpainted) |
| { |
| TPointD pd; |
| TRasterCM32P rt = (TRasterCM32P)ras; |
| TRaster32P rr = (TRaster32P)ras; |
| TRasterGR8P rgr = (TRasterGR8P)ras; |
| assert(rt || rr || rgr); |
| |
| if ( r->getInternalPoint(pd)) { |
| TPoint p = convert(pd) + offset; |
| if (ras->getBounds().contains(p)) { |
| int styleId = 0; |
| if (rt) { |
| TPixelCM32 col = rt->pixels(p.y)[p.x]; |
| int tone = col.getTone(); |
| if (tone < 100) { |
| styleId = col.getInk(); |
| if (styleId == 0 && !leaveUnpainted) |
| styleId = col.getPaint(); |
| } else if (!leaveUnpainted || col.getPaint() == 0) { |
| styleId = col.getPaint(); |
| if (styleId == 0) |
| styleId = col.getInk(); |
| } |
| } else { |
| TPixel32 color; |
| if (rr) |
| color = rr->pixels(p.y)[p.x]; |
| else { |
| int val = rgr->pixels(p.y)[p.x].value; |
| color = (val < 80) ? TPixel32::Black : TPixel32::White; |
| } |
| styleId = palette->getClosestStyle(color); |
| if (palette->getStyle(styleId)->getMainColor() != color) |
| styleId = palette->addStyle(color); |
| } |
| r->fill(pd, styleId); |
| } |
| } |
| |
| for (int i = 0; i < (int)r->getSubregionCount(); i++) |
| applyFillColors(r->getSubregion(i), ras, palette, offset, leaveUnpainted); |
| } |
| |
| |
| |
| void applyFillColors(TVectorImageP vi, const TImageP &img, TPalette *palette, bool leaveUnpainted) |
| { |
| TToonzImageP ti = (TToonzImageP)img; |
| TRasterImageP ri = (TRasterImageP)img; |
| TRectD vb = vi->getBBox(); |
| TRectD tb = img->getBBox(); |
| |
| TRasterP ras; |
| |
| if (ti) |
| ras = ti->getRaster(); |
| else if (ri) |
| ras = ri->getRaster(); |
| else |
| assert(false); |
| |
| vi->findRegions(); |
| for (int i = 0; i < (int)vi->getRegionCount(); i++) |
| applyFillColors(vi->getRegion(i), ras, palette, convert(tb.getP00() - vb.getP00()), leaveUnpainted); |
| } |
| |
| |
| |
| TVectorImageP vectorize(const TImageP &img, const VectorizerConfiguration &c, TPalette *plt) |
| { |
| TVectorImageP vi = c.m_outline |
| ? outlineVectorize(img, c, plt) |
| : centerlineVectorize(img, c, plt); |
| |
| applyFillColors(vi, img, plt, c.m_leaveUnpainted); |
| |
| |
| |
| |
| TPointD center; |
| if (TToonzImageP ti = img) |
| center = ti->getRaster()->getCenterD(); |
| else if (TRasterImageP ri = img) |
| center = ri->getRaster()->getCenterD(); |
| |
| TAffine aff = TTranslation(-center); |
| vi->transform(aff); |
| |
| return vi; |
| } |
| |
| |
| |