Files
ANSLibs/QRCode/oned/ODDataBarCommon.cpp

170 lines
5.2 KiB
C++
Raw Normal View History

/*
* Copyright 2020 Axel Waggershauser
*/
// SPDX-License-Identifier: Apache-2.0
#include "ODDataBarCommon.h"
#include <cmath>
namespace ZXing::OneD::DataBar {
static int combins(int n, int r)
{
int maxDenom;
int minDenom;
if (n - r > r) {
minDenom = r;
maxDenom = n - r;
} else {
minDenom = n - r;
maxDenom = r;
}
int val = 1;
int j = 1;
for (int i = n; i > maxDenom; i--) {
val *= i;
if (j <= minDenom) {
val /= j;
j++;
}
}
while (j <= minDenom) {
val /= j;
j++;
}
return val;
}
int GetValue(const Array4I& widths, int maxWidth, bool noNarrow)
{
int elements = Size(widths);
int n = Reduce(widths);
int val = 0;
int narrowMask = 0;
for (int bar = 0; bar < elements - 1; bar++) {
int elmWidth;
for (elmWidth = 1, narrowMask |= 1 << bar; elmWidth < widths[bar]; elmWidth++, narrowMask &= ~(1 << bar)) {
int subVal = combins(n - elmWidth - 1, elements - bar - 2);
if (noNarrow && (narrowMask == 0) && (n - elmWidth - (elements - bar - 1) >= elements - bar - 1)) {
subVal -= combins(n - elmWidth - (elements - bar), elements - bar - 2);
}
if (elements - bar - 1 > 1) {
int lessVal = 0;
for (int mxwElement = n - elmWidth - (elements - bar - 2); mxwElement > maxWidth; mxwElement--) {
lessVal += combins(n - elmWidth - mxwElement - 1, elements - bar - 3);
}
subVal -= lessVal * (elements - 1 - bar);
} else if (n - elmWidth > maxWidth) {
subVal--;
}
val += subVal;
}
n -= elmWidth;
}
return val;
}
template <typename T>
struct OddEven
{
T odd = {}, evn = {};
T& operator[](int i) { return i & 1 ? evn : odd; }
};
using Array4F = std::array<float, 4>;
bool ReadDataCharacterRaw(const PatternView& view, int numModules, bool reversed, Array4I& oddPattern,
Array4I& evnPattern)
{
OddEven<Array4I&> res = {oddPattern, evnPattern};
OddEven<Array4F> rem;
float moduleSize = static_cast<float>(view.sum(8)) / numModules;
auto* iter = view.data() + reversed * 7;
int inc = reversed ? -1 : 1;
for (int i = 0; i < 8; ++i, iter += inc) {
float v = *iter / moduleSize;
res[i % 2][i / 2] = int(v + .5f);
rem[i % 2][i / 2] = v - res[i % 2][i / 2];
}
// DataBarExpanded data character is 17 modules wide
// DataBar outer data character is 16 modules wide
// DataBar inner data character is 15 modules wide
int minSum = 4; // each data character has 4 bars and 4 spaces
int maxSum = numModules - minSum;
int oddSum = Reduce(res.odd);
int evnSum = Reduce(res.evn);
int sumErr = oddSum + evnSum - numModules;
// sum < min -> negative error; sum > max -> positive error
int oddSumErr = std::min(0, oddSum - (minSum + (numModules == 15))) + std::max(0, oddSum - maxSum);
int evnSumErr = std::min(0, evnSum - minSum) + std::max(0, evnSum - (maxSum - (numModules == 15)));
int oddParityErr = (oddSum & 1) == (numModules > 15);
int evnParityErr = (evnSum & 1) == (numModules < 17);
#if 0
// the 'signal improving' strategy of trying to fix off-by-one errors in the sum or parity leads to a massively
// increased likelihood of false positives / misreads especially with expanded codes that are composed of many
// pairs. the combinatorial explosion of possible pair combinations (see FindValidSequence) results in many possible
// sequences with valid checksums. It can slightly lower the minimum required resolution to detect something at all
// but the introduced error rate is clearly not worth it.
if ((sumErr == 0 && oddParityErr != evnParityErr) || (std::abs(sumErr) == 1 && oddParityErr == evnParityErr) ||
std::abs(sumErr) > 1 || std::abs(oddSumErr) > 1 || std::abs(evnSumErr) > 1)
return {};
if (sumErr == -1) {
oddParityErr *= -1;
evnParityErr *= -1;
} else if (sumErr == 0 && oddParityErr != 0) {
// both parity errors are 1 -> flip one of them
(oddSum < evnSum ? oddParityErr : evnParityErr) *= -1;
}
// check if parity and sum errors have opposite signs
if (oddParityErr * oddSumErr < 0 || evnParityErr * evnSumErr < 0)
return {};
// apparently the spec calls numbers at even indices 'odd'!?!
constexpr int odd = 0, evn = 1;
for (int i : {odd, evn}) {
int err = i == odd ? (oddSumErr | oddParityErr) : (evnSumErr | evnParityErr);
int mi = err < 0 ? std::max_element(rem[i].begin(), rem[i].end()) - rem[i].begin()
: std::min_element(rem[i].begin(), rem[i].end()) - rem[i].begin();
res[i][mi] -= err;
}
return true;
#else
// instead, we ignore any character that is not exactly fitting the requirements
return !(sumErr || oddSumErr || evnSumErr || oddParityErr || evnParityErr);
#endif
}
static bool IsStacked(const Pair& first, const Pair& last)
{
// check if we see two halfes that are far away from each other in y or overlapping in x
return std::abs(first.y - last.y) > (first.xStop - first.xStart) || last.xStart < (first.xStart + first.xStop) / 2;
}
Position EstimatePosition(const Pair& first, const Pair& last)
{
if (!IsStacked(first, last))
return Line((first.y + last.y) / 2, first.xStart, last.xStop);
else
return Position{{first.xStart, first.y}, {first.xStop, first.y}, {last.xStop, last.y}, {last.xStart, last.y}};
}
int EstimateLineCount(const Pair& first, const Pair& last)
{
// see incrementLineCount() in ODReader.cpp for the -1 here
return std::min(first.count, last.count) - 1 + IsStacked(first, last);
}
} // namespace ZXing::OneD::DataBar