| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| namespace boost_c = boost::container; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| TPixelCM32 pixel(const TRasterCM32 &ras, int x, int y) |
| { |
| |
| |
| |
| return ras.pixels(tcrop(y, 0, ras.getLy() - 1))[tcrop(x, 0, ras.getLx() - 1)]; |
| } |
| |
| |
| |
| T3DPointD firstInkChangePosition( |
| const TRasterCM32P &ras, const T3DPointD &start, const T3DPointD &end, |
| int threshold) |
| { |
| double dist = norm(end - start); |
| |
| int sampleMax = tceil(dist), sampleCount = sampleMax + 1; |
| double sampleMaxD = double(sampleMax); |
| |
| |
| int s, color = -1; |
| |
| for (s = 0; s != sampleCount; ++s) { |
| T3DPointD p = tcg::numeric_ops::lerp(start, end, s / sampleMaxD); |
| const TPixelCM32 &pix = pixel(*ras, p.x, p.y); |
| |
| if (pix.getTone() < threshold) { |
| color = pix.getInk(); |
| break; |
| } |
| } |
| |
| |
| for (; s != sampleCount; ++s) { |
| T3DPointD p = tcg::numeric_ops::lerp(start, end, s / sampleMaxD); |
| const TPixelCM32 &pix = pixel(*ras, p.x, p.y); |
| |
| if (pix.getTone() < threshold && pix.getInk() != color) |
| break; |
| } |
| |
| |
| if (s < sampleCount) |
| return tcg::numeric_ops::lerp(start, end, (s - 0.5) / sampleMaxD); |
| |
| return TConsts::nap3d; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void sampleColor(const TRasterCM32P &ras, int threshold, Sequence &seq, Sequence &seqOpposite, |
| SequenceList &singleSequences) |
| { |
| SkeletonGraph *currGraph = seq.m_graphHolder; |
| |
| |
| std::vector<unsigned int> nodes; |
| std::vector<double> params; |
| |
| |
| |
| |
| { |
| const T3DPointD &headPos = *currGraph->getNode(seq.m_head); |
| |
| if (!ras->getBounds().contains(TPoint(headPos.x, headPos.y))) { |
| if (headPos.x < 0 || ras->getLx() < headPos.x || |
| headPos.y < 0 || ras->getLy() < headPos.y) |
| return; |
| } |
| } |
| |
| unsigned int curr, currLink, next; |
| double meanThickness = currGraph->getNode(seq.m_head)->z; |
| |
| params.push_back(0); |
| nodes.push_back(seq.m_head); |
| |
| for (curr = seq.m_head, currLink = seq.m_headLink; |
| curr != seq.m_tail || params.size() == 1; |
| seq.next(curr, currLink)) { |
| next = currGraph->getNode(curr).getLink(currLink).getNext(); |
| |
| const T3DPointD &nextPos = *currGraph->getNode(next); |
| if (!ras->getBounds().contains(TPoint(nextPos.x, nextPos.y))) { |
| if (nextPos.x < 0 || ras->getLx() < nextPos.x || |
| nextPos.y < 0 || ras->getLy() < nextPos.y) |
| return; |
| } |
| |
| params.push_back(params.back() + tdistance(*currGraph->getNode(next), *currGraph->getNode(curr))); |
| nodes.push_back(next); |
| |
| meanThickness += currGraph->getNode(next)->z; |
| } |
| |
| meanThickness /= params.size(); |
| |
| |
| if (params.back() < 0.01) { |
| seq.m_color = pixel(*ras, currGraph->getNode(seq.m_head)->x, |
| currGraph->getNode(seq.m_head)->y) |
| .getInk(); |
| return; |
| } |
| |
| |
| int paramCount = params.size(), |
| paramMax = paramCount - 1; |
| |
| int sampleMax = std::max(params.back() / std::max(meanThickness, 1.0), 3.0), |
| sampleCount = sampleMax + 1; |
| |
| std::vector<double> sampleParams(sampleCount); |
| std::vector<TPoint> samplePoints(sampleCount); |
| std::vector<int> sampleSegments(sampleCount); |
| |
| |
| for (int s = 0, j = 0; s != sampleCount; ++s) { |
| double samplePar = params.back() * (s / double(sampleMax)); |
| |
| while (j != paramMax && params[j + 1] < samplePar) |
| ++j; |
| |
| double t = (samplePar - params[j]) / (params[j + 1] - params[j]); |
| |
| T3DPointD samplePoint(*currGraph->getNode(nodes[j]) * (1 - t) + *currGraph->getNode(nodes[j + 1]) * t); |
| |
| sampleParams[s] = samplePar; |
| samplePoints[s] = TPoint(std::min(samplePoint.x, double(ras->getLx() - 1)), |
| std::min(samplePoint.y, double(ras->getLy() - 1))); |
| sampleSegments[s] = j; |
| } |
| |
| |
| |
| |
| |
| T3DPointD first(*currGraph->getNode(seq.m_head)); |
| T3DPointD last(*currGraph->getNode(seq.m_tail)); |
| |
| int i, k; |
| |
| for (i = 1; params.back() * i / double(sampleMax) <= first.z && i < sampleCount; ++i) |
| ; |
| for (k = sampleMax - 1; params.back() * (sampleMax - k) / double(sampleMax) <= last.z && k >= 0; --k) |
| ; |
| |
| |
| |
| |
| seq.m_color = seqOpposite.m_color = |
| ras->pixels(samplePoints[0].y)[samplePoints[0].x].getInk(); |
| |
| int l; |
| |
| for (l = i - 1; l >= 0; --l) { |
| if (ras->pixels(samplePoints[l].y)[samplePoints[l].x].getTone() < threshold) { |
| seq.m_color = seqOpposite.m_color = |
| ras->pixels(samplePoints[l].y)[samplePoints[l].x].getInk(); |
| |
| break; |
| } |
| } |
| |
| |
| for (l = i; l <= k; ++l) { |
| if (ras->pixels(samplePoints[l].y)[samplePoints[l].x].getTone() < threshold) { |
| seq.m_color = seqOpposite.m_color = |
| ras->pixels(samplePoints[l].y)[samplePoints[l].x].getInk(); |
| |
| break; |
| } |
| } |
| |
| if (i >= k) |
| goto _getOut; |
| |
| |
| for (l = i; l < k; ++l) { |
| const TPixelCM32 &nextSample = ras->pixels(samplePoints[l + 1].y)[samplePoints[l + 1].x], |
| &nextSample2 = ras->pixels(samplePoints[l + 2].y)[samplePoints[l + 2].x]; |
| |
| if (nextSample.getTone() < threshold && nextSample.getInk() != seq.m_color && nextSample2.getTone() < threshold && nextSample2.getInk() == nextSample.getInk()) |
| { |
| |
| |
| |
| int nextColor = nextSample.getInk(); |
| |
| |
| int u; |
| |
| for (u = sampleSegments[l]; u < sampleSegments[l + 1]; ++u) { |
| const TPixelCM32 &pix = pixel(*ras, currGraph->getNode(nodes[u + 1])->x, |
| currGraph->getNode(nodes[u + 1])->y); |
| if (pix.getTone() < threshold && pix.getInk() != seq.m_color) |
| break; |
| } |
| |
| |
| const T3DPointD &nodeStartPos = *currGraph->getNode(nodes[u]), |
| &nodeEndPos = *currGraph->getNode(nodes[u + 1]); |
| |
| T3DPointD splitPoint = firstInkChangePosition(ras, nodeStartPos, nodeEndPos, threshold); |
| |
| if (splitPoint == TConsts::nap3d) |
| splitPoint = 0.5 * (nodeStartPos + nodeEndPos); |
| |
| |
| |
| unsigned int splitNode = currGraph->newNode(splitPoint); |
| |
| unsigned int nodesLink = currGraph->getNode(nodes[u]).linkOfNode(nodes[u + 1]); |
| currGraph->insert(splitNode, nodes[u], nodesLink); |
| *currGraph->node(splitNode).link(0) = *currGraph->getNode(nodes[u]).getLink(nodesLink); |
| |
| nodesLink = currGraph->getNode(nodes[u + 1]).linkOfNode(nodes[u]); |
| currGraph->insert(splitNode, nodes[u + 1], nodesLink); |
| *currGraph->node(splitNode).link(1) = *currGraph->getNode(nodes[u + 1]).getLink(nodesLink); |
| |
| currGraph->node(splitNode).setAttribute(SAMPLECOLOR_SIGN); |
| |
| if (seq.m_head == seq.m_tail && currGraph->getNode(seq.m_head).getLinksCount() == 2 && !currGraph->getNode(seq.m_head).hasAttribute(SAMPLECOLOR_SIGN)) { |
| |
| seq.m_head = seq.m_tail = splitNode; |
| sampleColor(ras, threshold, seq, seqOpposite, singleSequences); |
| } else { |
| |
| Sequence newSeq; |
| newSeq.m_graphHolder = currGraph; |
| newSeq.m_head = splitNode; |
| newSeq.m_headLink = 0; |
| newSeq.m_tail = seq.m_tail; |
| newSeq.m_tailLink = seq.m_tailLink; |
| |
| seq.m_tail = splitNode; |
| seq.m_tailLink = 1; |
| |
| seqOpposite.m_graphHolder = seq.m_graphHolder; |
| |
| |
| |
| if ((!(seq.m_head == newSeq.m_tail && currGraph->getNode(seq.m_head).getLinksCount() == 2)) && currGraph->getNode(seq.m_head).hasAttribute(SAMPLECOLOR_SIGN)) |
| singleSequences.push_back(seq); |
| |
| sampleColor(ras, threshold, newSeq, seqOpposite, singleSequences); |
| } |
| |
| return; |
| } |
| } |
| |
| _getOut: |
| |
| |
| if (currGraph->getNode(seq.m_head).hasAttribute(SAMPLECOLOR_SIGN)) { |
| seqOpposite.m_color = seq.m_color; |
| seqOpposite.m_head = seq.m_tail; |
| seqOpposite.m_headLink = seq.m_tailLink; |
| seqOpposite.m_tail = seq.m_head; |
| seqOpposite.m_tailLink = seq.m_headLink; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| void calculateSequenceColors(const TRasterP &ras, VectorizerCoreGlobals &g) |
| { |
| int threshold = g.currConfig->m_threshold; |
| SequenceList &singleSequences = g.singleSequences; |
| JointSequenceGraphList &organizedGraphs = g.organizedGraphs; |
| |
| TRasterCM32P cm = ras; |
| unsigned int i, j, k; |
| int l; |
| |
| if (cm && g.currConfig->m_maxThickness > 0.0) { |
| |
| |
| for (l = singleSequences.size() - 1; l >= 0; --l) { |
| Sequence rear; |
| sampleColor(ras, threshold, singleSequences[l], rear, singleSequences); |
| |
| |
| if (rear.m_graphHolder) |
| singleSequences.push_back(rear); |
| } |
| |
| for (i = 0; i < organizedGraphs.size(); ++i) |
| for (j = 0; j < organizedGraphs[i].getNodesCount(); ++j) |
| if (!organizedGraphs[i].getNode(j).hasAttribute(JointSequenceGraph::ELIMINATED)) |
| for (k = 0; k < organizedGraphs[i].getNode(j).getLinksCount(); ++k) { |
| Sequence &s = *organizedGraphs[i].node(j).link(k); |
| if (s.isForward() && !s.m_graphHolder->getNode(s.m_tail).hasAttribute(SAMPLECOLOR_SIGN)) { |
| unsigned int next = organizedGraphs[i].node(j).link(k).getNext(); |
| unsigned int nextLink = organizedGraphs[i].tailLinkOf(j, k); |
| |
| Sequence &sOpposite = *organizedGraphs[i].node(next).link(nextLink); |
| sampleColor(cm, threshold, s, sOpposite, singleSequences); |
| } |
| } |
| } |
| } |
| |
| |
| |
| inline void applyStrokeIndices(VectorizerCoreGlobals *globals) |
| { |
| unsigned int i, j, k, n; |
| unsigned int next, nextLink; |
| |
| for (i = 0; i < globals->singleSequences.size(); ++i) |
| globals->singleSequences[i].m_strokeIndex = i; |
| |
| n = i; |
| |
| for (i = 0; i < globals->organizedGraphs.size(); ++i) { |
| JointSequenceGraph *currJSGraph = &globals->organizedGraphs[i]; |
| for (j = 0; j < currJSGraph->getNodesCount(); ++j) |
| if (!currJSGraph->getNode(j).hasAttribute(JointSequenceGraph::ELIMINATED)) |
| for (k = 0; k < currJSGraph->getNode(j).getLinksCount(); ++k) { |
| Sequence &s = *currJSGraph->node(j).link(k); |
| if (s.isForward()) { |
| s.m_strokeIndex = n; |
| |
| if (!s.m_graphHolder->getNode(s.m_tail).hasAttribute(SAMPLECOLOR_SIGN)) { |
| next = currJSGraph->getNode(j).getLink(k).getNext(); |
| nextLink = currJSGraph->tailLinkOf(j, k); |
| currJSGraph->node(next).link(nextLink)->m_strokeIndex = n; |
| } |
| |
| ++n; |
| } |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| int getInkPredominance(const TRasterCM32P &ras, TPalette *palette, int x, int y, int radius, int threshold) |
| { |
| int i, j; |
| int mx, my, Mx, My; |
| std::vector<int> inksFound(palette->getStyleCount()); |
| |
| radius = std::min(radius, 7); |
| |
| mx = std::max(x - radius, 0); |
| my = std::max(y - radius, 0); |
| Mx = std::min(x + radius, ras->getLx() - 1); |
| My = std::min(y + radius, ras->getLy() - 1); |
| |
| |
| for (i = mx; i <= Mx; ++i) |
| for (j = my; j <= My; ++j) |
| if (sq(i) + sq(j) <= sq(radius) && ras->pixels(j)[i].getTone() < threshold) { |
| |
| inksFound[ras->pixels(j)[i].getInk()] += 255 - ras->pixels(j)[i].getTone(); |
| } |
| |
| |
| int maxCount = 0, mostFound = 0; |
| for (i = 0; i < (int)inksFound.size(); ++i) |
| if (inksFound[i] > maxCount) { |
| maxCount = inksFound[i]; |
| mostFound = i; |
| } |
| |
| return mostFound; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| int getBranchPredominance(const TRasterCM32P &ras, TPalette *palette, JointSequenceGraph::Node &node) |
| { |
| struct locals { |
| static inline bool valueLess( |
| const std::pair<int, int> &a, const std::pair<int, int> &b) |
| { |
| return (a.second < b.second); |
| } |
| }; |
| |
| boost_c::flat_map<int, int> branchInksHistogram; |
| |
| UINT l, lCount = node.getLinksCount(); |
| |
| for (l = 0; l != lCount; ++l) { |
| int color = node.getLink(l)->m_color; |
| if (color >= 0 && color <= palette->getStyleCount()) |
| ++branchInksHistogram[color]; |
| } |
| |
| |
| if (branchInksHistogram.empty()) |
| return -1; |
| |
| typedef boost_c::flat_map<int, int>::iterator histo_it; |
| |
| const std::pair<histo_it, histo_it> &histoRange = |
| boost::minmax_element(branchInksHistogram.begin(), branchInksHistogram.end(), |
| locals::valueLess); |
| |
| return (histoRange.first->second == histoRange.second->second) ? -1 : histoRange.second->first; |
| } |
| |
| |
| |
| |
| void sortJS(JointSequenceGraph *js, std::vector<std::pair<int, TStroke *>> &toOrder, |
| const TRasterCM32P &ras, TPalette *palette) |
| { |
| enum { SORTED = 0x10 }; |
| |
| std::vector<std::pair<unsigned int, int>> nodesToDo; |
| unsigned int currNodeIdx, nextNodeIdx; |
| int currColor, currHeight, nextColor, nextHeight; |
| T3DPointD pD; |
| TPoint p; |
| |
| SkeletonGraph *currGraph = js->getNode(0).getLink(0)->m_graphHolder; |
| |
| unsigned int n, nCount = js->getNodesCount(); |
| for (n = 0; n != nCount; ++n) { |
| |
| if (!js->getNode(n).hasAttribute(JointSequenceGraph::ELIMINATED | SORTED)) { |
| nodesToDo.push_back(std::make_pair(n, 0)); |
| |
| while (!nodesToDo.empty()) { |
| currNodeIdx = nodesToDo.back().first; |
| currHeight = nodesToDo.back().second; |
| nodesToDo.pop_back(); |
| |
| JointSequenceGraph::Node &currNode = js->node(currNodeIdx); |
| |
| |
| currNode.setAttribute(SORTED); |
| |
| |
| pD = *currGraph->getNode(currNode.getLink(0)->m_head); |
| p = TPoint(pD.x, pD.y); |
| |
| if (!ras->getBounds().contains(p)) |
| continue; |
| |
| |
| currColor = getBranchPredominance(ras, palette, currNode); |
| if (currColor < 0) |
| currColor = ras->pixels(p.y)[p.x].getInk(); |
| |
| int l, lCount = currNode.getLinksCount(); |
| for (l = 0; l != lCount; ++l) { |
| nextNodeIdx = currNode.getLink(l).getNext(); |
| Sequence &s = *currNode.link(l); |
| |
| |
| toOrder[s.m_strokeIndex].first = (s.m_color == currColor) ? currHeight : currHeight - 1; |
| |
| if (!(currNode.getLink(l).getAccess() == SORTED)) { |
| |
| |
| |
| if (!currGraph->getNode(s.m_tail).hasAttribute(SAMPLECOLOR_SIGN)) { |
| JointSequenceGraph::Node &nextNode = js->node(nextNodeIdx); |
| |
| |
| pD = *currGraph->getNode(nextNode.getLink(0)->m_head); |
| p = TPoint(pD.x, pD.y); |
| |
| if (!ras->getBounds().contains(p)) |
| continue; |
| |
| |
| if (!nextNode.hasAttribute(SORTED)) { |
| |
| nextColor = getBranchPredominance(ras, palette, nextNode); |
| if (nextColor < 0) |
| nextColor = ras->pixels(p.y)[p.x].getInk(); |
| |
| nextHeight = (s.m_color == nextColor) ? toOrder[s.m_strokeIndex].first : toOrder[s.m_strokeIndex].first + 1; |
| |
| nodesToDo.push_back(std::make_pair(nextNodeIdx, nextHeight)); |
| } |
| |
| |
| nextNode.link(js->tailLinkOf(currNodeIdx, l)).setAccess(SORTED); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| |
| inline void orderColoredStrokes(JointSequenceGraphList &organizedGraphs, std::vector<TStroke *> &strokes, |
| const TRasterCM32P &ras, TPalette *palette) |
| { |
| |
| std::vector<std::pair<int, TStroke *>> strokesByHeight( |
| strokes.size(), std::make_pair(-(std::numeric_limits<int>::max)(), (TStroke *)0)); |
| |
| size_t s, sCount = strokes.size(); |
| for (s = 0; s != sCount; ++s) |
| strokesByHeight[s].second = strokes[s]; |
| |
| size_t og, ogCount = organizedGraphs.size(); |
| for (og = 0; og != ogCount; ++og) |
| sortJS(&organizedGraphs[og], strokesByHeight, ras, palette); |
| |
| |
| std::sort(strokesByHeight.begin(), strokesByHeight.end()); |
| |
| for (s = 0; s != sCount; ++s) |
| strokes[s] = strokesByHeight[s].second; |
| } |
| |
| |
| |
| |
| |
| |
| void applyStrokeColors(std::vector<TStroke *> &strokes, const TRasterP &ras, TPalette *palette, |
| VectorizerCoreGlobals &g) |
| { |
| JointSequenceGraphList &organizedGraphs = g.organizedGraphs; |
| SequenceList &singleSequences = g.singleSequences; |
| |
| TRasterCM32P cm = ras; |
| unsigned int i, j, k, n; |
| |
| if (cm && g.currConfig->m_maxThickness > 0.0) { |
| applyStrokeIndices(&g); |
| |
| |
| for (i = 0; i < singleSequences.size(); ++i) |
| strokes[i]->setStyle(singleSequences[i].m_color); |
| |
| |
| n = i; |
| |
| for (i = 0; i < organizedGraphs.size(); ++i) |
| for (j = 0; j < organizedGraphs[i].getNodesCount(); ++j) |
| if (!organizedGraphs[i].getNode(j).hasAttribute(JointSequenceGraph::ELIMINATED)) |
| for (k = 0; k < organizedGraphs[i].getNode(j).getLinksCount(); ++k) { |
| Sequence &s = *organizedGraphs[i].node(j).link(k); |
| if (s.isForward()) { |
| |
| strokes[n]->setStyle(s.m_color); |
| ++n; |
| } |
| } |
| |
| |
| orderColoredStrokes(organizedGraphs, strokes, cm, palette); |
| } else { |
| |
| int blackStyleId = palette->getClosestStyle(TPixel32::Black); |
| |
| unsigned int i; |
| for (i = 0; i < strokes.size(); ++i) |
| strokes[i]->setStyle(blackStyleId); |
| } |
| } |
| |