#include "ODDXFilmEdgeReader.h"
#include "BarcodeData.h"
#include "SymbologyIdentifier.h"
#include <cmath>
#include <optional>
#include <vector>
namespace ZXing::OneD {
namespace {
constexpr int CLOCK_LENGTH_FN = 31;
constexpr int CLOCK_LENGTH_NO_FN = 23;
constexpr int DATA_LENGTH_FN = 23;
constexpr int DATA_LENGTH_NO_FN = 15;
constexpr auto CLOCK_PATTERN_FN =
FixedPattern<25, CLOCK_LENGTH_FN>{5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3};
constexpr auto CLOCK_PATTERN_NO_FN = FixedPattern<17, CLOCK_LENGTH_NO_FN>{5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3};
constexpr auto DATA_START_PATTERN = FixedPattern<5, 5>{1, 1, 1, 1, 1};
constexpr auto DATA_STOP_PATTERN = FixedPattern<3, 3>{1, 1, 1};
template <int N, int SUM>
bool IsPattern(PatternView& view, const FixedPattern<N, SUM>& pattern, float minQuietZone)
{
view = view.subView(0, N);
return view.isValid() && IsPattern(view, pattern, view.isAtFirstBar() ? std::numeric_limits<int>::max() : view[-1], minQuietZone);
}
bool DistIsBelowThreshold(PointI a, PointI b, PointI threshold)
{
return std::abs(a.x - b.x) <= threshold.x && std::abs(a.y - b.y) <= threshold.y;
}
struct Clock
{
bool hasFrameNr = false; int rowNumber = 0;
int xStart = 0; int xStop = 0;
int dataLength() const { return hasFrameNr ? DATA_LENGTH_FN : DATA_LENGTH_NO_FN; }
float moduleSize() const { return float(xStop + 1 - xStart) / (hasFrameNr ? CLOCK_LENGTH_FN : CLOCK_LENGTH_NO_FN); }
bool isCloseTo(PointI p, int x) const { return DistIsBelowThreshold(p, {x, rowNumber}, PointI(moduleSize() * PointF{0.5, 4})); }
bool isCloseToStart(int x, int y) const { return isCloseTo({x, y}, xStart); }
bool isCloseToStop(int x, int y) const { return isCloseTo({x, y}, xStop); }
};
struct DXFEState : public RowReader::DecodingState
{
int centerRow = 0;
std::vector<Clock> clocks;
Clock* findClock(int x, int y)
{
auto i = FindIf(clocks, [start = PointI{x, y}](auto& v) { return v.rowNumber != start.y && v.isCloseToStart(start.x, start.y); });
return i != clocks.end() ? &(*i) : nullptr;
}
void addClock(const Clock& clock)
{
if (Clock* i = findClock(clock.xStart, clock.rowNumber))
*i = clock;
else
clocks.push_back(clock);
}
};
std::optional<Clock> CheckForClock(int rowNumber, PatternView& view)
{
Clock clock;
if (IsPattern(view, CLOCK_PATTERN_FN, 0.5)) clock.hasFrameNr = true;
else if (IsPattern(view, CLOCK_PATTERN_NO_FN, 2.0))
clock.hasFrameNr = false;
else
return {};
clock.rowNumber = rowNumber;
clock.xStart = view.pixelsInFront();
clock.xStop = view.pixelsTillEnd();
return clock;
}
}
BarcodeData DXFilmEdgeReader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr<DecodingState>& state) const
{
if (!state) {
state = std::make_unique<DXFEState>();
static_cast<DXFEState*>(state.get())->centerRow = rowNumber;
}
auto dxState = static_cast<DXFEState*>(state.get());
if (!_opts.tryRotate() && rowNumber < dxState->centerRow - 1)
return {};
constexpr auto Is4x1 = [](const PatternView& view, int spaceInPixel) {
auto [m, M] = std::minmax({view[1], view[2], view[3], view[4]});
return M <= m * 4 / 3 + 1 && spaceInPixel > m / 2;
};
next = FindLeftGuard<4>(next, 10, Is4x1);
if (!next.isValid())
return {};
if (auto clock = CheckForClock(rowNumber, next)) {
dxState->addClock(*clock);
next.skipSymbol();
return {};
}
if (dxState->clocks.empty())
return {};
constexpr float minDataQuietZone = 0.5;
if (!IsPattern(next, DATA_START_PATTERN, minDataQuietZone))
return {};
auto xStart = next.pixelsInFront();
auto clock = dxState->findClock(xStart, rowNumber);
if (!clock)
return {};
if (std::fabs(next.sum() / clock->moduleSize() - 5) > 1.0 )
return {};
next.skipSymbol();
BitArray dataBits;
while (next.isValid(1) && dataBits.size() < clock->dataLength()) {
if (int modules = std::lround(next[0] / clock->moduleSize()); modules >= 1 && modules <= 20)
dataBits.appendBits(next.index() % 2 == 0 ? 0xFFFFFFFF : 0x0, modules);
else
return {};
next.shift(1);
}
if (dataBits.size() != clock->dataLength())
return {};
next = next.subView(0, DATA_STOP_PATTERN.size());
if (!IsRightGuard(next, DATA_STOP_PATTERN, minDataQuietZone))
return {};
if (dataBits.get(0) || dataBits.get(8) || (clock->hasFrameNr ? (dataBits.get(20) || dataBits.get(22)) : dataBits.get(14)))
return {};
auto signalSum = Reduce(dataBits.begin(), dataBits.end() - 2, 0);
auto parityBit = *(dataBits.end() - 2);
if (signalSum % 2 != (int)parityBit)
return {};
auto productNumber = ToInt(dataBits, 1, 7);
if (!productNumber)
return {};
auto generationNumber = ToInt(dataBits, 9, 4);
std::string txt;
txt.reserve(10);
txt = std::to_string(productNumber) + "-" + std::to_string(generationNumber);
if (clock->hasFrameNr) {
auto frameNr = ToInt(dataBits, 13, 6);
txt += "/" + std::to_string(frameNr);
if (dataBits.get(19))
txt += "A";
}
auto xStop = next.pixelsTillEnd();
if (!clock->isCloseToStop(xStop, rowNumber))
return {};
clock->xStart = xStart;
clock->xStop = xStop;
SymbologyIdentifier si {'X', 'F'};
return LinearBarcode(BarcodeFormat::DXFilmEdge, txt, rowNumber, xStart, xStop, si);
}
}