/* * Copyright 2020 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 #pragma once #include "BitMatrix.h" #include namespace ZXing { enum class Direction { LEFT = -1, RIGHT = 1 }; inline Direction opposite(Direction dir) noexcept { return dir == Direction::LEFT ? Direction::RIGHT : Direction::LEFT; } /** * @brief The BitMatrixCursor represents a current position inside an image and current direction it can advance towards. * * The current position and direction is a PointT. So depending on the type it can be used to traverse the image * in a Bresenham style (PointF) or in a discrete way (step only horizontal/vertical/diagonal (PointI)). */ template class BitMatrixCursor { using this_t = BitMatrixCursor; public: const BitMatrix* img; POINT p; // current position POINT d; // current direction BitMatrixCursor(const BitMatrix& image, POINT p, POINT d) : img(&image), p(p) { setDirection(d); } class Value { enum { INVALID = -1, WHITE = 0, BLACK = 1 }; int v = INVALID; public: Value() = default; Value(bool isBlack) : v(isBlack) {} bool isValid() const noexcept { return v != INVALID; } bool isWhite() const noexcept { return v == WHITE; } bool isBlack() const noexcept { return v == BLACK; } operator bool() const noexcept { return isValid(); } bool operator==(Value o) const { return v == o.v; } bool operator!=(Value o) const { return v != o.v; } }; template Value testAt(PointT p) const { return img->isIn(p) ? Value{img->get(p)} : Value{}; } bool blackAt(POINT pos) const noexcept { return testAt(pos).isBlack(); } bool whiteAt(POINT pos) const noexcept { return testAt(pos).isWhite(); } bool isIn(POINT p) const noexcept { return img->isIn(p); } bool isIn() const noexcept { return isIn(p); } bool isBlack() const noexcept { return blackAt(p); } bool isWhite() const noexcept { return whiteAt(p); } POINT front() const noexcept { return d; } POINT back() const noexcept { return {-d.x, -d.y}; } POINT left() const noexcept { return {d.y, -d.x}; } POINT right() const noexcept { return {-d.y, d.x}; } POINT direction(Direction dir) const noexcept { return static_cast(dir) * right(); } void turnBack() noexcept { d = back(); } void turnLeft() noexcept { d = left(); } void turnRight() noexcept { d = right(); } void turn(Direction dir) noexcept { d = direction(dir); } Value edgeAt(POINT d) const noexcept { Value v = testAt(p); return testAt(p + d) != v ? v : Value(); } Value edgeAtFront() const noexcept { return edgeAt(front()); } Value edgeAtBack() const noexcept { return edgeAt(back()); } Value edgeAtLeft() const noexcept { return edgeAt(left()); } Value edgeAtRight() const noexcept { return edgeAt(right()); } Value edgeAt(Direction dir) const noexcept { return edgeAt(direction(dir)); } this_t& setDirection(PointF dir) { return d = bresenhamDirection(dir), *this; } this_t& setDirection(PointI dir) { return d = dir, *this; } bool step(typename POINT::value_t s = 1) { p += s * d; return isIn(p); } this_t movedBy(POINT o) const noexcept { return {*img, p + o, d}; } this_t turnedBack() const noexcept { return {*img, p, back()}; } /** * @brief stepToEdge advances cursor to one step behind the next (or n-th) edge. * @param nth number of edges to pass * @param range max number of steps to take * @param backup whether or not to backup one step so we land in front of the edge * @return number of steps taken or 0 if moved outside of range/image */ int stepToEdge(int nth = 1, int range = 0, bool backup = false) { int steps = 0; auto lv = testAt(p); while (nth && (!range || steps < range) && lv.isValid()) { ++steps; auto v = testAt(p + steps * d); if (lv != v) { lv = v; --nth; } } if (backup) --steps; p += steps * d; return steps * (nth == 0); } bool stepAlongEdge(Direction dir, bool skipCorner = false) { if (!edgeAt(dir)) turn(dir); else if (edgeAtFront()) { turn(opposite(dir)); if (edgeAtFront()) { turn(opposite(dir)); if (edgeAtFront()) return false; } } bool ret = step(); if (ret && skipCorner && !edgeAt(dir)) { turn(dir); ret = step(); } return ret; } int countEdges(int range) { int res = 0; while (int steps = range ? stepToEdge(1, range) : 0) { range -= steps; ++res; } return res; } template ARRAY readPattern(int range = 0) { ARRAY res = {}; for (auto& i : res) { i = stepToEdge(1, range); if (!i) return res; if (range) range -= i; } return res; } template ARRAY readPatternFromBlack(int maxWhitePrefix, int range = 0) { if (maxWhitePrefix && isWhite() && !stepToEdge(1, maxWhitePrefix)) return {}; return readPattern(range); } }; using BitMatrixCursorF = BitMatrixCursor; using BitMatrixCursorI = BitMatrixCursor; class FastEdgeToEdgeCounter { const uint8_t* p = nullptr; int stride = 0; int stepsToBorder = 0; public: FastEdgeToEdgeCounter(const BitMatrixCursorI& cur) { stride = cur.d.y * cur.img->width() + cur.d.x; p = cur.img->row(cur.p.y).begin() + cur.p.x; int maxStepsX = cur.d.x ? (cur.d.x > 0 ? cur.img->width() - 1 - cur.p.x : cur.p.x) : INT_MAX; int maxStepsY = cur.d.y ? (cur.d.y > 0 ? cur.img->height() - 1 - cur.p.y : cur.p.y) : INT_MAX; stepsToBorder = std::min(maxStepsX, maxStepsY); } int stepToNextEdge(int range) { int maxSteps = std::min(stepsToBorder, range); int steps = 0; do { if (++steps > maxSteps) { if (maxSteps == stepsToBorder) break; else return 0; } } while (p[steps * stride] == p[0]); p += steps * stride; stepsToBorder -= steps; return steps; } }; } // ZXing