#include "DMDetector.h"
#include "BitMatrix.h"
#include "BitMatrixCursor.h"
#include "Matrix.h"
#include "DetectorResult.h"
#include "DMVersion.h"
#include "GridSampler.h"
#include "LogMatrix.h"
#include "LocalGrid.h"
#include "Point.h"
#include "RegressionLine.h"
#include "ResultPoint.h"
#include "StdScope.h"
#include "WhiteRectDetector.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <functional>
#include <map>
#include <utility>
#include <vector>
#ifndef PRINT_DEBUG
#define printf(...){}
#define printv(...){}
#else
#define printv(fmt, vec) \
for (auto v : vec) \
printf(fmt, v); \
printf("\n");
#endif
namespace ZXing::DataMatrix {
struct ResultPointsAndTransitions
{
const ResultPoint* from;
const ResultPoint* to;
int transitions;
};
static ResultPointsAndTransitions TransitionsBetween(const BitMatrix& image, const ResultPoint& from,
const ResultPoint& to)
{
int fromX = static_cast<int>(from.x());
int fromY = static_cast<int>(from.y());
int toX = static_cast<int>(to.x());
int toY = static_cast<int>(to.y());
bool steep = std::abs(toY - fromY) > std::abs(toX - fromX);
if (steep) {
std::swap(fromX, fromY);
std::swap(toX, toY);
}
int dx = std::abs(toX - fromX);
int dy = std::abs(toY - fromY);
int error = -dx / 2;
int ystep = fromY < toY ? 1 : -1;
int xstep = fromX < toX ? 1 : -1;
int transitions = 0;
bool inBlack = image.get(steep ? fromY : fromX, steep ? fromX : fromY);
for (int x = fromX, y = fromY; x != toX; x += xstep) {
bool isBlack = image.get(steep ? y : x, steep ? x : y);
if (isBlack != inBlack) {
transitions++;
inBlack = isBlack;
}
error += dy;
if (error > 0) {
if (y == toY) {
break;
}
y += ystep;
error -= dx;
}
}
return ResultPointsAndTransitions{ &from, &to, transitions };
}
static bool IsValidPoint(const ResultPoint& p, int imgWidth, int imgHeight)
{
return p.x() >= 0 && p.x() < imgWidth && p.y() > 0 && p.y() < imgHeight;
}
template <typename T>
static float RoundToNearestF(T x)
{
return static_cast<float>(std::round(x));
}
static bool CorrectTopRightRectangular(const BitMatrix& image, const ResultPoint& bottomLeft,
const ResultPoint& bottomRight, const ResultPoint& topLeft,
const ResultPoint& topRight, int dimensionTop, int dimensionRight,
ResultPoint& result)
{
float corr = RoundToNearestF(distance(bottomLeft, bottomRight)) / static_cast<float>(dimensionTop);
float norm = RoundToNearestF(distance(topLeft, topRight));
float cos = (topRight.x() - topLeft.x()) / norm;
float sin = (topRight.y() - topLeft.y()) / norm;
ResultPoint c1(topRight.x() + corr*cos, topRight.y() + corr*sin);
corr = RoundToNearestF(distance(bottomLeft, topLeft)) / (float)dimensionRight;
norm = RoundToNearestF(distance(bottomRight, topRight));
cos = (topRight.x() - bottomRight.x()) / norm;
sin = (topRight.y() - bottomRight.y()) / norm;
ResultPoint c2(topRight.x() + corr*cos, topRight.y() + corr*sin);
if (!IsValidPoint(c1, image.width(), image.height())) {
if (IsValidPoint(c2, image.width(), image.height())) {
result = c2;
return true;
}
return false;
}
if (!IsValidPoint(c2, image.width(), image.height())) {
result = c1;
return true;
}
int l1 = std::abs(dimensionTop - TransitionsBetween(image, topLeft, c1).transitions) +
std::abs(dimensionRight - TransitionsBetween(image, bottomRight, c1).transitions);
int l2 = std::abs(dimensionTop - TransitionsBetween(image, topLeft, c2).transitions) +
std::abs(dimensionRight - TransitionsBetween(image, bottomRight, c2).transitions);
result = l1 <= l2 ? c1 : c2;
return true;
}
static ResultPoint CorrectTopRight(const BitMatrix& image, const ResultPoint& bottomLeft, const ResultPoint& bottomRight,
const ResultPoint& topLeft, const ResultPoint& topRight, int dimension)
{
float corr = RoundToNearestF(distance(bottomLeft, bottomRight)) / (float)dimension;
float norm = RoundToNearestF(distance(topLeft, topRight));
float cos = (topRight.x() - topLeft.x()) / norm;
float sin = (topRight.y() - topLeft.y()) / norm;
ResultPoint c1(topRight.x() + corr * cos, topRight.y() + corr * sin);
corr = RoundToNearestF(distance(bottomLeft, topLeft)) / (float)dimension;
norm = RoundToNearestF(distance(bottomRight, topRight));
cos = (topRight.x() - bottomRight.x()) / norm;
sin = (topRight.y() - bottomRight.y()) / norm;
ResultPoint c2(topRight.x() + corr * cos, topRight.y() + corr * sin);
if (!IsValidPoint(c1, image.width(), image.height())) {
if (!IsValidPoint(c2, image.width(), image.height()))
return topRight;
return c2;
}
if (!IsValidPoint(c2, image.width(), image.height()))
return c1;
int l1 = std::abs(TransitionsBetween(image, topLeft, c1).transitions -
TransitionsBetween(image, bottomRight, c1).transitions);
int l2 = std::abs(TransitionsBetween(image, topLeft, c2).transitions -
TransitionsBetween(image, bottomRight, c2).transitions);
return l1 <= l2 ? c1 : c2;
}
static DetectorResult SampleGrid(const BitMatrix& image, const ResultPoint& topLeft, const ResultPoint& bottomLeft,
const ResultPoint& bottomRight, const ResultPoint& topRight, int width, int height)
{
return SampleGrid(image, width, height,
{Rectangle(width, height, 0), {topLeft, topRight, bottomRight, bottomLeft}});
}
static float CrossProductZ(const ResultPoint& a, const ResultPoint& b, const ResultPoint& c)
{
return (c.x() - b.x())*(a.y() - b.y()) - (c.y() - b.y())*(a.x() - b.x());
}
static void OrderByBestPatterns(const ResultPoint*& p0, const ResultPoint*& p1, const ResultPoint*& p2)
{
auto zeroOneDistance = distance(*p0, *p1);
auto oneTwoDistance = distance(*p1, *p2);
auto zeroTwoDistance = distance(*p0, *p2);
const ResultPoint* pointA;
const ResultPoint* pointB;
const ResultPoint* pointC;
if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) {
pointB = p0;
pointA = p1;
pointC = p2;
}
else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) {
pointB = p1;
pointA = p0;
pointC = p2;
}
else {
pointB = p2;
pointA = p0;
pointC = p1;
}
if (CrossProductZ(*pointA, *pointB, *pointC) < 0.0f) {
std::swap(pointA, pointC);
}
p0 = pointA;
p1 = pointB;
p2 = pointC;
}
static DetectorResult DetectOld(const BitMatrix& image)
{
ResultPoint pointA, pointB, pointC, pointD;
if (!DetectWhiteRect(image, pointA, pointB, pointC, pointD))
return {};
std::array transitions = {
TransitionsBetween(image, pointA, pointB),
TransitionsBetween(image, pointA, pointC),
TransitionsBetween(image, pointB, pointD),
TransitionsBetween(image, pointC, pointD),
};
std::sort(transitions.begin(), transitions.end(),
[](const auto& a, const auto& b) { return a.transitions < b.transitions; });
const auto& lSideOne = transitions[0];
const auto& lSideTwo = transitions[1];
if (lSideTwo.transitions > 2)
return {};
std::map<const ResultPoint*, int> pointCount;
pointCount[lSideOne.from] += 1;
pointCount[lSideOne.to] += 1;
pointCount[lSideTwo.from] += 1;
pointCount[lSideTwo.to] += 1;
const ResultPoint* bottomRight = nullptr;
const ResultPoint* bottomLeft = nullptr;
const ResultPoint* topLeft = nullptr;
for (const auto& [point, count] : pointCount) {
if (count == 2) {
bottomLeft = point; }
else {
if (bottomRight == nullptr) {
bottomRight = point;
}
else {
topLeft = point;
}
}
}
if (bottomRight == nullptr || bottomLeft == nullptr || topLeft == nullptr)
return {};
OrderByBestPatterns(bottomRight, bottomLeft, topLeft);
const ResultPoint* topRight;
if (pointCount.find(&pointA) == pointCount.end()) {
topRight = &pointA;
}
else if (pointCount.find(&pointB) == pointCount.end()) {
topRight = &pointB;
}
else if (pointCount.find(&pointC) == pointCount.end()) {
topRight = &pointC;
}
else {
topRight = &pointD;
}
int dimensionTop = TransitionsBetween(image, *topLeft, *topRight).transitions;
int dimensionRight = TransitionsBetween(image, *bottomRight, *topRight).transitions;
if ((dimensionTop & 0x01) == 1) {
dimensionTop++;
}
dimensionTop += 2;
if ((dimensionRight & 0x01) == 1) {
dimensionRight++;
}
dimensionRight += 2;
if (dimensionTop < 10 || dimensionTop > 144 || dimensionRight < 8 || dimensionRight > 144 )
return {};
ResultPoint correctedTopRight;
if (4 * dimensionTop >= 7 * dimensionRight || 4 * dimensionRight >= 7 * dimensionTop) {
if (!CorrectTopRightRectangular(image, *bottomLeft, *bottomRight, *topLeft, *topRight, dimensionTop,
dimensionRight, correctedTopRight)) {
correctedTopRight = *topRight;
}
dimensionTop = TransitionsBetween(image, *topLeft, correctedTopRight).transitions;
dimensionRight = TransitionsBetween(image, *bottomRight, correctedTopRight).transitions;
if ((dimensionTop & 0x01) == 1) {
dimensionTop++;
}
if ((dimensionRight & 0x01) == 1) {
dimensionRight++;
}
}
else {
int dimension = std::min(dimensionRight, dimensionTop);
correctedTopRight = CorrectTopRight(image, *bottomLeft, *bottomRight, *topLeft, *topRight, dimension);
int dimensionCorrected = std::max(TransitionsBetween(image, *topLeft, correctedTopRight).transitions,
TransitionsBetween(image, *bottomRight, correctedTopRight).transitions);
dimensionCorrected++;
if ((dimensionCorrected & 0x01) == 1) {
dimensionCorrected++;
}
dimensionTop = dimensionRight = dimensionCorrected;
}
return SampleGrid(image, *topLeft, *bottomLeft, *bottomRight, correctedTopRight, dimensionTop, dimensionRight);
}
class DMRegressionLine : public RegressionLine
{
template <typename Container, typename Filter>
static double average(const Container& c, Filter f)
{
double sum = 0;
int num = 0;
for (const auto& v : c)
if (f(v)) {
sum += v;
++num;
}
return sum / num;
}
public:
void reverse() { std::reverse(_points.begin(), _points.end()); }
double modules(PointF beg, PointF end)
{
assert(_points.size() > 3);
evaluate(1.2, true);
std::vector<double> gapSizes, modSizes;
gapSizes.reserve(_points.size());
for (size_t i = 1; i < _points.size(); ++i)
gapSizes.push_back(distance(project(_points[i]), project(_points[i - 1])));
auto unitPixelDist = ZXing::length(bresenhamDirection(_points.back() - _points.front()));
double sumFront = distance(beg, project(_points.front())) - unitPixelDist;
double sumBack = 0; for (auto dist : gapSizes) {
if (dist > 1.9 * unitPixelDist)
modSizes.push_back(std::exchange(sumBack, 0.0));
sumFront += dist;
sumBack += dist;
if (dist > 1.9 * unitPixelDist)
modSizes.push_back(std::exchange(sumFront, 0.0));
}
if (modSizes.empty())
return 0;
modSizes.push_back(sumFront + distance(end, project(_points.back())));
modSizes.front() = 0; auto lineLength = distance(beg, end) - unitPixelDist;
auto [iMin, iMax] = std::minmax_element(modSizes.begin() + 1, modSizes.end());
auto meanModSize = average(modSizes, [](double dist){ return dist > 0; });
printf("unit pixel dist: %.1f\n", unitPixelDist);
printf("lineLength: %.1f, meanModSize: %.1f (min: %.1f, max: %.1f), gaps: %lu\n", lineLength, meanModSize, *iMin, *iMax,
modSizes.size());
printf("modSizes: ");
printv("%.1f ", modSizes);
if (*iMax > 2 * *iMin) {
for (int i = 1; i < Size(modSizes) - 2; ++i) {
if (modSizes[i] > 0 && modSizes[i] + modSizes[i + 2] < meanModSize * 1.4)
modSizes[i] += std::exchange(modSizes[i + 2], 0);
else if (modSizes[i] > meanModSize * 1.6)
modSizes[i] = 0;
}
printf("filtered: ");
printv("%.1f ", modSizes);
meanModSize = average(modSizes, [](double dist) { return dist > 0; });
}
printf("post filter meanModSize: %.1f\n", meanModSize);
return lineLength / meanModSize;
}
bool truncateIfLShape()
{
auto lenThis = Size(_points);
auto lineAB = RegressionLine(_points.front(), _points.back());
if (lenThis < 16 || lineAB.distance(_points[lenThis / 2]) < 5)
return false;
auto maxP = _points.begin();
double maxD = 0.0;
for (auto p = _points.begin(); p != _points.end(); ++p) {
auto d = lineAB.distance(*p);
if (d > maxD) {
maxP = p;
maxD = d;
}
}
auto lenL = distance(_points.front(), *maxP) - 1;
auto lenB = distance(*maxP, _points.back()) - 1;
if (maxD < std::min(lenL, lenB) / 2)
return false;
setDirectionInward(_points.back() - *maxP);
_points.resize(std::distance(_points.begin(), maxP) - 1);
return true;
}
};
class EdgeTracer : public BitMatrixCursorF
{
enum class StepResult { FOUND, OPEN_END, CLOSED_END };
#if defined(__clang__) || defined(__GNUC__)
inline __attribute__((always_inline))
#elif defined(_MSC_VER)
__forceinline
#endif
StepResult traceStep(PointF dEdge, int maxStepSize, bool goodDirection)
{
dEdge = mainDirection(dEdge);
for (int breadth = 1; breadth <= (maxStepSize == 1 ? 2 : (goodDirection ? 1 : 3)); ++breadth)
for (int step = 1; step <= maxStepSize; ++step)
for (int i = 0; i <= 2*(step/4+1) * breadth; ++i) {
auto pEdge = p + step * d + (i&1 ? (i+1)/2 : -i/2) * dEdge;
log(pEdge);
if (!blackAt(pEdge + dEdge))
continue;
for (int j = 0; j < std::max(maxStepSize, 3) && isIn(pEdge); ++j) {
if (whiteAt(pEdge)) {
assert(p != centered(pEdge));
p = centered(pEdge);
if (history && maxStepSize == 1) {
if (history->get(PointI(p)) == state)
return StepResult::CLOSED_END;
history->set(PointI(p), state);
}
return StepResult::FOUND;
}
pEdge = pEdge - dEdge;
if (blackAt(pEdge - d))
pEdge = pEdge - d;
log(pEdge);
}
return StepResult::CLOSED_END;
}
return StepResult::OPEN_END;
}
public:
using StateMatrix = Matrix<int8_t>;
StateMatrix* history = nullptr;
int state = 0;
using BitMatrixCursorF::BitMatrixCursor;
bool updateDirectionFromOrigin(PointF origin)
{
auto old_d = d;
setDirection(p - origin);
if (dot(d, old_d) < 0)
return false;
if (std::abs(d.x) == std::abs(d.y))
d = mainDirection(old_d) + 0.99f * (d - mainDirection(old_d));
else if (mainDirection(d) != mainDirection(old_d))
d = mainDirection(old_d) + 0.99f * mainDirection(d);
return true;
}
bool updateDirectionFromLine(RegressionLine& line)
{
return line.evaluate(1.5) && updateDirectionFromOrigin(p - line.project(p) + line.points().front());
}
bool updateDirectionFromLineCentroid(RegressionLine& line)
{
return updateDirectionFromOrigin(line.centroid());
}
bool traceLine(PointF dEdge, RegressionLine& line)
{
line.setDirectionInward(dEdge);
do {
log(p);
line.add(p);
if (line.points().size() % 50 == 10 && !updateDirectionFromLineCentroid(line))
return false;
auto stepResult = traceStep(dEdge, 1, line.isValid());
if (stepResult != StepResult::FOUND)
return stepResult == StepResult::OPEN_END && line.points().size() > 1 && updateDirectionFromLineCentroid(line);
} while (true);
}
bool traceGaps(PointF dEdge, RegressionLine& line, int maxStepSize, const RegressionLine& finishLine = {}, double minDist = 0)
{
line.setDirectionInward(dEdge);
int gaps = 0, steps = 0, maxStepsPerGap = maxStepSize;
PointF lastP;
do {
if (p == std::exchange(lastP, p) || steps++ > (gaps == 0 ? 2 : gaps + 1) * maxStepsPerGap)
return false;
log(p);
if (line.isValid() && line.signedDistance(p) < -5 && (!line.evaluate() || line.signedDistance(p) < -5))
return false;
if (line.isValid() && line.signedDistance(p) > 3) {
if (std::abs(dot(normalized(d), line.normal())) > 0.7) return false;
if (!line.evaluate(1.5))
return false;
auto np = line.project(p);
while (distance(np, line.project(line.points().back())) < 1)
np = np + d;
p = centered(np);
}
else {
auto curStep = line.points().empty() ? PointF() : p - line.points().back();
auto stepLengthInMainDir = line.points().empty() ? 0.0 : dot(mainDirection(d), curStep);
line.add(p);
if (stepLengthInMainDir > 1 || maxAbsComponent(curStep) >= 2) {
++gaps;
if (gaps >= 2 || line.points().size() > 5) {
if (!updateDirectionFromLine(line))
return false;
if (minDist && gaps >= 4 && distance(p, line.points().front()) > minDist) {
line.pop_back();
--gaps;
return true;
}
}
} else if (gaps == 0 && Size(line.points()) >= 2 * maxStepSize) {
return false; }
}
if (finishLine.isValid())
UpdateMin(maxStepSize, static_cast<int>(finishLine.signedDistance(p)));
auto stepResult = traceStep(dEdge, maxStepSize, line.isValid());
if (stepResult != StepResult::FOUND)
return stepResult == StepResult::OPEN_END && finishLine.isValid() &&
static_cast<int>(finishLine.signedDistance(p)) <= maxStepSize + 1;
} while (true);
}
bool traceCorner(PointF dir, PointF& corner)
{
step();
log(p);
corner = p;
std::swap(d, dir);
traceStep(-1 * dir, 2, false);
printf("turn: %.0f x %.0f -> %.2f, %.2f\n", p.x, p.y, d.x, d.y);
return isIn(corner) && isIn(p);
}
bool moveToNextWhiteAfterBlack()
{
assert(std::abs(d.x + d.y) == 1);
FastEdgeToEdgeCounter e2e(BitMatrixCursorI(*img, PointI(p), PointI(d)));
int steps = e2e.stepToNextEdge(INT_MAX);
if (!steps)
return false;
step(steps);
if(isWhite())
return true;
steps = e2e.stepToNextEdge(INT_MAX);
if (!steps)
return false;
return step(steps);
}
};
struct ModuleCenterLUT : std::vector<double>
{
using std::vector<double>::vector;
double operator()(int i) const { return empty() ? i + 0.5 : (*this)[i]; }
};
static ModuleCenterLUT BuildModuleCenterLUT(const std::vector<PointF>& points, PointF start, PointF end, int numModules,
const std::function<double(double)>& edgeFracToModule)
{
if (Size(points) < 5)
return {};
auto edgeDir = end - start;
auto edgeLen = length(edgeDir);
auto unitDir = normalized(edgeDir);
auto modSize = edgeLen / numModules;
std::vector<double> proj;
proj.reserve(points.size());
for (const auto& p : points)
proj.push_back(dot(p - start, unitDir));
bool startsWithGap = false;
if (proj.front() > proj.back()) {
std::reverse(proj.begin(), proj.end());
startsWithGap = true;
}
auto unitPixelDist = length(bresenhamDirection(edgeDir));
double gapThreshold = 1.4 * unitPixelDist;
std::vector<double> gapMids;
for (int i = 1; i < Size(proj); ++i)
if (proj[i] - proj[i - 1] > gapThreshold) {
auto gapMid = (proj[i] + proj[i - 1]) / 2;
if (gapMids.empty() || gapMid - gapMids.back() > 0.75 * 2 * modSize)
gapMids.push_back(gapMid);
}
if (gapMids.empty()) {
return {};
}
std::vector<int> modIdx(gapMids.size());
modIdx[0] = static_cast<int>(std::round((gapMids.front() / modSize - (1.5 + startsWithGap)) / 2)) * 2 + 1 + startsWithGap;
for (int i = 1; i < Size(gapMids); ++i)
modIdx[i] = modIdx[i - 1] + std::max(1, static_cast<int>(std::round((gapMids[i] - gapMids[i - 1]) / (2 * modSize)))) * 2;
printf("mIdx: ");
printv("%d ", modIdx);
struct Knot
{
double mc, corrMc;
};
std::vector<Knot> knots;
knots.reserve(Size(gapMids) + 2);
knots.push_back({0.0, 0.0});
for (int k = 0; k < Size(gapMids); ++k)
knots.push_back({modIdx[k] + 0.5, edgeFracToModule(gapMids[k] / edgeLen)});
knots.push_back({double(numModules), double(numModules)});
if (Reduce(knots, 0.0, [](double m, const Knot& k) { return std::max(m, std::abs(k.mc - k.corrMc)); }) < 0.5)
return {};
ModuleCenterLUT centerLUT(numModules);
#ifdef ZXING_USE_POLY_TIMING_REMAP
{
double s0 = Size(knots), s1 = 0, s2 = 0, s3 = 0, s4 = 0;
double b0 = 0, b1 = 0, b2 = 0;
for (const auto& k : knots) {
double m = k.mc, c = k.corrMc, m2 = m * m;
s1 += m;
s2 += m2;
s3 += m2 * m;
s4 += m2 * m2;
b0 += c;
b1 += m * c;
b2 += m2 * c;
}
double mat[3][4] = {{s0, s1, s2, b0}, {s1, s2, s3, b1}, {s2, s3, s4, b2}};
for (int col = 0; col < 3; ++col)
for (int row = col + 1; row < 3; ++row) {
double f = mat[row][col] / mat[col][col];
for (int j = col; j <= 3; ++j)
mat[row][j] -= f * mat[col][j];
}
double p2 = mat[2][3] / mat[2][2];
double p1 = (mat[1][3] - mat[1][2] * p2) / mat[1][1];
double p0 = (mat[0][3] - mat[0][2] * p2 - mat[0][1] * p1) / mat[0][0];
for (int i = 0; i < numModules; ++i) {
double mc = i + 0.5;
centerLUT[i] = p0 + p1 * mc + p2 * mc * mc;
}
}
#elif defined(ZXING_USE_POLY_TIMING_REMAP_CONSTRAINED)
{
double gg = 0, gb = 0;
for (const auto& k : knots) {
double g = k.mc * (k.mc - numModules);
gg += g * g;
gb += g * (k.corrMc - k.mc);
}
double a = gb / gg;
double p0 = 0;
double p1 = 1 - a * numModules;
double p2 = a;
for (int i = 0; i < numModules; ++i) {
double mc = i + 0.5;
centerLUT[i] = p0 + p1 * mc + p2 * mc * mc;
}
}
#else
int ki = 0;
for (int i = 0; i < numModules; ++i) {
double mc = i + 0.5;
while (ki + 1 < Size(knots) - 1 && knots[ki + 1].mc < mc)
++ki;
double denom = knots[ki + 1].mc - knots[ki].mc;
assert(denom >= 0.1);
double t = (mc - knots[ki].mc) / denom;
centerLUT[i] = knots[ki].corrMc + t * (knots[ki + 1].corrMc - knots[ki].corrMc);
}
#endif
printf("corr: ");
for (int i = 0; i < numModules; ++i)
printf("%.2f ", centerLUT[i] - (i + 0.5));
printf("\n");
return centerLUT;
}
static DetectorResult SampleGridCorrected(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix,
const ModuleCenterLUT& topCenterLUT, const ModuleCenterLUT& rightCenterLUT)
{
#ifdef PRINT_DEBUG
LogMatrix log;
static int i = 0;
LogMatrixWriter lmw(log, image, 5, "grid_c" + std::to_string(i++) + ".pnm");
#endif
BitMatrix res(width, height);
for (int y = 0; y < height; ++y) {
double my = rightCenterLUT(y);
for (int x = 0; x < width; ++x) {
auto p = mod2Pix(PointF{topCenterLUT(x), my});
if (!image.isIn(p))
return {};
#ifdef PRINT_DEBUG
log(p, 3);
#endif
if (image.get(p))
res.set(x, y);
}
}
auto projectCorner = [&](PointI p) { return PointI(mod2Pix(PointF(p)) + PointF(0.5, 0.5)); };
return {std::move(res),
{projectCorner({0, 0}), projectCorner({width, 0}), projectCorner({width, height}), projectCorner({0, height})}};
}
static DetectorResults Scan(EdgeTracer& startTracer, std::array<DMRegressionLine, 4>& lines)
{
while (startTracer.moveToNextWhiteAfterBlack()) {
log(startTracer.p);
PointF tl, bl, br, tr;
auto& [lineL, lineB, lineR, lineT] = lines;
for (auto& l : lines)
l.reset();
#ifdef PRINT_DEBUG
SCOPE_EXIT([&] {
for (auto& l : lines)
log(l.points());
});
# define CHECK(A) if (!(A)) { printf("broke at %d\n", __LINE__); continue; }
#else
# define CHECK(A) if(!(A)) continue
#endif
auto t = startTracer;
PointF up, right;
t.turnRight();
t.state = 1;
CHECK(t.traceLine(t.right(), lineL));
CHECK(t.traceCorner(t.right(), tl));
lineL.reverse();
auto tlTracer = t;
t = startTracer;
t.state = 1;
t.setDirection(tlTracer.right());
CHECK(t.traceLine(t.left(), lineL));
if (lineL.truncateIfLShape())
t.p = lineL.points().back();
t.updateDirectionFromOrigin(tl);
up = t.back();
CHECK(t.traceCorner(t.left(), bl));
t.state = 2;
CHECK(t.traceLine(t.left(), lineB));
t.updateDirectionFromOrigin(bl);
right = t.front();
CHECK(t.traceCorner(t.left(), br));
auto lenL = distance(tl, bl) - 1;
auto lenB = distance(bl, br) - 1;
CHECK(lenL >= 8 && lenB >= 10 && lenB >= lenL / 4 && lenB <= lenL * 18);
auto maxStepSize = static_cast<int>(lenB / 5 + 1);
tlTracer.setDirection(right);
CHECK(tlTracer.traceGaps(tlTracer.right(), lineT, maxStepSize, {}, lenB / 2));
maxStepSize = std::min(lineT.length() / 3, static_cast<int>(lenL / 5)) * 2;
t.setDirection(up);
t.state = 3;
CHECK(t.traceGaps(t.left(), lineR, maxStepSize, lineT));
CHECK(t.traceCorner(t.left(), tr));
auto lenT = distance(tl, tr) - 1;
auto lenR = distance(tr, br) - 1;
CHECK(std::abs(lenT - lenB) / lenB < 0.5 && std::abs(lenR - lenL) / lenL < 0.5 &&
lineT.points().size() >= 5 && lineR.points().size() >= 5);
CHECK(tlTracer.traceGaps(tlTracer.right(), lineT, maxStepSize, lineR));
printf("L: %.1f, %.1f ^ %.1f, %.1f > %.1f, %.1f (%d : %d : %d : %d)\n", bl.x, bl.y,
tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, (int)lenL, (int)lenB, (int)lenT, (int)lenR);
for (auto* l : {&lineL, &lineB, &lineT, &lineR})
l->evaluate(1.0);
bl = intersect(lineB, lineL);
tl = intersect(lineT, lineL);
tr = intersect(lineT, lineR);
br = intersect(lineB, lineR);
int dimT, dimR;
double fracT, fracR;
auto splitDouble = [](double d, int* i, double* f) {
*i = std::isnormal(d) ? narrow_cast<int>(std::lround(d)) : 0;
*f = std::isnormal(d) ? std::abs(d - *i) : INFINITY;
};
splitDouble(lineT.modules(tl, tr), &dimT, &fracT);
splitDouble(lineR.modules(br, tr), &dimR, &fracR);
dimT *= 2;
dimR *= 2;
printf("L: %.1f, %.1f ^ %.1f, %.1f > %.1f, %.1f ^> %.1f, %.1f\n", bl.x, bl.y,
tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, tr.x, tr.y);
printf("dim: %d x %d (%.2f, %.2f)\n", dimT, dimR, fracT, fracR);
auto* version = VersionForDimensions(dimR, dimT);
if (!version && std::abs(dimT - dimR) < 10) {
auto* versionT = VersionForDimensions(dimT, dimT);
auto* versionR = VersionForDimensions(dimR, dimR);
if (bool(versionT) ^ bool(versionR))
dimT = dimR = versionT ? dimT : dimR;
else
dimT = dimR = fracR < fracT ? dimR : dimT;
version = VersionForDimensions(dimR, dimT);
}
CHECK(version);
auto movedTowardsBy = [](PointF a, PointF b1, PointF b2, auto d) {
return a + d * normalized(normalized(b1 - a) + normalized(b2 - a));
};
QuadrilateralF sourcePoints = {
movedTowardsBy(tl, tr, bl, 0.5f),
movedTowardsBy(tr, br, tl, 0.3f),
movedTowardsBy(br, bl, tr, 0.5f),
movedTowardsBy(bl, tl, br, 0.5f),
};
auto mod2Pix = PerspectiveTransform(Rectangle(dimT, dimR, 0), sourcePoints);
auto pix2Mod = PerspectiveTransform(sourcePoints, Rectangle(dimT, dimR, 0));
#if 1
std::vector<int> apX(version->dataBlocksX() + 1), apY(version->dataBlocksY() + 1);
std::generate(apX.begin(), apX.end(), [i = 0, version]() mutable { return i++ * (version->dataBlockWidth + 2); });
std::generate(apY.begin(), apY.end(), [i = 0, version]() mutable { return i++ * (version->dataBlockHeight + 2); });
apX.back() = dimT - 1;
apY.back() = dimR - 1;
auto apP = Matrix<std::optional<PointF>>(Size(apX), Size(apY));
constexpr PointI l(-1, 0), r(1, 0), u(0, -1), d(0, 1);
auto findAP = [&](PointI p, PointI offset, int radius, PointI timingStart, LocalGrid::Directions timingDirs, PointI blackStart,
LocalGrid::Directions blackDirs, PointI whiteStart,
LocalGrid::Directions whiteDirs) -> std::optional<PointF> {
auto lg = LocalGrid(*startTracer.img, mod2Pix, p, {dimT, dimR}, offset);
if (lg.findPattern(radius, timingStart, timingDirs, blackStart, blackDirs, whiteStart, whiteDirs))
return lg.getPos();
return {};
};
for (int y = 0; y < Size(apY); ++y)
for (int x = 0; x < Size(apX); ++x) {
std::optional<PointF> ap;
PointI api{apX[x], apY[y]};
int Nx = Size(apX) - 1, Ny = Size(apY) - 1;
if (x == 0 && y == 0) ap = findAP(api, {2, 0}, 4, {1, 0}, std::array{r}, {0, 0}, std::array{d}, {-1, -1}, std::array{d, r});
else if (x == Nx && y == 0) ap = findAP(api, {-1, 1}, 4, {0, 0}, std::array{l, d}, {}, {}, {1, -1}, std::array{d, l});
else if (x == Nx && y == Ny) ap = findAP(api, {-0, -2}, 4, {0, -1}, std::array{u}, {0, 0}, std::array{l}, {1, 1}, std::array{u, l});
else if (x == 0 && y == Ny) ap = findAP(api, {1, -1}, 4, {}, {}, {0, 0}, std::array{u, r}, {-1, 1}, std::array{u, r});
else if (x == 0) ap = findAP(api, {2, 0}, 3, {1, 0}, std::array{r}, {0, -1}, std::array{u, d, r}, {}, {});
else if (x == Nx) ap = findAP(api, {-1, 0}, 3, {0, 0}, std::array{l, u, d}, {0, -1}, std::array{l}, {}, {});
else if (y == 0) ap = findAP(api, {-1, 0}, 3, {-1, 0}, std::array{l, r, d}, {0, 0}, std::array{d}, {}, {});
else if (y == Ny) ap = findAP(api, {-1, -1}, 3, {-1, -1}, std::array{u}, {0, 0}, std::array{l, r, u}, {}, {});
else ap = findAP(api, {-1, 0}, 3, {-1, 0}, std::array{l, r, u, d}, {0, -1}, std::array{l, r, u, d}, {}, {});
if (ap)
apP.set(x, y, *ap);
}
if (auto res = SampleGrid(*startTracer.img, dimT, dimR, mod2Pix, std::move(apP), apX, apY); res.isValid())
co_yield std::move(res);
#else#endif
if (dimT > 42 && dimR > 42) continue;
tl = sourcePoints[0], tr = sourcePoints[1], br = sourcePoints[2];
auto tLUT = BuildModuleCenterLUT(lineT.points(), tl, tr, dimT, [&](double t) { return pix2Mod((1 - t) * tl + t * tr).x; });
auto rLUT = BuildModuleCenterLUT(lineR.points(), tr, br, dimR, [&](double t) { return pix2Mod((1 - t) * tr + t * br).y; });
if (!tLUT.empty() || !rLUT.empty())
if (auto res = SampleGridCorrected(*startTracer.img, dimT, dimR, mod2Pix, tLUT, rLUT); res.isValid())
co_yield std::move(res);
}
}
static DetectorResults DetectNew(const BitMatrix& image, bool tryHarder, bool tryRotate)
{
#ifdef PRINT_DEBUG
LogMatrixWriter lmw(log, image, 3, "dm-log.pnm");
#endif
EdgeTracer::StateMatrix history;
if (tryHarder)
history = EdgeTracer::StateMatrix(image.width(), image.height());
std::array<DMRegressionLine, 4> lines;
constexpr int minSymbolSize = 8 * 2;
for (auto dir : {PointF{-1, 0}, {1, 0}, {0, -1}, {0, 1}}) {
auto center = PointI(image.width() / 2, image.height() / 2);
auto startPos = centered(center - center * dir + minSymbolSize / 2 * dir);
history.clear();
for (int i = 1;; ++i) {
EdgeTracer tracer(image, startPos, dir);
tracer.p += i / 2 * minSymbolSize * (i & 1 ? -1 : 1) * tracer.right();
if (tryHarder)
tracer.history = &history;
if (!tracer.isIn())
break;
for (auto&& res : Scan(tracer, lines))
co_yield std::move(res);
if (!tryHarder)
break; }
if (!tryRotate)
break; }
}
static DetectorResult DetectPure(const BitMatrix& image)
{
int left, top, width, height;
if (!image.findBoundingBox(left, top, width, height, 8))
return {};
BitMatrixCursorI cur(image, {left, top}, {0, 1});
if (cur.countEdges(height - 1) != 0)
return {};
cur.turnLeft();
if (cur.countEdges(width - 1) != 0)
return {};
cur.turnLeft();
int dimR = cur.countEdges(height - 1) + 1;
cur.turnLeft();
int dimT = cur.countEdges(width - 1) + 1;
auto modSizeX = float(width) / dimT;
auto modSizeY = float(height) / dimR;
auto modSize = (modSizeX + modSizeY) / 2;
if (dimT % 2 != 0 || dimR % 2 != 0 || dimT < 10 || dimT > 144 || dimR < 8 || dimR > 144
|| std::abs(modSizeX - modSizeY) > 1
|| !image.isIn(PointF{left + modSizeX / 2 + (dimT - 1) * modSize, top + modSizeY / 2 + (dimR - 1) * modSize}))
return {};
return {Deflate(image, dimT, dimR, top + modSizeY / 2, left + modSizeX / 2, modSize),
Rectangle<PointI>(left, top, width, height)};
}
DetectorResults Detect(const BitMatrix& image, bool tryHarder, bool tryRotate, bool isPure)
{
if (auto r = DetectPure(image); r.isValid())
co_yield std::move(r);
else if (!isPure) { bool found = false;
for (auto&& r : DetectNew(image, tryHarder, tryRotate)) {
found = true;
co_yield std::move(r);
}
if (!found && tryHarder) {
if (auto r = DetectOld(image); r.isValid())
co_yield std::move(r);
}
}
}
}