| |
| |
| #include "tcenterlinevectP.h" |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class RawBorderPoint |
| { |
| |
| TPoint m_position; |
| int m_ambiguousTurn; |
| |
| |
| public: |
| RawBorderPoint() : m_ambiguousTurn(0) {} |
| RawBorderPoint(int i, int j) : m_position(i, j), m_ambiguousTurn(0) {} |
| |
| inline TPoint pos() const { return m_position; } |
| inline int x() const { return m_position.x; } |
| inline int y() const { return m_position.y; } |
| |
| enum { left = 1, |
| right = 2 }; |
| inline int getAmbiguous() const { return m_ambiguousTurn; } |
| inline void setAmbiguous(int direction) { m_ambiguousTurn = direction; } |
| }; |
| |
| |
| |
| class RawBorder : public std::vector<RawBorderPoint> |
| { |
| int m_xExternal; |
| |
| TPointD *m_coordinateSums; |
| TPointD *m_coordinateSquareSums; |
| double *m_coordinateMixedSums; |
| |
| public: |
| RawBorder() {} |
| ~RawBorder() {} |
| |
| void setXExternalPixel(int a) { m_xExternal = a; } |
| int xExternalPixel() { return m_xExternal; } |
| TPointD *&sums() { return m_coordinateSums; } |
| TPointD *&sums2() { return m_coordinateSquareSums; } |
| double *&sumsMix() { return m_coordinateMixedSums; } |
| }; |
| |
| |
| |
| |
| |
| typedef std::vector<RawBorder *> BorderFamily; |
| typedef std::vector<BorderFamily> BorderList; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| namespace |
| { |
| |
| enum { white = 0, |
| black = 1 }; |
| enum { inner = 0, |
| outer = 1, |
| none = 2, |
| invalid = 3 }; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| template <typename T> |
| class PixelEvaluator |
| { |
| TRasterPT<T> m_ras; |
| int m_threshold; |
| |
| public: |
| PixelEvaluator(const TRasterPT<T> &ras, int threshold) |
| : m_ras(ras), m_threshold(threshold) {} |
| |
| inline unsigned char getBlackOrWhite(int x, int y); |
| }; |
| |
| |
| |
| template <> |
| inline unsigned char PixelEvaluator<TPixel32>::getBlackOrWhite(int x, int y) |
| { |
| |
| |
| |
| |
| |
| return tmax(m_ras->pixels(y)[x].r, tmax(m_ras->pixels(y)[x].g, m_ras->pixels(y)[x].b)) < |
| m_threshold * (m_ras->pixels(y)[x].m / 255.0); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| |
| template <> |
| inline unsigned char PixelEvaluator<TPixelGR8>::getBlackOrWhite(int x, int y) |
| { |
| return m_ras->pixels(y)[x].value < m_threshold; |
| } |
| |
| template <> |
| inline unsigned char PixelEvaluator<TPixelCM32>::getBlackOrWhite(int x, int y) |
| { |
| return m_ras->pixels(y)[x].getTone() < m_threshold; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| class Signaturemap |
| { |
| unsigned char *m_array; |
| int m_rowSize; |
| int m_colSize; |
| |
| public: |
| Signaturemap(const TRasterP &ras, int threshold); |
| ~Signaturemap() { delete[] m_array; } |
| |
| template <typename T> |
| void readRasterData(const TRasterPT<T> &ras, int threshold); |
| |
| inline int getRowSize() const { return m_rowSize; } |
| inline int getColSize() const { return m_colSize; } |
| |
| unsigned char *pixelByte(int x, int y) { return &m_array[(y + 1) * m_rowSize + x + 1]; } |
| |
| bool getBitmapColor(int x, int y) const |
| { |
| return m_array[(y + 1) * m_rowSize + x + 1] & 1; |
| } |
| |
| inline unsigned char getSignature(int x, int y) const |
| { |
| return m_array[(y + 1) * m_rowSize + x + 1] >> 1; |
| } |
| |
| void setSignature(int x, int y, int val) |
| { |
| unsigned char *pixel = pixelByte(x, y); |
| *pixel &= 1; |
| *pixel |= (val << 1); |
| } |
| }; |
| |
| |
| |
| Signaturemap::Signaturemap(const TRasterP &ras, int threshold) |
| { |
| |
| TRaster32P rr = (TRaster32P)ras; |
| TRasterGR8P rgr = (TRasterGR8P)ras; |
| TRasterCM32P rt = (TRasterCM32P)ras; |
| assert(rr || rgr || rt); |
| |
| |
| if (rr) { |
| rr->lock(); |
| readRasterData(rr, threshold); |
| rr->unlock(); |
| } else if (rgr) { |
| rgr->lock(); |
| readRasterData(rgr, threshold); |
| rgr->unlock(); |
| } else { |
| rt->lock(); |
| readRasterData(rt, threshold); |
| rt->unlock(); |
| } |
| } |
| |
| |
| |
| template <typename T> |
| void Signaturemap::readRasterData(const TRasterPT<T> &ras, int threshold) |
| { |
| unsigned char *currByte; |
| int x, y; |
| |
| PixelEvaluator<T> evaluator(ras, threshold); |
| |
| m_rowSize = ras->getLx() + 2; |
| m_colSize = ras->getLy() + 2; |
| m_array = new unsigned char[m_rowSize * m_colSize]; |
| |
| memset(m_array, none << 1, m_rowSize); |
| |
| currByte = m_array + m_rowSize; |
| for (y = 0; y < ras->getLy(); ++y) { |
| *currByte = none << 1; |
| currByte++; |
| |
| for (x = 0; x < ras->getLx(); ++x, ++currByte) |
| *currByte = evaluator.getBlackOrWhite(x, y) | (none << 1); |
| |
| *currByte = none << 1; |
| currByte++; |
| } |
| |
| memset(currByte, none << 1, m_rowSize); |
| } |
| |
| |
| |
| |
| inline bool getMinorityCheck(const Signaturemap &ras, int x, int y) |
| { |
| |
| return (ras.getBitmapColor(x + 1, y) + |
| ras.getBitmapColor(x + 1, y - 1) + |
| ras.getBitmapColor(x - 2, y) + |
| ras.getBitmapColor(x - 2, y - 1) + |
| ras.getBitmapColor(x - 1, y + 1) + |
| ras.getBitmapColor(x - 1, y - 2) + |
| ras.getBitmapColor(x, y + 1) + |
| ras.getBitmapColor(x, y - 2)) > 4; |
| } |
| |
| |
| |
| |
| inline void setSignature(Signaturemap &ras, const RawBorder &border, int val) |
| { |
| unsigned int j; |
| int yOld; |
| |
| |
| yOld = border.back().y(); |
| for (j = 0; j < border.size(); ++j) { |
| if (border[j].y() < yOld) { |
| ras.setSignature(border[j].x(), border[j].y(), val); |
| } else if (border[j].y() > yOld) { |
| ras.setSignature(border[j].x(), yOld, val); |
| } |
| yOld = border[j].y(); |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| RawBorder *extractPath(Signaturemap &ras, int x0, int y0, |
| int pathType, int xOuterPixel, int despeckling) |
| { |
| RawBorder *path = new RawBorder; |
| int x, y; |
| short dirX, dirY; |
| long int area = 0; |
| bool nextLeftPixel, nextRightPixel; |
| |
| if (pathType == outer) { |
| dirX = 0; |
| dirY = 1; |
| } else { |
| dirX = 1; |
| dirY = 0; |
| area += y0; |
| path->setXExternalPixel(xOuterPixel); |
| } |
| |
| path->push_back(RawBorderPoint(x0, y0)); |
| |
| |
| nextLeftPixel = ras.getBitmapColor(x0 + (dirY - dirX - 1) / 2, y0 + (-dirY - dirX - 1) / 2); |
| nextRightPixel = ras.getBitmapColor(x0 + (-dirX - dirY - 1) / 2, y0 + (dirX - dirY - 1) / 2); |
| if ((nextRightPixel == black) && (nextLeftPixel == white)) |
| path->back().setAmbiguous(dirX ? RawBorderPoint::left : RawBorderPoint::right); |
| |
| |
| |
| for (x = x0 + dirX, y = y0 + dirY; !(x == x0 && y == y0); x += dirX, y += dirY) { |
| path->push_back(RawBorderPoint(x, y)); |
| |
| |
| nextLeftPixel = ras.getBitmapColor(x + (dirX - dirY - 1) / 2, y + (dirY + dirX - 1) / 2); |
| nextRightPixel = ras.getBitmapColor(x + (dirX + dirY - 1) / 2, y + (dirY - dirX - 1) / 2); |
| |
| if ((nextRightPixel == black) && (nextLeftPixel == black)) { |
| |
| std::swap(dirY, dirX); |
| dirX = -dirX; |
| } else if ((nextRightPixel == white) && (nextLeftPixel == white)) { |
| |
| std::swap(dirY, dirX); |
| dirY = -dirY; |
| } else if ((nextRightPixel == white) && (nextLeftPixel == black)) { |
| |
| |
| |
| |
| if (getMinorityCheck(ras, x, y) == black) { |
| std::swap(dirY, dirX); |
| dirY = -dirY; |
| path->back().setAmbiguous(RawBorderPoint::right); |
| } |
| else { |
| std::swap(dirY, dirX); |
| dirX = -dirX; |
| path->back().setAmbiguous(RawBorderPoint::left); |
| } |
| } |
| |
| |
| area += y * dirX; |
| |
| |
| if (dirY != 0) |
| ras.setSignature(x, y + (dirY - 1) / 2, pathType); |
| } |
| |
| |
| |
| if (abs(area) < despeckling) { |
| setSignature(ras, *path, invalid); |
| delete path; |
| path = 0; |
| } |
| |
| return path; |
| } |
| |
| |
| |
| BorderList *extractBorders(const TRasterP &ras, int threshold, int despeckling) |
| { |
| Signaturemap byteImage(ras, threshold); |
| |
| BorderList *borderHierarchy = new BorderList; |
| vector<RawBorder *> outerBorders; |
| list<RawBorder *> innerBorders; |
| RawBorder *foundPath; |
| |
| int x, y; |
| bool Color, oldColor; |
| int xOuterPixel = 0; |
| bool enteredRegionType; |
| unsigned char signature; |
| |
| |
| for (y = 0; y < ras->getLy(); ++y) { |
| oldColor = white; |
| enteredRegionType = outer; |
| for (x = 0; x < ras->getLx(); ++x) { |
| if (oldColor ^ (Color = byteImage.getBitmapColor(x, y))) { |
| |
| enteredRegionType = !enteredRegionType; |
| |
| if ((signature = byteImage.getSignature(x, y)) == none) { |
| |
| if (foundPath = extractPath(byteImage, x, y, !enteredRegionType, xOuterPixel, despeckling)) |
| if (enteredRegionType == outer) |
| innerBorders.push_back(foundPath); |
| else |
| outerBorders.push_back(foundPath); |
| } |
| |
| |
| |
| if (enteredRegionType == inner && signature != invalid) |
| xOuterPixel = x; |
| |
| |
| if (signature == invalid) |
| byteImage.setSignature(x, y, none); |
| |
| oldColor = Color; |
| } |
| } |
| } |
| |
| |
| |
| unsigned int i; |
| list<RawBorder *>::iterator l; |
| |
| |
| |
| innerBorders.push_front(0); |
| |
| for (i = 0; i < outerBorders.size(); ++i) { |
| |
| |
| borderHierarchy->push_back(BorderFamily()); |
| borderHierarchy->back().push_back(outerBorders[i]); |
| |
| |
| setSignature(byteImage, *outerBorders[i], none); |
| |
| |
| |
| for (l = innerBorders.begin(), ++l; l != innerBorders.end(); ++l) { |
| if (byteImage.getSignature((*l)->xExternalPixel(), (**l)[0].y()) == none) { |
| borderHierarchy->back().push_back(*l); |
| setSignature(byteImage, **l, none); |
| l = innerBorders.erase(l); |
| --l; |
| } |
| } |
| } |
| |
| return borderHierarchy; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| inline bool isCircular(int a, int b, int c) |
| { |
| return a <= c ? a <= b && b < c : c > b || b >= a; |
| } |
| |
| |
| |
| |
| inline int *findNextCorners(RawBorder &path) |
| { |
| int i, currentCorner; |
| int *corners = new int[path.size()]; |
| |
| |
| currentCorner = 0; |
| for (i = path.size() - 1; i >= 0; --i) { |
| if (path[currentCorner].x() != path[i].x() && |
| path[currentCorner].y() != path[i].y()) |
| currentCorner = i + 1; |
| corners[i] = currentCorner; |
| } |
| |
| return corners; |
| } |
| |
| |
| |
| |
| inline int *furthestKs(RawBorder &path, int *&nextCorners) |
| { |
| |
| int n = path.size(); |
| int *kVector = new int[n]; |
| |
| enum { left, |
| up, |
| right, |
| down }; |
| int directionsOccurred[4]; |
| |
| nextCorners = findNextCorners(path); |
| |
| int i, j, k; |
| TPoint shift; |
| TPoint leftConstraint, rightConstraint, violatedConstraint; |
| TPoint newLeftConstraint, newRightConstraint; |
| TPoint jPoint, jNextPoint, iPoint, direction; |
| |
| int directionSignature; |
| |
| for (i = 0; i < n; ++i) { |
| |
| leftConstraint = rightConstraint = TPoint(); |
| directionsOccurred[0] = directionsOccurred[1] = directionsOccurred[2] = |
| directionsOccurred[3] = 0; |
| j = i; |
| jNextPoint = iPoint = path[i].pos(); |
| |
| |
| while (1) { |
| |
| |
| |
| |
| jPoint = jNextPoint; |
| jNextPoint = path[nextCorners[j]].pos(); |
| |
| |
| directionSignature = jNextPoint.x > jPoint.x ? right : jNextPoint.x < jPoint.x ? left |
| : jNextPoint.y > jPoint.y ? up : down; |
| directionsOccurred[directionSignature] = 1; |
| |
| |
| if (directionsOccurred[left] && directionsOccurred[right] && |
| directionsOccurred[up] && directionsOccurred[down]) { |
| k = j; |
| goto foundK; |
| } |
| |
| |
| shift = jNextPoint - iPoint; |
| |
| |
| |
| if (cross(shift, leftConstraint) < 0) { |
| violatedConstraint = leftConstraint; |
| break; |
| } |
| if (cross(shift, rightConstraint) > 0) { |
| violatedConstraint = rightConstraint; |
| break; |
| } |
| |
| |
| if (abs(shift.x) > 1 || abs(shift.y) > 1) { |
| |
| newLeftConstraint.x = shift.x + |
| (shift.y < 0 || (shift.y == 0 && shift.x < 0) ? 1 : -1); |
| newLeftConstraint.y = shift.y + |
| (shift.x > 0 || (shift.x == 0 && shift.y < 0) ? 1 : -1); |
| |
| if (cross(newLeftConstraint, leftConstraint) >= 0) |
| leftConstraint = newLeftConstraint; |
| |
| newRightConstraint.x = shift.x + |
| (shift.y > 0 || (shift.y == 0 && shift.x < 0) ? 1 : -1); |
| |
| newRightConstraint.y = shift.y + |
| (shift.x < 0 || (shift.x == 0 && shift.y < 0) ? 1 : -1); |
| |
| if (cross(newRightConstraint, rightConstraint) <= 0) |
| rightConstraint = newRightConstraint; |
| } |
| |
| |
| |
| if (path[nextCorners[j]].getAmbiguous()) { |
| if (path[nextCorners[j]].getAmbiguous() == RawBorderPoint::left) |
| rightConstraint = shift; |
| else |
| leftConstraint = shift; |
| } |
| |
| j = nextCorners[j]; |
| } |
| |
| |
| |
| |
| direction = convert(normalize(convert(jNextPoint - jPoint))); |
| k = (j + cross(jPoint - iPoint, violatedConstraint) / cross(violatedConstraint, direction)) % n; |
| |
| foundK: |
| |
| kVector[i] = k; |
| } |
| |
| return kVector; |
| } |
| |
| |
| |
| |
| |
| |
| |
| inline int *calculateForwardArcs(RawBorder &border, bool ambiguitiesCheck) |
| { |
| int i, j, n = (int)border.size(); |
| |
| int *nextCorners; |
| int *k = furthestKs(border, nextCorners); |
| int *K = new int[n]; |
| int *res = new int[n]; |
| |
| |
| for (i = 0; i < n; ++i) { |
| for (j = i, K[i] = k[i]; isCircular(i, j, K[i]); j = (j + 1) % n) |
| if (isCircular(j, k[j], K[i])) |
| K[i] = k[j]; |
| } |
| |
| |
| |
| |
| |
| |
| |
| for (i = n - 1, j = 0; j < n; i = j, ++j) { |
| res[j] = K[i] < j ? (K[i] == 0 ? n - 1 : n) : K[i] - 1; |
| } |
| |
| |
| |
| |
| if (ambiguitiesCheck) { |
| for (i = 1; nextCorners[i] > 0; i = nextCorners[i]) { |
| if (border[i].getAmbiguous() == RawBorderPoint::right) { |
| |
| |
| |
| int rrPlus1 = (res[res[i] % n] + 1) % n; |
| |
| for (j = nextCorners[i]; |
| isCircular(i, j, rrPlus1) && j != i; |
| j = nextCorners[j]) { |
| if (border[j].getAmbiguous() && (border[j].pos() == border[i].pos())) { |
| res[res[i] % n] = j - 1; |
| assert((res[i] % n) != j - 1); |
| |
| |
| for (int k = res[i] % n; res[k] >= j - 1 && k >= 0; --k) { |
| res[k] = j - 1; |
| assert(k != j - 1); |
| } |
| |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| delete[] k; |
| delete[] K; |
| delete[] nextCorners; |
| |
| return res; |
| } |
| |
| |
| |
| |
| |
| |
| inline void calculateSums(RawBorder &path) |
| { |
| unsigned int i, n = path.size(); |
| TPointD currentRelativePos; |
| |
| path.sums() = new TPointD[n + 1]; |
| path.sums2() = new TPointD[n + 1]; |
| path.sumsMix() = new double[n + 1]; |
| |
| path.sums()[0].x = path.sums()[0].y = path.sums2()[0].x = path.sums2()[0].y = 0; |
| for (i = 1; i < path.size(); ++i) { |
| currentRelativePos = convert(path[i].pos() - path[0].pos()); |
| |
| path.sums()[i] = path.sums()[i - 1] + currentRelativePos; |
| |
| path.sums2()[i].x = path.sums2()[i - 1].x + currentRelativePos.x * currentRelativePos.x; |
| path.sums2()[i].y = path.sums2()[i - 1].y + currentRelativePos.y * currentRelativePos.y; |
| |
| path.sumsMix()[i] = path.sumsMix()[i - 1] + currentRelativePos.x * currentRelativePos.y; |
| } |
| |
| |
| |
| path.sums()[n].x = path.sums()[n].y = path.sums2()[n].x = path.sums2()[n].y = 0; |
| } |
| |
| |
| |
| |
| |
| inline double penalty(RawBorder &path, int a, int b) |
| { |
| int n = b - a + 1; |
| |
| TPointD v = convert(rotate90(path[b == path.size() ? 0 : b].pos() - path[a].pos())); |
| TPointD sum = path.sums()[b] - path.sums()[a]; |
| TPointD sum2 = path.sums2()[b] - path.sums2()[a]; |
| double sumMix = path.sumsMix()[b] - path.sumsMix()[a]; |
| |
| double F1 = sum2.x - 2 * sum.x * path[a].x() + n * path[a].x() * path[a].x(); |
| double F2 = sum2.y - 2 * sum.y * path[a].y() + n * path[a].y() * path[a].y(); |
| double F3 = sumMix - sum.x * path[a].y() - sum.y * path[a].x() + n * path[a].x() * path[a].y(); |
| |
| return sqrt((v.y * v.y * F1 + v.x * v.x * F2 - 2 * v.x * v.y * F3) / n); |
| } |
| |
| |
| |
| |
| |
| |
| inline void reduceBorder(RawBorder &border, Contour &res, bool ambiguitiesCheck) |
| { |
| int i, j, k, a, *b, n = border.size(), m; |
| double newPenalty; |
| int minPenaltyNext; |
| int *minPenaltyNextArray = new int[n]; |
| |
| |
| int *longestArcFrom = calculateForwardArcs(border, ambiguitiesCheck); |
| calculateSums(border); |
| |
| double *penaltyToEnd = new double[n + 1]; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| for (i = 0, m = 0; i < n; i = longestArcFrom[i]) |
| ++m; |
| |
| |
| b = new int[m + 1]; |
| b[m] = n; |
| for (i = 0, j = 0; j < m; i = longestArcFrom[i], ++j) |
| b[j] = i; |
| |
| |
| |
| |
| |
| for (j = m - 1, a = n; j >= 0; --j) { |
| for (k = b[j]; k >= 0 && longestArcFrom[k] >= a; --k) { |
| penaltyToEnd[k] = infinity; |
| for (i = a; i <= longestArcFrom[k]; ++i) { |
| newPenalty = penaltyToEnd[i] + penalty(border, k, i); |
| if (newPenalty < penaltyToEnd[k]) |
| penaltyToEnd[k] = newPenalty; |
| minPenaltyNext = i; |
| } |
| minPenaltyNextArray[k] = minPenaltyNext; |
| } |
| a = k + 1; |
| } |
| |
| |
| |
| res.resize(m); |
| for (i = j = 0; i < n; i = minPenaltyNextArray[i], ++j) { |
| res[j] = ContourNode(border[i].x(), border[i].y()); |
| |
| |
| if (border[i].getAmbiguous() == RawBorderPoint::left) |
| res[j].setAttribute(ContourNode::AMBIGUOUS_LEFT); |
| if (border[i].getAmbiguous() == RawBorderPoint::right) |
| res[j].setAttribute(ContourNode::AMBIGUOUS_RIGHT); |
| } |
| |
| delete[] longestArcFrom; |
| delete[] minPenaltyNextArray; |
| delete[] border.sums(); |
| delete[] border.sums2(); |
| delete[] border.sumsMix(); |
| delete[] penaltyToEnd; |
| delete[] b; |
| } |
| |
| |
| |
| |
| inline void reduceBorders(BorderList &borders, Contours &result, bool ambiguitiesCheck) |
| { |
| unsigned int i, j; |
| |
| |
| result.resize(borders.size()); |
| |
| |
| for (i = 0; i < borders.size(); ++i) { |
| result[i].resize(borders[i].size()); |
| for (j = 0; j < borders[i].size(); ++j) { |
| reduceBorder(*borders[i][j], result[i][j], ambiguitiesCheck); |
| delete borders[i][j]; |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void polygonize(const TRasterP &ras, Contours &polygons, VectorizerCoreGlobals &g) |
| { |
| BorderList *borders; |
| |
| borders = extractBorders(ras, g.currConfig->m_threshold, g.currConfig->m_despeckling); |
| reduceBorders(*borders, polygons, g.currConfig->m_maxThickness > 0.0); |
| } |
| |