283 lines
9.4 KiB
C++
283 lines
9.4 KiB
C++
/*
|
|
* Copyright 2016 Nu-book Inc.
|
|
* Copyright 2016 ZXing authors
|
|
* Copyright 2020 Axel Waggershauser
|
|
*/
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
#include "ODReader.h"
|
|
|
|
#include "BinaryBitmap.h"
|
|
#include "ReaderOptions.h"
|
|
#include "ODCodabarReader.h"
|
|
#include "ODCode128Reader.h"
|
|
#include "ODCode39Reader.h"
|
|
#include "ODCode93Reader.h"
|
|
#include "ODDataBarExpandedReader.h"
|
|
#include "ODDataBarReader.h"
|
|
#include "ODDXFilmEdgeReader.h"
|
|
#include "ODITFReader.h"
|
|
#include "ODMultiUPCEANReader.h"
|
|
#include "Barcode.h"
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#ifdef PRINT_DEBUG
|
|
#include "BitMatrix.h"
|
|
#include "BitMatrixIO.h"
|
|
#endif
|
|
|
|
namespace ZXing {
|
|
|
|
void IncrementLineCount(Barcode& r)
|
|
{
|
|
++r._lineCount;
|
|
}
|
|
|
|
} // namespace ZXing
|
|
|
|
namespace ZXing::OneD {
|
|
|
|
Reader::Reader(const ReaderOptions& opts) : ZXing::Reader(opts)
|
|
{
|
|
_readers.reserve(8);
|
|
|
|
auto formats = opts.formats().empty() ? BarcodeFormat::Any : opts.formats();
|
|
|
|
if (formats.testFlags(BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::EAN8 | BarcodeFormat::UPCE))
|
|
_readers.emplace_back(new MultiUPCEANReader(opts));
|
|
|
|
if (formats.testFlag(BarcodeFormat::Code39))
|
|
_readers.emplace_back(new Code39Reader(opts));
|
|
if (formats.testFlag(BarcodeFormat::Code93))
|
|
_readers.emplace_back(new Code93Reader(opts));
|
|
if (formats.testFlag(BarcodeFormat::Code128))
|
|
_readers.emplace_back(new Code128Reader(opts));
|
|
if (formats.testFlag(BarcodeFormat::ITF))
|
|
_readers.emplace_back(new ITFReader(opts));
|
|
if (formats.testFlag(BarcodeFormat::Codabar))
|
|
_readers.emplace_back(new CodabarReader(opts));
|
|
if (formats.testFlags(BarcodeFormat::DataBar))
|
|
_readers.emplace_back(new DataBarReader(opts));
|
|
if (formats.testFlags(BarcodeFormat::DataBarExpanded))
|
|
_readers.emplace_back(new DataBarExpandedReader(opts));
|
|
if (formats.testFlag(BarcodeFormat::DXFilmEdge))
|
|
_readers.emplace_back(new DXFilmEdgeReader(opts));
|
|
}
|
|
|
|
Reader::~Reader() = default;
|
|
|
|
/**
|
|
* We're going to examine rows from the middle outward, searching alternately above and below the
|
|
* middle, and farther out each time. rowStep is the number of rows between each successive
|
|
* attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
|
|
* middle + rowStep, then middle - (2 * rowStep), etc.
|
|
* rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
|
|
* decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
|
|
* image if "trying harder".
|
|
*/
|
|
static Barcodes DoDecode(const std::vector<std::unique_ptr<RowReader>>& readers, const BinaryBitmap& image, bool tryHarder,
|
|
bool rotate, bool isPure, int maxSymbols, int minLineCount, bool returnErrors)
|
|
{
|
|
Barcodes res;
|
|
|
|
std::vector<std::unique_ptr<RowReader::DecodingState>> decodingState(readers.size());
|
|
|
|
int width = image.width();
|
|
int height = image.height();
|
|
|
|
if (rotate)
|
|
std::swap(width, height);
|
|
|
|
int middle = height / 2;
|
|
// TODO: find a better heuristic/parameterization if maxSymbols != 1
|
|
int rowStep = std::max(1, height / ((tryHarder && !isPure) ? (maxSymbols == 1 ? 256 : 512) : 32));
|
|
int maxLines = tryHarder ?
|
|
height : // Look at the whole image, not just the center
|
|
15; // 15 rows spaced 1/32 apart is roughly the middle half of the image
|
|
|
|
if (isPure)
|
|
minLineCount = 1;
|
|
else
|
|
minLineCount = std::min(minLineCount, height);
|
|
std::vector<int> checkRows;
|
|
|
|
PatternRow bars;
|
|
bars.reserve(128); // e.g. EAN-13 has 59 bars/spaces
|
|
|
|
#ifdef PRINT_DEBUG
|
|
BitMatrix dbg(width, height);
|
|
#endif
|
|
|
|
for (int i = 0; i < maxLines; i++) {
|
|
|
|
// Scanning from the middle out. Determine which row we're looking at next:
|
|
int rowStepsAboveOrBelow = (i + 1) / 2;
|
|
bool isAbove = (i & 0x01) == 0; // i.e. is x even?
|
|
int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
|
|
bool isCheckRow = false;
|
|
if (rowNumber < 0 || rowNumber >= height) {
|
|
// Oops, if we run off the top or bottom, stop
|
|
break;
|
|
}
|
|
|
|
// See if we have additional check rows (see below) to process
|
|
if (checkRows.size()) {
|
|
--i;
|
|
rowNumber = checkRows.back();
|
|
checkRows.pop_back();
|
|
isCheckRow = true;
|
|
if (rowNumber < 0 || rowNumber >= height)
|
|
continue;
|
|
}
|
|
|
|
if (!image.getPatternRow(rowNumber, rotate ? 90 : 0, bars))
|
|
continue;
|
|
|
|
#ifdef PRINT_DEBUG
|
|
bool val = false;
|
|
int x = 0;
|
|
for (auto b : bars) {
|
|
for(int j = 0; j < b; ++j)
|
|
dbg.set(x++, rowNumber, val);
|
|
val = !val;
|
|
}
|
|
#endif
|
|
|
|
// While we have the image data in a PatternRow, it's fairly cheap to reverse it in place to
|
|
// handle decoding upside down barcodes.
|
|
// TODO: the DataBarExpanded (stacked) decoder depends on seeing each line from both directions. This
|
|
// 'surprising' and inconsistent. It also requires the decoderState to be shared between normal and reversed
|
|
// scans, which makes no sense in general because it would mix partial detection data from two codes of the same
|
|
// type next to each other. See also https://github.com/zxing-cpp/zxing-cpp/issues/87
|
|
for (bool upsideDown : {false, true}) {
|
|
// trying again?
|
|
if (upsideDown) {
|
|
// reverse the row and continue
|
|
std::reverse(bars.begin(), bars.end());
|
|
}
|
|
// Look for a barcode
|
|
for (size_t r = 0; r < readers.size(); ++r) {
|
|
// If this is a pure symbol, then checking a single non-empty line is sufficient for all but the stacked
|
|
// DataBar codes. They are the only ones using the decodingState, which we can use as a flag here.
|
|
if (isPure && i && !decodingState[r])
|
|
continue;
|
|
|
|
PatternView next(bars);
|
|
do {
|
|
Barcode result = readers[r]->decodePattern(rowNumber, next, decodingState[r]);
|
|
if (result.isValid() || (returnErrors && result.error())) {
|
|
IncrementLineCount(result);
|
|
if (upsideDown) {
|
|
// update position (flip horizontally).
|
|
auto points = result.position();
|
|
for (auto& p : points) {
|
|
p = {width - p.x - 1, p.y};
|
|
}
|
|
result.setPosition(std::move(points));
|
|
}
|
|
if (rotate) {
|
|
auto points = result.position();
|
|
for (auto& p : points) {
|
|
p = {p.y, width - p.x - 1};
|
|
}
|
|
result.setPosition(std::move(points));
|
|
}
|
|
|
|
// check if we know this code already
|
|
for (auto& other : res) {
|
|
if (result == other) {
|
|
// merge the position information
|
|
auto dTop = maxAbsComponent(other.position().topLeft() - result.position().topLeft());
|
|
auto dBot = maxAbsComponent(other.position().bottomLeft() - result.position().topLeft());
|
|
auto points = other.position();
|
|
if (dTop < dBot || (dTop == dBot && rotate ^ (sumAbsComponent(points[0]) >
|
|
sumAbsComponent(result.position()[0])))) {
|
|
points[0] = result.position()[0];
|
|
points[1] = result.position()[1];
|
|
} else {
|
|
points[2] = result.position()[2];
|
|
points[3] = result.position()[3];
|
|
}
|
|
other.setPosition(points);
|
|
IncrementLineCount(other);
|
|
// clear the result, so we don't insert it again below
|
|
result = Barcode();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (result.format() != BarcodeFormat::None) {
|
|
res.push_back(std::move(result));
|
|
|
|
// if we found a valid code we have not seen before but a minLineCount > 1,
|
|
// add additional check rows above and below the current one
|
|
if (!isCheckRow && minLineCount > 1 && rowStep > 1) {
|
|
checkRows = {rowNumber - 1, rowNumber + 1};
|
|
if (rowStep > 2)
|
|
checkRows.insert(checkRows.end(), {rowNumber - 2, rowNumber + 2});
|
|
}
|
|
}
|
|
|
|
if (maxSymbols && Reduce(res, 0, [&](int s, const Barcode& r) {
|
|
return s + (r.lineCount() >= minLineCount);
|
|
}) == maxSymbols) {
|
|
goto out;
|
|
}
|
|
}
|
|
// make sure we make progress and we start the next try on a bar
|
|
next.shift(2 - (next.index() % 2));
|
|
next.extend();
|
|
} while (tryHarder && next.size());
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
// remove all symbols with insufficient line count
|
|
auto it = std::remove_if(res.begin(), res.end(), [&](auto&& r) { return r.lineCount() < minLineCount; });
|
|
res.erase(it, res.end());
|
|
|
|
// if symbols overlap, remove the one with a lower line count
|
|
for (auto a = res.begin(); a != res.end(); ++a)
|
|
for (auto b = std::next(a); b != res.end(); ++b)
|
|
if (HaveIntersectingBoundingBoxes(a->position(), b->position()))
|
|
*(a->lineCount() < b->lineCount() ? a : b) = Barcode();
|
|
|
|
//TODO: C++20 res.erase_if()
|
|
it = std::remove_if(res.begin(), res.end(), [](auto&& r) { return r.format() == BarcodeFormat::None; });
|
|
res.erase(it, res.end());
|
|
|
|
#ifdef PRINT_DEBUG
|
|
SaveAsPBM(dbg, rotate ? "od-log-r.pnm" : "od-log.pnm");
|
|
#endif
|
|
|
|
return res;
|
|
}
|
|
|
|
Barcode Reader::decode(const BinaryBitmap& image) const
|
|
{
|
|
auto result =
|
|
DoDecode(_readers, image, _opts.tryHarder(), false, _opts.isPure(), 1, _opts.minLineCount(), _opts.returnErrors());
|
|
|
|
if (result.empty() && _opts.tryRotate())
|
|
result = DoDecode(_readers, image, _opts.tryHarder(), true, _opts.isPure(), 1, _opts.minLineCount(), _opts.returnErrors());
|
|
|
|
return FirstOrDefault(std::move(result));
|
|
}
|
|
|
|
Barcodes Reader::decode(const BinaryBitmap& image, int maxSymbols) const
|
|
{
|
|
auto resH = DoDecode(_readers, image, _opts.tryHarder(), false, _opts.isPure(), maxSymbols, _opts.minLineCount(),
|
|
_opts.returnErrors());
|
|
if ((!maxSymbols || Size(resH) < maxSymbols) && _opts.tryRotate()) {
|
|
auto resV = DoDecode(_readers, image, _opts.tryHarder(), true, _opts.isPure(), maxSymbols - Size(resH),
|
|
_opts.minLineCount(), _opts.returnErrors());
|
|
resH.insert(resH.end(), resV.begin(), resV.end());
|
|
}
|
|
return resH;
|
|
}
|
|
|
|
} // namespace ZXing::OneD
|