stcp: ed25519 pubkey exchange in handshake, fix instance destroy segfault, fix chat_tcp_test
- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll - stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close - utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP), add deferred drain in both Phase J and L - stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39, exchange ed25519_pubkey in both handshake directions - etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb - chat_sync.c: fix lk->conn NULL dereference for TCP links - test_stcp.c: update stcp_server_create/stcp_client_connect callsites - tools/chat_tcp_test: change transport udp→tcp
This commit is contained in:
1 parent
a3783b5f55
commit
38c9f229bc
1272 files changed
+968388
-1
No files matched your search
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "Barcode.h"
|
||||
|
||||
#include "BarcodeData.h"
|
||||
#include "JSON.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include "BitMatrix.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <numbers>
|
||||
#include <utility>
|
||||
|
||||
#ifdef ZXING_USE_ZINT
|
||||
#include <zint.h>
|
||||
void zint_symbol_deleter::operator()(zint_symbol* p) const noexcept
|
||||
{
|
||||
ZBarcode_Delete(p);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
Barcode::Barcode() : d(std::make_shared<Data>()) {}
|
||||
|
||||
Barcode::Barcode(Data&& data) : d(std::make_shared<Data>(std::move(data))) {}
|
||||
|
||||
bool Barcode::isValid() const
|
||||
{
|
||||
return d->isValid();
|
||||
}
|
||||
|
||||
const Error& Barcode::error() const
|
||||
{
|
||||
return d->error;
|
||||
}
|
||||
|
||||
BarcodeFormat Barcode::format() const
|
||||
{
|
||||
return d->format;
|
||||
}
|
||||
|
||||
const Position& Barcode::position() const
|
||||
{
|
||||
return d->position;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& Barcode::bytes() const
|
||||
{
|
||||
return d->content.bytes;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Barcode::bytesECI() const
|
||||
{
|
||||
return d->content.bytesECI();
|
||||
}
|
||||
|
||||
std::string Barcode::text(TextMode mode) const
|
||||
{
|
||||
return d->content.text(mode);
|
||||
}
|
||||
|
||||
std::string Barcode::text() const
|
||||
{
|
||||
return text(d->defaultTextMode);
|
||||
}
|
||||
|
||||
ContentType Barcode::contentType() const
|
||||
{
|
||||
return d->content.type();
|
||||
}
|
||||
|
||||
bool Barcode::hasECI() const
|
||||
{
|
||||
return d->content.hasECI;
|
||||
}
|
||||
|
||||
int Barcode::orientation() const
|
||||
{
|
||||
return narrow_cast<int>(std::lround(d->position.orientation() * 180 / std::numbers::pi));
|
||||
}
|
||||
|
||||
bool Barcode::isMirrored() const
|
||||
{
|
||||
return d->isMirrored;
|
||||
}
|
||||
|
||||
bool Barcode::isInverted() const
|
||||
{
|
||||
return d->isInverted;
|
||||
}
|
||||
|
||||
std::string Barcode::symbologyIdentifier() const
|
||||
{
|
||||
return d->content.symbology.toString();
|
||||
}
|
||||
|
||||
int Barcode::sequenceSize() const
|
||||
{
|
||||
return d->sai.count;
|
||||
}
|
||||
|
||||
int Barcode::sequenceIndex() const
|
||||
{
|
||||
return d->sai.index;
|
||||
}
|
||||
|
||||
std::string Barcode::sequenceId() const
|
||||
{
|
||||
return d->sai.id;
|
||||
}
|
||||
|
||||
int Barcode::lineCount() const
|
||||
{
|
||||
return d->lineCount;
|
||||
}
|
||||
|
||||
Barcode& Barcode::setReaderOptions(const ReaderOptions& opts)
|
||||
{
|
||||
if (opts.characterSet() != CharacterSet::Unknown)
|
||||
d->content.defaultCharset = opts.characterSet();
|
||||
d->defaultTextMode = opts.textMode();
|
||||
return *this;
|
||||
}
|
||||
|
||||
ImageView Barcode::symbol() const
|
||||
{
|
||||
return !d->symbol.empty() ? ImageView{d->symbol.row(0).begin(), d->symbol.width(), d->symbol.height(), ImageFormat::Lum}
|
||||
: ImageView{};
|
||||
}
|
||||
|
||||
#if defined(ZXING_USE_ZINT) && defined(ZXING_EXPERIMENTAL_API)
|
||||
zint_symbol* Barcode::zint() const
|
||||
{
|
||||
return d->zint.get();
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string Barcode::extra(std::string_view key) const
|
||||
{
|
||||
if (key == "ALL") {
|
||||
if (format() == BarcodeFormat::None)
|
||||
return {};
|
||||
auto res =
|
||||
StrCat("{", JsonProp("Text", text(TextMode::Plain)), JsonProp("HRI", text(TextMode::HRI)),
|
||||
JsonProp("TextECI", text(TextMode::ECI)), JsonProp("Bytes", text(TextMode::Hex)),
|
||||
JsonProp("Identifier", symbologyIdentifier()), JsonProp("Format", Name(format())),
|
||||
JsonProp("Symbology", Name(Symbology(format()))), JsonProp("ContentType", isValid() ? ToString(contentType()) : ""),
|
||||
JsonProp("Position", ToString(position())), JsonProp("HasECI", hasECI()), JsonProp("IsMirrored", isMirrored()),
|
||||
JsonProp("IsInverted", isInverted()), d->extra, JsonProp("Error", ToString(error())));
|
||||
res.back() = '}';
|
||||
return res;
|
||||
}
|
||||
return d->extra.empty() ? ""
|
||||
: key.empty() ? StrCat("{", std::string_view(d->extra).substr(0, d->extra.size() - 1), "}") // remove trailing ','
|
||||
: JsonGet<std::string>(d->extra, key).value_or(""); // make sure JsonUnescape() is called
|
||||
}
|
||||
|
||||
bool Barcode::operator==(const Barcode& o) const
|
||||
{
|
||||
return *d == *o.d;
|
||||
}
|
||||
|
||||
bool BarcodeData::operator==(const BarcodeData& o) const
|
||||
{
|
||||
if (format != o.format)
|
||||
return false;
|
||||
|
||||
// handle MatrixCodes first
|
||||
if (!(format & BarcodeFormat::AllLinear)) {
|
||||
if (content.bytes != o.content.bytes)
|
||||
return false;
|
||||
|
||||
// At this point both are valid with the same content or both are in error.
|
||||
// Treat them as equal if their positions are about the same (center of one is inside the other).
|
||||
return IsInside(Center(o.position), position);
|
||||
}
|
||||
|
||||
if (content.bytes != o.content.bytes || error != o.error || orientation() != o.orientation())
|
||||
return false;
|
||||
|
||||
if (lineCount > 1 && o.lineCount > 1)
|
||||
return HaveIntersectingBoundingBoxes(o.position, position);
|
||||
|
||||
// the following code is only meant for this or other lineCount == 1
|
||||
assert(lineCount == 1 || o.lineCount == 1);
|
||||
|
||||
// sl == single line, ml = multi line
|
||||
const auto& sl = lineCount == 1 ? *this : o;
|
||||
const auto& ml = lineCount == 1 ? o : *this;
|
||||
|
||||
// If one line is less than half the length of the other away from the
|
||||
// latter, we consider it to belong to the same symbol.
|
||||
// Additionally, both need to have roughly the same length (see #367).
|
||||
auto dTop = maxAbsComponent(ml.position.topLeft() - sl.position.topLeft());
|
||||
auto dBot = maxAbsComponent(ml.position.bottomLeft() - sl.position.topLeft());
|
||||
auto slLength = maxAbsComponent(sl.position.topLeft() - sl.position.bottomRight());
|
||||
bool isHorizontal = sl.position.topLeft().y == sl.position.bottomRight().y;
|
||||
// Measure the multi line length in the same direction as the single line one (not diagonally)
|
||||
// to make sure overly tall symbols don't get segmented (see #769).
|
||||
auto mlLength = isHorizontal ? std::abs(ml.position.topLeft().x - ml.position.bottomRight().x)
|
||||
: std::abs(ml.position.topLeft().y - ml.position.bottomRight().y);
|
||||
|
||||
return std::min(dTop, dBot) < slLength / 2 && std::abs(slLength - mlLength) < slLength / 5;
|
||||
}
|
||||
|
||||
Barcode MergeStructuredAppendSequence(const Barcodes& barcodes)
|
||||
{
|
||||
if (barcodes.empty())
|
||||
return {};
|
||||
|
||||
std::list<Barcode> allBarcodes(barcodes.begin(), barcodes.end());
|
||||
allBarcodes.sort([](const Barcode& r1, const Barcode& r2) { return r1.sequenceIndex() < r2.sequenceIndex(); });
|
||||
|
||||
Barcode res = allBarcodes.front();
|
||||
for (auto i = std::next(allBarcodes.begin()); i != allBarcodes.end(); ++i)
|
||||
res.d->content.append(i->d->content);
|
||||
|
||||
res.d->position = {};
|
||||
res.d->sai.index = -1;
|
||||
|
||||
if (allBarcodes.back().sequenceSize() != Size(allBarcodes) ||
|
||||
!std::all_of(allBarcodes.begin(), allBarcodes.end(),
|
||||
[&](Barcode& it) { return it.sequenceId() == allBarcodes.front().sequenceId(); }))
|
||||
res.d->error = FormatError("sequenceIDs not matching during structured append sequence merging");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Barcodes MergeStructuredAppendSequences(const Barcodes& barcodes)
|
||||
{
|
||||
std::map<std::string, Barcodes> sas;
|
||||
for (auto& barcode : barcodes) {
|
||||
if (barcode.isPartOfSequence())
|
||||
sas[barcode.sequenceId()].push_back(barcode);
|
||||
}
|
||||
|
||||
Barcodes res;
|
||||
for (auto& [id, seq] : sas) {
|
||||
auto barcode = MergeStructuredAppendSequence(seq);
|
||||
if (barcode.isValid())
|
||||
res.push_back(std::move(barcode));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BarcodeFormat.h"
|
||||
#include "ContentType.h"
|
||||
#include "Error.h"
|
||||
#include "ImageView.h"
|
||||
#include "Quadrilateral.h"
|
||||
#include "ReaderOptions.h" // for TextMode
|
||||
#include "Version.h" // ZXING_... macros
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ZXING_USE_ZINT
|
||||
extern "C" struct zint_symbol;
|
||||
#endif
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class CreatorOptions;
|
||||
class ReaderOptions;
|
||||
class WriterOptions;
|
||||
class Barcode;
|
||||
struct BarcodeData;
|
||||
|
||||
using Position = QuadrilateralI;
|
||||
using Barcodes = std::vector<Barcode>;
|
||||
|
||||
/**
|
||||
* @brief String keys for the extra() metadata of Barcode. The values depend on the symbology and may not be present for all barcodes.
|
||||
*/
|
||||
namespace BarcodeExtra {
|
||||
#define ZX_EXTRA(NAME) static constexpr auto NAME = #NAME
|
||||
ZX_EXTRA(DataMask); ///< QRCodes
|
||||
ZX_EXTRA(Version); ///< QRCodes, DataMatrix, MaxiCode (e.g. "12x12" for DataMatrix)
|
||||
ZX_EXTRA(EanAddOn); ///< The EAN/UPC add-on content if detected
|
||||
ZX_EXTRA(ECLevel); ///< Error correction level (e.g. "L", "M", "Q", "H" for QRCode)
|
||||
ZX_EXTRA(UEC); ///< Unused error correction margin in the range [0, 1]
|
||||
ZX_EXTRA(UPCE); ///< The original (non-normalized) UPC-E code if UPC-E is detected
|
||||
ZX_EXTRA(ReaderInit);
|
||||
#undef ZX_EXTRA
|
||||
} // namespace BarcodeExtra
|
||||
|
||||
/**
|
||||
* @brief The Barcode class encapsulates a decoded or created barcode symbol.
|
||||
*
|
||||
* Barcode represents a decoded or created barcode symbol, providing access to its content, format,
|
||||
* position, and other metadata. It serves as the primary interface for working with barcodes in the library.
|
||||
*
|
||||
* Barcode objects are obtained from the ReadBarcodes function when scanning an image, or from the
|
||||
* CreateBarcodeFrom... functions when generating a new barcode. To convert a Barcode to an image or SVG,
|
||||
* use the WriteBarcodeTo... functions.
|
||||
*
|
||||
* @see ReadBarcodes, CreateBarcodeFromText, CreateBarcodeFromBytes, WriteBarcodeToImage, WriteBarcodeToSVG
|
||||
*/
|
||||
class Barcode
|
||||
{
|
||||
using Data = BarcodeData;
|
||||
|
||||
std::shared_ptr<Data> d;
|
||||
|
||||
Barcode& setReaderOptions(const ReaderOptions& opts);
|
||||
|
||||
friend Barcode MergeStructuredAppendSequence(const Barcodes&);
|
||||
friend Barcodes ReadBarcodes(const ImageView&, const ReaderOptions&);
|
||||
friend Barcode CreateBarcode(const void*, int, int, const CreatorOptions&);
|
||||
friend Image WriteBarcodeToImage(const Barcode&, const WriterOptions&);
|
||||
friend std::string WriteBarcodeToSVG(const Barcode&, const WriterOptions&);
|
||||
|
||||
public:
|
||||
Barcode();
|
||||
Barcode(Barcode::Data&& data);
|
||||
|
||||
/// Returns whether the barcode is valid, i.e. it contains a successfully decoded or created symbol.
|
||||
bool isValid() const;
|
||||
|
||||
/// Returns the error associated with the barcode, if any.
|
||||
const Error& error() const;
|
||||
|
||||
/// Returns the BarcodeFormat of the barcode.
|
||||
BarcodeFormat format() const;
|
||||
|
||||
/// Returns the symbology of the barcode format (e.g. EAN/UPC for EAN13, EAN8, UPCA, etc.).
|
||||
BarcodeFormat symbology() const { return Symbology(format()); }
|
||||
|
||||
/// Returns the raw / standard content without any modifications like character set conversions.
|
||||
const std::vector<uint8_t>& bytes() const;
|
||||
|
||||
/// Returns the raw / standard content following the ECI protocol.
|
||||
std::vector<uint8_t> bytesECI() const;
|
||||
|
||||
/// Returns the bytes() content rendered to unicode/utf8 text according to specified TextMode.
|
||||
std::string text(TextMode mode) const;
|
||||
|
||||
/// Returns the bytes() content rendered to unicode/utf8 text according to the TextMode set in the ReaderOptions.
|
||||
std::string text() const;
|
||||
|
||||
/// Returns the content type, giving a hint to the type of content found (Text/Binary/GS1/etc.).
|
||||
ContentType contentType() const;
|
||||
|
||||
/// Returns whether or not an ECI tag was found.
|
||||
bool hasECI() const;
|
||||
|
||||
/// Returns the position of the barcode in the image as a quadrilateral of 4 points (topLeft, topRight, bottomRight,
|
||||
/// bottomLeft).
|
||||
const Position& position() const;
|
||||
|
||||
/// Returns the orientation of the barcode in degrees, see also Position::orientation()
|
||||
int orientation() const;
|
||||
|
||||
/// Returns whether the symbol is mirrored (currently only supported by QRCode and DataMatrix).
|
||||
bool isMirrored() const;
|
||||
/// Returns whether the symbol is inverted / has reversed reflectance (see ReaderOptions::tryInvert).
|
||||
bool isInverted() const;
|
||||
|
||||
/// Returns the symbology identifier "]cm" where "c" is symbology code character, "m" the modifier.
|
||||
std::string symbologyIdentifier() const;
|
||||
|
||||
/// @brief Returns the number of symbols in a structured append sequence.
|
||||
///
|
||||
/// If this is not part of a structured append sequence, the returned value is -1.
|
||||
/// If it is a structured append symbol but the total number of symbols is unknown, the
|
||||
/// returned value is 0 (see PDF417 if optional "Segment Count" not given).
|
||||
int sequenceSize() const;
|
||||
|
||||
/// Returns the 0-based index of this symbol in a structured append sequence.
|
||||
int sequenceIndex() const;
|
||||
|
||||
/// @brief Returns the sequenceId to check if a set of symbols belongs to the same structured append sequence.
|
||||
///
|
||||
/// If the symbology does not support this feature, the returned value is empty (see MaxiCode).
|
||||
/// For QR Code, this is the parity integer converted to a string.
|
||||
/// For PDF417 and DataMatrix, this is the "fileId".
|
||||
std::string sequenceId() const;
|
||||
|
||||
/// Returns whether this symbol is the last in a structured append sequence.
|
||||
bool isLastInSequence() const { return sequenceSize() == sequenceIndex() + 1; }
|
||||
|
||||
/// Returns whether this symbol is part of a structured append sequence.
|
||||
bool isPartOfSequence() const { return sequenceSize() > -1 && sequenceIndex() > -1; }
|
||||
|
||||
/// Returns how many lines have been detected with this code (applies only to linear symbologies).
|
||||
int lineCount() const;
|
||||
|
||||
/// Returns the bit matrix of the symbol as an ImageView.
|
||||
ImageView symbol() const;
|
||||
|
||||
/// @brief Retrieve supplementary metadata associated with this barcode.
|
||||
///
|
||||
/// Returns a string containing additional and symbology specific information. In form of a JSON object
|
||||
/// serialization. The optional parameter @p key can be used to retrieve a specific item only.
|
||||
/// Key values are case insensitive. See BarcodeExtra namespace for valid keys.
|
||||
/// If the key is not found or there is no info available, an empty string is returned.
|
||||
std::string extra(std::string_view key = "") const;
|
||||
|
||||
/// @cond DEPRECATED
|
||||
/// Returns the error correction level of the symbol (empty string if not applicable).
|
||||
// [[deprecated ("use extra(BarcodeExtra::ECLevel) instead")]]
|
||||
std::string ecLevel() const { return extra(BarcodeExtra::ECLevel); }
|
||||
|
||||
/// Returns whether the symbol is a Reader Initialisation/Programming symbol.
|
||||
// [[deprecated ("use extra(BarcodeExtra::ReaderInit) instead")]]
|
||||
bool readerInit() const { return !extra(BarcodeExtra::ReaderInit).empty(); }
|
||||
|
||||
/// Returns the version QRCode / DataMatrix / Aztec version or size.
|
||||
// [[deprecated ("use extra(BarcodeExtra::Version) instead")]]
|
||||
std::string version() const { return extra(BarcodeExtra::Version); }
|
||||
/// @endcond
|
||||
|
||||
#if defined(ZXING_USE_ZINT) && defined(ZXING_EXPERIMENTAL_API)
|
||||
zint_symbol* zint() const;
|
||||
#endif
|
||||
|
||||
bool operator==(const Barcode& o) const;
|
||||
};
|
||||
|
||||
/// Merge a list of Barcodes from one Structured Append sequence to a single barcode.
|
||||
Barcode MergeStructuredAppendSequence(const Barcodes& barcodes);
|
||||
|
||||
/// Automatically merge all Structured Append sequences found in the given list of barcodes.
|
||||
Barcodes MergeStructuredAppendSequences(const Barcodes& barcodes);
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Barcode.h"
|
||||
#include "BitMatrix.h"
|
||||
#include "Content.h"
|
||||
#include "DecoderResult.h"
|
||||
#include "DetectorResult.h"
|
||||
#include "Error.h"
|
||||
#include "Quadrilateral.h"
|
||||
#include "ReaderOptions.h"
|
||||
#include "StructuredAppend.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <numbers>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ZXING_USE_ZINT
|
||||
extern "C" struct zint_symbol;
|
||||
struct zint_symbol_deleter
|
||||
{
|
||||
void operator()(zint_symbol* p) const noexcept;
|
||||
};
|
||||
using unique_zint_symbol = std::unique_ptr<zint_symbol, zint_symbol_deleter>;
|
||||
#else
|
||||
struct zint_symbol {};
|
||||
using unique_zint_symbol = std::unique_ptr<zint_symbol>;
|
||||
#endif
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
struct BarcodeData
|
||||
{
|
||||
Content content;
|
||||
Error error = {};
|
||||
Position position = {};
|
||||
BarcodeFormat format = BarcodeFormat::None;
|
||||
std::string extra = {};
|
||||
StructuredAppendInfo sai = {};
|
||||
BitMatrix symbol = {};
|
||||
#ifdef ZXING_USE_ZINT
|
||||
unique_zint_symbol zint = {};
|
||||
mutable std::unique_ptr<std::mutex> zintMutex = {};
|
||||
#endif
|
||||
TextMode defaultTextMode = TextMode::HRI;
|
||||
int lineCount = 0;
|
||||
bool isMirrored = false;
|
||||
bool isInverted = false;
|
||||
|
||||
bool operator==(const BarcodeData& other) const;
|
||||
|
||||
inline bool isValid() const { return format != BarcodeFormat::None && !content.bytes.empty() && !error; }
|
||||
|
||||
inline int orientation() const { return narrow_cast<int>(std::lround(position.orientation() * 180 / std::numbers::pi)); }
|
||||
};
|
||||
|
||||
using BarcodesData = std::vector<BarcodeData>;
|
||||
|
||||
inline BarcodeData LinearBarcode(BarcodeFormat format, const std::string& text, int y, int xStart, int xStop, SymbologyIdentifier si,
|
||||
Error error = {}, std::string extra = {})
|
||||
{
|
||||
return {.content = Content(ByteArray(text), si, CharacterSet::ISO8859_1),
|
||||
.error = std::move(error),
|
||||
.position = Line(y, xStart, xStop),
|
||||
.format = format,
|
||||
.extra = std::move(extra)};
|
||||
}
|
||||
|
||||
inline BarcodeData MatrixBarcode(DecoderResult&& decodeResult, DetectorResult&& detectorResult, BarcodeFormat format)
|
||||
{
|
||||
auto extra = std::move(decodeResult).json();
|
||||
if (JsonFind(extra, BarcodeExtra::Version).empty() && decodeResult.versionNumber())
|
||||
extra += JsonProp(BarcodeExtra::Version, std::to_string(decodeResult.versionNumber()));
|
||||
if (JsonFind(extra, BarcodeExtra::ECLevel).empty() && !decodeResult.ecLevel().empty())
|
||||
extra += JsonProp(BarcodeExtra::ECLevel, decodeResult.ecLevel());
|
||||
|
||||
extra += JsonProp(BarcodeExtra::ReaderInit, decodeResult.readerInit());
|
||||
|
||||
// the BitMatrix stores 'black'/foreground as 0xFF and 'white'/background as 0, but we
|
||||
// want the ImageView returned by symbol() to be a standard luminance image (black == 0)
|
||||
std::move(detectorResult).bits().flipAll();
|
||||
|
||||
return {.content = std::move(decodeResult).content(),
|
||||
.error = std::move(decodeResult).error(),
|
||||
.position = std::move(detectorResult).position(),
|
||||
.format = format,
|
||||
.extra = std::move(extra),
|
||||
.sai = std::move(decodeResult).structuredAppend(),
|
||||
.symbol = std::move(detectorResult).bits(),
|
||||
.lineCount = decodeResult.lineCount(),
|
||||
.isMirrored = decodeResult.isMirrored()};
|
||||
}
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BarcodeFormat.h"
|
||||
|
||||
#include "Version.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
// =================================== BarcodeFormat =================================
|
||||
|
||||
static_assert(ZX_BCF_ID('\0', '\0') == 0, "BarcodeFormat encoding error");
|
||||
|
||||
BarcodeFormat Symbology(BarcodeFormat format)
|
||||
{
|
||||
return BarcodeFormat(ZX_BCF_ID(SymbologyKey(format), ' ' * (VariantKey(format) != 0)));
|
||||
}
|
||||
|
||||
std::string_view Name(BarcodeFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \
|
||||
case BarcodeFormat(ZX_BCF_ID(SYM, VAR)): return HRI;
|
||||
ZX_BCF_LIST(X)
|
||||
#undef X
|
||||
default: return "Unknown";
|
||||
};
|
||||
}
|
||||
|
||||
BarcodeFormat BarcodeFormatFromString(std::string_view str)
|
||||
{
|
||||
if (str.size() < 3)
|
||||
throw std::invalid_argument(StrCat("This is not a valid barcode format: '", str, "'"));
|
||||
#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \
|
||||
if ((str[0] == ']' && str[1] == SYM && str[2] == VAR) || IsEqualIgnoreCase(str, #NAME) || IsEqualIgnoreCaseAnd(str, HRI, " -_/")) \
|
||||
return BarcodeFormat(ZX_BCF_ID(SYM, VAR));
|
||||
ZX_BCF_LIST(X)
|
||||
#undef X
|
||||
throw std::invalid_argument(StrCat("This is not a valid barcode format: '", str, "'"));
|
||||
}
|
||||
|
||||
std::string ToString(BarcodeFormat format)
|
||||
{
|
||||
return std::string(Name(format));
|
||||
}
|
||||
|
||||
bool operator<=(BarcodeFormat e, BarcodeFormat s)
|
||||
{
|
||||
return e == s || s == BarcodeFormat::All
|
||||
|| (SymbologyKey(s) == '*' ? SymbologyKey(e) != '*' && (e & s)
|
||||
: SymbologyKey(s) == SymbologyKey(e) && VariantKey(s) == ' ');
|
||||
}
|
||||
|
||||
bool operator&(BarcodeFormat a, BarcodeFormat b)
|
||||
{
|
||||
if (SymbologyKey(a) == '*' && SymbologyKey(b) == '*') {
|
||||
switch (VariantKey(a)) {
|
||||
case 'l': return ZXING_ENABLE_1D && !Contains("smp", VariantKey(b));
|
||||
case 's': return ZXING_ENABLE_PDF417 && !Contains("lmp", VariantKey(b));
|
||||
case 'p': return !Contains("lms", VariantKey(b)); // TODO: postal codes
|
||||
case 'm':
|
||||
return (ZXING_ENABLE_MAXICODE || ZXING_ENABLE_QRCODE || ZXING_ENABLE_DATAMATRIX || ZXING_ENABLE_AZTEC)
|
||||
&& !Contains("lsp", VariantKey(b));
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (SymbologyKey(a) == '*')
|
||||
std::swap(a, b);
|
||||
|
||||
if (SymbologyKey(b) == '*') {
|
||||
char vkb = VariantKey(b);
|
||||
if (vkb == '*')
|
||||
return true;
|
||||
|
||||
switch (a) {
|
||||
#ifdef ZXING_USE_ZINT
|
||||
#define USING_ZINT 1
|
||||
#else
|
||||
#define USING_ZINT 0
|
||||
#endif
|
||||
#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \
|
||||
case BarcodeFormat(ZX_BCF_ID(SYM, VAR)): \
|
||||
return ENABLED \
|
||||
&& ((vkb == 'w' && USING_ZINT) ? ZINT : (FLAGS[0] == vkb || FLAGS[1] == vkb || FLAGS[2] == vkb || FLAGS[3] == vkb || FLAGS[4] == vkb));
|
||||
ZX_BCF_LIST(X)
|
||||
#undef X
|
||||
};
|
||||
}
|
||||
|
||||
if (a == b || Symbology(a) == b || a == Symbology(b))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==================================== BarcodeFormats =================================
|
||||
|
||||
inline bool operator<(BarcodeFormat a, BarcodeFormat b)
|
||||
{
|
||||
return SymbologyKey(a) < SymbologyKey(b) || (SymbologyKey(a) == SymbologyKey(b) && VariantKey(a) < VariantKey(b));
|
||||
}
|
||||
|
||||
void BarcodeFormats::normalize()
|
||||
{
|
||||
std::erase_if(formats_, [](BarcodeFormat t) { return t == BarcodeFormat::None; });
|
||||
std::sort(formats_.begin(), formats_.end());
|
||||
formats_.erase(std::unique(formats_.begin(), formats_.end()), formats_.end());
|
||||
}
|
||||
|
||||
BarcodeFormats::BarcodeFormats(std::string_view str)
|
||||
{
|
||||
while (str.size() >= 3 && str[0] == ']') {
|
||||
formats_.push_back(BarcodeFormat(ZX_BCF_ID(str[1], str[2])));
|
||||
str.remove_prefix(3);
|
||||
}
|
||||
|
||||
ForEachToken(TrimWS(str, " []"), ",|", [this](std::string_view token) {
|
||||
if (!token.empty())
|
||||
formats_.push_back(BarcodeFormatFromString(token));
|
||||
});
|
||||
|
||||
normalize();
|
||||
}
|
||||
|
||||
BarcodeFormats BarcodeFormats::list(const BarcodeFormats& filter)
|
||||
{
|
||||
std::vector<BarcodeFormat> res;
|
||||
res.reserve(100);
|
||||
if (filter.empty()) {
|
||||
#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \
|
||||
if (ENABLED) \
|
||||
res.push_back(BarcodeFormat(ZX_BCF_ID(SYM, VAR)));
|
||||
ZX_BCF_LIST(X)
|
||||
#undef X
|
||||
return res;
|
||||
}
|
||||
for (auto f : filter) {
|
||||
// printf("Filter for: %s\n", IdStr(f).c_str());
|
||||
#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \
|
||||
if (ENABLED && SYM != '*' \
|
||||
&& (SymbologyKey(f) == '*' ? BarcodeFormat(ZX_BCF_ID(SYM, VAR)) & f \
|
||||
: SYM == SymbologyKey(f) && (VariantKey(f) == ' ' || VariantKey(f) == VAR))) \
|
||||
res.push_back(BarcodeFormat(ZX_BCF_ID(SYM, VAR))); //, printf("adding: %c %c\n", SYM, VAR);
|
||||
ZX_BCF_LIST(X)
|
||||
#undef X
|
||||
// printf("N: %d\n", (int)res.size());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// BarcodeFormats&& BarcodeFormats::operator|(BarcodeFormat bt) &&
|
||||
// {
|
||||
// types_.push_back(bt);
|
||||
// normalize();
|
||||
// return std::move(*this);
|
||||
// }
|
||||
|
||||
// BarcodeFormats&& BarcodeFormats::operator|(const BarcodeFormats& other) &&
|
||||
// {
|
||||
// types_.insert(std::end(types_), other.begin(), other.end());
|
||||
// normalize();
|
||||
// return std::move(*this);
|
||||
// }
|
||||
|
||||
BarcodeFormats BarcodeFormats::operator&(const BarcodeFormats& other)
|
||||
{
|
||||
std::vector<BarcodeFormat> res;
|
||||
for (auto l : formats_)
|
||||
for (auto r : other.formats_)
|
||||
if (l <= r)
|
||||
res.push_back(l);
|
||||
else if (r <= l)
|
||||
res.push_back(r);
|
||||
return res;
|
||||
}
|
||||
|
||||
BarcodeFormats BarcodeFormatsFromString(std::string_view str)
|
||||
{
|
||||
return BarcodeFormats(str);
|
||||
}
|
||||
|
||||
std::string ToString(const BarcodeFormats& formats)
|
||||
{
|
||||
if (formats.empty())
|
||||
return {};
|
||||
std::string res;
|
||||
for (auto f : formats)
|
||||
res += std::string(Name(f)) + ", ";
|
||||
return res.substr(0, res.size() - 2);
|
||||
}
|
||||
|
||||
} // namespace ZXing
|
||||
|
||||
#if 0
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BarcodeFormat.h"
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
struct BarcodeFormatName
|
||||
{
|
||||
BarcodeFormat format;
|
||||
std::string_view name;
|
||||
};
|
||||
|
||||
static const BarcodeFormatName NAMES[] = {
|
||||
{BarcodeFormat::None, "None"},
|
||||
{BarcodeFormat::Aztec, "Aztec"},
|
||||
{BarcodeFormat::Codabar, "Codabar"},
|
||||
{BarcodeFormat::Code39, "Code39"},
|
||||
{BarcodeFormat::Code93, "Code93"},
|
||||
{BarcodeFormat::Code128, "Code128"},
|
||||
{BarcodeFormat::DataBar, "DataBar"},
|
||||
{BarcodeFormat::DataBarExpanded, "DataBarExpanded"},
|
||||
{BarcodeFormat::DataBarLimited, "DataBarLimited"},
|
||||
{BarcodeFormat::DataMatrix, "DataMatrix"},
|
||||
{BarcodeFormat::DXFilmEdge, "DXFilmEdge"},
|
||||
{BarcodeFormat::EAN8, "EAN-8"},
|
||||
{BarcodeFormat::EAN13, "EAN-13"},
|
||||
{BarcodeFormat::ITF, "ITF"},
|
||||
{BarcodeFormat::MaxiCode, "MaxiCode"},
|
||||
{BarcodeFormat::MicroPDF417, "MicroPDF417"},
|
||||
{BarcodeFormat::MicroQRCode, "MicroQRCode"},
|
||||
{BarcodeFormat::PDF417, "PDF417"},
|
||||
{BarcodeFormat::QRCode, "QRCode"},
|
||||
{BarcodeFormat::RMQRCode, "rMQRCode"},
|
||||
{BarcodeFormat::UPCA, "UPC-A"},
|
||||
{BarcodeFormat::UPCE, "UPC-E"},
|
||||
{BarcodeFormat::LinearCodes, "Linear-Codes"},
|
||||
{BarcodeFormat::MatrixCodes, "Matrix-Codes"},
|
||||
};
|
||||
|
||||
std::string ToString(BarcodeFormat format)
|
||||
{
|
||||
auto i = FindIf(NAMES, [format](auto& v) { return v.format == format; });
|
||||
return i == std::end(NAMES) ? std::string() : std::string(i->name);
|
||||
}
|
||||
|
||||
std::string ToString(BarcodeFormats formats)
|
||||
{
|
||||
if (formats.empty())
|
||||
return ToString(BarcodeFormat::None);
|
||||
std::string res;
|
||||
for (auto f : formats)
|
||||
res += ToString(f) + "|";
|
||||
return res.substr(0, res.size() - 1);
|
||||
}
|
||||
|
||||
BarcodeFormat BarcodeFormatFromString(std::string_view str)
|
||||
{
|
||||
auto i = FindIf(NAMES, [str](auto& v) { return IsEqualIgnoreCaseAnd(v.name, str, "_-"); });
|
||||
return i == std::end(NAMES) ? BarcodeFormat::None : i->format;
|
||||
}
|
||||
|
||||
BarcodeFormats BarcodeFormatsFromString(std::string_view str)
|
||||
{
|
||||
BarcodeFormats res;
|
||||
ForEachToken(TrimWS(str, " []"), " ,|", [&res](std::string_view token) {
|
||||
if (!token.empty()) {
|
||||
auto bc = BarcodeFormatFromString(token);
|
||||
if (bc == BarcodeFormat::None)
|
||||
throw std::invalid_argument("This is not a valid barcode format: '" + std::string(token) + "'");
|
||||
res |= bc;
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
#endif // 0
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Version.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// FLAGS[0]: l == linear, m == matrix
|
||||
// FLAGS[1]: r == readable
|
||||
// FLAGS[2]: w == writable (MultiformatWriter))
|
||||
// FLAGS[3]: G == GS1
|
||||
// FLAGS[4]: R == Retail, I == Industrial, O == Other
|
||||
|
||||
// clang-format off
|
||||
// NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI
|
||||
#define ZX_BCF_LIST(X) \
|
||||
X(None, 0 , 0, " ", 0, 1, "None") \
|
||||
X(All, '*', '*', " ", 0, 1, "All") \
|
||||
X(AllReadable, '*', 'r', " ", 0, 1, "All Readable") \
|
||||
X(AllCreatable, '*', 'w', " ", 0, 1, "All Creatable") \
|
||||
X(AllLinear, '*', 'l', " ", 0, 1, "All Linear") \
|
||||
X(AllMatrix, '*', 'm', " ", 0, 1, "All Matrix") \
|
||||
X(AllGS1, '*', 'G', " ", 0, 1, "All GS1") \
|
||||
X(AllRetail, '*', 'R', " ", 0, 1, "All Retail") \
|
||||
X(AllIndustrial, '*', 'I', " ", 0, 1, "All Industrial") \
|
||||
X(Codabar, 'F', ' ', "lrw ", 18, ZXING_ENABLE_1D, "Codabar") \
|
||||
X(Code39, 'A', ' ', "lrw I", 8, ZXING_ENABLE_1D, "Code 39") \
|
||||
X(Code39Std, 'A', 's', "lrw I", 8, ZXING_ENABLE_1D, "Code 39 Standard") \
|
||||
X(Code39Ext, 'A', 'e', "lr I", 9, ZXING_ENABLE_1D, "Code 39 Extended") \
|
||||
X(Code32, 'A', '2', "lr I", 129, ZXING_ENABLE_1D, "Code 32") \
|
||||
X(PZN, 'A', 'p', "lr I", 52, ZXING_ENABLE_1D, "Pharmazentralnummer") \
|
||||
X(Code93, 'G', ' ', "lrw I", 25, ZXING_ENABLE_1D, "Code 93") \
|
||||
X(Code128, 'C', ' ', "lrwGI", 20, ZXING_ENABLE_1D, "Code 128") \
|
||||
X(ITF, 'I', ' ', "lrw I", 3, ZXING_ENABLE_1D, "ITF") \
|
||||
X(ITF14, 'I', '4', "lr I", 89, ZXING_ENABLE_1D, "ITF-14") \
|
||||
X(DataBar, 'e', ' ', "lr GR", 29, ZXING_ENABLE_1D, "DataBar") \
|
||||
X(DataBarOmni, 'e', 'o', "lr GR", 29, ZXING_ENABLE_1D, "DataBar Omni") \
|
||||
X(DataBarStk, 'e', 's', "lr GR", 79, ZXING_ENABLE_1D, "DataBar Stacked") \
|
||||
X(DataBarStkOmni, 'e', 'O', "lr GR", 80, ZXING_ENABLE_1D, "DataBar Stacked Omni") \
|
||||
X(DataBarLtd, 'e', 'l', "lr GR", 30, ZXING_ENABLE_1D, "DataBar Limited") \
|
||||
X(DataBarExp, 'e', 'e', "lr GR", 31, ZXING_ENABLE_1D, "DataBar Expanded") \
|
||||
X(DataBarExpStk, 'e', 'E', "lr GR", 81, ZXING_ENABLE_1D, "DataBar Expanded Stacked") \
|
||||
X(EANUPC, 'E', ' ', "lr R", 15, ZXING_ENABLE_1D, "EAN/UPC") \
|
||||
X(EAN13, 'E', '1', "lrw R", 15, ZXING_ENABLE_1D, "EAN-13") \
|
||||
X(EAN8, 'E', '8', "lrw R", 10, ZXING_ENABLE_1D, "EAN-8") \
|
||||
X(EAN5, 'E', '5', "l R", 12, ZXING_ENABLE_1D, "EAN-5") \
|
||||
X(EAN2, 'E', '2', "l R", 11, ZXING_ENABLE_1D, "EAN-2") \
|
||||
X(ISBN, 'E', 'i', "lr R", 69, ZXING_ENABLE_1D, "ISBN") \
|
||||
X(UPCA, 'E', 'a', "lrw R", 34, ZXING_ENABLE_1D, "UPC-A") \
|
||||
X(UPCE, 'E', 'e', "lrw R", 37, ZXING_ENABLE_1D, "UPC-E") \
|
||||
X(Telepen, 'B', ' ', "lr I", 32, ZXING_ENABLE_1D, "Telepen") \
|
||||
X(TelepenAlpha, 'B', '0', "lr I", 32, ZXING_ENABLE_1D, "Telepen Alpha") \
|
||||
X(TelepenNumeric, 'B', '1', "lr I", 87, ZXING_ENABLE_1D, "Telepen Numeric") \
|
||||
X(OtherBarcode, 'X', ' ', " r ", 0, ZXING_ENABLE_1D, "Other barcode") /* see ISO/IEC 15424:2025 */ \
|
||||
X(DXFilmEdge, 'X', 'x', "lr ", 147, ZXING_ENABLE_1D, "DX Film Edge") \
|
||||
X(PDF417, 'L', ' ', "mrw ", 55, ZXING_ENABLE_PDF417, "PDF417") \
|
||||
X(CompactPDF417, 'L', 'c', "mr ", 56, ZXING_ENABLE_PDF417, "Compact PDF417") \
|
||||
X(MicroPDF417, 'L', 'm', "mr ", 84, ZXING_ENABLE_PDF417, "MicroPDF417") \
|
||||
X(Aztec, 'z', ' ', "mr G ", 92, ZXING_ENABLE_AZTEC, "Aztec") \
|
||||
X(AztecCode, 'z', 'c', "mrwG ", 92, ZXING_ENABLE_AZTEC, "Aztec Code") \
|
||||
X(AztecRune, 'z', 'r', "mr ", 128, ZXING_ENABLE_AZTEC, "Aztec Rune") \
|
||||
X(QRCode, 'Q', ' ', "mrwG ", 58, ZXING_ENABLE_QRCODE, "QR Code") \
|
||||
X(QRCodeModel1, 'Q', '1', "mr ", 0, ZXING_ENABLE_QRCODE, "QR Code Model 1") \
|
||||
X(QRCodeModel2, 'Q', '2', "mr ", 58, ZXING_ENABLE_QRCODE, "QR Code Model 2") \
|
||||
X(MicroQRCode, 'Q', 'm', "mr ", 97, ZXING_ENABLE_QRCODE, "Micro QR Code") \
|
||||
X(RMQRCode, 'Q', 'r', "mr G ", 145, ZXING_ENABLE_QRCODE, "rMQR Code") \
|
||||
X(DataMatrix, 'd', ' ', "mrwG ", 71, ZXING_ENABLE_DATAMATRIX, "Data Matrix") \
|
||||
X(MaxiCode, 'U', ' ', "mr ", 57, ZXING_ENABLE_MAXICODE, "MaxiCode") \
|
||||
/* Add new formats here */
|
||||
// clang-format on
|
||||
|
||||
#define ZX_BCF_ID(SYM, VAR) (((uint8_t)(SYM) << 0) | ((uint8_t)(VAR) << 8))
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* @brief Enumerates barcode formats known to this package.
|
||||
*
|
||||
* Some formats represent a symbology (e.g. EANUPC) with multiple variants (EAN13, EAN8, etc.), while others represent a specific
|
||||
* symbology variant (e.g. MicroQRCode). Both can be used with ReaderOptions::formats() to select which formats to read/scan for, but
|
||||
* only specific variants are returned by the library when reading barcodes.
|
||||
*
|
||||
* If the format name starts with "All", it is a pseudo-format representing a set of formats (e.g. AllLinear, AllGS1, etc.). These can
|
||||
* be used for filtering and selection purposes, but are not returned by the library when reading barcodes and can not be used when
|
||||
* creating barcodes.
|
||||
*/
|
||||
enum class BarcodeFormat : unsigned int
|
||||
{
|
||||
#define ZX_(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) NAME = ZX_BCF_ID(SYM, VAR),
|
||||
ZX_BCF_LIST(ZX_)
|
||||
#undef ZX_
|
||||
/// @cond DEPRECATED
|
||||
DataBarExpanded [[deprecated("Use DataBarExp instead")]] = DataBarExp,
|
||||
DataBarLimited [[deprecated("Use DataBarLtd instead")]] = DataBarLtd,
|
||||
LinearCodes [[deprecated("Use AllLinear instead")]] = AllLinear,
|
||||
MatrixCodes [[deprecated("Use AllMatrix instead")]] = AllMatrix,
|
||||
Any [[deprecated("Use All instead")]] = All,
|
||||
/// @endcond
|
||||
};
|
||||
|
||||
/// @cond INTERNAL
|
||||
inline char SymbologyKey(const BarcodeFormat& format)
|
||||
{
|
||||
return uint32_t(format) & 0xFF;
|
||||
}
|
||||
|
||||
inline char VariantKey(const BarcodeFormat& format)
|
||||
{
|
||||
return (uint32_t(format) >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
inline std::string IdStr(const BarcodeFormat& format)
|
||||
{
|
||||
return {']', SymbologyKey(format), VariantKey(format)};
|
||||
}
|
||||
/// @endcond
|
||||
|
||||
/// Returns the symbology (base type) of the given barcode format (e.g. EAN/UPC for EAN13, EAN8, UPCA, etc.).
|
||||
BarcodeFormat Symbology(BarcodeFormat format);
|
||||
|
||||
/// Returns the human-readable name of the given barcode format.
|
||||
std::string_view Name(BarcodeFormat format);
|
||||
|
||||
/// Test if left hand side (e == element) is 'inside' right hand side (s == set) (e.g. MicroQRCode <= QRCode).
|
||||
bool operator<=(BarcodeFormat e, BarcodeFormat s);
|
||||
|
||||
/// Test if the two BarcodeFormats have a non-empty intersection (e.g. AllMatrix & QRCode).
|
||||
bool operator&(BarcodeFormat a, BarcodeFormat b);
|
||||
|
||||
template <std::size_t N>
|
||||
using BarcodeFormatArray = std::array<BarcodeFormat, N>;
|
||||
|
||||
constexpr BarcodeFormatArray<2> operator|(BarcodeFormat a, BarcodeFormat b)
|
||||
{
|
||||
return {a, b};
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
constexpr BarcodeFormatArray<N + 1> operator|(BarcodeFormatArray<N> bts, BarcodeFormat bt)
|
||||
{
|
||||
BarcodeFormatArray<N + 1> ret{};
|
||||
for (std::size_t i = 0; i < N; ++i)
|
||||
ret[i] = bts[i];
|
||||
ret[N] = bt;
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
constexpr bool operator&(BarcodeFormat lhs, const BarcodeFormatArray<N>& rhs)
|
||||
{
|
||||
return std::any_of(rhs.begin(), rhs.end(), [lhs](BarcodeFormat bt) { return lhs & bt; });
|
||||
}
|
||||
|
||||
/// @brief Parse a string into a BarcodeFormat. '-', '_', '/' and ' ' are optional.
|
||||
/// @throws std::invalid_parameter if the string can not be fully parsed.
|
||||
BarcodeFormat BarcodeFormatFromString(std::string_view str);
|
||||
|
||||
std::string ToString(BarcodeFormat format);
|
||||
|
||||
/**
|
||||
* @brief A small container representing a collection of BarcodeFormat values.
|
||||
*
|
||||
* BarcodeFormats encapsulates an ordered collection of BarcodeFormat values and
|
||||
* provides convenient construction, iteration, and set-like operations.
|
||||
*
|
||||
* General behavior and invariants:
|
||||
* - The internal representation is kept in a canonical form (sorted, no duplicates)
|
||||
* using normalize().
|
||||
* - The API exposes read-only access and iteration.
|
||||
*/
|
||||
class BarcodeFormats
|
||||
{
|
||||
std::vector<BarcodeFormat> formats_;
|
||||
|
||||
void normalize();
|
||||
|
||||
public:
|
||||
BarcodeFormats() = default;
|
||||
BarcodeFormats(BarcodeFormat f) : formats_{f} {}
|
||||
|
||||
/// @brief Constructs a collection from a compile-time array of BarcodeFormat values.
|
||||
/// ```c++
|
||||
/// BarcodeFormats formats = BarcodeFormat::QRCode | BarcodeFormat::EAN13;
|
||||
/// ```
|
||||
template <std::size_t N>
|
||||
BarcodeFormats(BarcodeFormatArray<N> formats) : formats_{formats.begin(), formats.end()}
|
||||
{
|
||||
normalize();
|
||||
}
|
||||
|
||||
BarcodeFormats(std::vector<BarcodeFormat>&& formats) : formats_(std::move(formats)) { normalize(); }
|
||||
|
||||
/// Constructs a collection from a textual representation (e.g. a comma-separated list of format identifiers).
|
||||
explicit BarcodeFormats(std::string_view str);
|
||||
|
||||
BarcodeFormats(const BarcodeFormats&) = default;
|
||||
BarcodeFormats(BarcodeFormats&&) = default;
|
||||
BarcodeFormats& operator=(const BarcodeFormats&) = default;
|
||||
BarcodeFormats& operator=(BarcodeFormats&&) = default;
|
||||
bool operator==(const BarcodeFormats& o) const noexcept { return formats_ == o.formats_; }
|
||||
bool operator!=(const BarcodeFormats& o) const noexcept { return !(*this == o); }
|
||||
// ~BarcodeFormats() = default;
|
||||
|
||||
auto begin() const noexcept { return formats_.cbegin(); }
|
||||
auto end() const noexcept { return formats_.cend(); }
|
||||
|
||||
using value_type = typename std::vector<BarcodeFormat>::value_type;
|
||||
using pointer = typename std::vector<BarcodeFormat>::pointer;
|
||||
auto data() const noexcept { return formats_.data(); }
|
||||
|
||||
bool empty() const noexcept { return formats_.empty(); }
|
||||
int size() const noexcept { return static_cast<int>(formats_.size()); }
|
||||
|
||||
// BarcodeFormats&& operator|(BarcodeFormat bt) &&;
|
||||
// BarcodeFormats&& operator|(const BarcodeFormats& other) &&;
|
||||
|
||||
/// Returns a BarcodeFormats containing the intersection of this collection and other.
|
||||
BarcodeFormats operator&(const BarcodeFormats& other);
|
||||
|
||||
/// Returns a list of available/supported barcode formats, optionally filtered by the provided formats.
|
||||
/// e.g. `BarcodeFormats::list(BarcodeFormat::AllReadable);`
|
||||
static BarcodeFormats list(const BarcodeFormats& filter = {});
|
||||
|
||||
/// @cond DEPRECATED
|
||||
[[deprecated]] inline int count() const noexcept { return size(); }
|
||||
[[deprecated]] inline bool testFlag(BarcodeFormat format) const noexcept
|
||||
{
|
||||
return std::any_of(begin(), end(), [format](BarcodeFormat f) { return f & format; });
|
||||
}
|
||||
[[deprecated]] inline bool testFlags(const BarcodeFormats& formats) const noexcept
|
||||
{
|
||||
return std::any_of(begin(), end(), [formats](BarcodeFormat fo) {
|
||||
return std::any_of(formats.begin(), formats.end(), [fo](BarcodeFormat fi) { return fo & fi; });
|
||||
});
|
||||
}
|
||||
/// @endcond
|
||||
};
|
||||
|
||||
inline bool operator<=(BarcodeFormat lhs, const BarcodeFormats& rhs)
|
||||
{
|
||||
return std::any_of(rhs.begin(), rhs.end(), [lhs](BarcodeFormat bf) { return lhs <= bf; });
|
||||
}
|
||||
|
||||
inline bool operator&(BarcodeFormat lhs, const BarcodeFormats& rhs)
|
||||
{
|
||||
return std::any_of(rhs.begin(), rhs.end(), [lhs](BarcodeFormat bf) { return lhs & bf; });
|
||||
}
|
||||
|
||||
/// @brief Parses a string into a set of BarcodeFormats.
|
||||
/// Separators can be (any combination of) '|' or ','. Input can be lower case and any of '-', '_', '/' or ' ' are optional,
|
||||
/// e.g. "EAN-8 | qrcode, Itf" would be parsed into [EAN8, QRCode, ITF].
|
||||
/// @throws std::invalid_parameter if the string can not be fully parsed.
|
||||
BarcodeFormats BarcodeFormatsFromString(std::string_view str);
|
||||
|
||||
std::string ToString(const BarcodeFormats& formats);
|
||||
|
||||
} // namespace ZXing
|
||||
|
||||
#endif // __cplusplus
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BinaryBitmap.h"
|
||||
|
||||
#include "BitMatrix.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
struct BinaryBitmap::Cache
|
||||
{
|
||||
std::shared_ptr<const BitMatrix> matrix, transposed;
|
||||
};
|
||||
|
||||
BitMatrix BinaryBitmap::binarize(const uint8_t threshold) const
|
||||
{
|
||||
BitMatrix res(width(), height());
|
||||
|
||||
if (_buffer.pixStride() == 1 && _buffer.rowStride() == _buffer.width()) {
|
||||
// Specialize for a packed buffer with pixStride 1 to support auto vectorization (16x speedup on AVX2)
|
||||
auto dst = res.row(0).begin();
|
||||
for (auto src = _buffer.data(0, 0), end = _buffer.data(0, height()); src != end; ++src, ++dst)
|
||||
*dst = (*src <= threshold) * BitMatrix::SET_V;
|
||||
} else {
|
||||
auto processLine = [&res, threshold](int y, const auto* src, const int stride) {
|
||||
for (auto& dst : res.row(y)) {
|
||||
dst = (*src <= threshold) * BitMatrix::SET_V;
|
||||
src += stride;
|
||||
}
|
||||
};
|
||||
for (int y = 0; y < res.height(); ++y) {
|
||||
auto src = _buffer.data(0, y) + GreenIndex(_buffer.format());
|
||||
// Specialize the inner loop for strides 1 and 4 to support auto vectorization
|
||||
switch (_buffer.pixStride()) {
|
||||
case 1: processLine(y, src, 1); break;
|
||||
case 4: processLine(y, src, 4); break;
|
||||
default: processLine(y, src, _buffer.pixStride()); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
BinaryBitmap::BinaryBitmap(const ImageView& buffer) : _cache(new Cache), _buffer(buffer) {}
|
||||
|
||||
BinaryBitmap::~BinaryBitmap() = default;
|
||||
|
||||
const BitMatrix* BinaryBitmap::getBitMatrix(bool transposed) const
|
||||
{
|
||||
if (!_cache->matrix) {
|
||||
_cache->matrix = getBlackMatrix();
|
||||
}
|
||||
if (transposed) {
|
||||
if (!_cache->transposed) {
|
||||
auto copy = std::make_shared<BitMatrix>(_cache->matrix->copy());
|
||||
copy->rotate90();
|
||||
_cache->transposed = std::move(copy);
|
||||
}
|
||||
return _cache->transposed.get();
|
||||
}
|
||||
return _cache->matrix.get();
|
||||
}
|
||||
|
||||
void BinaryBitmap::invert()
|
||||
{
|
||||
if (_cache->matrix)
|
||||
const_cast<BitMatrix*>(_cache->matrix.get())->flipAll();
|
||||
|
||||
if (_cache->transposed)
|
||||
const_cast<BitMatrix*>(_cache->transposed.get())->flipAll();
|
||||
|
||||
_inverted = !_inverted;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void SumFilter(const BitMatrix& in, BitMatrix& out, F func)
|
||||
{
|
||||
assert(in.height() >= 3);
|
||||
|
||||
const auto* in0 = in.row(0).begin();
|
||||
const auto* in1 = in.row(1).begin();
|
||||
const auto* in2 = in.row(2).begin();
|
||||
|
||||
for (auto *out1 = out.row(1).begin() + 1, *end = out.row(out.height() - 1).begin() - 1; out1 != end; ++in0, ++in1, ++in2, ++out1) {
|
||||
int sum = 0;
|
||||
for (int j = 0; j < 3; ++j)
|
||||
sum += in0[j] + in1[j] + in2[j];
|
||||
|
||||
*out1 = func(sum);
|
||||
}
|
||||
}
|
||||
|
||||
void BinaryBitmap::close()
|
||||
{
|
||||
if (_cache->matrix) {
|
||||
auto& matrix = *const_cast<BitMatrix*>(_cache->matrix.get());
|
||||
BitMatrix tmp(matrix.width(), matrix.height());
|
||||
|
||||
// dilate
|
||||
SumFilter(matrix, tmp, [](int sum) { return (sum > 0 * BitMatrix::SET_V) * BitMatrix::SET_V; });
|
||||
// erode
|
||||
SumFilter(tmp, matrix, [](int sum) { return (sum == 9 * BitMatrix::SET_V) * BitMatrix::SET_V; });
|
||||
}
|
||||
_cache->transposed.reset();
|
||||
_closed = true;
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2021 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ImageView.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class BitMatrix;
|
||||
|
||||
using PatternRow = std::vector<uint16_t>;
|
||||
|
||||
/**
|
||||
* This class is the core bitmap class used by ZXing to represent 1 bit data. Reader objects
|
||||
* accept a BinaryBitmap and attempt to decode it.
|
||||
*/
|
||||
class BinaryBitmap
|
||||
{
|
||||
struct Cache;
|
||||
std::unique_ptr<Cache> _cache;
|
||||
bool _inverted = false;
|
||||
bool _closed = false;
|
||||
|
||||
protected:
|
||||
const ImageView _buffer;
|
||||
|
||||
/**
|
||||
* Converts a 2D array of luminance data to 1 bit (true means black).
|
||||
*
|
||||
* @return The 2D array of bits for the image, nullptr on error.
|
||||
*/
|
||||
virtual std::shared_ptr<const BitMatrix> getBlackMatrix() const = 0;
|
||||
|
||||
BitMatrix binarize(uint8_t threshold) const;
|
||||
|
||||
public:
|
||||
BinaryBitmap(const ImageView& buffer);
|
||||
virtual ~BinaryBitmap();
|
||||
|
||||
int width() const { return _buffer.width(); }
|
||||
int height() const { return _buffer.height(); }
|
||||
|
||||
/**
|
||||
* Converts one row of luminance data to a vector of ints denoting the widths of the bars and spaces.
|
||||
*/
|
||||
virtual bool getPatternRow(int row, int rotation, PatternRow& res) const = 0;
|
||||
|
||||
const BitMatrix* getBitMatrix(bool transposed = false) const;
|
||||
|
||||
void invert();
|
||||
bool inverted() const { return _inverted; }
|
||||
|
||||
void close();
|
||||
bool closed() const { return _closed; }
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BitArray.h"
|
||||
|
||||
#include "ByteArray.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
void
|
||||
BitArray::bitwiseXOR(const BitArray& other)
|
||||
{
|
||||
if (size() != other.size()) {
|
||||
throw std::invalid_argument("BitArray::xor(): Sizes don't match");
|
||||
}
|
||||
for (size_t i = 0; i < _bits.size(); i++) {
|
||||
// The last int could be incomplete (i.e. not have 32 bits in
|
||||
// it) but there is no problem since 0 XOR 0 == 0.
|
||||
_bits[i] ^= other._bits[i];
|
||||
}
|
||||
}
|
||||
|
||||
ByteArray BitArray::toBytes(int bitOffset, int numBytes) const
|
||||
{
|
||||
ByteArray res(numBytes == -1 ? (size() - bitOffset + 7) / 8 : numBytes);
|
||||
for (int i = 0; i < Size(res); i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
AppendBit(res[i], (numBytes != -1 || bitOffset < size()) ? get(bitOffset++) : false);
|
||||
return res;
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2017 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Range.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class ByteArray;
|
||||
|
||||
/**
|
||||
* A simple, fast array of bits.
|
||||
*/
|
||||
class BitArray
|
||||
{
|
||||
std::vector<uint8_t> _bits;
|
||||
|
||||
friend class BitMatrix;
|
||||
|
||||
// Nothing wrong to support it, just to make it explicit, instead of by mistake.
|
||||
// Use copy() below.
|
||||
BitArray(const BitArray &) = default;
|
||||
|
||||
public:
|
||||
|
||||
using Iterator = std::vector<uint8_t>::const_iterator;
|
||||
|
||||
BitArray() = default;
|
||||
|
||||
explicit BitArray(int size) : _bits(size, 0) {}
|
||||
|
||||
BitArray(BitArray&& other) noexcept = default;
|
||||
BitArray& operator=(BitArray&& other) noexcept = default;
|
||||
BitArray& operator=(const BitArray &) = delete;
|
||||
|
||||
BitArray copy() const { return *this; }
|
||||
|
||||
int size() const noexcept { return Size(_bits); }
|
||||
|
||||
int sizeInBytes() const noexcept { return (size() + 7) / 8; }
|
||||
|
||||
bool get(int i) const { return _bits.at(i) != 0; }
|
||||
void set(int i, bool val) { _bits.at(i) = val; }
|
||||
|
||||
// If you know exactly how may bits you are going to iterate
|
||||
// and that you access bit in sequence, iterator is faster than get().
|
||||
// However, be extremely careful since there is no check whatsoever.
|
||||
// (Performance is the reason for the iterator to exist in the first place.)
|
||||
Iterator iterAt(int i) const noexcept { return {_bits.cbegin() + i}; }
|
||||
Iterator begin() const noexcept { return _bits.cbegin(); }
|
||||
Iterator end() const noexcept { return _bits.cend(); }
|
||||
|
||||
/**
|
||||
* Appends the least-significant bits, from value, in order from most-significant to
|
||||
* least-significant. For example, appending 6 bits from 0x000001E will append the bits
|
||||
* 0, 1, 1, 1, 1, 0 in that order.
|
||||
*
|
||||
* @param value {@code int} containing bits to append
|
||||
* @param numBits bits from value to append
|
||||
*/
|
||||
void appendBits(int value, int numBits)
|
||||
{
|
||||
assert(numBits >= 1 && numBits <= 32);
|
||||
|
||||
for (; numBits; --numBits)
|
||||
_bits.push_back((value >> (numBits-1)) & 1);
|
||||
}
|
||||
|
||||
void appendBits(uint32_t value, int numBits)
|
||||
{
|
||||
appendBits(static_cast<int>(value), numBits);
|
||||
}
|
||||
|
||||
void appendBit(bool bit) { _bits.push_back(bit); }
|
||||
|
||||
void appendBitArray(const BitArray& other) { _bits.insert(_bits.end(), other.begin(), other.end()); }
|
||||
|
||||
/**
|
||||
* Reverses all bits in the array.
|
||||
*/
|
||||
void reverse() { std::reverse(_bits.begin(), _bits.end()); }
|
||||
|
||||
void bitwiseXOR(const BitArray& other);
|
||||
|
||||
/**
|
||||
* @param bitOffset first bit to extract
|
||||
* @param numBytes how many bytes to extract (-1 == until the end, padded with '0')
|
||||
* @return Bytes are written most-significant bit first.
|
||||
*/
|
||||
ByteArray toBytes(int bitOffset = 0, int numBytes = -1) const;
|
||||
|
||||
using Range = ZXing::Range<Iterator>;
|
||||
Range range() const { return {begin(), end()}; }
|
||||
|
||||
friend bool operator==(const BitArray& a, const BitArray& b) { return a._bits == b._bits; }
|
||||
};
|
||||
|
||||
template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
constexpr T& AppendBit(T& val, bool bit)
|
||||
{
|
||||
return (val <<= 1) |= static_cast<T>(bit);
|
||||
}
|
||||
|
||||
template <typename ARRAY, typename = std::enable_if_t<std::is_integral_v<typename ARRAY::value_type>>>
|
||||
constexpr int ToInt(const ARRAY& a)
|
||||
{
|
||||
assert(Reduce(a) <= 32);
|
||||
|
||||
int pattern = 0;
|
||||
for (int i = 0; i < Size(a); i++) {
|
||||
if (a[i] < 0) // negative shift is undefined behavior
|
||||
return -1;
|
||||
pattern = (pattern << a[i]) | ~(0xffffffff << a[i]) * (~i & 1);
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
|
||||
template <typename T = int, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
T ToInt(const BitArray& bits, int pos = 0, int count = 8 * sizeof(T))
|
||||
{
|
||||
assert(0 <= count && count <= 8 * (int)sizeof(T));
|
||||
assert(0 <= pos && pos + count <= bits.size());
|
||||
|
||||
count = std::min(count, bits.size());
|
||||
int res = 0;
|
||||
auto it = bits.iterAt(pos);
|
||||
for (int i = 0; i < count; ++i, ++it)
|
||||
AppendBit(res, *it);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T = int, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
std::vector<T> ToInts(const BitArray& bits, int wordSize, int totalWords, int offset = 0)
|
||||
{
|
||||
assert(totalWords >= bits.size() / wordSize);
|
||||
assert(wordSize <= 8 * (int)sizeof(T));
|
||||
|
||||
std::vector<T> res(totalWords, 0);
|
||||
for (int i = offset; i < bits.size(); i += wordSize)
|
||||
res[(i - offset) / wordSize] = ToInt(bits, i, wordSize);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
class BitArrayView
|
||||
{
|
||||
const BitArray& bits;
|
||||
BitArray::Iterator cur;
|
||||
|
||||
public:
|
||||
BitArrayView(const BitArray& bits) : bits(bits), cur(bits.begin()) {}
|
||||
|
||||
BitArrayView& skipBits(int n)
|
||||
{
|
||||
if (cur + n > bits.end())
|
||||
throw std::out_of_range("BitArrayView::skipBits() out of range.");
|
||||
cur += n;
|
||||
return *this;
|
||||
}
|
||||
|
||||
int peekBits(int n) const
|
||||
{
|
||||
assert(n <= 32);
|
||||
if (cur + n > bits.end())
|
||||
throw std::out_of_range("BitArrayView::peekBits() out of range.");
|
||||
int res = 0;
|
||||
for (auto i = cur; n > 0; --n, i++)
|
||||
AppendBit(res, *i);
|
||||
return res;
|
||||
}
|
||||
|
||||
int readBits(int n)
|
||||
{
|
||||
int res = peekBits(n);
|
||||
cur += n;
|
||||
return res;
|
||||
}
|
||||
|
||||
int size() const
|
||||
{
|
||||
return narrow_cast<int>(bits.end() - cur);
|
||||
}
|
||||
|
||||
explicit operator bool() const { return size(); }
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BitMatrix.h"
|
||||
|
||||
#include "Pattern.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
void
|
||||
BitMatrix::setRegion(int left, int top, int width, int height)
|
||||
{
|
||||
if (top < 0 || left < 0) {
|
||||
throw std::invalid_argument("BitMatrix::setRegion(): Left and top must be nonnegative");
|
||||
}
|
||||
if (height < 1 || width < 1) {
|
||||
throw std::invalid_argument("BitMatrix::setRegion(): Height and width must be at least 1");
|
||||
}
|
||||
int right = left + width;
|
||||
int bottom = top + height;
|
||||
if (bottom > _height || right > _width) {
|
||||
throw std::invalid_argument("BitMatrix::setRegion(): The region must fit inside the matrix");
|
||||
}
|
||||
for (int y = top; y < bottom; y++) {
|
||||
auto offset = y * _width;
|
||||
for (int x = left; x < right; x++) {
|
||||
_bits[offset + x] = SET_V;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BitMatrix::rotate90()
|
||||
{
|
||||
BitMatrix result(height(), width());
|
||||
for (int x = 0; x < width(); ++x) {
|
||||
for (int y = 0; y < height(); ++y) {
|
||||
if (get(x, y)) {
|
||||
result.set(y, width() - x - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
*this = std::move(result);
|
||||
}
|
||||
|
||||
void
|
||||
BitMatrix::rotate180()
|
||||
{
|
||||
std::reverse(_bits.begin(), _bits.end());
|
||||
}
|
||||
|
||||
void
|
||||
BitMatrix::mirror()
|
||||
{
|
||||
for (int x = 0; x < _width; x++) {
|
||||
for (int y = x + 1; y < _height; y++) {
|
||||
if (get(x, y) != get(y, x)) {
|
||||
flip(y, x);
|
||||
flip(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
BitMatrix::findBoundingBox(int &left, int& top, int& width, int& height, int minSize) const
|
||||
{
|
||||
int right, bottom;
|
||||
if (!getTopLeftOnBit(left, top) || !getBottomRightOnBit(right, bottom) || bottom - top + 1 < minSize)
|
||||
return false;
|
||||
|
||||
for (int y = top; y <= bottom; y++ ) {
|
||||
for (int x = 0; x < left; ++x)
|
||||
if (get(x, y)) {
|
||||
left = x;
|
||||
break;
|
||||
}
|
||||
for (int x = _width-1; x > right; x--)
|
||||
if (get(x, y)) {
|
||||
right = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
width = right - left + 1;
|
||||
height = bottom - top + 1;
|
||||
return width >= minSize && height >= minSize;
|
||||
}
|
||||
|
||||
static auto isSet = [](auto v) { return bool(v); };
|
||||
|
||||
bool
|
||||
BitMatrix::getTopLeftOnBit(int& left, int& top) const
|
||||
{
|
||||
int bitsOffset = (int)std::distance(_bits.begin(), std::find_if(_bits.begin(), _bits.end(), isSet));
|
||||
if (bitsOffset == Size(_bits)) {
|
||||
return false;
|
||||
}
|
||||
top = bitsOffset / _width;
|
||||
left = (bitsOffset % _width);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BitMatrix::getBottomRightOnBit(int& right, int& bottom) const
|
||||
{
|
||||
int bitsOffset = Size(_bits) - 1 - (int)std::distance(_bits.rbegin(), std::find_if(_bits.rbegin(), _bits.rend(), isSet));
|
||||
if (bitsOffset < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bottom = bitsOffset / _width;
|
||||
right = (bitsOffset % _width);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GetPatternRow(const BitMatrix& matrix, int r, std::vector<uint16_t>& pr, bool transpose)
|
||||
{
|
||||
if (transpose)
|
||||
GetPatternRow(matrix.col(r), pr);
|
||||
else
|
||||
GetPatternRow(matrix.row(r), pr);
|
||||
}
|
||||
|
||||
BitMatrix Inflate(BitMatrix&& input, int width, int height, int quietZone)
|
||||
{
|
||||
const int codeWidth = input.width();
|
||||
const int codeHeight = input.height();
|
||||
const int outputWidth = std::max(width, codeWidth + 2 * quietZone);
|
||||
const int outputHeight = std::max(height, codeHeight + 2 * quietZone);
|
||||
|
||||
if (input.width() == outputWidth && input.height() == outputHeight)
|
||||
return std::move(input);
|
||||
|
||||
const int scale = std::min((outputWidth - 2*quietZone) / codeWidth, (outputHeight - 2*quietZone) / codeHeight);
|
||||
// Padding includes both the quiet zone and the extra white pixels to
|
||||
// accommodate the requested dimensions.
|
||||
const int leftPadding = (outputWidth - (codeWidth * scale)) / 2;
|
||||
const int topPadding = (outputHeight - (codeHeight * scale)) / 2;
|
||||
|
||||
BitMatrix result(outputWidth, outputHeight);
|
||||
|
||||
for (int inputY = 0, outputY = topPadding; inputY < input.height(); ++inputY, outputY += scale) {
|
||||
for (int inputX = 0, outputX = leftPadding; inputX < input.width(); ++inputX, outputX += scale) {
|
||||
if (input.get(inputX, inputY))
|
||||
result.setRegion(outputX, outputY, scale, scale);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
BitMatrix Deflate(const BitMatrix& input, int width, int height, float top, float left, float subSampling)
|
||||
{
|
||||
BitMatrix result(width, height);
|
||||
|
||||
for (int y = 0; y < result.height(); y++) {
|
||||
auto yOffset = top + y * subSampling;
|
||||
for (int x = 0; x < result.width(); x++) {
|
||||
if (input.get(PointF(left + x * subSampling, yOffset)))
|
||||
result.set(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Matrix.h"
|
||||
#include "Point.h"
|
||||
|
||||
#ifdef ZXING_INTERNAL
|
||||
#include "Range.h"
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class BitArray;
|
||||
|
||||
/**
|
||||
* @brief A simple, fast 2D array of bits.
|
||||
*/
|
||||
class BitMatrix
|
||||
{
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
using data_t = uint8_t;
|
||||
|
||||
std::vector<data_t> _bits;
|
||||
// There is nothing wrong to support this but disable to make it explicit since we may copy something very big here.
|
||||
// Use copy() below.
|
||||
BitMatrix(const BitMatrix&) = default;
|
||||
|
||||
const data_t& get(int i) const
|
||||
{
|
||||
#if 1
|
||||
return _bits.at(i);
|
||||
#else
|
||||
return _bits[i];
|
||||
#endif
|
||||
}
|
||||
|
||||
data_t& get(int i) { return const_cast<data_t&>(static_cast<const BitMatrix*>(this)->get(i)); }
|
||||
|
||||
bool getTopLeftOnBit(int &left, int& top) const;
|
||||
bool getBottomRightOnBit(int &right, int& bottom) const;
|
||||
|
||||
public:
|
||||
static constexpr data_t SET_V = 0xff; // allows playing with SIMD binarization
|
||||
static constexpr data_t UNSET_V = 0;
|
||||
static_assert(bool(SET_V) && !bool(UNSET_V), "SET_V needs to evaluate to true, UNSET_V to false, see iterator usage");
|
||||
|
||||
BitMatrix() = default;
|
||||
|
||||
#if defined(__llvm__) || (defined(__GNUC__) && (__GNUC__ > 7))
|
||||
__attribute__((no_sanitize("signed-integer-overflow")))
|
||||
#endif
|
||||
BitMatrix(int width, int height) : _width(width), _height(height), _bits(width * height, UNSET_V)
|
||||
{
|
||||
if (width != 0 && int(_bits.size()) / width != height)
|
||||
throw std::invalid_argument("Invalid size: width * height is too big");
|
||||
}
|
||||
|
||||
explicit BitMatrix(int dimension) : BitMatrix(dimension, dimension) {} // Construct a square matrix.
|
||||
|
||||
BitMatrix(BitMatrix&& other) noexcept = default;
|
||||
BitMatrix& operator=(BitMatrix&& other) noexcept = default;
|
||||
BitMatrix& operator=(const BitMatrix&) = delete;
|
||||
|
||||
BitMatrix copy() const { return *this; }
|
||||
|
||||
#ifdef ZXING_INTERNAL
|
||||
Range<data_t*> row(int y) { return {_bits.data() + y * _width, _bits.data() + (y + 1) * _width}; }
|
||||
Range<const data_t*> row(int y) const { return {_bits.data() + y * _width, _bits.data() + (y + 1) * _width}; }
|
||||
|
||||
Range<StrideIter<const data_t*>> col(int x) const
|
||||
{
|
||||
return {{_bits.data() + x + (_height - 1) * _width, -_width}, {_bits.data() + x - _width, -_width}};
|
||||
}
|
||||
#endif
|
||||
|
||||
bool get(int x, int y) const { return get(y * _width + x); }
|
||||
void set(int x, int y, bool val = true) { get(y * _width + x) = val * SET_V; }
|
||||
|
||||
/**
|
||||
* <p>Flips the given bit.</p>
|
||||
*
|
||||
* @param x The horizontal component (i.e. which column)
|
||||
* @param y The vertical component (i.e. which row)
|
||||
*/
|
||||
void flip(int x, int y)
|
||||
{
|
||||
auto& v = get(y * _width + x);
|
||||
v = !v;
|
||||
}
|
||||
|
||||
void flipAll()
|
||||
{
|
||||
for (auto& i : _bits)
|
||||
i = !i * SET_V;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets a square region of the bit matrix to true.</p>
|
||||
*
|
||||
* @param left The horizontal position to begin at (inclusive)
|
||||
* @param top The vertical position to begin at (inclusive)
|
||||
* @param width The width of the region
|
||||
* @param height The height of the region
|
||||
*/
|
||||
void setRegion(int left, int top, int width, int height);
|
||||
|
||||
void rotate90();
|
||||
|
||||
void rotate180();
|
||||
|
||||
void mirror();
|
||||
|
||||
/**
|
||||
* Find the rectangle that contains all non-white pixels. Useful for detection of 'pure' barcodes.
|
||||
*
|
||||
* @return True iff this rectangle is at least minWidth x minHeight pixels big
|
||||
*/
|
||||
bool findBoundingBox(int &left, int& top, int& width, int& height, int minSize = 1) const;
|
||||
|
||||
int width() const { return _width; }
|
||||
|
||||
int height() const { return _height; }
|
||||
|
||||
bool empty() const { return _bits.empty(); }
|
||||
|
||||
friend bool operator==(const BitMatrix& a, const BitMatrix& b)
|
||||
{
|
||||
return a._width == b._width && a._height == b._height && a._bits == b._bits;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool isIn(PointT<T> p, int b = 0) const noexcept
|
||||
{
|
||||
return b <= p.x && p.x < width() - b && b <= p.y && p.y < height() - b;
|
||||
}
|
||||
|
||||
bool get(PointI p) const { return get(p.x, p.y); }
|
||||
bool get(PointF p) const { return get(PointI(p)); }
|
||||
void set(PointI p, bool v = true) { set(p.x, p.y, v); }
|
||||
void set(PointF p, bool v = true) { set(PointI(p), v); }
|
||||
};
|
||||
|
||||
void GetPatternRow(const BitMatrix& matrix, int r, std::vector<uint16_t>& pr, bool transpose);
|
||||
|
||||
/**
|
||||
* @brief Inflate scales a BitMatrix up and adds a quiet Zone plus padding
|
||||
* @param input matrix to be expanded
|
||||
* @param width new width in bits (pixel)
|
||||
* @param height new height in bits (pixel)
|
||||
* @param quietZone size of quiet zone to add in modules
|
||||
* @return expanded BitMatrix, maybe move(input) if size did not change
|
||||
*/
|
||||
BitMatrix Inflate(BitMatrix&& input, int width, int height, int quietZone);
|
||||
|
||||
/**
|
||||
* @brief Deflate (crop + subsample) a bit matrix
|
||||
* @param input matrix to be shrunk
|
||||
* @param width new width
|
||||
* @param height new height
|
||||
* @param top cropping starts at top row
|
||||
* @param left cropping starts at left col
|
||||
* @param subSampling typically the module size
|
||||
* @return deflated input
|
||||
*/
|
||||
BitMatrix Deflate(const BitMatrix& input, int width, int height, float top, float left, float subSampling);
|
||||
|
||||
template<typename T>
|
||||
BitMatrix ToBitMatrix(const Matrix<T>& in, T trueValue = {true})
|
||||
{
|
||||
BitMatrix out(in.width(), in.height());
|
||||
for (int y = 0; y < in.height(); ++y)
|
||||
for (int x = 0; x < in.width(); ++x)
|
||||
if (in.get(x, y) == trueValue)
|
||||
out.set(x, y);
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrix<T> ToMatrix(const BitMatrix& in, T black = 0, T white = ~0)
|
||||
{
|
||||
Matrix<T> res(in.width(), in.height());
|
||||
for (int y = 0; y < in.height(); ++y)
|
||||
for (int x = 0; x < in.width(); ++x)
|
||||
res.set(x, y, in.get(x, y) ? black : white);
|
||||
return res;
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BitMatrix.h"
|
||||
#include "Point.h"
|
||||
#include "StdGenerator.h"
|
||||
|
||||
#include <climits>
|
||||
|
||||
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<T>. 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<typename POINT>
|
||||
class BitMatrixCursor
|
||||
{
|
||||
using this_t = BitMatrixCursor<POINT>;
|
||||
|
||||
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; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Value testAt(PointT<T> 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<int>(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<typename ARRAY>
|
||||
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<typename ARRAY>
|
||||
ARRAY readPatternFromBlack(int maxWhitePrefix, int range = 0)
|
||||
{
|
||||
if (maxWhitePrefix && isWhite() && !stepToEdge(1, maxWhitePrefix))
|
||||
return {};
|
||||
return readPattern<ARRAY>(range);
|
||||
}
|
||||
};
|
||||
|
||||
using BitMatrixCursorF = BitMatrixCursor<PointF>;
|
||||
using BitMatrixCursorI = BitMatrixCursor<PointI>;
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
// Generate points in a spiral pattern around the center
|
||||
inline std::generator<PointI> Spiral(int radius)
|
||||
{
|
||||
co_yield{0, 0};
|
||||
for (int r = 1; r <= radius; ++r) {
|
||||
// clang-format off
|
||||
for (int k = 0; k < 2 * r; ++k) co_yield{ r, -(r - 1) + k}; // right -> down
|
||||
for (int k = 0; k < 2 * r; ++k) co_yield{ (r - 1) - k, r}; // bottom -> left
|
||||
for (int k = 0; k < 2 * r; ++k) co_yield{ -r, (r - 1) - k}; // left -> up
|
||||
for (int k = 0; k < 2 * r; ++k) co_yield{-(r - 1) + k, -r}; // top -> right
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
* Copyright 2017 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BitMatrixIO.h"
|
||||
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::string ToString(const BitMatrix& matrix, char one, char zero, bool addSpace, bool printAsCString)
|
||||
{
|
||||
std::string result;
|
||||
result.reserve((addSpace ? 2 : 1) * (matrix.width() * matrix.height()) + matrix.height());
|
||||
for (int y = 0; y < matrix.height(); ++y) {
|
||||
if (printAsCString)
|
||||
result += '"';
|
||||
for (auto bit : matrix.row(y)) {
|
||||
result += bit ? one : zero;
|
||||
if (addSpace)
|
||||
result += ' ';
|
||||
}
|
||||
if (printAsCString)
|
||||
result += "\\n\"";
|
||||
result += '\n';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ToString(const BitMatrix& matrix, bool inverted)
|
||||
{
|
||||
constexpr auto map = std::array{" ", "▀", "▄", "█"};
|
||||
std::string res;
|
||||
|
||||
for (int y = 0; y < matrix.height(); y += 2) {
|
||||
for (int x = 0; x < matrix.width(); ++x) {
|
||||
int tp = matrix.get(x, y) ^ inverted;
|
||||
int bt = (matrix.height() == 1 && tp) || (y + 1 < matrix.height() && (matrix.get(x, y + 1) ^ inverted));
|
||||
res += map[tp | (bt << 1)];
|
||||
}
|
||||
res.push_back('\n');
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string ToSVG(const BitMatrix& matrix)
|
||||
{
|
||||
// see https://stackoverflow.com/questions/10789059/create-qr-code-in-vector-image/60638350#60638350
|
||||
|
||||
const int width = matrix.width();
|
||||
const int height = matrix.height();
|
||||
std::ostringstream out;
|
||||
|
||||
out << R"(<?xml version="1.0" encoding="UTF-8"?>)" << "\n"
|
||||
<< R"(<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 )" << width << ' ' << height << R"(" stroke="none">)"
|
||||
<< "\n<path d=\"";
|
||||
|
||||
for (int y = 0; y < height; ++y)
|
||||
for (int x = 0; x < width; ++x)
|
||||
if (matrix.get(x, y))
|
||||
out << "M" << x << "," << y << "h1v1h-1z";
|
||||
|
||||
out << "\"/>\n</svg>";
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
BitMatrix ParseBitMatrix(const std::string& str, char one, bool expectSpace)
|
||||
{
|
||||
auto lineLength = str.find('\n');
|
||||
if (lineLength == std::string::npos)
|
||||
return {};
|
||||
|
||||
int strStride = expectSpace ? 2 : 1;
|
||||
int height = narrow_cast<int>(str.length() / (lineLength + 1));
|
||||
int width = narrow_cast<int>(lineLength / strStride);
|
||||
BitMatrix mat(width, height);
|
||||
for (int y = 0; y < height; ++y) {
|
||||
size_t offset = y * (lineLength + 1);
|
||||
for (int x = 0; x < width; ++x, offset += strStride) {
|
||||
if (str[offset] == one)
|
||||
mat.set(x, y);
|
||||
}
|
||||
}
|
||||
return mat;
|
||||
}
|
||||
|
||||
void SaveAsPBM(const BitMatrix& matrix, const std::string& filename, int quietZone)
|
||||
{
|
||||
auto out = ToMatrix<uint8_t>(Inflate(matrix.copy(), 0, 0, quietZone));
|
||||
std::ofstream file(filename);
|
||||
file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n";
|
||||
file.write(reinterpret_cast<const char*>(out.data()), out.size());
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
* Copyright 2017 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "BitMatrix.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::string ToString(const BitMatrix& matrix, bool inverted = false);
|
||||
std::string ToString(const BitMatrix& matrix, char one, char zero = ' ', bool addSpace = true, bool printAsCString = false);
|
||||
std::string ToSVG(const BitMatrix& matrix);
|
||||
BitMatrix ParseBitMatrix(const std::string& str, char one = 'X', bool expectSpace = true);
|
||||
void SaveAsPBM(const BitMatrix& matrix, const std::string& filename, int quietZone = 0);
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BitSource.h"
|
||||
|
||||
#include "ByteArray.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
int
|
||||
BitSource::available() const
|
||||
{
|
||||
return 8 * (Size(_bytes) - _byteOffset) - _bitOffset;
|
||||
}
|
||||
|
||||
static int ReadBitsImpl(int numBits, const ByteArray& _bytes, int available, int& _byteOffset, int& _bitOffset)
|
||||
{
|
||||
if (numBits < 1 || numBits > 32 || numBits > available) {
|
||||
throw std::out_of_range("BitSource::readBits: out of range");
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
|
||||
// First, read remainder from current byte
|
||||
if (_bitOffset > 0) {
|
||||
int bitsLeft = 8 - _bitOffset;
|
||||
int toRead = numBits < bitsLeft ? numBits : bitsLeft;
|
||||
int bitsToNotRead = bitsLeft - toRead;
|
||||
int mask = (0xFF >> (8 - toRead)) << bitsToNotRead;
|
||||
result = (_bytes[_byteOffset] & mask) >> bitsToNotRead;
|
||||
numBits -= toRead;
|
||||
_bitOffset += toRead;
|
||||
if (_bitOffset == 8) {
|
||||
_bitOffset = 0;
|
||||
_byteOffset++;
|
||||
}
|
||||
}
|
||||
|
||||
// Next read whole bytes
|
||||
if (numBits > 0) {
|
||||
while (numBits >= 8) {
|
||||
result = (result << 8) | _bytes[_byteOffset];
|
||||
_byteOffset++;
|
||||
numBits -= 8;
|
||||
}
|
||||
|
||||
// Finally read a partial byte
|
||||
if (numBits > 0) {
|
||||
int bitsToNotRead = 8 - numBits;
|
||||
int mask = (0xFF >> bitsToNotRead) << bitsToNotRead;
|
||||
result = (result << numBits) | ((_bytes[_byteOffset] & mask) >> bitsToNotRead);
|
||||
_bitOffset += numBits;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int BitSource::readBits(int numBits)
|
||||
{
|
||||
return ReadBitsImpl(numBits, _bytes, available(), _byteOffset, _bitOffset);
|
||||
}
|
||||
|
||||
int BitSource::peekBits(int numBits) const
|
||||
{
|
||||
int bitOffset = _bitOffset;
|
||||
int byteOffset = _byteOffset;
|
||||
return ReadBitsImpl(numBits, _bytes, available(), byteOffset, bitOffset);
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class ByteArray;
|
||||
|
||||
/**
|
||||
* <p>This provides an easy abstraction to read bits at a time from a sequence of bytes, where the
|
||||
* number of bits read is not often a multiple of 8.</p>
|
||||
*
|
||||
* <p>This class is thread-safe but not reentrant -- unless the caller modifies the bytes array
|
||||
* it passed in, in which case all bets are off.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
class BitSource
|
||||
{
|
||||
const ByteArray& _bytes;
|
||||
int _byteOffset = 0;
|
||||
int _bitOffset = 0;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param bytes bytes from which this will read bits. Bits will be read from the first byte first.
|
||||
* Bits are read within a byte from most-significant to least-significant bit.
|
||||
* IMPORTANT: Bit source DOES NOT copy data byte, thus make sure that the bytes outlive the bit source object.
|
||||
*/
|
||||
explicit BitSource(const ByteArray& bytes) : _bytes(bytes) {}
|
||||
|
||||
BitSource(BitSource &) = delete;
|
||||
BitSource& operator=(const BitSource &) = delete;
|
||||
|
||||
/**
|
||||
* @return index of next bit in current byte which would be read by the next call to {@link #readBits(int)}.
|
||||
*/
|
||||
int bitOffset() const {
|
||||
return _bitOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return index of next byte in input byte array which would be read by the next call to {@link #readBits(int)}.
|
||||
*/
|
||||
int byteOffset() const {
|
||||
return _byteOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numBits number of bits to read
|
||||
* @return int representing the bits read. The bits will appear as the least-significant bits of the int
|
||||
*/
|
||||
int readBits(int numBits);
|
||||
|
||||
/**
|
||||
* @param numBits number of bits to peek at
|
||||
* @return int representing the bits peeked at. The bits will appear as the least-significant
|
||||
* bits of the int
|
||||
*/
|
||||
int peekBits(int numBits) const;
|
||||
|
||||
/**
|
||||
* @return number of bits that can be read successfully
|
||||
*/
|
||||
int available() const;
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Range.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
ByteArray is an extension of std::vector<uint8_t>.
|
||||
*/
|
||||
class ByteArray : public std::vector<uint8_t>
|
||||
{
|
||||
public:
|
||||
ByteArray() = default;
|
||||
ByteArray(std::initializer_list<uint8_t> list) : std::vector<uint8_t>(list) {}
|
||||
explicit ByteArray(int len) : std::vector<uint8_t>(len, 0) {}
|
||||
explicit ByteArray(std::string_view str) : std::vector<uint8_t>(str.begin(), str.end()) {}
|
||||
|
||||
void append(ByteView other) { insert(end(), other.begin(), other.end()); }
|
||||
void append(std::string_view other) { insert(end(), other.begin(), other.end()); }
|
||||
|
||||
std::string_view asString(size_t pos = 0, size_t len = std::string_view::npos) const
|
||||
{
|
||||
return std::string_view(reinterpret_cast<const char*>(data()), size()).substr(pos, len);
|
||||
}
|
||||
|
||||
ByteView asView(size_t pos = 0, size_t len = size_t(-1)) const
|
||||
{
|
||||
return ByteView(*this).subview(pos, len);
|
||||
}
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "CharacterSet.h"
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
struct CharacterSetName
|
||||
{
|
||||
std::string_view name;
|
||||
CharacterSet cs;
|
||||
};
|
||||
|
||||
static CharacterSetName NAME_TO_CHARSET[] = {
|
||||
{"Cp437", CharacterSet::Cp437},
|
||||
{"ISO-8859-1", CharacterSet::ISO8859_1},
|
||||
{"ISO-8859-2", CharacterSet::ISO8859_2},
|
||||
{"ISO-8859-3", CharacterSet::ISO8859_3},
|
||||
{"ISO-8859-4", CharacterSet::ISO8859_4},
|
||||
{"ISO-8859-5", CharacterSet::ISO8859_5},
|
||||
{"ISO-8859-6", CharacterSet::ISO8859_6},
|
||||
{"ISO-8859-7", CharacterSet::ISO8859_7},
|
||||
{"ISO-8859-8", CharacterSet::ISO8859_8},
|
||||
{"ISO-8859-9", CharacterSet::ISO8859_9},
|
||||
{"ISO-8859-10", CharacterSet::ISO8859_10},
|
||||
{"ISO-8859-11", CharacterSet::ISO8859_11},
|
||||
{"ISO-8859-13", CharacterSet::ISO8859_13},
|
||||
{"ISO-8859-14", CharacterSet::ISO8859_14},
|
||||
{"ISO-8859-15", CharacterSet::ISO8859_15},
|
||||
{"ISO-8859-16", CharacterSet::ISO8859_16},
|
||||
{"SJIS", CharacterSet::Shift_JIS},
|
||||
{"Shift_JIS", CharacterSet::Shift_JIS},
|
||||
{"Cp1250", CharacterSet::Cp1250},
|
||||
{"windows-1250",CharacterSet::Cp1250},
|
||||
{"Cp1251", CharacterSet::Cp1251},
|
||||
{"windows-1251",CharacterSet::Cp1251},
|
||||
{"Cp1252", CharacterSet::Cp1252},
|
||||
{"windows-1252",CharacterSet::Cp1252},
|
||||
{"Cp1256", CharacterSet::Cp1256},
|
||||
{"windows-1256",CharacterSet::Cp1256},
|
||||
{"UTF-16BE", CharacterSet::UTF16BE},
|
||||
{"UTF-16LE", CharacterSet::UTF16LE},
|
||||
{"UTF-32BE", CharacterSet::UTF32BE},
|
||||
{"UTF-32LE", CharacterSet::UTF32LE},
|
||||
{"UnicodeBigUnmarked", CharacterSet::UTF16BE},
|
||||
{"UnicodeBig", CharacterSet::UTF16BE},
|
||||
{"UTF-8", CharacterSet::UTF8},
|
||||
{"ASCII", CharacterSet::ASCII},
|
||||
{"US-ASCII", CharacterSet::ASCII},
|
||||
{"Big5", CharacterSet::Big5},
|
||||
{"GB2312", CharacterSet::GB2312},
|
||||
{"GB18030", CharacterSet::GB18030},
|
||||
{"EUC-CN", CharacterSet::GB18030},
|
||||
{"GBK", CharacterSet::GB18030},
|
||||
{"EUC-KR", CharacterSet::EUC_KR},
|
||||
{"BINARY", CharacterSet::BINARY},
|
||||
};
|
||||
|
||||
CharacterSet CharacterSetFromString(std::string_view name)
|
||||
{
|
||||
auto i = FindIf(NAME_TO_CHARSET, [name](auto& v) { return IsEqualIgnoreCaseAnd(v.name, name, "_- "); });
|
||||
return i == std::end(NAME_TO_CHARSET) ? CharacterSet::Unknown : i->cs;
|
||||
}
|
||||
|
||||
std::string ToString(CharacterSet cs)
|
||||
{
|
||||
auto i = FindIf(NAME_TO_CHARSET, [cs](auto& v) { return v.cs == cs; });
|
||||
return i == std::end(NAME_TO_CHARSET) ? "" : std::string(i->name);
|
||||
}
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
enum class CharacterSet : unsigned char
|
||||
{
|
||||
Unknown,
|
||||
ASCII,
|
||||
ISO8859_1,
|
||||
ISO8859_2,
|
||||
ISO8859_3,
|
||||
ISO8859_4,
|
||||
ISO8859_5,
|
||||
ISO8859_6,
|
||||
ISO8859_7,
|
||||
ISO8859_8,
|
||||
ISO8859_9,
|
||||
ISO8859_10,
|
||||
ISO8859_11,
|
||||
ISO8859_13,
|
||||
ISO8859_14,
|
||||
ISO8859_15,
|
||||
ISO8859_16,
|
||||
Cp437,
|
||||
Cp1250,
|
||||
Cp1251,
|
||||
Cp1252,
|
||||
Cp1256,
|
||||
|
||||
Shift_JIS,
|
||||
Big5,
|
||||
GB2312,
|
||||
GB18030,
|
||||
EUC_JP,
|
||||
EUC_KR,
|
||||
UTF16BE,
|
||||
UTF8,
|
||||
UTF16LE,
|
||||
UTF32BE,
|
||||
UTF32LE,
|
||||
|
||||
BINARY,
|
||||
|
||||
CharsetCount
|
||||
};
|
||||
|
||||
CharacterSet CharacterSetFromString(std::string_view name);
|
||||
std::string ToString(CharacterSet cs);
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ConcentricFinder.h"
|
||||
|
||||
#include "LogMatrix.h"
|
||||
#include "RegressionLine.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::optional<PointF> AverageEdgePixels(BitMatrixCursorI cur, int range, int numOfEdges)
|
||||
{
|
||||
PointF sum = {};
|
||||
for (int i = 0; i < numOfEdges; ++i) {
|
||||
if (!cur.isIn())
|
||||
return {};
|
||||
cur.stepToEdge(1, range);
|
||||
sum += centered(cur.p) + centered(cur.p + cur.back());
|
||||
log(cur.p + cur.back(), 2);
|
||||
}
|
||||
return sum / (2 * numOfEdges);
|
||||
}
|
||||
|
||||
std::optional<PointF> CenterOfDoubleCross(const BitMatrix& image, PointI center, int range, int numOfEdges)
|
||||
{
|
||||
PointF sum = {};
|
||||
for (auto d : {PointI{0, 1}, {1, 0}, {1, 1}, {1, -1}}) {
|
||||
auto avr1 = AverageEdgePixels({image, center, d}, range, numOfEdges);
|
||||
auto avr2 = AverageEdgePixels({image, center, -d}, range, numOfEdges);
|
||||
if (!avr1 || !avr2)
|
||||
return {};
|
||||
sum += *avr1 + *avr2;
|
||||
}
|
||||
return sum / 8;
|
||||
}
|
||||
|
||||
std::optional<PointF> CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle)
|
||||
{
|
||||
#if 0
|
||||
if (requireCircle) {
|
||||
// alternative implementation with the aim of discarding closed loops that are not all circle like (M > 5*m)
|
||||
auto points = CollectRingPoints(image, center, range, std::abs(nth), nth < 0);
|
||||
if (points.empty())
|
||||
return {};
|
||||
auto res = Reduce(points, PointF{}, std::plus{}) / Size(points);
|
||||
|
||||
double m = range, M = 0;
|
||||
for (auto p : points)
|
||||
UpdateMinMax(m, M, maxAbsComponent(p - res));
|
||||
|
||||
if (M > 5 * m)
|
||||
return {};
|
||||
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
// range is the approximate width/height of the nth ring, if nth>1 then it would be plausible to limit the search radius
|
||||
// to approximately range / 2 * sqrt(2) == range * 0.75 but it turned out to be too limiting with realworld/noisy data.
|
||||
int radius = range;
|
||||
bool inner = nth < 0;
|
||||
nth = std::abs(nth);
|
||||
log(center, 3);
|
||||
BitMatrixCursorI cur(image, center, {1, 0});
|
||||
if (!cur.stepToEdge(nth, radius, inner))
|
||||
return {};
|
||||
cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup
|
||||
const auto edgeDir = inner ? Direction::LEFT : Direction::RIGHT;
|
||||
|
||||
uint32_t neighbourMask = 0;
|
||||
auto start = cur.p;
|
||||
PointF sum = {};
|
||||
int n = 0;
|
||||
do {
|
||||
log(cur.p, 4);
|
||||
sum += centered(cur.p);
|
||||
++n;
|
||||
|
||||
// find out if we come full circle around the center. 8 bits have to be set in the end.
|
||||
neighbourMask |= (1 << (4 + dot(bresenhamDirection(cur.p - center), PointI(1, 3))));
|
||||
|
||||
if (!cur.stepAlongEdge(edgeDir))
|
||||
return {};
|
||||
|
||||
// use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate
|
||||
if (maxAbsComponent(cur.p - center) > radius || center == cur.p || n > 4 * 2 * range)
|
||||
return {};
|
||||
} while (cur.p != start);
|
||||
|
||||
if (requireCircle && neighbourMask != 0b111101111)
|
||||
return {};
|
||||
|
||||
return sum / n;
|
||||
}
|
||||
|
||||
std::optional<PointF> CenterOfRings(const BitMatrix& image, PointF center, int range, int numOfRings)
|
||||
{
|
||||
int n = 1;
|
||||
PointF sum = center;
|
||||
for (int i = 2; i < numOfRings + 1; ++i) {
|
||||
auto c = CenterOfRing(image, PointI(center), range, i);
|
||||
if (!c) {
|
||||
if (n == 1)
|
||||
return {};
|
||||
else
|
||||
return sum / n;
|
||||
} else if (distance(*c, center) > range / numOfRings / 2) {
|
||||
return {};
|
||||
}
|
||||
|
||||
sum += *c;
|
||||
n++;
|
||||
}
|
||||
return sum / n;
|
||||
}
|
||||
|
||||
static std::vector<PointF> CollectRingPoints(const BitMatrix& image, PointF center, int range, int edgeIndex, bool backup)
|
||||
{
|
||||
PointI centerI(center);
|
||||
int radius = range;
|
||||
BitMatrixCursorI cur(image, centerI, {1, 0});
|
||||
if (!cur.stepToEdge(edgeIndex, radius, backup))
|
||||
return {};
|
||||
cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup
|
||||
const auto edgeDir = backup ? Direction::LEFT : Direction::RIGHT;
|
||||
|
||||
uint32_t neighbourMask = 0;
|
||||
auto start = cur.p;
|
||||
std::vector<PointF> points;
|
||||
points.reserve(4 * range);
|
||||
|
||||
do {
|
||||
log(cur.p, 4);
|
||||
points.push_back(centered(cur.p));
|
||||
|
||||
// find out if we come full circle around the center. 8 bits have to be set in the end.
|
||||
neighbourMask |= (1 << (4 + dot(bresenhamDirection(cur.p - centerI), PointI(1, 3))));
|
||||
|
||||
if (!cur.stepAlongEdge(edgeDir))
|
||||
return {};
|
||||
|
||||
// use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate
|
||||
if (maxAbsComponent(cur.p - centerI) > radius || centerI == cur.p || Size(points) > 4 * 2 * range)
|
||||
return {};
|
||||
|
||||
} while (cur.p != start);
|
||||
|
||||
if (neighbourMask != 0b111101111)
|
||||
return {};
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
static std::optional<QuadrilateralF> FitQadrilateralToPoints(PointF center, std::vector<PointF>& points)
|
||||
{
|
||||
auto dist2Center = [c = center](auto a, auto b) { return distance(a, c) < distance(b, c); };
|
||||
auto [minDistElem, maxDistElem] = std::minmax_element(points.begin(), points.end(), dist2Center);
|
||||
|
||||
// check if points are on a circle: for a square the min/max ratio is 0.7, for a circle it is 1
|
||||
if (distance(center, *minDistElem) / distance(center, *maxDistElem) > 0.85)
|
||||
return {};
|
||||
|
||||
// rotate points such that the first one is the furthest away from the center (hence, a corner)
|
||||
std::rotate(points.begin(), maxDistElem, points.end());
|
||||
|
||||
std::array<const PointF*, 4> corners;
|
||||
corners[0] = &points[0];
|
||||
// find the opposite corner by looking for the farthest point near the opposite point
|
||||
corners[2] = std::max_element(&points[Size(points) * 3 / 8], &points[Size(points) * 5 / 8], dist2Center);
|
||||
|
||||
// find the two in between corners by looking for the points farthest from the long diagonal
|
||||
auto dist2Diagonal = [l = RegressionLine(*corners[0], *corners[2])](auto a, auto b) { return l.distance(a) < l.distance(b); };
|
||||
corners[1] = std::max_element(&points[Size(points) * 1 / 8], &points[Size(points) * 3 / 8], dist2Diagonal);
|
||||
corners[3] = std::max_element(&points[Size(points) * 5 / 8], &points[Size(points) * 7 / 8], dist2Diagonal);
|
||||
|
||||
std::array lines{RegressionLine{corners[0] + 1, corners[1]}, RegressionLine{corners[1] + 1, corners[2]},
|
||||
RegressionLine{corners[2] + 1, corners[3]}, RegressionLine{corners[3] + 1, &points.back() + 1}};
|
||||
|
||||
if (std::any_of(lines.begin(), lines.end(), [](const auto& line) { return !line.isValid(); }))
|
||||
return {};
|
||||
|
||||
std::array<const PointF*, 4> beg = {corners[0] + 1, corners[1] + 1, corners[2] + 1, corners[3] + 1};
|
||||
std::array<const PointF*, 4> end = {corners[1], corners[2], corners[3], &points.back() + 1};
|
||||
|
||||
// check if all points belonging to each line segment are sufficiently close to that line
|
||||
for (int i = 0; i < 4; ++i)
|
||||
for (const PointF* p = beg[i]; p != end[i]; ++p) {
|
||||
auto len = std::distance(beg[i], end[i]);
|
||||
if (len > 3 && lines[i].distance(*p) > std::max(1., std::min(8., len / 8.))) {
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("%d: %.2f > %.2f @ %.fx%.f\n", i, lines[i].distance(*p), std::distance(beg[i], end[i]) / 1., p->x, p->y);
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QuadrilateralF res;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
res[i] = intersect(lines[i], lines[(i + 1) % 4]);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool QuadrilateralIsPlausibleSquare(const QuadrilateralF q, int lineIndex)
|
||||
{
|
||||
double m, M;
|
||||
m = M = distance(q[0], q[3]);
|
||||
for (int i = 1; i < 4; ++i)
|
||||
UpdateMinMax(m, M, distance(q[i - 1], q[i]));
|
||||
|
||||
return m >= lineIndex * 2 && m > M / 3;
|
||||
}
|
||||
|
||||
std::optional<QuadrilateralF> FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup)
|
||||
{
|
||||
auto points = CollectRingPoints(image, center, range, lineIndex, backup);
|
||||
if (points.empty())
|
||||
return {};
|
||||
|
||||
auto res = FitQadrilateralToPoints(center, points);
|
||||
if (!res || !QuadrilateralIsPlausibleSquare(*res, lineIndex - backup))
|
||||
return {};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<QuadrilateralF> FindConcentricPatternCorners(const BitMatrix& image, PointF center, int range, int ringIndex)
|
||||
{
|
||||
auto innerCorners = FitSquareToPoints(image, center, range, ringIndex, false);
|
||||
if (!innerCorners)
|
||||
return {};
|
||||
|
||||
auto outerCorners = FitSquareToPoints(image, center, range, ringIndex + 1, true);
|
||||
if (!outerCorners)
|
||||
return {};
|
||||
|
||||
auto res = Blend(*innerCorners, *outerCorners);
|
||||
|
||||
for (auto p : *innerCorners)
|
||||
log(p, 3);
|
||||
|
||||
for (auto p : *outerCorners)
|
||||
log(p, 3);
|
||||
|
||||
for (auto p : res)
|
||||
log(p, 3);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<PointF> FinetuneConcentricPatternCenter(const BitMatrix& image, PointF center, int range, int finderPatternSize)
|
||||
{
|
||||
// make sure we have at least one path of white around the center
|
||||
if (auto res1 = CenterOfRing(image, PointI(center), range, 1); res1 && image.get(*res1)) {
|
||||
// and then either at least one more ring around that
|
||||
if (auto res2 = CenterOfRings(image, *res1, range, finderPatternSize / 2); res2 && image.get(*res2))
|
||||
return res2;
|
||||
// or the center can be approximated by a square
|
||||
if (FitSquareToPoints(image, *res1, range, 1, false))
|
||||
return res1;
|
||||
// TODO: this is currently only keeping #258 alive, evaluate if still worth it
|
||||
if (auto res2 = CenterOfDoubleCross(image, PointI(*res1), range, finderPatternSize / 2 + 1); res2 && image.get(*res2))
|
||||
return res2;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BitMatrixCursor.h"
|
||||
#include "Pattern.h"
|
||||
#include "Quadrilateral.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
template <typename T, size_t N>
|
||||
static float CenterFromEnd(const std::array<T, N>& pattern, float end)
|
||||
{
|
||||
if (N == 5) {
|
||||
float a = pattern[4] + pattern[3] + pattern[2] / 2.f;
|
||||
float b = pattern[4] + (pattern[3] + pattern[2] + pattern[1]) / 2.f;
|
||||
float c = (pattern[4] + pattern[3] + pattern[2] + pattern[1] + pattern[0]) / 2.f;
|
||||
return end - (2 * a + b + c) / 4;
|
||||
} else if (N == 3) {
|
||||
float a = pattern[2] + pattern[1] / 2.f;
|
||||
float b = (pattern[2] + pattern[1] + pattern[0]) / 2.f;
|
||||
return end - (2 * a + b) / 3;
|
||||
} else { // aztec
|
||||
auto a = Reduce(pattern.begin() + (N/2 + 1), pattern.end(), pattern[N/2] / 2.f);
|
||||
return end - a;
|
||||
}
|
||||
}
|
||||
|
||||
template<int N, typename Cursor>
|
||||
std::optional<Pattern<N>> ReadSymmetricPattern(Cursor& cur, int range)
|
||||
{
|
||||
static_assert(N % 2 == 1);
|
||||
assert(range > 0);
|
||||
Pattern<N> res = {};
|
||||
auto constexpr s_2 = Size(res)/2;
|
||||
auto cuo = cur.turnedBack();
|
||||
|
||||
auto next = [&](auto& cur, int i) {
|
||||
auto v = cur.stepToEdge(1, range);
|
||||
res[s_2 + i] += v;
|
||||
if (range)
|
||||
range -= v;
|
||||
return v;
|
||||
};
|
||||
|
||||
for (int i = 0; i <= s_2; ++i) {
|
||||
if (!next(cur, i) || !next(cuo, -i))
|
||||
return {};
|
||||
}
|
||||
res[s_2]--; // the starting pixel has been counted twice, fix this
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<bool RELAXED_THRESHOLD = false, typename PATTERN>
|
||||
int CheckSymmetricPattern(BitMatrixCursorI& cur, PATTERN pattern, int range, bool updatePosition)
|
||||
{
|
||||
FastEdgeToEdgeCounter curFwd(cur), curBwd(cur.turnedBack());
|
||||
|
||||
int centerFwd = curFwd.stepToNextEdge(range);
|
||||
if (!centerFwd)
|
||||
return 0;
|
||||
int centerBwd = curBwd.stepToNextEdge(range);
|
||||
if (!centerBwd)
|
||||
return 0;
|
||||
|
||||
assert(range > 0);
|
||||
Pattern<pattern.size()> res = {};
|
||||
auto constexpr s_2 = Size(res)/2;
|
||||
res[s_2] = centerFwd + centerBwd - 1; // -1 because the starting pixel is counted twice
|
||||
range -= res[s_2];
|
||||
|
||||
auto next = [&](auto& cur, int i) {
|
||||
auto v = cur.stepToNextEdge(range);
|
||||
res[s_2 + i] = v;
|
||||
range -= v;
|
||||
return v;
|
||||
};
|
||||
|
||||
for (int i = 1; i <= s_2; ++i) {
|
||||
if (!next(curFwd, i) || !next(curBwd, -i))
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!IsPattern<RELAXED_THRESHOLD>(res, pattern))
|
||||
return 0;
|
||||
|
||||
if (updatePosition)
|
||||
cur.step(res[s_2] / 2 - (centerBwd - 1));
|
||||
|
||||
return Reduce(res);
|
||||
}
|
||||
|
||||
std::optional<PointF> CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle = true);
|
||||
|
||||
std::optional<PointF> FinetuneConcentricPatternCenter(const BitMatrix& image, PointF center, int range, int finderPatternSize);
|
||||
|
||||
std::optional<QuadrilateralF> FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup);
|
||||
|
||||
std::optional<QuadrilateralF> FindConcentricPatternCorners(const BitMatrix& image, PointF center, int range, int ringIndex);
|
||||
|
||||
struct ConcentricPattern : public PointF
|
||||
{
|
||||
int size = 0;
|
||||
};
|
||||
|
||||
template <bool E2E = false, typename PATTERN>
|
||||
std::optional<ConcentricPattern> LocateConcentricPattern(const BitMatrix& image, PATTERN pattern, PointF center, int range)
|
||||
{
|
||||
auto cur = BitMatrixCursor(image, PointI(center), {});
|
||||
int minSpread = image.width(), maxSpread = 0;
|
||||
// TODO: setting maxError to 1 can subtantially help with detecting symbols with low print quality resulting in damaged
|
||||
// finder patterns, but it sutantially increases the runtime (approx. 20% slower for the falsepositive images).
|
||||
int maxError = 0;
|
||||
for (auto d : {PointI{0, 1}, {1, 0}}) {
|
||||
int spread = CheckSymmetricPattern<E2E>(cur.setDirection(d), pattern, range, true);
|
||||
if (spread)
|
||||
UpdateMinMax(minSpread, maxSpread, spread);
|
||||
else if (--maxError < 0)
|
||||
return {};
|
||||
}
|
||||
|
||||
#if 1
|
||||
for (auto d : {PointI{1, 1}, {1, -1}}) {
|
||||
int spread = CheckSymmetricPattern<true>(cur.setDirection(d), pattern, range * 2, false);
|
||||
if (spread)
|
||||
UpdateMinMax(minSpread, maxSpread, spread);
|
||||
else if (--maxError < 0)
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
|
||||
if (maxSpread > 5 * minSpread)
|
||||
return {};
|
||||
|
||||
auto newCenter = FinetuneConcentricPatternCenter(image, PointF(cur.p), range, pattern.size());
|
||||
if (!newCenter)
|
||||
return {};
|
||||
|
||||
return ConcentricPattern{*newCenter, (maxSpread + minSpread) / 2};
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
|
||||
@@ -0,0 +1,471 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "Content.h"
|
||||
|
||||
#include "ByteArray.h"
|
||||
#include "CharacterSet.h"
|
||||
#include "ECI.h"
|
||||
#include "HRI.h"
|
||||
#include "TextDecoder.h"
|
||||
#include "Utf.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::string ToString(ContentType type)
|
||||
{
|
||||
const char* t2s[] = {"Text", "Binary", "Mixed", "GS1", "ISO15434", "UnknownECI"};
|
||||
int idx = static_cast<int>(type);
|
||||
if (idx < 0 || idx >= Size(t2s))
|
||||
return "InvalidContentType";
|
||||
return t2s[idx];
|
||||
}
|
||||
|
||||
template <typename FUNC>
|
||||
void Content::ForEachECIBlock(FUNC func) const
|
||||
{
|
||||
ECI defaultECI = hasECI ? ECI::ISO8859_1 : ECI::Unknown;
|
||||
if (encodings.empty())
|
||||
func(defaultECI, 0, Size(bytes));
|
||||
else if (encodings.front().pos != 0)
|
||||
func(defaultECI, 0, encodings.front().pos);
|
||||
|
||||
for (int i = 0; i < Size(encodings); ++i) {
|
||||
auto [eci, start] = encodings[i];
|
||||
int end = i + 1 == Size(encodings) ? Size(bytes) : encodings[i + 1].pos;
|
||||
|
||||
if (start != end)
|
||||
func(eci, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
void Content::switchEncoding(ECI eci, bool isECI)
|
||||
{
|
||||
// remove all non-ECI entries on first ECI entry
|
||||
if (isECI && !hasECI)
|
||||
encodings.clear();
|
||||
if (isECI || !hasECI)
|
||||
encodings.push_back({eci, Size(bytes)});
|
||||
|
||||
hasECI |= isECI;
|
||||
}
|
||||
|
||||
Content::Content(ByteArray&& bytes, SymbologyIdentifier si, CharacterSet defaultCharset)
|
||||
: bytes(std::move(bytes)), symbology(si), defaultCharset(defaultCharset) {}
|
||||
|
||||
void Content::switchEncoding(CharacterSet cs)
|
||||
{
|
||||
switchEncoding(ToECI(cs), false);
|
||||
}
|
||||
|
||||
void Content::append(const Content& other)
|
||||
{
|
||||
if (!hasECI && other.hasECI)
|
||||
encodings.clear();
|
||||
if (other.hasECI || !hasECI)
|
||||
for (auto& e : other.encodings)
|
||||
encodings.push_back({e.eci, Size(bytes) + e.pos});
|
||||
append(other.bytes);
|
||||
|
||||
hasECI |= other.hasECI;
|
||||
}
|
||||
|
||||
void Content::erase(int pos, int n)
|
||||
{
|
||||
bytes.erase(bytes.begin() + pos, bytes.begin() + pos + n);
|
||||
for (auto& e : encodings)
|
||||
if (e.pos > pos)
|
||||
e.pos -= n;
|
||||
}
|
||||
|
||||
void Content::insert(int pos, std::string_view str)
|
||||
{
|
||||
bytes.insert(bytes.begin() + pos, str.begin(), str.end());
|
||||
for (auto& e : encodings)
|
||||
if (e.pos > pos)
|
||||
e.pos += Size(str);
|
||||
}
|
||||
|
||||
bool Content::canProcess() const
|
||||
{
|
||||
return std::all_of(encodings.begin(), encodings.end(), [](Encoding e) { return CanProcess(e.eci); });
|
||||
}
|
||||
|
||||
std::string Content::render(bool withECI) const
|
||||
{
|
||||
if (empty() || !canProcess())
|
||||
return {};
|
||||
|
||||
#ifdef ZXING_READERS
|
||||
std::string res;
|
||||
res.reserve(bytes.size() * 2);
|
||||
if (withECI)
|
||||
res += symbology.toString(true);
|
||||
ECI lastECI = ECI::Unknown;
|
||||
auto fallbackCS = defaultCharset;
|
||||
if (!hasECI && fallbackCS == CharacterSet::Unknown)
|
||||
fallbackCS = guessEncoding();
|
||||
|
||||
ForEachECIBlock([&](ECI eci, int begin, int end) {
|
||||
// basic idea: if IsText(eci), we transcode it to UTF8, otherwise we treat it as binary but
|
||||
// transcoded it to valid UTF8 bytes sequences representing the code points 0-255. The eci we report
|
||||
// back to the caller by inserting their "\XXXXXX" ECI designator is UTF8 for text and
|
||||
// the original ECI for everything else.
|
||||
// first determine how to decode the content (use fallback if unknown)
|
||||
auto inEci = IsText(eci) ? eci : eci == ECI::Unknown ? ToECI(fallbackCS) : ECI::Binary;
|
||||
if (withECI) {
|
||||
// then find the eci to report back in the ECI designator
|
||||
auto outEci = IsText(inEci) ? ECI::UTF8 : eci;
|
||||
|
||||
if (lastECI != outEci)
|
||||
res += ToString(outEci);
|
||||
lastECI = outEci;
|
||||
|
||||
for (auto c : BytesToUtf8(bytes.asView(begin, end - begin), inEci)) {
|
||||
res += c;
|
||||
if (c == '\\') // in the ECI protocol a '\' (0x5c) has to be doubled, works only because 0x5c can only mean `\`
|
||||
res += c;
|
||||
}
|
||||
} else {
|
||||
res += BytesToUtf8(bytes.asView(begin, end - begin), inEci);
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
#elif defined(ZXING_USE_ZINT)
|
||||
assert(!utf8Cache.empty());
|
||||
if (!withECI)
|
||||
return std::accumulate(utf8Cache.begin(), utf8Cache.end(), std::string());
|
||||
|
||||
std::string res;
|
||||
res.reserve(3 + TransformReduce(utf8Cache, 0, std::size<std::string>) * 2 + encodings.size() * 7);
|
||||
res += symbology.toString(true);
|
||||
|
||||
ECI lastECI = ECI::Unknown;
|
||||
auto fallbackCS = defaultCharset;
|
||||
if (!hasECI && fallbackCS == CharacterSet::Unknown)
|
||||
fallbackCS = guessEncoding();
|
||||
|
||||
assert(utf8Cache.size() == encodings.size());
|
||||
for (int i = 0; i < Size(encodings); ++i) {
|
||||
const auto eci = encodings[i].eci;
|
||||
const auto inEci = IsText(eci) ? eci : eci == ECI::Unknown ? ToECI(fallbackCS) : ECI::Binary;
|
||||
const auto outEci = IsText(inEci) ? ECI::UTF8 : eci;
|
||||
|
||||
if (lastECI != outEci)
|
||||
res += ToString(outEci);
|
||||
lastECI = outEci;
|
||||
|
||||
for (auto c : utf8Cache[i]) {
|
||||
res += c;
|
||||
if (c == '\\') // in the ECI protocol a '\' (0x5c) has to be doubled, works only because 0x5c can only mean `\`
|
||||
res += c;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
#else
|
||||
(void)withECI;
|
||||
return std::string(bytes.asString());
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Content::text(TextMode mode) const
|
||||
{
|
||||
switch (mode) {
|
||||
case TextMode::Plain: return render(false);
|
||||
case TextMode::ECI: return render(true);
|
||||
case TextMode::HRI:
|
||||
switch (type()) {
|
||||
#if defined(ZXING_READERS) || defined(ZXING_USE_ZINT)
|
||||
case ContentType::GS1: {
|
||||
auto plain = render(false);
|
||||
auto hri = HRIFromGS1(plain);
|
||||
return hri.empty() ? EscapeNonGraphical(plain) : hri;
|
||||
}
|
||||
case ContentType::ISO15434: return HRIFromISO15434(render(false));
|
||||
case ContentType::Text: return render(false);
|
||||
#endif
|
||||
default: return text(TextMode::Escaped);
|
||||
}
|
||||
case TextMode::Escaped: return EscapeNonGraphical(render(false));
|
||||
case TextMode::Hex: return ToHex(bytes);
|
||||
case TextMode::HexECI: return ToHex(bytesECI());
|
||||
}
|
||||
|
||||
return {}; // silence compiler warning
|
||||
}
|
||||
|
||||
std::wstring Content::utfW() const
|
||||
{
|
||||
return FromUtf8(render(false));
|
||||
}
|
||||
|
||||
ByteArray Content::bytesECI() const
|
||||
{
|
||||
if (empty())
|
||||
return {};
|
||||
|
||||
ByteArray res;
|
||||
res.reserve(3 + bytes.size() + hasECI * encodings.size() * 7);
|
||||
|
||||
// report ECI protocol only if actually found ECI data in the barode bit stream
|
||||
// see also https://github.com/zxing-cpp/zxing-cpp/issues/936
|
||||
res.append(symbology.toString(hasECI));
|
||||
|
||||
if (hasECI)
|
||||
ForEachECIBlock([&](ECI eci, int begin, int end) {
|
||||
if (hasECI)
|
||||
res.append(ToString(eci));
|
||||
|
||||
for (auto b : bytes.asView(begin, end - begin)) {
|
||||
res.push_back(b);
|
||||
if (b == '\\') // in the ECI protocol a '\' has to be doubled
|
||||
res.push_back(b);
|
||||
}
|
||||
});
|
||||
else
|
||||
res.append(bytes);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#if defined(ZXING_READERS) || defined(ZXING_USE_ZINT)
|
||||
/**
|
||||
* @param bytes bytes encoding a string, whose encoding should be guessed
|
||||
* @return name of guessed encoding; at the moment will only guess one of:
|
||||
* {@link #SHIFT_JIS}, {@link #UTF8}, {@link #ISO88591}, or the platform
|
||||
* default encoding if none of these can possibly be correct
|
||||
*/
|
||||
CharacterSet GuessTextEncoding(ByteView bytes, CharacterSet fallback = CharacterSet::ISO8859_1)
|
||||
{
|
||||
// For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS,
|
||||
// which should be by far the most common encodings.
|
||||
bool canBeISO88591 = true;
|
||||
bool canBeShiftJIS = true;
|
||||
bool canBeUTF8 = IsValidUtf8(bytes);
|
||||
int utf8BytesLeft = 0;
|
||||
//int utf8LowChars = 0;
|
||||
int utf2BytesChars = 0;
|
||||
int utf3BytesChars = 0;
|
||||
int utf4BytesChars = 0;
|
||||
int sjisBytesLeft = 0;
|
||||
//int sjisLowChars = 0;
|
||||
int sjisKatakanaChars = 0;
|
||||
//int sjisDoubleBytesChars = 0;
|
||||
int sjisCurKatakanaWordLength = 0;
|
||||
int sjisCurDoubleBytesWordLength = 0;
|
||||
int sjisMaxKatakanaWordLength = 0;
|
||||
int sjisMaxDoubleBytesWordLength = 0;
|
||||
//int isoLowChars = 0;
|
||||
//int isoHighChars = 0;
|
||||
int isoHighOther = 0;
|
||||
|
||||
bool utf8bom = bytes.size() > 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF;
|
||||
|
||||
for (int value : bytes)
|
||||
{
|
||||
if(!(canBeISO88591 || canBeShiftJIS || canBeUTF8))
|
||||
break;
|
||||
|
||||
// UTF-8 stuff
|
||||
if (canBeUTF8) {
|
||||
if (utf8BytesLeft > 0) {
|
||||
if ((value & 0x80) == 0) {
|
||||
canBeUTF8 = false;
|
||||
}
|
||||
else {
|
||||
utf8BytesLeft--;
|
||||
}
|
||||
}
|
||||
else if ((value & 0x80) != 0) {
|
||||
if ((value & 0x40) == 0) {
|
||||
canBeUTF8 = false;
|
||||
}
|
||||
else {
|
||||
utf8BytesLeft++;
|
||||
if ((value & 0x20) == 0) {
|
||||
utf2BytesChars++;
|
||||
}
|
||||
else {
|
||||
utf8BytesLeft++;
|
||||
if ((value & 0x10) == 0) {
|
||||
utf3BytesChars++;
|
||||
}
|
||||
else {
|
||||
utf8BytesLeft++;
|
||||
if ((value & 0x08) == 0) {
|
||||
utf4BytesChars++;
|
||||
}
|
||||
else {
|
||||
canBeUTF8 = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} //else {
|
||||
//utf8LowChars++;
|
||||
//}
|
||||
}
|
||||
|
||||
// ISO-8859-1 stuff
|
||||
if (canBeISO88591) {
|
||||
if (value > 0x7F && value < 0xA0) {
|
||||
canBeISO88591 = false;
|
||||
}
|
||||
else if (value > 0x9F) {
|
||||
if (value < 0xC0 || value == 0xD7 || value == 0xF7) {
|
||||
isoHighOther++;
|
||||
} //else {
|
||||
//isoHighChars++;
|
||||
//}
|
||||
} //else {
|
||||
//isoLowChars++;
|
||||
//}
|
||||
}
|
||||
|
||||
// Shift_JIS stuff
|
||||
if (canBeShiftJIS) {
|
||||
if (sjisBytesLeft > 0) {
|
||||
if (value < 0x40 || value == 0x7F || value > 0xFC) {
|
||||
canBeShiftJIS = false;
|
||||
}
|
||||
else {
|
||||
sjisBytesLeft--;
|
||||
}
|
||||
}
|
||||
else if (value == 0x80 || value == 0xA0 || value > 0xEF || (value < 0x20 && value != 0xa && value != 0xd)) {
|
||||
canBeShiftJIS = false; // use non-printable ASCII as indication for binary content
|
||||
}
|
||||
else if (value > 0xA0 && value < 0xE0) {
|
||||
sjisKatakanaChars++;
|
||||
sjisCurDoubleBytesWordLength = 0;
|
||||
sjisCurKatakanaWordLength++;
|
||||
if (sjisCurKatakanaWordLength > sjisMaxKatakanaWordLength) {
|
||||
sjisMaxKatakanaWordLength = sjisCurKatakanaWordLength;
|
||||
}
|
||||
}
|
||||
else if (value > 0x7F) {
|
||||
sjisBytesLeft++;
|
||||
//sjisDoubleBytesChars++;
|
||||
sjisCurKatakanaWordLength = 0;
|
||||
sjisCurDoubleBytesWordLength++;
|
||||
if (sjisCurDoubleBytesWordLength > sjisMaxDoubleBytesWordLength) {
|
||||
sjisMaxDoubleBytesWordLength = sjisCurDoubleBytesWordLength;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//sjisLowChars++;
|
||||
sjisCurKatakanaWordLength = 0;
|
||||
sjisCurDoubleBytesWordLength = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (canBeUTF8 && utf8BytesLeft > 0) {
|
||||
canBeUTF8 = false;
|
||||
}
|
||||
if (canBeShiftJIS && sjisBytesLeft > 0) {
|
||||
canBeShiftJIS = false;
|
||||
}
|
||||
|
||||
// Easy -- if there is BOM or at least 1 valid not-single byte character (and no evidence it can't be UTF-8), done
|
||||
if (canBeUTF8 && (utf8bom || utf2BytesChars + utf3BytesChars + utf4BytesChars > 0)) {
|
||||
return CharacterSet::UTF8;
|
||||
}
|
||||
|
||||
bool assumeShiftJIS = fallback == CharacterSet::Shift_JIS || fallback == CharacterSet::EUC_JP;
|
||||
// Easy -- if assuming Shift_JIS or at least 3 valid consecutive not-ascii characters (and no evidence it can't be), done
|
||||
if (canBeShiftJIS && (assumeShiftJIS || sjisMaxKatakanaWordLength >= 3 || sjisMaxDoubleBytesWordLength >= 3)) {
|
||||
return CharacterSet::Shift_JIS;
|
||||
}
|
||||
// Distinguishing Shift_JIS and ISO-8859-1 can be a little tough for short words. The crude heuristic is:
|
||||
// - If we saw
|
||||
// - only two consecutive katakana chars in the whole text, or
|
||||
// - at least 10% of bytes that could be "upper" not-alphanumeric Latin1,
|
||||
// - then we conclude Shift_JIS, else ISO-8859-1
|
||||
if (canBeISO88591 && canBeShiftJIS) {
|
||||
return (sjisMaxKatakanaWordLength == 2 && sjisKatakanaChars == 2) || isoHighOther * 10 >= Size(bytes)
|
||||
? CharacterSet::Shift_JIS : CharacterSet::ISO8859_1;
|
||||
}
|
||||
|
||||
// Otherwise, try in order ISO-8859-1, Shift JIS, UTF-8 and fall back to default platform encoding
|
||||
if (canBeISO88591) {
|
||||
return CharacterSet::ISO8859_1;
|
||||
}
|
||||
if (canBeShiftJIS) {
|
||||
return CharacterSet::Shift_JIS;
|
||||
}
|
||||
if (canBeUTF8) {
|
||||
return CharacterSet::UTF8;
|
||||
}
|
||||
// Otherwise, we take a wild guess with platform encoding
|
||||
return fallback;
|
||||
}
|
||||
#endif
|
||||
|
||||
CharacterSet Content::guessEncoding() const
|
||||
{
|
||||
#if defined(ZXING_READERS) || defined(ZXING_USE_ZINT)
|
||||
// assemble all blocks with unknown encoding
|
||||
ByteArray input;
|
||||
ForEachECIBlock([&](ECI eci, int begin, int end) {
|
||||
if (eci == ECI::Unknown)
|
||||
input.append(bytes.asView(begin, end - begin));
|
||||
});
|
||||
|
||||
if (input.empty())
|
||||
return CharacterSet::Unknown;
|
||||
|
||||
return GuessTextEncoding(input);
|
||||
#else
|
||||
return CharacterSet::ISO8859_1;
|
||||
#endif
|
||||
}
|
||||
|
||||
ContentType Content::type() const
|
||||
{
|
||||
#if 1 //def ZXING_READERS
|
||||
if (empty())
|
||||
return ContentType::Text;
|
||||
|
||||
if (!canProcess())
|
||||
return ContentType::UnknownECI;
|
||||
|
||||
if (symbology.aiFlag == AIFlag::GS1)
|
||||
return ContentType::GS1;
|
||||
|
||||
// check for the absolute minimum of a ISO 15434 conforming message ("[)>" + RS + digit + digit)
|
||||
if (bytes.size() > 6 && bytes.asString(0, 4) == "[)>\x1E" && IsDigit(bytes[4]) && IsDigit(bytes[5]))
|
||||
return ContentType::ISO15434;
|
||||
|
||||
ECI fallback = ToECI(guessEncoding());
|
||||
std::vector<bool> binaryECIs;
|
||||
ForEachECIBlock([&](ECI eci, int begin, int end) {
|
||||
if (eci == ECI::Unknown)
|
||||
eci = fallback;
|
||||
binaryECIs.push_back((!IsText(eci)
|
||||
|| (ToInt(eci) > 0 && ToInt(eci) < 28 && ToInt(eci) != 25
|
||||
&& std::any_of(bytes.begin() + begin, bytes.begin() + end,
|
||||
[](auto c) { return c < 0x20 && c != 0x9 && c != 0xa && c != 0xd; }))));
|
||||
});
|
||||
|
||||
if (!Contains(binaryECIs, true))
|
||||
return ContentType::Text;
|
||||
if (!Contains(binaryECIs, false))
|
||||
return ContentType::Binary;
|
||||
|
||||
return ContentType::Mixed;
|
||||
#else
|
||||
//TODO: replace by proper construction from encoded data from within zint
|
||||
return ContentType::Text;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ByteArray.h"
|
||||
#include "CharacterSet.h"
|
||||
#include "ContentType.h"
|
||||
#include "ECI.h"
|
||||
#include "ReaderOptions.h"
|
||||
#include "SymbologyIdentifier.h"
|
||||
#include "Version.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class Content
|
||||
{
|
||||
template <typename FUNC>
|
||||
void ForEachECIBlock(FUNC f) const;
|
||||
|
||||
void switchEncoding(ECI eci, bool isECI);
|
||||
std::string render(bool withECI) const;
|
||||
|
||||
public:
|
||||
struct Encoding
|
||||
{
|
||||
ECI eci;
|
||||
int pos;
|
||||
};
|
||||
|
||||
ByteArray bytes;
|
||||
std::vector<Encoding> encodings;
|
||||
#if !defined(ZXING_READERS) && defined(ZXING_USE_ZINT)
|
||||
std::vector<std::string> utf8Cache;
|
||||
#endif
|
||||
SymbologyIdentifier symbology;
|
||||
CharacterSet defaultCharset = CharacterSet::Unknown;
|
||||
bool hasECI = false;
|
||||
|
||||
Content() = default;
|
||||
Content(ByteArray&& bytes, SymbologyIdentifier si, CharacterSet defaultCharset = CharacterSet::Unknown);
|
||||
|
||||
// make movable but not copyable
|
||||
Content(const Content& other) = delete;
|
||||
Content& operator=(const Content& other) = delete;
|
||||
Content(Content&& other) = default;
|
||||
Content& operator=(Content&& other) = default;
|
||||
|
||||
void switchEncoding(ECI eci) { switchEncoding(eci, true); }
|
||||
void switchEncoding(CharacterSet cs);
|
||||
|
||||
void reserve(int count) { bytes.reserve(bytes.size() + count); }
|
||||
|
||||
void push_back(uint8_t val) { bytes.push_back(val); }
|
||||
void push_back(int val) { bytes.push_back(narrow_cast<uint8_t>(val)); }
|
||||
void append(std::string_view str) { bytes.insert(bytes.end(), str.begin(), str.end()); }
|
||||
void append(ByteView bv) { bytes.insert(bytes.end(), bv.begin(), bv.end()); }
|
||||
void append(const Content& other);
|
||||
|
||||
void erase(int pos, int n);
|
||||
void insert(int pos, std::string_view str);
|
||||
|
||||
bool empty() const { return bytes.empty(); }
|
||||
bool canProcess() const;
|
||||
|
||||
std::string text(TextMode mode) const;
|
||||
std::wstring utfW() const; // utf16 or utf32 depending on the platform, i.e. on size_of(wchar_t)
|
||||
std::string utf8() const { return render(false); }
|
||||
|
||||
ByteArray bytesECI() const;
|
||||
CharacterSet guessEncoding() const;
|
||||
ContentType type() const;
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* @brief Provides a hint to the type of content encoded in a barcode, such as text, binary data, etc.
|
||||
*/
|
||||
enum class ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI };
|
||||
|
||||
std::string ToString(ContentType type);
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,490 @@
|
||||
/*
|
||||
* Copyright 2024 Axel Waggershauser
|
||||
* Copyright 2025 gitlost
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "CreateBarcode.h"
|
||||
|
||||
#include "BarcodeData.h"
|
||||
#include "BitMatrix.h"
|
||||
#include "ByteArray.h"
|
||||
#include "DecoderResult.h"
|
||||
#include "DetectorResult.h"
|
||||
#include "JSON.h"
|
||||
#include "Version.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#ifdef ZXING_READERS
|
||||
#include "ReadBarcode.h"
|
||||
#endif
|
||||
|
||||
#ifdef ZXING_USE_ZINT
|
||||
#include "ECI.h"
|
||||
#include "TextEncoder.h"
|
||||
#include <zint.h>
|
||||
#else
|
||||
#include "MultiFormatWriter.h"
|
||||
#endif // ZXING_USE_ZINT
|
||||
|
||||
#include <charconv>
|
||||
#include <optional>
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
struct CreatorOptions::Data
|
||||
{
|
||||
BarcodeFormat format;
|
||||
std::string options;
|
||||
|
||||
// symbol size (qrcode, datamatrix, etc), map from I, 'WxH'
|
||||
// structured_append (idx, cnt, ID)
|
||||
|
||||
mutable unique_zint_symbol zint;
|
||||
|
||||
#ifndef __cpp_aggregate_paren_init // make XCode 14.x happy
|
||||
Data(BarcodeFormat format, std::string options) : format(format), options(std::move(options)) {}
|
||||
#endif
|
||||
};
|
||||
|
||||
// TODO: check return type
|
||||
#define ZX_PROPERTY(TYPE, NAME) \
|
||||
const TYPE& CreatorOptions::NAME() const noexcept { return d->NAME; } \
|
||||
CreatorOptions& CreatorOptions::NAME(TYPE v)& { return d->NAME = std::move(v), *this; } \
|
||||
CreatorOptions&& CreatorOptions::NAME(TYPE v)&& { return d->NAME = std::move(v), std::move(*this); }
|
||||
|
||||
ZX_PROPERTY(BarcodeFormat, format)
|
||||
ZX_PROPERTY(std::string, options)
|
||||
|
||||
#undef ZX_PROPERTY
|
||||
|
||||
#define ZX_RO_PROPERTY(TYPE, NAME) \
|
||||
std::optional<TYPE> CreatorOptions::NAME() const noexcept { return JsonGet<TYPE>(d->options, #NAME); }
|
||||
|
||||
ZX_RO_PROPERTY(std::string, ecLevel);
|
||||
ZX_RO_PROPERTY(std::string, eci);
|
||||
ZX_RO_PROPERTY(bool, gs1);
|
||||
ZX_RO_PROPERTY(bool, readerInit);
|
||||
ZX_RO_PROPERTY(bool, forceSquare);
|
||||
ZX_RO_PROPERTY(int, columns);
|
||||
ZX_RO_PROPERTY(int, rows);
|
||||
ZX_RO_PROPERTY(int, version);
|
||||
ZX_RO_PROPERTY(int, dataMask);
|
||||
|
||||
#undef ZX_RO_PROPERTY
|
||||
|
||||
CreatorOptions::CreatorOptions(BarcodeFormat format, std::string options) : d(std::make_unique<Data>(format, std::move(options))) {}
|
||||
CreatorOptions::~CreatorOptions() = default;
|
||||
CreatorOptions::CreatorOptions(CreatorOptions&&) noexcept = default;
|
||||
CreatorOptions& CreatorOptions::operator=(CreatorOptions&&) noexcept = default;
|
||||
|
||||
inline bool IsAscii(ByteView bv)
|
||||
{
|
||||
return std::all_of(bv.begin(), bv.end(), [](uint8_t c) { return c <= 127; });
|
||||
}
|
||||
|
||||
#ifdef ZXING_WRITERS
|
||||
#ifdef ZXING_USE_ZINT
|
||||
|
||||
struct String2Int
|
||||
{
|
||||
const char* str;
|
||||
int val;
|
||||
};
|
||||
|
||||
static int ParseECLevel(int symbology, std::string_view s)
|
||||
{
|
||||
constexpr std::string_view EC_LABELS_QR[4] = {"L", "M", "Q", "H"};
|
||||
int res = 0;
|
||||
|
||||
// Convert L/M/Q/H to Zint 1-4
|
||||
if (Contains({BARCODE_QRCODE, BARCODE_MICROQR, BARCODE_RMQR}, symbology))
|
||||
if ((res = IndexOf(EC_LABELS_QR, s)) != -1)
|
||||
return res + 1;
|
||||
|
||||
if (std::from_chars(s.data(), s.data() + s.size() - (s.back() == '%'), res).ec != std::errc{})
|
||||
throw std::invalid_argument(StrCat("Invalid ecLevel: '", s, "'"));
|
||||
|
||||
auto findClosestECLevel = [](const std::vector<int>& list, int val) {
|
||||
int mIdx = -2, mAbs = 100;
|
||||
for (int i = 0; i < Size(list); ++i)
|
||||
if (int abs = std::abs(val - list[i]); abs < mAbs) {
|
||||
mIdx = i;
|
||||
mAbs = abs;
|
||||
}
|
||||
return mIdx + 1;
|
||||
};
|
||||
|
||||
// Convert percentage to Zint
|
||||
if (s.back() == '%') {
|
||||
switch (symbology) {
|
||||
case BARCODE_QRCODE: return findClosestECLevel({20, 37, 55, 65}, res);
|
||||
case BARCODE_MICROQR: return findClosestECLevel({20, 37, 55}, res);
|
||||
case BARCODE_RMQR: return res <= 46 ? 2 : 4;
|
||||
case BARCODE_AZTEC: return findClosestECLevel({10, 23, 36, 50}, res);
|
||||
case BARCODE_PDF417:
|
||||
// TODO: do something sensible with PDF417?
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
static constexpr struct { BarcodeFormat format; SymbologyIdentifier si; } barcodeFormat2SymbologyIdentifier[] = {
|
||||
{BarcodeFormat::Aztec, {'z', '0', 3}}, // '1' GS1, '2' AIM
|
||||
{BarcodeFormat::Codabar, {'F', '0'}}, // if checksum processing were implemented and checksum present and stripped then modifier would be 4
|
||||
// {BarcodeFormat::CodablockF, {'O', '4'}}, // '5' GS1
|
||||
{BarcodeFormat::Code128, {'C', '0'}}, // '1' GS1, '2' AIM
|
||||
// {BarcodeFormat::Code16K, {'K', '0'}}, // '1' GS1, '2' AIM, '4' D1 PAD
|
||||
// {BarcodeFormat::Code39, {'A', '0'}}, // '3' checksum, '4' extended, '7' checksum,extended
|
||||
{BarcodeFormat::Code39Ext, {'A', '4'}}, // '3' checksum, '4' extended, '7' checksum,extended
|
||||
{BarcodeFormat::DataBar, {'e', '0', 0, AIFlag::GS1}},
|
||||
{BarcodeFormat::DataBarOmni, {'e', '0', 0, AIFlag::GS1}},
|
||||
{BarcodeFormat::DataBarStk, {'e', '0', 0, AIFlag::GS1}},
|
||||
{BarcodeFormat::DataBarStkOmni, {'e', '0', 0, AIFlag::GS1}},
|
||||
{BarcodeFormat::DataBarExp, {'e', '0', 0, AIFlag::GS1}},
|
||||
{BarcodeFormat::DataBarExpStk, {'e', '0', 0, AIFlag::GS1}},
|
||||
{BarcodeFormat::DataBarLtd, {'e', '0', 0, AIFlag::GS1}},
|
||||
{BarcodeFormat::DataMatrix, {'d', '1', 3}}, // '2' GS1, '3' AIM
|
||||
// {BarcodeFormat::DotCode, {'J', '0', 3}}, // '1' GS1, '2' AIM
|
||||
{BarcodeFormat::DXFilmEdge, {'X', 'F'}},
|
||||
{BarcodeFormat::EAN8, {'E', '4'}},
|
||||
// {BarcodeFormat::HanXin, {'h', '0', 1}}, // '2' GS1
|
||||
// {BarcodeFormat::ITF, {'I', '0'}}, // '1' check digit
|
||||
{BarcodeFormat::MaxiCode, {'U', '0', 2}}, // '1' mode 2 or 3
|
||||
{BarcodeFormat::MicroPDF417, {'L', '2', -1}},
|
||||
{BarcodeFormat::MicroQRCode, {'Q', '1', 1}},
|
||||
{BarcodeFormat::PDF417, {'L', '2', -1}},
|
||||
{BarcodeFormat::QRCode, {'Q', '1', 1}}, // '3' GS1, '5' AIM
|
||||
{BarcodeFormat::RMQRCode, {'Q', '1', 1}}, // '3' GS1, '5' AIM
|
||||
};
|
||||
|
||||
static SymbologyIdentifier SymbologyIdentifierZint2ZXing(const CreatorOptions& opts, const ByteArray& ba)
|
||||
{
|
||||
using enum BarcodeFormat;
|
||||
|
||||
const BarcodeFormat format = opts.format();
|
||||
|
||||
auto i = FindIf(barcodeFormat2SymbologyIdentifier, [format](auto& v) { return v.format == format; });
|
||||
SymbologyIdentifier ret = i != std::end(barcodeFormat2SymbologyIdentifier) ? i->si : SymbologyIdentifier{SymbologyKey(format), '0'};
|
||||
|
||||
if (format & (EAN13 | UPCA | UPCE)) {
|
||||
if (ba.size() > 13) // Have EAN-2/5 add-on?
|
||||
ret.modifier = '3'; // Combined packet, EAN-13, UPC-A, UPC-E, with add-on
|
||||
} else if (opts.gs1() && format & AllGS1) {
|
||||
if (format & (Aztec | Code128))
|
||||
ret.modifier = '1';
|
||||
else if (format == DataMatrix)
|
||||
ret.modifier = '2';
|
||||
else if (format & (QRCode | RMQRCode))
|
||||
ret.modifier = '3';
|
||||
ret.aiFlag = AIFlag::GS1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static std::string ECLevelZint2ZXing(const zint_symbol* zint)
|
||||
{
|
||||
constexpr char EC_LABELS_QR[4] = {'L', 'M', 'Q', 'H'};
|
||||
|
||||
const int symbology = zint->symbology;
|
||||
const int option_1 = zint->option_1;
|
||||
|
||||
switch (symbology) {
|
||||
case BARCODE_AZTEC:
|
||||
if ((option_1 >> 8) >= 0 && (option_1 >> 8) <= 99)
|
||||
return std::to_string(option_1 >> 8) + "%";
|
||||
break;
|
||||
case BARCODE_MAXICODE:
|
||||
// Mode
|
||||
if (option_1 >= 2 && option_1 <= 6)
|
||||
return std::to_string(option_1);
|
||||
break;
|
||||
case BARCODE_PDF417:
|
||||
case BARCODE_PDF417COMP:
|
||||
// Convert to percentage
|
||||
if (option_1 >= 0 && option_1 <= 8) {
|
||||
int overhead = symbology == BARCODE_PDF417COMP ? 35 : 69;
|
||||
int cols = (zint->width - overhead) / 17;
|
||||
int tot_cws = zint->rows * cols;
|
||||
assert(tot_cws);
|
||||
return std::to_string((2 << option_1) * 100 / tot_cws) + "%";
|
||||
}
|
||||
break;
|
||||
// case BARCODE_MICROPDF417:
|
||||
// if ((option_1 >> 8) >= 0 && (option_1 >> 8) <= 99)
|
||||
// return std::to_string(option_1 >> 8) + "%";
|
||||
// break;
|
||||
case BARCODE_QRCODE:
|
||||
case BARCODE_MICROQR:
|
||||
case BARCODE_RMQR:
|
||||
// Convert to L/M/Q/H
|
||||
if (option_1 >= 1 && option_1 <= 4)
|
||||
return {EC_LABELS_QR[option_1 - 1]};
|
||||
break;
|
||||
// case BARCODE_HANXIN:
|
||||
// if (option_1 >= 1 && option_1 <= 4)
|
||||
// return "L" + std::to_string(option_1);
|
||||
// break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifndef ZXING_READERS
|
||||
// Convert bytes to UTF-8 code points 0-255
|
||||
static std::string BinaryToUtf8(ByteView ba)
|
||||
{
|
||||
std::string utf8;
|
||||
utf8.reserve(ba.size() * 2);
|
||||
for (auto c : ba)
|
||||
if (c < 0x80) {
|
||||
utf8.push_back(c);
|
||||
} else if (c < 0xC0) {
|
||||
utf8.push_back(0xC2);
|
||||
utf8.push_back(c);
|
||||
} else {
|
||||
utf8.push_back(0xC3);
|
||||
utf8.push_back(c - 0x40);
|
||||
}
|
||||
return utf8;
|
||||
}
|
||||
#endif
|
||||
|
||||
zint_symbol* CreatorOptions::zint() const
|
||||
{
|
||||
using enum BarcodeFormat;
|
||||
|
||||
auto& zint = d->zint;
|
||||
|
||||
if (!zint) {
|
||||
#ifdef PRINT_DEBUG
|
||||
// printf("zint version: %d, sizeof(zint_symbol): %ld, options: %s\n", ZBarcode_Version(), sizeof(zint_symbol), options().c_str());
|
||||
#endif
|
||||
zint.reset(ZBarcode_Create());
|
||||
|
||||
switch (format()) {
|
||||
#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) \
|
||||
case BarcodeFormat(ZX_BCF_ID(SYM, VAR)): zint->symbology = ZINT; break;
|
||||
ZX_BCF_LIST(X)
|
||||
#undef X
|
||||
};
|
||||
|
||||
if (zint->symbology == 0)
|
||||
throw std::invalid_argument(StrCat("Unsupported barcode format for creation: ", ToString(format())));
|
||||
|
||||
if (format() == Code128 && gs1())
|
||||
zint->symbology = BARCODE_GS1_128;
|
||||
|
||||
zint->scale = 0.5f;
|
||||
|
||||
if (auto val = ecLevel(); val)
|
||||
zint->option_1 = ParseECLevel(zint->symbology, *val);
|
||||
|
||||
if (auto val = version(); val && !(format() & AllLinear))
|
||||
zint->option_2 = *val;
|
||||
|
||||
if (auto val = columns(); val && format() & (DataBarExpStk | PDF417 | MicroPDF417 | CompactPDF417))
|
||||
zint->option_2 = *val;
|
||||
|
||||
if (auto val = rows(); val && format() & (DataBarExpStk | PDF417))
|
||||
zint->option_3 = *val;
|
||||
|
||||
if (auto val = dataMask(); val && format() & (QRCode | MicroQRCode))
|
||||
zint->option_3 = (zint->option_3 & 0xFF) | (*val + 1) << 8;
|
||||
|
||||
if (format() == DataMatrix)
|
||||
zint->option_3 = (forceSquare() ? DM_SQUARE : DM_DMRE) | DM_ISO_144;
|
||||
}
|
||||
|
||||
return zint.get();
|
||||
}
|
||||
|
||||
#define CHECK_WARN(ZINT_CALL, WARN) \
|
||||
if (WARN = (ZINT_CALL); WARN >= ZINT_ERROR) \
|
||||
throw std::invalid_argument(StrCat(zint->errtxt, " (retval: ", std::to_string(WARN), ")"));
|
||||
|
||||
Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions& opts)
|
||||
{
|
||||
if (!data || size < 1)
|
||||
throw std::invalid_argument("Can not create a barcode from NULL or empty data");
|
||||
|
||||
auto zint = opts.zint();
|
||||
|
||||
zint->input_mode = mode == UNICODE_MODE && opts.gs1() && (opts.format() & BarcodeFormat::AllGS1) ? GS1_MODE : mode;
|
||||
if (mode == UNICODE_MODE && static_cast<const char*>(data)[0] != '[')
|
||||
zint->input_mode |= GS1PARENS_MODE;
|
||||
zint->output_options |= OUT_BUFFER_INTERMEDIATE | BARCODE_NO_QUIET_ZONES | BARCODE_CONTENT_SEGS;
|
||||
if (opts.readerInit())
|
||||
zint->output_options |= READER_INIT;
|
||||
|
||||
if (ZBarcode_Cap(zint->symbology, ZINT_CAP_ECI)) {
|
||||
if (auto eci = opts.eci(); eci) {
|
||||
if (auto cs = CharacterSetFromString(*eci); cs != CharacterSet::Unknown) {
|
||||
zint->eci = static_cast<int>(ToECI(cs));
|
||||
} else if (std::all_of(eci->begin(), eci->end(), IsDigit<char>)) {
|
||||
zint->eci = std::stoi(*eci);
|
||||
}
|
||||
} else if (mode == DATA_MODE) {
|
||||
zint->eci = static_cast<int>(ECI::Binary);
|
||||
}
|
||||
// the following would make sure that non-ASCII text is encoded as UTF-8, hence the GuessTextEncoding for
|
||||
// non-ECI data can't produce a wrong result.
|
||||
// else if (mode == UNICODE_MODE && !IsAscii({data, narrow_cast<size_t>(size)}))
|
||||
// zint->eci = static_cast<int>(ECI::UTF8);
|
||||
}
|
||||
|
||||
if (opts.format() == BarcodeFormat::Telepen && (std::all_of((const char*)data, (const char*)data + size, IsDigit<char>)))
|
||||
zint->symbology = BARCODE_TELEPEN_NUM;
|
||||
|
||||
int warning;
|
||||
CHECK_WARN(ZBarcode_Encode_and_Buffer(zint, (uint8_t*)data, size, 0), warning);
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("create symbol with size: %dx%d\n", zint->width, zint->rows);
|
||||
#endif
|
||||
|
||||
#if 0 // use ReadBarcode to create Barcode object
|
||||
auto buffer = std::vector<uint8_t>(zint->bitmap_width * zint->bitmap_height);
|
||||
std::transform(zint->bitmap, zint->bitmap + zint->bitmap_width * zint->bitmap_height, buffer.data(),
|
||||
[](unsigned char v) { return (v == '0') * 0xff; });
|
||||
|
||||
auto res = ReadBarcode({buffer.data(), zint->bitmap_width, zint->bitmap_height, ImageFormat::Lum},
|
||||
ReaderOptions().formats(opts.format()).isPure(true).binarizer(Binarizer::BoolCast));
|
||||
#else
|
||||
assert(zint->content_seg_count == 1);
|
||||
const auto& content_seg = zint->content_segs[0];
|
||||
const size_t content_seg_len =
|
||||
static_cast<size_t>(content_seg.length - (opts.format() == BarcodeFormat::Code93 && content_seg.length >= 2 ? 2 : 0));
|
||||
|
||||
Content content;
|
||||
|
||||
if (zint->eci || warning == ZINT_WARN_USES_ECI)
|
||||
content.switchEncoding(ECI(content_seg.eci));
|
||||
else
|
||||
content.switchEncoding(ToCharacterSet(ECI(content_seg.eci)));
|
||||
|
||||
if ((zint->input_mode & 0x07) == UNICODE_MODE) {
|
||||
// `content_segs` returned as UTF-8
|
||||
std::string utf8(reinterpret_cast<const char *>(content_seg.source), content_seg_len);
|
||||
content.append(TextEncoder::FromUnicode(utf8, ToCharacterSet(ECI(content_seg.eci))));
|
||||
#ifndef ZXING_READERS
|
||||
content.utf8Cache.push_back(std::move(utf8));
|
||||
#endif
|
||||
} else {
|
||||
content.append({content_seg.source, content_seg_len});
|
||||
#ifndef ZXING_READERS
|
||||
content.utf8Cache.push_back(BinaryToUtf8(content.bytes));
|
||||
#endif
|
||||
}
|
||||
|
||||
content.symbology = SymbologyIdentifierZint2ZXing(opts, content.bytes);
|
||||
|
||||
DecoderResult decRes(std::move(content));
|
||||
decRes.setEcLevel(ECLevelZint2ZXing(zint));
|
||||
decRes.setReaderInit(zint->output_options & READER_INIT);
|
||||
auto bits = BitMatrix(zint->bitmap_width, zint->bitmap_height);
|
||||
std::transform(zint->bitmap, zint->bitmap + zint->bitmap_width * zint->bitmap_height, bits.row(0).begin(),
|
||||
[](unsigned char v) { return (v == '1') * BitMatrix::SET_V; });
|
||||
int left, top, width, height;
|
||||
bits.findBoundingBox(left, top, width, height);
|
||||
|
||||
auto res = MatrixBarcode(std::move(decRes), {std::move(bits), Rectangle<PointI>(left, top, width, height)}, opts.format());
|
||||
#endif
|
||||
|
||||
res.zint = std::move(opts.d->zint);
|
||||
res.zintMutex = std::make_unique<std::mutex>();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& options)
|
||||
{
|
||||
return CreateBarcode(contents.data(), contents.size(), UNICODE_MODE, options);
|
||||
}
|
||||
|
||||
Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& options)
|
||||
{
|
||||
return CreateBarcode(contents.data(), contents.size(), UNICODE_MODE, options);
|
||||
}
|
||||
|
||||
Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& options)
|
||||
{
|
||||
return CreateBarcode(data, size, DATA_MODE, options);
|
||||
}
|
||||
|
||||
#else // ZXING_USE_ZINT
|
||||
|
||||
zint_symbol* CreatorOptions::zint() const { return nullptr; }
|
||||
|
||||
static Barcode CreateBarcode(BitMatrix&& bits, std::string_view contents, const CreatorOptions& opts)
|
||||
{
|
||||
Content content;
|
||||
content.append(contents);
|
||||
|
||||
DecoderResult decRes(std::move(content));
|
||||
DetectorResult detRes(std::move(bits), Rectangle<PointI>(0, 0, bits.width(), bits.height()));
|
||||
return MatrixBarcode(std::move(decRes), std::move(detRes), opts.format());
|
||||
}
|
||||
|
||||
Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& opts)
|
||||
{
|
||||
auto writer = MultiFormatWriter(opts.format()).setMargin(0);
|
||||
if (auto ecLevel = opts.ecLevel(); ecLevel && ecLevel->size() == 1 && strchr("012345678", (*ecLevel)[0]))
|
||||
writer.setEccLevel(std::stoi(*ecLevel));
|
||||
if (!IsAscii({(const uint8_t*)contents.data(), contents.size()}))
|
||||
writer.setEncoding(CharacterSet::UTF8); // write UTF8 (ECI value 26) for maximum compatibility
|
||||
|
||||
return CreateBarcode(writer.encode(std::string(contents), 0, opts.format() & BarcodeFormat::AllLinear ? 50 : 0), contents, opts);
|
||||
}
|
||||
|
||||
Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& opts)
|
||||
{
|
||||
return CreateBarcodeFromText({reinterpret_cast<const char*>(contents.data()), contents.size()}, opts);
|
||||
}
|
||||
|
||||
Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& opts)
|
||||
{
|
||||
std::wstring bytes;
|
||||
for (uint8_t c : ByteView(data, size))
|
||||
bytes.push_back(c);
|
||||
|
||||
auto writer = MultiFormatWriter(opts.format()).setMargin(0);
|
||||
if (auto ecLevel = opts.ecLevel(); ecLevel && ecLevel->size() == 1 && strchr("012345678", (*ecLevel)[0]))
|
||||
writer.setEccLevel(std::stoi(*ecLevel));
|
||||
writer.setEncoding(CharacterSet::BINARY);
|
||||
|
||||
return CreateBarcode(writer.encode(bytes, 0, opts.format() & BarcodeFormat::AllLinear ? 50 : 0), {(const char*)data, (size_t)size}, opts);
|
||||
}
|
||||
|
||||
#endif // ZXING_USE_ZINT
|
||||
|
||||
#else // ZXING_WRITERS
|
||||
|
||||
zint_symbol* CreatorOptions::zint() const { return nullptr; }
|
||||
|
||||
Barcode CreateBarcodeFromText(std::string_view, const CreatorOptions&)
|
||||
{
|
||||
throw std::runtime_error("This build of zxing-cpp does not support creating barcodes.");
|
||||
}
|
||||
|
||||
Barcode CreateBarcodeFromText(std::u8string_view, const CreatorOptions&)
|
||||
{
|
||||
throw std::runtime_error("This build of zxing-cpp does not support creating barcodes.");
|
||||
}
|
||||
|
||||
Barcode CreateBarcodeFromBytes(const void*, int, const CreatorOptions&)
|
||||
{
|
||||
throw std::runtime_error("This build of zxing-cpp does not support creating barcodes.");
|
||||
}
|
||||
|
||||
#endif // ZXING_WRITERS
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2024 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Barcode.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
extern "C" struct zint_symbol;
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* @brief Configuration options for barcode creation.
|
||||
*
|
||||
* This class encapsulates all the parameters needed to create a barcode with
|
||||
* specific format and settings.
|
||||
*
|
||||
* The options property is a string that can contain multiple key-value pairs
|
||||
* separated by commas. Each key-value pair configures a specific aspect of the
|
||||
* barcode generation process and are dependent on the used BarcodeFormat.
|
||||
* Boolean properties are interpreted as true if only the property name is present.
|
||||
* Keys are case-insensitive. Passing a serialized JSON object is also supported.
|
||||
*
|
||||
* For a list of all supported options, see the list of read-only properties below.
|
||||
*
|
||||
* ```c++
|
||||
* // example of creating a QR code with 30% error correction level and GS1 mode enabled
|
||||
* auto opts = CreatorOptions(BarcodeFormat::QRCode, "ecLevel=30%, gs1");
|
||||
* ```
|
||||
*/
|
||||
class CreatorOptions
|
||||
{
|
||||
struct Data;
|
||||
|
||||
std::unique_ptr<Data> d;
|
||||
|
||||
friend Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions& options);
|
||||
|
||||
public:
|
||||
CreatorOptions(BarcodeFormat format, std::string options = {});
|
||||
~CreatorOptions();
|
||||
CreatorOptions(CreatorOptions&&) noexcept;
|
||||
CreatorOptions& operator=(CreatorOptions&&) noexcept;
|
||||
|
||||
zint_symbol* zint() const;
|
||||
|
||||
#define ZX_PROPERTY(TYPE, NAME) \
|
||||
const TYPE& NAME() const noexcept; \
|
||||
CreatorOptions& NAME(TYPE v)&; \
|
||||
CreatorOptions&& NAME(TYPE v)&&;
|
||||
|
||||
ZX_PROPERTY(BarcodeFormat, format)
|
||||
ZX_PROPERTY(std::string, options)
|
||||
|
||||
#undef ZX_PROPERTY
|
||||
|
||||
#define ZX_RO_PROPERTY(TYPE, NAME) \
|
||||
std::optional<TYPE> NAME() const noexcept;
|
||||
|
||||
/// EcLevel, e.g. "30%", see also libzint docs for supported values per symbology
|
||||
ZX_RO_PROPERTY(std::string, ecLevel);
|
||||
|
||||
/// Specify ECI designator to use (e.g. "UTF-8" or "26" for UTF-8), see also libzint docs (most 2D symbologies)
|
||||
ZX_RO_PROPERTY(std::string, eci);
|
||||
|
||||
/// GS1 mode (most 2D symbologies + Code128)
|
||||
ZX_RO_PROPERTY(bool, gs1);
|
||||
|
||||
/// Set the "reader init" flag (most 2D symbologies)
|
||||
ZX_RO_PROPERTY(bool, readerInit);
|
||||
|
||||
/// DataMatrix: only consider square symbol versions
|
||||
ZX_RO_PROPERTY(bool, forceSquare);
|
||||
|
||||
/// Specify number of columns (e.g. for DataBarExpStk, PDF417)
|
||||
ZX_RO_PROPERTY(int, columns);
|
||||
|
||||
/// Specify number of rows (e.g. for DataBarExpStk, PDF417)
|
||||
ZX_RO_PROPERTY(int, rows);
|
||||
|
||||
/// Specify the version/size of the symbol (most 2D symbologies)
|
||||
ZX_RO_PROPERTY(int, version);
|
||||
|
||||
/// Specify dataMask to use (QRCode/MicroQRCode)
|
||||
ZX_RO_PROPERTY(int, dataMask);
|
||||
|
||||
#undef ZX_RO_PROPERTY
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Generate Barcode from unicode text
|
||||
*
|
||||
* @param contents UTF-8 string to encode into a barcode
|
||||
* @param options CreatorOptions (including BarcodeFormat)
|
||||
*/
|
||||
Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& options);
|
||||
|
||||
/**
|
||||
* @brief Generate Barcode from raw binary data
|
||||
*
|
||||
* @param data array of bytes to encode into a barcode
|
||||
* @param size size of byte array
|
||||
* @param options CreatorOptions (including BarcodeFormat)
|
||||
*/
|
||||
Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& options);
|
||||
|
||||
#if defined(__cpp_lib_char8_t)
|
||||
Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& options);
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_lib_ranges)
|
||||
template <typename R>
|
||||
requires std::ranges::contiguous_range<R> && std::ranges::sized_range<R> && (sizeof(std::ranges::range_value_t<R>) == 1)
|
||||
Barcode CreateBarcodeFromBytes(const R& contents, const CreatorOptions& options)
|
||||
{
|
||||
return CreateBarcodeFromBytes(std::ranges::data(contents), std::ranges::size(contents), options);
|
||||
}
|
||||
#else
|
||||
template <typename R>
|
||||
Barcode CreateBarcodeFromBytes(const R& contents, const CreatorOptions& options)
|
||||
{
|
||||
return CreateBarcodeFromBytes(std::data(contents), std::size(contents), options);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Content.h"
|
||||
#include "Error.h"
|
||||
#include "JSON.h"
|
||||
#include "StructuredAppend.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
struct CustomData
|
||||
{
|
||||
virtual ~CustomData() = default;
|
||||
};
|
||||
|
||||
class DecoderResult
|
||||
{
|
||||
Content _content;
|
||||
std::string _ecLevel;
|
||||
int _lineCount = 0;
|
||||
int _versionNumber = 0;
|
||||
StructuredAppendInfo _structuredAppend;
|
||||
bool _isMirrored = false;
|
||||
bool _readerInit = false;
|
||||
Error _error;
|
||||
std::string _json;
|
||||
std::shared_ptr<CustomData> _customData;
|
||||
|
||||
public:
|
||||
DecoderResult() = default;
|
||||
DecoderResult(Error error) : _error(std::move(error)) {}
|
||||
DecoderResult(Content&& bytes) : _content(std::move(bytes)) {}
|
||||
|
||||
DecoderResult(DecoderResult&&) noexcept = default;
|
||||
DecoderResult& operator=(DecoderResult&&) noexcept = default;
|
||||
|
||||
DecoderResult(const DecoderResult&) = delete;
|
||||
DecoderResult& operator=(const DecoderResult&) = delete;
|
||||
|
||||
bool isValid(bool includeErrors = false) const
|
||||
{
|
||||
return (!_content.bytes.empty() && !_error) || (includeErrors && !!_error);
|
||||
}
|
||||
|
||||
const Content& content() const & { return _content; }
|
||||
Content&& content() && { return std::move(_content); }
|
||||
|
||||
// to keep the unit tests happy for now:
|
||||
std::wstring text() const { return _content.utfW(); }
|
||||
std::string symbologyIdentifier() const { return _content.symbology.toString(false); }
|
||||
|
||||
// Simple macro to set up getter/setter methods that save lots of boilerplate.
|
||||
// It sets up a standard 'const & () const', 2 setters for setting lvalues via
|
||||
// copy and 2 for setting rvalues via move. They are provided each to work
|
||||
// either on lvalues (normal 'void (...)') or on rvalues (returning '*this' as
|
||||
// rvalue). The latter can be used to optionally initialize a temporary in a
|
||||
// return statement, e.g.
|
||||
// return DecoderResult(bytes, text).setEcLevel(level);
|
||||
#define ZX_PROPERTY(TYPE, GETTER, SETTER) \
|
||||
const TYPE& GETTER() const & { return _##GETTER; } \
|
||||
TYPE&& GETTER() && { return std::move(_##GETTER); } \
|
||||
void SETTER(const TYPE& v) & { _##GETTER = v; } \
|
||||
void SETTER(TYPE&& v) & { _##GETTER = std::move(v); } \
|
||||
DecoderResult&& SETTER(const TYPE& v) && { _##GETTER = v; return std::move(*this); } \
|
||||
DecoderResult&& SETTER(TYPE&& v) && { _##GETTER = std::move(v); return std::move(*this); }
|
||||
|
||||
ZX_PROPERTY(std::string, ecLevel, setEcLevel)
|
||||
ZX_PROPERTY(int, lineCount, setLineCount)
|
||||
ZX_PROPERTY(int, versionNumber, setVersionNumber)
|
||||
ZX_PROPERTY(StructuredAppendInfo, structuredAppend, setStructuredAppend)
|
||||
ZX_PROPERTY(Error, error, setError)
|
||||
ZX_PROPERTY(bool, isMirrored, setIsMirrored)
|
||||
ZX_PROPERTY(bool, readerInit, setReaderInit)
|
||||
ZX_PROPERTY(std::string, json, setJson)
|
||||
ZX_PROPERTY(std::shared_ptr<CustomData>, customData, setCustomData)
|
||||
#undef ZX_PROPERTY
|
||||
|
||||
template<typename T>
|
||||
DecoderResult&& addExtra(std::string_view key, T val, T ignore = {}) { _json += JsonProp(key, val, ignore); return std::move(*this); }
|
||||
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BitMatrix.h"
|
||||
#include "Quadrilateral.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* Encapsulates the result of detecting a barcode in an image. This includes the raw
|
||||
* matrix of black/white pixels corresponding to the barcode and the position of the code
|
||||
* in the input image.
|
||||
*/
|
||||
class DetectorResult
|
||||
{
|
||||
BitMatrix _bits;
|
||||
QuadrilateralI _position;
|
||||
|
||||
public:
|
||||
DetectorResult() = default;
|
||||
DetectorResult(DetectorResult&&) noexcept = default;
|
||||
DetectorResult& operator=(DetectorResult&&) noexcept = default;
|
||||
|
||||
DetectorResult(const DetectorResult&) = delete;
|
||||
DetectorResult& operator=(const DetectorResult&) = delete;
|
||||
|
||||
DetectorResult(BitMatrix&& bits, QuadrilateralI&& position) : _bits(std::move(bits)), _position(std::move(position)) {}
|
||||
|
||||
const BitMatrix& bits() const & { return _bits; }
|
||||
BitMatrix&& bits() && { return std::move(_bits); }
|
||||
const QuadrilateralI& position() const & { return _position; }
|
||||
QuadrilateralI&& position() && { return std::move(_position); }
|
||||
|
||||
bool isValid() const { return !_bits.empty(); }
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ECI.h"
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
static const std::map<ECI, CharacterSet> ECI_TO_CHARSET = {
|
||||
{ECI(0), CharacterSet::Cp437}, // Obsolete
|
||||
{ECI(1), CharacterSet::ISO8859_1}, // Obsolete
|
||||
{ECI::Cp437, CharacterSet::Cp437}, // Obsolete but still used by PDF417 Macro fields (ISO/IEC 15438:2015 Annex H.2.3)
|
||||
{ECI::ISO8859_1, CharacterSet::ISO8859_1},
|
||||
{ECI::ISO8859_2, CharacterSet::ISO8859_2},
|
||||
{ECI::ISO8859_3, CharacterSet::ISO8859_3},
|
||||
{ECI::ISO8859_4, CharacterSet::ISO8859_4},
|
||||
{ECI::ISO8859_5, CharacterSet::ISO8859_5},
|
||||
{ECI::ISO8859_6, CharacterSet::ISO8859_6},
|
||||
{ECI::ISO8859_7, CharacterSet::ISO8859_7},
|
||||
{ECI::ISO8859_8, CharacterSet::ISO8859_8},
|
||||
{ECI::ISO8859_9, CharacterSet::ISO8859_9},
|
||||
{ECI::ISO8859_10, CharacterSet::ISO8859_10},
|
||||
{ECI::ISO8859_11, CharacterSet::ISO8859_11},
|
||||
{ECI::ISO8859_13, CharacterSet::ISO8859_13},
|
||||
{ECI::ISO8859_14, CharacterSet::ISO8859_14},
|
||||
{ECI::ISO8859_15, CharacterSet::ISO8859_15},
|
||||
{ECI::ISO8859_16, CharacterSet::ISO8859_16},
|
||||
{ECI::Shift_JIS, CharacterSet::Shift_JIS},
|
||||
{ECI::Cp1250, CharacterSet::Cp1250},
|
||||
{ECI::Cp1251, CharacterSet::Cp1251},
|
||||
{ECI::Cp1252, CharacterSet::Cp1252},
|
||||
{ECI::Cp1256, CharacterSet::Cp1256},
|
||||
{ECI::UTF8, CharacterSet::UTF8},
|
||||
{ECI::UTF16BE, CharacterSet::UTF16BE},
|
||||
{ECI::UTF16LE, CharacterSet::UTF16LE},
|
||||
{ECI::UTF32BE, CharacterSet::UTF32BE},
|
||||
{ECI::UTF32LE, CharacterSet::UTF32LE},
|
||||
{ECI::ASCII, CharacterSet::ASCII},
|
||||
{ECI::Big5, CharacterSet::Big5},
|
||||
{ECI::GB18030, CharacterSet::GB18030},
|
||||
{ECI::GB2312, CharacterSet::GB2312},
|
||||
{ECI::EUC_KR, CharacterSet::EUC_KR},
|
||||
{ECI::ISO646_Inv, CharacterSet::ASCII},
|
||||
{ECI::Binary, CharacterSet::BINARY},
|
||||
};
|
||||
|
||||
std::string ToString(ECI eci)
|
||||
{
|
||||
return '\\' + ToString(ToInt(eci), 6);
|
||||
}
|
||||
|
||||
CharacterSet ToCharacterSet(ECI eci)
|
||||
{
|
||||
if (auto it = ECI_TO_CHARSET.find(eci); it != ECI_TO_CHARSET.end())
|
||||
return it->second;
|
||||
|
||||
return CharacterSet::Unknown;
|
||||
}
|
||||
|
||||
ECI ToECI(CharacterSet cs)
|
||||
{
|
||||
// Special case ISO8859_1 to avoid obsolete ECI 1
|
||||
if (cs == CharacterSet::ISO8859_1)
|
||||
return ECI::ISO8859_1;
|
||||
// Special case Cp437 to avoid obsolete ECI 0 for slightly less obsolete ECI 2
|
||||
if (cs == CharacterSet::Cp437)
|
||||
return ECI::Cp437;
|
||||
|
||||
for (auto& [key, value] : ECI_TO_CHARSET)
|
||||
if (value == cs)
|
||||
return key;
|
||||
|
||||
return ECI::Unknown;
|
||||
}
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CharacterSet.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
enum class ECI : int
|
||||
{
|
||||
Unknown = -1,
|
||||
Cp437 = 2, // obsolete
|
||||
ISO8859_1 = 3,
|
||||
ISO8859_2 = 4,
|
||||
ISO8859_3 = 5,
|
||||
ISO8859_4 = 6,
|
||||
ISO8859_5 = 7,
|
||||
ISO8859_6 = 8,
|
||||
ISO8859_7 = 9,
|
||||
ISO8859_8 = 10,
|
||||
ISO8859_9 = 11,
|
||||
ISO8859_10 = 12,
|
||||
ISO8859_11 = 13,
|
||||
ISO8859_13 = 15,
|
||||
ISO8859_14 = 16,
|
||||
ISO8859_15 = 17,
|
||||
ISO8859_16 = 18,
|
||||
Shift_JIS = 20,
|
||||
Cp1250 = 21,
|
||||
Cp1251 = 22,
|
||||
Cp1252 = 23,
|
||||
Cp1256 = 24,
|
||||
UTF16BE = 25,
|
||||
UTF8 = 26,
|
||||
ASCII = 27,
|
||||
Big5 = 28,
|
||||
GB2312 = 29,
|
||||
EUC_KR = 30,
|
||||
GB18030 = 32,
|
||||
UTF16LE = 33,
|
||||
UTF32BE = 34,
|
||||
UTF32LE = 35,
|
||||
ISO646_Inv = 170,
|
||||
Binary = 899
|
||||
};
|
||||
|
||||
inline constexpr int ToInt(ECI eci)
|
||||
{
|
||||
return static_cast<int>(eci);
|
||||
}
|
||||
|
||||
inline constexpr bool IsText(ECI eci)
|
||||
{
|
||||
return ToInt(eci) >= 0 && ToInt(eci) <= 170;
|
||||
}
|
||||
|
||||
inline constexpr bool CanProcess(ECI eci)
|
||||
{
|
||||
// see https://github.com/zxing-cpp/zxing-cpp/commit/d8587545434d533c4e568181e1c12ef04a8e42d9#r74864359
|
||||
return ToInt(eci) <= 899;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ToString converts the numerical ECI value to a 7 character string as used in the ECI protocol
|
||||
* @return e.g. "\000020"
|
||||
*/
|
||||
std::string ToString(ECI eci);
|
||||
|
||||
CharacterSet ToCharacterSet(ECI eci);
|
||||
|
||||
ECI ToECI(CharacterSet cs);
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "Error.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::string Error::location() const
|
||||
{
|
||||
if (!_file)
|
||||
return {};
|
||||
std::string file(_file);
|
||||
return file.substr(file.find_last_of("/\\") + 1) + ":" + std::to_string(_line);
|
||||
}
|
||||
|
||||
std::string ToString(const Error& e)
|
||||
{
|
||||
const char* name[] = {"", "FormatError", "ChecksumError", "Unsupported"};
|
||||
std::string ret = name[static_cast<int>(e.type())];
|
||||
if (!e.msg().empty())
|
||||
ret += " (" + e.msg() + ")";
|
||||
if (auto location = e.location(); !location.empty())
|
||||
ret += " @ " + e.location();
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* @brief The Error class is a value type for the error() member of Barcode
|
||||
*
|
||||
* The use-case of this class is to communicate whether or not a particular Barcode
|
||||
* symbol is in error. It is (primarily) not meant to be thrown as an exception and
|
||||
* therefore not derived from std::exception. The library code may throw (and catch!)
|
||||
* objects of this class as a convenient means of flow control (c++23's std::expected
|
||||
* will allow to replace those use-cases with something similarly convenient). In
|
||||
* those situations, the author is advised to make sure any thrown Error object is
|
||||
* caught before leaking into user/wrapper code, i.e. the functions of the public
|
||||
* API should be considered `noexcept` with respect to this class.
|
||||
*/
|
||||
|
||||
class Error
|
||||
{
|
||||
public:
|
||||
enum class Type : uint8_t
|
||||
{
|
||||
None, ///< No error
|
||||
Format, ///< Format error (i.e. the decoded data is not valid for the detected symbology)
|
||||
Checksum, ///< Checksum error (i.e. the decoded data does not pass the checksum validation)
|
||||
Unsupported ///< Unsupported error (i.e. a particular feature detected is not supported by zxing-cpp)
|
||||
};
|
||||
Type type() const noexcept { return _type; }
|
||||
const std::string& msg() const noexcept { return _msg; }
|
||||
explicit operator bool() const noexcept { return _type != Type::None; }
|
||||
|
||||
/// The source code location where the error was detected (if available)
|
||||
std::string location() const;
|
||||
|
||||
Error() = default;
|
||||
Error(Type type, std::string msg = {}) : _msg(std::move(msg)), _type(type) {}
|
||||
Error(const char* file, short line, Type type, std::string msg = {}) : _msg(std::move(msg)), _file(file), _line(line), _type(type) {}
|
||||
|
||||
static constexpr auto Format = Type::Format;
|
||||
static constexpr auto Checksum = Type::Checksum;
|
||||
static constexpr auto Unsupported = Type::Unsupported;
|
||||
|
||||
inline bool operator==(const Error& o) const noexcept
|
||||
{
|
||||
return _type == o._type && _msg == o._msg && _file == o._file && _line == o._line;
|
||||
}
|
||||
inline bool operator!=(const Error& o) const noexcept { return !(*this == o); }
|
||||
|
||||
protected:
|
||||
/// @cond INTERNAL
|
||||
std::string _msg;
|
||||
const char* _file = nullptr;
|
||||
short _line = -1;
|
||||
Type _type = Type::None;
|
||||
/// @endcond
|
||||
};
|
||||
|
||||
/// @cond INTERNAL
|
||||
inline bool operator==(const Error& e, Error::Type t) noexcept { return e.type() == t; }
|
||||
inline bool operator!=(const Error& e, Error::Type t) noexcept { return !(e == t); }
|
||||
inline bool operator==(Error::Type t, const Error& e) noexcept { return e.type() == t; }
|
||||
inline bool operator!=(Error::Type t, const Error& e) noexcept { return !(t == e); }
|
||||
|
||||
#define FormatError(...) Error(__FILE__, __LINE__, Error::Format, std::string(__VA_ARGS__))
|
||||
#define ChecksumError(...) Error(__FILE__, __LINE__, Error::Checksum, std::string(__VA_ARGS__))
|
||||
#define UnsupportedError(...) Error(__FILE__, __LINE__, Error::Unsupported, std::string(__VA_ARGS__))
|
||||
/// @endcond
|
||||
|
||||
std::string ToString(const Error& e);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include <version>
|
||||
#ifdef __cpp_lib_bitops
|
||||
#include <bit>
|
||||
#else
|
||||
namespace std {
|
||||
template <class T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
|
||||
constexpr int popcount(T x) noexcept
|
||||
{
|
||||
int n = 0;
|
||||
for (; x; ++n)
|
||||
x &= x - 1; // clear lowest bit
|
||||
return n;
|
||||
}
|
||||
|
||||
template <class T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
|
||||
constexpr int countr_zero(T x) noexcept
|
||||
{
|
||||
if (x == 0)
|
||||
return std::numeric_limits<T>::digits;
|
||||
|
||||
int n = 0;
|
||||
while ((x & T(1)) == 0) {
|
||||
x >>= 1;
|
||||
++n;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
template <class T, typename = std::enable_if_t<std::is_unsigned_v<T>>>
|
||||
constexpr int countl_zero(T x) noexcept
|
||||
{
|
||||
if (x == 0)
|
||||
return std::numeric_limits<T>::digits;
|
||||
|
||||
int n = 0;
|
||||
for (int i = std::numeric_limits<T>::digits; i-- && !(x & (T(1) << i)); ++n)
|
||||
;
|
||||
return n;
|
||||
}
|
||||
} // namespace std
|
||||
#endif
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
template<typename Enum>
|
||||
class Flags
|
||||
{
|
||||
static_assert(std::is_enum<Enum>::value, "Flags is only usable on enumeration types.");
|
||||
|
||||
using Int = std::make_unsigned_t<typename std::underlying_type<Enum>::type>;
|
||||
Int i = 0;
|
||||
|
||||
constexpr inline Flags(Int other) : i(other) {}
|
||||
|
||||
constexpr static inline auto highestBitSet(Int x) noexcept
|
||||
{
|
||||
return std::numeric_limits<Int>::digits - 1 - std::countl_zero(x);
|
||||
}
|
||||
|
||||
public:
|
||||
using enum_type = Enum;
|
||||
|
||||
constexpr inline Flags() noexcept = default;
|
||||
constexpr inline Flags(Enum flag) noexcept : i(Int(flag)) {}
|
||||
|
||||
// constexpr inline Flags(std::initializer_list<Enum> flags) noexcept
|
||||
// : i(initializer_list_helper(flags.begin(), flags.end()))
|
||||
// {}
|
||||
|
||||
class iterator
|
||||
{
|
||||
friend class Flags;
|
||||
const Int _flags = 0;
|
||||
int _pos = 0;
|
||||
iterator(Int i, int p) : _flags(i), _pos(p) {}
|
||||
public:
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using value_type = Enum;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = Enum*;
|
||||
using reference = Enum&;
|
||||
|
||||
Enum operator*() const noexcept { return Enum(Int(1) << _pos); }
|
||||
|
||||
iterator& operator++() noexcept
|
||||
{
|
||||
while (++_pos < highestBitSet(_flags) && !((Int(1) << _pos) & _flags))
|
||||
;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const iterator& rhs) const noexcept { return _pos == rhs._pos; }
|
||||
bool operator!=(const iterator& rhs) const noexcept { return !(*this == rhs); }
|
||||
};
|
||||
|
||||
iterator begin() const noexcept { return {i, std::countr_zero(i)}; }
|
||||
iterator end() const noexcept { return {i, highestBitSet(i) + 1}; }
|
||||
|
||||
bool empty() const noexcept { return i == 0; }
|
||||
int count() const noexcept { return std::popcount(i); }
|
||||
|
||||
constexpr inline bool operator==(Flags other) const noexcept { return i == other.i; }
|
||||
|
||||
inline Flags& operator&=(Flags mask) noexcept { return i &= mask.i, *this; }
|
||||
inline Flags& operator&=(Enum mask) noexcept { return i &= Int(mask), *this; }
|
||||
inline Flags& operator|=(Flags other) noexcept { return i |= other.i, *this; }
|
||||
inline Flags& operator|=(Enum other) noexcept { return i |= Int(other), *this; }
|
||||
// inline Flags &operator^=(Flags other) noexcept { return i ^= other.i, *this; }
|
||||
// inline Flags &operator^=(Enum other) noexcept { return i ^= Int(other), *this; }
|
||||
|
||||
constexpr inline Flags operator&(Flags other) const noexcept { return i & other.i; }
|
||||
constexpr inline Flags operator&(Enum other) const noexcept { return i & Int(other); }
|
||||
constexpr inline Flags operator|(Flags other) const noexcept { return i | other.i; }
|
||||
constexpr inline Flags operator|(Enum other) const noexcept { return i | Int(other); }
|
||||
// constexpr inline Flags operator^(Flags other) const noexcept { return i ^ other.i; }
|
||||
// constexpr inline Flags operator^(Enum other) const noexcept { return i ^ Int(other); }
|
||||
// constexpr inline Flags operator~() const noexcept { return ~i; }
|
||||
|
||||
// constexpr inline operator Int() const noexcept { return i; }
|
||||
// constexpr inline bool operator!() const noexcept { return !i; }
|
||||
// constexpr inline Int asInt() const noexcept { return i; }
|
||||
|
||||
constexpr inline bool testFlag(Enum flag) const noexcept
|
||||
{
|
||||
return (i & Int(flag)) == Int(flag) && (Int(flag) != 0 || i == Int(flag));
|
||||
}
|
||||
constexpr inline bool testFlags(Flags mask) const noexcept { return i & mask.i; }
|
||||
inline Flags& setFlag(Enum flag, bool on = true) noexcept
|
||||
{
|
||||
return on ? (*this |= flag) : (*this &= ~Int(flag));
|
||||
}
|
||||
inline void clear() noexcept { i = 0; }
|
||||
|
||||
constexpr static Flags all() noexcept { return ~(Int(~0) << highestBitSet(Int(Enum::_max))); }
|
||||
|
||||
private:
|
||||
// constexpr static inline Int
|
||||
// initializer_list_helper(typename std::initializer_list<Enum>::const_iterator it,
|
||||
// typename std::initializer_list<Enum>::const_iterator end) noexcept
|
||||
// {
|
||||
// return (it == end ? Int(0) : (Int(*it) | initializer_list_helper(it + 1, end)));
|
||||
// }
|
||||
};
|
||||
|
||||
#define ZX_DECLARE_FLAGS(FLAGS, ENUM) \
|
||||
using FLAGS = Flags<ENUM>; \
|
||||
constexpr inline FLAGS operator|(FLAGS::enum_type e1, FLAGS::enum_type e2) noexcept { return FLAGS(e1) | e2; } \
|
||||
constexpr inline FLAGS operator|(FLAGS::enum_type e, FLAGS f) noexcept { return f | e; } \
|
||||
constexpr inline bool operator==(FLAGS::enum_type e, FLAGS f) noexcept { return FLAGS(e) == f; } \
|
||||
constexpr inline bool operator==(FLAGS f, FLAGS::enum_type e) noexcept { return FLAGS(e) == f; }
|
||||
|
||||
} // ZXing
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "GTIN.h"
|
||||
|
||||
#include <algorithm>
|
||||
#ifdef __cpp_lib_format // not available on gcc 12
|
||||
#include <format>
|
||||
#endif
|
||||
#include <iomanip>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
|
||||
namespace ZXing::GTIN {
|
||||
|
||||
struct CountryId
|
||||
{
|
||||
uint16_t first;
|
||||
uint16_t last;
|
||||
const char id[3];
|
||||
};
|
||||
|
||||
bool operator<(const CountryId& lhs, const CountryId& rhs)
|
||||
{
|
||||
return lhs.last < rhs.last;
|
||||
}
|
||||
|
||||
// https://www.gs1.org/standards/id-keys/company-prefix (as of 7 Feb 2022)
|
||||
// and https://en.wikipedia.org/wiki/List_of_GS1_country_codes
|
||||
static const CountryId COUNTRIES[] = {
|
||||
// clang-format off
|
||||
{1, 19, "US"},
|
||||
{30, 39, "US"},
|
||||
{60, 99, "US"}, // Note 99 coupon identification
|
||||
{100, 139, "US"},
|
||||
{300, 379, "FR"}, // France (and Monaco according to Wikipedia)
|
||||
{380, 380, "BG"}, // Bulgaria
|
||||
{383, 383, "SI"}, // Slovenia
|
||||
{385, 385, "HR"}, // Croatia
|
||||
{387, 387, "BA"}, // Bosnia and Herzegovina
|
||||
{389, 389, "ME"}, // Montenegro
|
||||
//{390, 390, "XK"}, // (Kosovo according to Wikipedia, unsourced)
|
||||
{400, 440, "DE"}, // Germany
|
||||
{450, 459, "JP"}, // Japan
|
||||
{460, 469, "RU"}, // Russia
|
||||
{470, 470, "KG"}, // Kyrgyzstan
|
||||
{471, 471, "TW"}, // Taiwan
|
||||
{474, 474, "EE"}, // Estonia
|
||||
{475, 475, "LV"}, // Latvia
|
||||
{476, 476, "AZ"}, // Azerbaijan
|
||||
{477, 477, "LT"}, // Lithuania
|
||||
{478, 478, "UZ"}, // Uzbekistan
|
||||
{479, 479, "LK"}, // Sri Lanka
|
||||
{480, 480, "PH"}, // Philippines
|
||||
{481, 481, "BY"}, // Belarus
|
||||
{482, 482, "UA"}, // Ukraine
|
||||
{483, 483, "TM"}, // Turkmenistan
|
||||
{484, 484, "MD"}, // Moldova
|
||||
{485, 485, "AM"}, // Armenia
|
||||
{486, 486, "GE"}, // Georgia
|
||||
{487, 487, "KZ"}, // Kazakhstan
|
||||
{488, 488, "TJ"}, // Tajikistan
|
||||
{489, 489, "HK"}, // Hong Kong
|
||||
{490, 499, "JP"}, // Japan
|
||||
{500, 509, "GB"}, // UK
|
||||
{520, 521, "GR"}, // Greece
|
||||
{528, 528, "LB"}, // Lebanon
|
||||
{529, 529, "CY"}, // Cyprus
|
||||
{530, 530, "AL"}, // Albania
|
||||
{531, 531, "MK"}, // North Macedonia
|
||||
{535, 535, "MT"}, // Malta
|
||||
{539, 539, "IE"}, // Ireland
|
||||
{540, 549, "BE"}, // Belgium & Luxembourg
|
||||
{560, 560, "PT"}, // Portugal
|
||||
{569, 569, "IS"}, // Iceland
|
||||
{570, 579, "DK"}, // Denmark (and Faroe Islands and Greenland according to Wikipedia)
|
||||
{590, 590, "PL"}, // Poland
|
||||
{594, 594, "RO"}, // Romania
|
||||
{599, 599, "HU"}, // Hungary
|
||||
{600, 601, "ZA"}, // South Africa
|
||||
{603, 603, "GH"}, // Ghana
|
||||
{604, 604, "SN"}, // Senegal
|
||||
{608, 608, "BH"}, // Bahrain
|
||||
{609, 609, "MU"}, // Mauritius
|
||||
{611, 611, "MA"}, // Morocco
|
||||
{613, 613, "DZ"}, // Algeria
|
||||
{615, 615, "NG"}, // Nigeria
|
||||
{616, 616, "KE"}, // Kenya
|
||||
{617, 617, "CM"}, // Cameroon
|
||||
{618, 618, "CI"}, // Côte d'Ivoire
|
||||
{619, 619, "TN"}, // Tunisia
|
||||
{620, 620, "TZ"}, // Tanzania
|
||||
{621, 621, "SY"}, // Syria
|
||||
{622, 622, "EG"}, // Egypt
|
||||
{623, 623, "BN"}, // Brunei
|
||||
{624, 624, "LY"}, // Libya
|
||||
{625, 625, "JO"}, // Jordan
|
||||
{626, 626, "IR"}, // Iran
|
||||
{627, 627, "KW"}, // Kuwait
|
||||
{628, 628, "SA"}, // Saudi Arabia
|
||||
{629, 629, "AE"}, // United Arab Emirates
|
||||
{630, 630, "QA"}, // Qatar
|
||||
{631, 631, "NA"}, // Namibia
|
||||
{640, 649, "FI"}, // Finland
|
||||
{690, 699, "CN"}, // China
|
||||
{700, 709, "NO"}, // Norway
|
||||
{729, 729, "IL"}, // Israel
|
||||
{730, 739, "SE"}, // Sweden
|
||||
{740, 740, "GT"}, // Guatemala
|
||||
{741, 741, "SV"}, // El Salvador
|
||||
{742, 742, "HN"}, // Honduras
|
||||
{743, 743, "NI"}, // Nicaragua
|
||||
{744, 744, "CR"}, // Costa Rica
|
||||
{745, 745, "PA"}, // Panama
|
||||
{746, 746, "DO"}, // Dominican Republic
|
||||
{750, 750, "MX"}, // Mexico
|
||||
{754, 755, "CA"}, // Canada
|
||||
{759, 759, "VE"}, // Venezuela
|
||||
{760, 769, "CH"}, // Switzerland (and Liechtenstein according to Wikipedia)
|
||||
{770, 771, "CO"}, // Colombia
|
||||
{773, 773, "UY"}, // Uruguay
|
||||
{775, 775, "PE"}, // Peru
|
||||
{777, 777, "BO"}, // Bolivia
|
||||
{778, 779, "AR"}, // Argentina
|
||||
{780, 780, "CL"}, // Chile
|
||||
{784, 784, "PY"}, // Paraguay
|
||||
{786, 786, "EC"}, // Ecuador
|
||||
{789, 790, "BR"}, // Brazil
|
||||
{800, 839, "IT"}, // Italy (and San Marino and Vatican City according to Wikipedia)
|
||||
{840, 849, "ES"}, // Spain (and Andorra according to Wikipedia)
|
||||
{850, 850, "CU"}, // Cuba
|
||||
{858, 858, "SK"}, // Slovakia
|
||||
{859, 859, "CZ"}, // Czechia
|
||||
{860, 860, "RS"}, // Serbia
|
||||
{865, 865, "MN"}, // Mongolia
|
||||
{867, 867, "KP"}, // North Korea
|
||||
{868, 869, "TR"}, // Turkey
|
||||
{870, 879, "NL"}, // Netherlands
|
||||
{880, 880, "KR"}, // South Korea
|
||||
{883, 883, "MM"}, // Myanmar
|
||||
{884, 884, "KH"}, // Cambodia
|
||||
{885, 885, "TH"}, // Thailand
|
||||
{888, 888, "SG"}, // Singapore
|
||||
{890, 890, "IN"}, // India
|
||||
{893, 893, "VN"}, // Vietnam
|
||||
{896, 896, "PK"}, // Pakistan
|
||||
{899, 899, "ID"}, // Indonesia
|
||||
{900, 919, "AT"}, // Austria
|
||||
{930, 939, "AU"}, // Australia
|
||||
{940, 949, "NZ"}, // New Zealand
|
||||
{955, 955, "MY"}, // Malaysia
|
||||
{958, 958, "MO"}, // Macao
|
||||
//{960, 961, "GB"}, // Global Office - assigned to GS1 UK for GTIN-8 allocations (also 9620-9624999)
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
std::string LookupCountryIdentifier(std::string_view GTIN, const BarcodeFormat format)
|
||||
{
|
||||
// Ignore add-on if any
|
||||
const auto space = GTIN.find(' ');
|
||||
const std::string::size_type size = space != std::string::npos ? space : GTIN.size();
|
||||
|
||||
if (size != 14 && size != 13 && size != 12 && size != 8)
|
||||
return {};
|
||||
|
||||
// GTIN-14 leading packaging level indicator
|
||||
const int first = size == 14 ? 1 : 0;
|
||||
// UPC-A/E implicit leading 0
|
||||
const int implicitZero = size == 12 || (size == 8 && format != BarcodeFormat::EAN8) ? 1 : 0;
|
||||
|
||||
if (size != 8 || format != BarcodeFormat::EAN8) { // Assuming following doesn't apply to EAN-8
|
||||
// 0000000 Restricted Circulation Numbers; 0000001-0000099 unused to avoid collision with GTIN-8
|
||||
int prefix = FromString<int>(GTIN.substr(first, 7 - implicitZero));
|
||||
if (prefix >= 0 && prefix <= 99)
|
||||
return {};
|
||||
|
||||
// 00001-00009 US
|
||||
prefix = FromString<int>(GTIN.substr(first, 5 - implicitZero));
|
||||
if (prefix >= 1 && prefix <= 9)
|
||||
return "US";
|
||||
|
||||
// 0001-0009 US
|
||||
prefix = FromString<int>(GTIN.substr(first, 4 - implicitZero));
|
||||
if (prefix >= 1 && prefix <= 9)
|
||||
return "US";
|
||||
}
|
||||
|
||||
const int prefix = FromString<int>(GTIN.substr(first, 3 - implicitZero));
|
||||
|
||||
// Special case EAN-8 for prefix < 100 (GS1 General Specifications Figure 1.4.3-1)
|
||||
if (size == 8 && format == BarcodeFormat::EAN8 && prefix <= 99) // Restricted Circulation Numbers
|
||||
return {};
|
||||
|
||||
const auto it = std::lower_bound(std::begin(COUNTRIES), std::end(COUNTRIES), CountryId{0, narrow_cast<uint16_t>(prefix), ""});
|
||||
|
||||
return it != std::end(COUNTRIES) && prefix >= it->first && prefix <= it->last ? it->id : std::string();
|
||||
}
|
||||
|
||||
std::string EanAddOn(const Barcode& barcode)
|
||||
{
|
||||
if (barcode.symbologyIdentifier() != "]E3")
|
||||
return {};
|
||||
return barcode.text().substr(barcode.format() == BarcodeFormat::EAN8 ? 8 : 13);
|
||||
}
|
||||
|
||||
std::string IssueNr(const std::string& ean2AddOn)
|
||||
{
|
||||
if (ean2AddOn.size() != 2)
|
||||
return {};
|
||||
|
||||
return std::to_string(std::stoi(ean2AddOn));
|
||||
}
|
||||
|
||||
std::string Price(const std::string& ean5AddOn)
|
||||
{
|
||||
if (ean5AddOn.size() != 5)
|
||||
return {};
|
||||
|
||||
std::string currency;
|
||||
switch (ean5AddOn.front()) {
|
||||
case '0': [[fallthrough]];
|
||||
case '1': currency = "GBP £"; break; // UK
|
||||
case '3': currency = "AUD $"; break; // AUS
|
||||
case '4': currency = "NZD $"; break; // NZ
|
||||
case '5': currency = "USD $"; break; // US
|
||||
case '6': currency = "CAD $"; break; // CA
|
||||
case '9':
|
||||
// Reference: http://www.jollytech.com
|
||||
if (ean5AddOn == "90000") // No suggested retail price
|
||||
return {};
|
||||
if (ean5AddOn == "99991") // Complementary
|
||||
return "0.00";
|
||||
if (ean5AddOn == "99990")
|
||||
return "Used";
|
||||
|
||||
// Otherwise... unknown currency?
|
||||
currency = "";
|
||||
break;
|
||||
default: currency = ""; break;
|
||||
}
|
||||
|
||||
int rawAmount = std::stoi(ean5AddOn.substr(1));
|
||||
#if !defined(__cpp_lib_to_chars) || !defined(__cpp_lib_format) // not available on older macOS / gcc 12
|
||||
return currency + std::to_string(rawAmount / 100) + '.' + std::to_string(rawAmount % 100);
|
||||
#else
|
||||
return std::format("{}{:.2f}", currency, float(rawAmount) / 100);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ZXing::GTIN
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Barcode.h"
|
||||
#include "BarcodeFormat.h"
|
||||
|
||||
#ifdef ZXING_INTERNAL
|
||||
#include "ZXAlgorithms.h"
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ZXing::GTIN {
|
||||
|
||||
#ifdef ZXING_INTERNAL
|
||||
template <typename T>
|
||||
T ComputeCheckDigit(const std::basic_string<T>& digits, bool skipTail = false)
|
||||
{
|
||||
int sum = 0, N = Size(digits) - skipTail;
|
||||
for (int i = N - 1; i >= 0; i -= 2)
|
||||
sum += digits[i] - '0';
|
||||
sum *= 3;
|
||||
for (int i = N - 2; i >= 0; i -= 2)
|
||||
sum += digits[i] - '0';
|
||||
return ToDigit<T>((10 - (sum % 10)) % 10);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool IsCheckDigitValid(const std::basic_string<T>& s)
|
||||
{
|
||||
return ComputeCheckDigit(s, true) == s.back();
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Evaluate the prefix of the GTIN to estimate the country of origin. See
|
||||
* <a href="https://www.gs1.org/standards/id-keys/company-prefix">
|
||||
* https://www.gs1.org/standards/id-keys/company-prefix</a> and
|
||||
* <a href="https://en.wikipedia.org/wiki/List_of_GS1_country_codes">
|
||||
* https://en.wikipedia.org/wiki/List_of_GS1_country_codes</a>.
|
||||
*
|
||||
* `format` required for EAN-8 (UPC-E assumed if not given)
|
||||
*/
|
||||
std::string LookupCountryIdentifier(std::string_view GTIN, BarcodeFormat format = BarcodeFormat::None);
|
||||
|
||||
std::string EanAddOn(const Barcode& barcode);
|
||||
|
||||
std::string IssueNr(const std::string& ean2AddOn);
|
||||
std::string Price(const std::string& ean5AddOn);
|
||||
|
||||
} // namespace ZXing::GTIN
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "GlobalHistogramBinarizer.h"
|
||||
|
||||
#include "BitMatrix.h"
|
||||
#include "Pattern.h"
|
||||
#include "ZXConfig.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
static constexpr int LUMINANCE_BITS = 5;
|
||||
static constexpr int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
|
||||
static constexpr int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
|
||||
|
||||
using Histogram = std::array<uint16_t, LUMINANCE_BUCKETS>;
|
||||
|
||||
GlobalHistogramBinarizer::GlobalHistogramBinarizer(const ImageView& buffer) : BinaryBitmap(buffer) {}
|
||||
|
||||
GlobalHistogramBinarizer::~GlobalHistogramBinarizer() = default;
|
||||
|
||||
using ImageLineView = Range<StrideIter<const uint8_t*>>;
|
||||
|
||||
inline ImageLineView RowView(const ImageView& iv, int row)
|
||||
{
|
||||
return {{iv.data(0, row), iv.pixStride()}, {iv.data(iv.width(), row), iv.pixStride()}};
|
||||
}
|
||||
|
||||
template <typename I>
|
||||
static void ThresholdSharpened(Range<I> in, int threshold, std::vector<uint8_t>& out)
|
||||
{
|
||||
out.resize(in.size());
|
||||
auto i = in.begin();
|
||||
auto o = out.begin();
|
||||
|
||||
*o++ = (*i++ <= threshold) * BitMatrix::SET_V;
|
||||
for (auto end = in.end() - 1; i != end; ++i)
|
||||
*o++ = ((-i[-1] + (int(i[0]) * 4) - i[1]) / 2 <= threshold) * BitMatrix::SET_V;
|
||||
*o++ = (*i++ <= threshold) * BitMatrix::SET_V;
|
||||
}
|
||||
|
||||
template <typename I>
|
||||
static auto GenHistogram(Range<I> line)
|
||||
{
|
||||
// This code causes about 20% of the total runtime on an AVX2 system for a EAN13 search on Lum input data.
|
||||
// Trying to increase the performance by performing 2 or 4 "parallel" histograms helped nothing on the Intel
|
||||
// system but was able to make the total runtime for that use case go down about 10% on an ARM M4.
|
||||
#if 1
|
||||
Histogram res0 = {}, res1 = {}, res2 = {}, res3 = {};
|
||||
|
||||
auto src = line.begin();
|
||||
const auto end = line.end();
|
||||
|
||||
while (end - src >= 4) {
|
||||
res0[*src++ >> LUMINANCE_SHIFT]++;
|
||||
res1[*src++ >> LUMINANCE_SHIFT]++;
|
||||
res2[*src++ >> LUMINANCE_SHIFT]++;
|
||||
res3[*src++ >> LUMINANCE_SHIFT]++;
|
||||
}
|
||||
|
||||
Histogram res = {};
|
||||
for (int i = 0; i < LUMINANCE_BUCKETS; ++i)
|
||||
res[i] = res0[i] + res1[i] + res2[i] + res3[i];
|
||||
|
||||
while (src != end)
|
||||
res[*src++ >> LUMINANCE_SHIFT]++;
|
||||
|
||||
return res;
|
||||
#else
|
||||
Histogram res = {};
|
||||
for (auto pix : line)
|
||||
res[pix >> LUMINANCE_SHIFT]++;
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return -1 on error
|
||||
static int EstimateBlackPoint(const Histogram& buckets)
|
||||
{
|
||||
// Find the tallest peak in the histogram.
|
||||
auto firstPeakPos = std::max_element(buckets.begin(), buckets.end());
|
||||
int firstPeak = narrow_cast<int>(firstPeakPos - buckets.begin());
|
||||
int firstPeakSize = *firstPeakPos;
|
||||
int maxBucketCount = firstPeakSize;
|
||||
|
||||
// Find the second-tallest peak which is somewhat far from the tallest peak.
|
||||
int secondPeak = 0;
|
||||
int secondPeakScore = 0;
|
||||
for (int x = 0; x < Size(buckets); x++) {
|
||||
int distanceToBiggest = x - firstPeak;
|
||||
// Encourage more distant second peaks by multiplying by square of distance.
|
||||
int score = buckets[x] * distanceToBiggest * distanceToBiggest;
|
||||
if (score > secondPeakScore) {
|
||||
secondPeak = x;
|
||||
secondPeakScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure firstPeak corresponds to the black peak.
|
||||
if (firstPeak > secondPeak) {
|
||||
std::swap(firstPeak, secondPeak);
|
||||
}
|
||||
|
||||
// If there is too little contrast in the image to pick a meaningful black point, throw rather
|
||||
// than waste time trying to decode the image, and risk false positives.
|
||||
if (secondPeak - firstPeak <= LUMINANCE_BUCKETS / 16) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Find a valley between them that is low and closer to the white peak.
|
||||
int bestValley = secondPeak - 1;
|
||||
int bestValleyScore = -1;
|
||||
for (int x = secondPeak - 1; x > firstPeak; x--) {
|
||||
int fromFirst = x - firstPeak;
|
||||
int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]);
|
||||
if (score > bestValleyScore) {
|
||||
bestValley = x;
|
||||
bestValleyScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
return bestValley << LUMINANCE_SHIFT;
|
||||
}
|
||||
|
||||
bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& res) const
|
||||
{
|
||||
auto buffer = _buffer.rotated(rotation);
|
||||
auto lineView = RowView(buffer, row);
|
||||
|
||||
if (buffer.width() < 3)
|
||||
return false; // special casing the code below for a width < 3 makes no sense
|
||||
|
||||
#if defined(__AVX__) || defined(__ARM_NEON)
|
||||
// If we are extracting a column (instead of a row), we run into cache misses on every pixel access both
|
||||
// during the histogram calculation and during the sharpen+threshold operation. Additionally, if we
|
||||
// perform the ThresholdSharpened function on pixStride==1 data, the auto-vectorizer makes that part
|
||||
// 8x faster on an AVX2 cpu which easily recovers the extra cost that we pay for the copying.
|
||||
ZX_THREAD_LOCAL std::vector<uint8_t> line;
|
||||
if (std::abs(buffer.pixStride()) > 4) {
|
||||
line.resize(lineView.size());
|
||||
std::copy(lineView.begin(), lineView.end(), line.begin());
|
||||
lineView = {{line.data(), 1}, {line.data() + line.size(), 1}};
|
||||
}
|
||||
#endif
|
||||
|
||||
auto packedLineView =
|
||||
lineView.begin().stride == 1 ? Range(lineView.begin().pos, lineView.end().pos) : Range<const uint8_t*>(nullptr, nullptr);
|
||||
auto histogram = packedLineView ? GenHistogram(packedLineView) : GenHistogram(lineView);
|
||||
auto threshold = EstimateBlackPoint(histogram) - 1;
|
||||
if (threshold <= 0)
|
||||
return false;
|
||||
|
||||
ZX_THREAD_LOCAL std::vector<uint8_t> binarized;
|
||||
// the optimizer can generate a specialized version for pixStride==1 (non-rotated input) that is about 8x faster on AVX2 hardware
|
||||
if (packedLineView)
|
||||
ThresholdSharpened(packedLineView, threshold, binarized);
|
||||
else
|
||||
ThresholdSharpened(lineView, threshold, binarized);
|
||||
GetPatternRow(Range(binarized), res);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Does not sharpen the data, as this call is intended to only be used by 2D Readers.
|
||||
std::shared_ptr<const BitMatrix>
|
||||
GlobalHistogramBinarizer::getBlackMatrix() const
|
||||
{
|
||||
// Quickly calculates the histogram by sampling four rows from the image. This proved to be
|
||||
// more robust on the blackbox tests than sampling a diagonal as we used to do.
|
||||
Histogram localBuckets = {};
|
||||
{
|
||||
for (int y = 1; y < 5; y++) {
|
||||
int row = height() * y / 5;
|
||||
const uint8_t* luminances = _buffer.data(0, row);
|
||||
int right = (width() * 4) / 5;
|
||||
for (int x = width() / 5; x < right; x++)
|
||||
localBuckets[luminances[x] >> LUMINANCE_SHIFT]++;
|
||||
}
|
||||
}
|
||||
|
||||
int blackPoint = EstimateBlackPoint(localBuckets);
|
||||
if (blackPoint <= 0)
|
||||
return {};
|
||||
|
||||
|
||||
|
||||
return std::make_shared<const BitMatrix>(binarize(blackPoint));
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BinaryBitmap.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* This Binarizer implementation uses the old ZXing global histogram approach. It is suitable
|
||||
* for low-end mobile devices which don't have enough CPU or memory to use a local thresholding
|
||||
* algorithm. However, because it picks a global black point, it cannot handle difficult shadows
|
||||
* and gradients.
|
||||
*
|
||||
* Faster mobile devices and all desktop applications should probably use HybridBinarizer instead.
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author Sean Owen
|
||||
*/
|
||||
class GlobalHistogramBinarizer : public BinaryBitmap
|
||||
{
|
||||
public:
|
||||
explicit GlobalHistogramBinarizer(const ImageView& buffer);
|
||||
~GlobalHistogramBinarizer() override;
|
||||
|
||||
bool getPatternRow(int row, int rotation, PatternRow &res) const override;
|
||||
std::shared_ptr<const BitMatrix> getBlackMatrix() const override;
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "GridSampler.h"
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
#include "LogMatrix.h"
|
||||
#include "BitMatrixIO.h"
|
||||
#endif
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
LogMatrix log;
|
||||
#endif
|
||||
|
||||
DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix)
|
||||
{
|
||||
return SampleGrid(image, width, height, {ROI{0, width, 0, height, mod2Pix}});
|
||||
}
|
||||
|
||||
DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const ROIs& rois)
|
||||
{
|
||||
#ifdef PRINT_DEBUG
|
||||
LogMatrix log;
|
||||
static int i = 0;
|
||||
LogMatrixWriter lmw(log, image, 5, "grid" + std::to_string(i++) + ".pnm");
|
||||
#endif
|
||||
if (width <= 0 || height <= 0)
|
||||
return {};
|
||||
|
||||
for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) {
|
||||
// Precheck the corners of every roi to bail out early if the grid is "obviously" not completely inside the image
|
||||
auto isInside = [&mod2Pix = mod2Pix, &image](int x, int y) { return image.isIn(mod2Pix(centered(PointI(x, y)))); };
|
||||
if (!mod2Pix.isValid() || !isInside(x0, y0) || !isInside(x1 - 1, y0) || !isInside(x1 - 1, y1 - 1) || !isInside(x0, y1 - 1))
|
||||
return {};
|
||||
}
|
||||
|
||||
BitMatrix res(width, height);
|
||||
for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) {
|
||||
for (int y = y0; y < y1; ++y)
|
||||
for (int x = x0; x < x1; ++x) {
|
||||
auto p = mod2Pix(centered(PointI{x, y}));
|
||||
// Due to a "numerical instability" in the PerspectiveTransform generation/application it has been observed
|
||||
// that even though all boundary grid points get projected inside the image, it can still happen that an
|
||||
// inner grid points is not. See #563. A true perspective transformation cannot have this property.
|
||||
// The following check takes 100% care of the issue and turned out to be less of a performance impact than feared.
|
||||
// TODO: Check some mathematical/numercial property of mod2Pix to determine if it is a perspective transformation.
|
||||
if (!image.isIn(p))
|
||||
return {};
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
log(p, 3);
|
||||
#endif
|
||||
#if 0
|
||||
int sum = 0;
|
||||
for (int dy = -1; dy <= 1; ++dy)
|
||||
for (int dx = -1; dx <= 1; ++dx)
|
||||
sum += image.get(p + PointF(dx, dy));
|
||||
if (sum >= 5)
|
||||
#else
|
||||
if (image.get(p))
|
||||
#endif
|
||||
res.set(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("width: %d, height: %d\n", width, height);
|
||||
// printf("%s", ToString(res).c_str());
|
||||
#endif
|
||||
|
||||
auto projectCorner = [&](PointI p) {
|
||||
for (auto&& [x0, x1, y0, y1, mod2Pix] : rois)
|
||||
if (x0 <= p.x && p.x <= x1 && y0 <= p.y && p.y <= y1)
|
||||
return PointI(mod2Pix(PointF(p)) + PointF(0.5, 0.5));
|
||||
|
||||
return PointI();
|
||||
};
|
||||
|
||||
return {std::move(res),
|
||||
{projectCorner({0, 0}), projectCorner({width, 0}), projectCorner({width, height}), projectCorner({0, height})}};
|
||||
}
|
||||
|
||||
DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix,
|
||||
Matrix<std::optional<PointF>>&& apP, const std::vector<int>& apMX, const std::vector<int>& apMY)
|
||||
{
|
||||
const int W = Size(apMX) - 1, H = Size(apMY) - 1;
|
||||
|
||||
// go over the set of alignment patters and fill any remaining gaps by a projection based on the fallback
|
||||
// mod2Pix projection. This works if the symbol is flat, which is a reasonable fall-back assumption.
|
||||
for (int y = 0; y <= H; ++y)
|
||||
for (int x = 0; x <= W; ++x) {
|
||||
if (apP(x, y)) {
|
||||
#ifdef PRINT_DEBUG
|
||||
log(*apP(x, y), 2);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("locate failed at %dx%d\n", x, y);
|
||||
#endif
|
||||
// project the alignment pattern at module coordinates x/y to pixel coordinate based on current mod2Pix
|
||||
apP.set(x, y, mod2Pix(centered(PointI(apMX[x], apMY[y]))));
|
||||
}
|
||||
}
|
||||
|
||||
// assemble a list of region-of-interests based on the found alignment pattern pixel positions
|
||||
ROIs rois;
|
||||
for (int y = 0; y < H; ++y)
|
||||
for (int x = 0; x < W; ++x) {
|
||||
int x0 = apMX[x], x1 = apMX[x + 1], y0 = apMY[y], y1 = apMY[y + 1];
|
||||
rois.push_back({x == 0 ? 0 : x0, (x == W - 1) ? width : x1, (y == 0) ? 0 : y0, (y == H - 1) ? height : y1,
|
||||
PerspectiveTransform{Rectangle(x0, x1, y0, y1, 0.5),
|
||||
{*apP(x, y), *apP(x + 1, y), *apP(x + 1, y + 1), *apP(x, y + 1)}}});
|
||||
}
|
||||
|
||||
return SampleGrid(image, width, height, rois);
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DetectorResult.h"
|
||||
#include "Matrix.h"
|
||||
#include "PerspectiveTransform.h"
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* Samples an image for a rectangular matrix of bits of the given dimension. The sampling
|
||||
* transformation is determined by the coordinates of 4 points, in the original and transformed
|
||||
* image space.
|
||||
*
|
||||
* The following figure is showing the layout a 'pixel'. The point (0,0) is the upper left corner
|
||||
* of the first pixel. (1,1) is its lower right corner.
|
||||
*
|
||||
* 0 1 ... w
|
||||
* 0 #----#-- ... --#
|
||||
* | | ... |
|
||||
* | | ... |
|
||||
* 1 #----# ... --#
|
||||
* | | ... |
|
||||
*
|
||||
* | | ... |
|
||||
* h #----#-- ... --#
|
||||
*
|
||||
* @param image image to sample
|
||||
* @param width width of {@link BitMatrix} to sample from image
|
||||
* @param height height of {@link BitMatrix} to sample from image
|
||||
* @param mod2Pix transforming a module (grid) coordinate into an image (pixel) coordinate
|
||||
* @return {@link DetectorResult} representing a grid of points sampled from the image within a region
|
||||
* defined by the "src" parameters. Result is empty if transformation is invalid (out of bound access).
|
||||
*/
|
||||
DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix);
|
||||
|
||||
class ROI
|
||||
{
|
||||
public:
|
||||
int x0, x1, y0, y1;
|
||||
PerspectiveTransform mod2Pix;
|
||||
};
|
||||
|
||||
using ROIs = std::vector<ROI>;
|
||||
|
||||
DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const ROIs& rois);
|
||||
|
||||
DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix,
|
||||
Matrix<std::optional<PointF>>&& apP, const std::vector<int>& apMX, const std::vector<int>& apMY);
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "HRI.h"
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
struct AiInfo
|
||||
{
|
||||
const char aiPrefix[5];
|
||||
int8_t _fieldSize; // if negative, the length is variable and abs(length) give the max size
|
||||
|
||||
bool isVariableLength() const noexcept { return _fieldSize < 0; }
|
||||
int fieldSize() const noexcept { return std::abs(_fieldSize); }
|
||||
int aiSize() const
|
||||
{
|
||||
using namespace std::literals;
|
||||
if ((aiPrefix[0] == '3' && Contains("1234569", aiPrefix[1])) || aiPrefix == "703"sv || aiPrefix == "723"sv)
|
||||
return 4;
|
||||
else
|
||||
return narrow_cast<int>(strlen(aiPrefix));
|
||||
}
|
||||
};
|
||||
|
||||
// https://github.com/gs1/gs1-syntax-dictionary 2025-12-08 HEAD [c7e0c5b]
|
||||
static const AiInfo aiInfos[] = {
|
||||
//TWO_DIGIT_DATA_LENGTH
|
||||
{ "00", 18 },
|
||||
{ "01", 14 },
|
||||
{ "02", 14 },
|
||||
{ "03", 14 },
|
||||
|
||||
{ "10", -20 },
|
||||
{ "11", 6 },
|
||||
{ "12", 6 },
|
||||
{ "13", 6 },
|
||||
{ "15", 6 },
|
||||
{ "16", 6 },
|
||||
{ "17", 6 },
|
||||
|
||||
{ "20", 2 },
|
||||
{ "21", -20 },
|
||||
{ "22", -20 },
|
||||
|
||||
{ "30", -8 },
|
||||
{ "37", -8 },
|
||||
|
||||
{ "90", -30 },
|
||||
{ "91", -90 },
|
||||
{ "92", -90 },
|
||||
{ "93", -90 },
|
||||
{ "94", -90 },
|
||||
{ "95", -90 },
|
||||
{ "96", -90 },
|
||||
{ "97", -90 },
|
||||
{ "98", -90 },
|
||||
{ "99", -90 },
|
||||
|
||||
//THREE_DIGIT_DATA_LENGTH
|
||||
{ "235", -28 },
|
||||
{ "240", -30 },
|
||||
{ "241", -30 },
|
||||
{ "242", -6 },
|
||||
{ "243", -20 },
|
||||
{ "250", -30 },
|
||||
{ "251", -30 },
|
||||
{ "253", -30 },
|
||||
{ "254", -20 },
|
||||
{ "255", -25 },
|
||||
|
||||
{ "400", -30 },
|
||||
{ "401", -30 },
|
||||
{ "402", 17 },
|
||||
{ "403", -30 },
|
||||
{ "410", 13 },
|
||||
{ "411", 13 },
|
||||
{ "412", 13 },
|
||||
{ "413", 13 },
|
||||
{ "414", 13 },
|
||||
{ "415", 13 },
|
||||
{ "416", 13 },
|
||||
{ "417", 13 },
|
||||
{ "420", -20 },
|
||||
{ "421", -12 },
|
||||
{ "422", 3 },
|
||||
{ "423", -15 },
|
||||
{ "424", 3 },
|
||||
{ "425", -15 },
|
||||
{ "426", 3 },
|
||||
{ "427", -3 },
|
||||
|
||||
{ "710", -20 },
|
||||
{ "711", -20 },
|
||||
{ "712", -20 },
|
||||
{ "713", -20 },
|
||||
{ "714", -20 },
|
||||
{ "715", -20 },
|
||||
{ "716", -20 },
|
||||
{ "717", -20 },
|
||||
|
||||
//THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH
|
||||
{ "310", 6 },
|
||||
{ "311", 6 },
|
||||
{ "312", 6 },
|
||||
{ "313", 6 },
|
||||
{ "314", 6 },
|
||||
{ "315", 6 },
|
||||
{ "316", 6 },
|
||||
{ "320", 6 },
|
||||
{ "321", 6 },
|
||||
{ "322", 6 },
|
||||
{ "323", 6 },
|
||||
{ "324", 6 },
|
||||
{ "325", 6 },
|
||||
{ "326", 6 },
|
||||
{ "327", 6 },
|
||||
{ "328", 6 },
|
||||
{ "329", 6 },
|
||||
{ "330", 6 },
|
||||
{ "331", 6 },
|
||||
{ "332", 6 },
|
||||
{ "333", 6 },
|
||||
{ "334", 6 },
|
||||
{ "335", 6 },
|
||||
{ "336", 6 },
|
||||
{ "337", 6 },
|
||||
{ "340", 6 },
|
||||
{ "341", 6 },
|
||||
{ "342", 6 },
|
||||
{ "343", 6 },
|
||||
{ "344", 6 },
|
||||
{ "345", 6 },
|
||||
{ "346", 6 },
|
||||
{ "347", 6 },
|
||||
{ "348", 6 },
|
||||
{ "349", 6 },
|
||||
{ "350", 6 },
|
||||
{ "351", 6 },
|
||||
{ "352", 6 },
|
||||
{ "353", 6 },
|
||||
{ "354", 6 },
|
||||
{ "355", 6 },
|
||||
{ "356", 6 },
|
||||
{ "357", 6 },
|
||||
{ "360", 6 },
|
||||
{ "361", 6 },
|
||||
{ "362", 6 },
|
||||
{ "363", 6 },
|
||||
{ "364", 6 },
|
||||
{ "365", 6 },
|
||||
{ "366", 6 },
|
||||
{ "367", 6 },
|
||||
{ "368", 6 },
|
||||
{ "369", 6 },
|
||||
{ "390", -15 },
|
||||
{ "391", -18 },
|
||||
{ "392", -15 },
|
||||
{ "393", -18 },
|
||||
{ "394", 4 },
|
||||
{ "395", 6 },
|
||||
{ "703", -30 },
|
||||
{ "723", -30 },
|
||||
|
||||
//FOUR_DIGIT_DATA_LENGTH
|
||||
{ "4300", -35 },
|
||||
{ "4301", -35 },
|
||||
{ "4302", -70 },
|
||||
{ "4303", -70 },
|
||||
{ "4304", -70 },
|
||||
{ "4305", -70 },
|
||||
{ "4306", -70 },
|
||||
{ "4307", 2 },
|
||||
{ "4308", -30 },
|
||||
{ "4309", 20 },
|
||||
{ "4310", -35 },
|
||||
{ "4311", -35 },
|
||||
{ "4312", -70 },
|
||||
{ "4313", -70 },
|
||||
{ "4314", -70 },
|
||||
{ "4315", -70 },
|
||||
{ "4316", -70 },
|
||||
{ "4317", 2 },
|
||||
{ "4318", -20 },
|
||||
{ "4319", -30 },
|
||||
{ "4320", -35 },
|
||||
{ "4321", 1 },
|
||||
{ "4322", 1 },
|
||||
{ "4323", 1 },
|
||||
{ "4324", 10 },
|
||||
{ "4325", 10 },
|
||||
{ "4326", 6 },
|
||||
{ "4330", -7 },
|
||||
{ "4331", -7 },
|
||||
{ "4332", -7 },
|
||||
{ "4333", -7 },
|
||||
|
||||
{ "7001", 13 },
|
||||
{ "7002", -30 },
|
||||
{ "7003", 10 },
|
||||
{ "7004", -4 },
|
||||
{ "7005", -12 },
|
||||
{ "7006", 6 },
|
||||
{ "7007", -12 },
|
||||
{ "7008", -3 },
|
||||
{ "7009", -10 },
|
||||
{ "7010", -2 },
|
||||
{ "7011", -10 },
|
||||
{ "7020", -20 },
|
||||
{ "7021", -20 },
|
||||
{ "7022", -20 },
|
||||
{ "7023", -30 },
|
||||
{ "7040", 4 },
|
||||
{ "7041", -4 },
|
||||
{ "7240", -20 },
|
||||
{ "7241", 2 },
|
||||
{ "7242", -25 },
|
||||
{ "7250", 8 },
|
||||
{ "7251", 12 },
|
||||
{ "7252", 1 },
|
||||
{ "7253", -40 },
|
||||
{ "7254", -40 },
|
||||
{ "7255", -10 },
|
||||
{ "7256", -90 },
|
||||
{ "7257", -70 },
|
||||
{ "7258", 3 },
|
||||
{ "7259", -40 },
|
||||
|
||||
{ "8001", 14 },
|
||||
{ "8002", -20 },
|
||||
{ "8003", -30 },
|
||||
{ "8004", -30 },
|
||||
{ "8005", 6 },
|
||||
{ "8006", 18 },
|
||||
{ "8007", -34 },
|
||||
{ "8008", -12 },
|
||||
{ "8009", -50 },
|
||||
{ "8010", -30 },
|
||||
{ "8011", -12 },
|
||||
{ "8012", -20 },
|
||||
{ "8013", -25 },
|
||||
{ "8014", -25 },
|
||||
{ "8017", 18 },
|
||||
{ "8018", 18 },
|
||||
{ "8019", -10 },
|
||||
{ "8020", -25 },
|
||||
{ "8026", 18 },
|
||||
{ "8030", -90 },
|
||||
{ "8040", 15 },
|
||||
{ "8041", 15 },
|
||||
{ "8042", 32 },
|
||||
{ "8043", -20 },
|
||||
{ "8110", -70 },
|
||||
{ "8111", 4 },
|
||||
{ "8112", -70 },
|
||||
{ "8200", -70 },
|
||||
};
|
||||
|
||||
std::string HRIFromGS1(std::string_view gs1)
|
||||
{
|
||||
constexpr char GS = 29; // GS character (29 / 0x1D)
|
||||
|
||||
std::string_view rem = gs1;
|
||||
std::string res;
|
||||
|
||||
while (rem.size()) {
|
||||
const AiInfo* i = FindIf(aiInfos, [&](const AiInfo& i) { return rem.starts_with(i.aiPrefix); });
|
||||
if (i == std::end(aiInfos))
|
||||
return {};
|
||||
|
||||
int aiSize = i->aiSize();
|
||||
if (Size(rem) < aiSize)
|
||||
return {};
|
||||
|
||||
res += '(';
|
||||
res += rem.substr(0, aiSize);
|
||||
res += ')';
|
||||
rem.remove_prefix(aiSize);
|
||||
|
||||
int fieldSize = i->fieldSize();
|
||||
if (i->isVariableLength()) {
|
||||
auto gsPos = rem.find(GS);
|
||||
#if 1
|
||||
fieldSize = std::min(gsPos == std::string_view::npos ? Size(rem) : narrow_cast<int>(gsPos), fieldSize);
|
||||
#else
|
||||
// TODO: ignore the 'max field size' part for now as it breaks rssexpanded-3/13.png?
|
||||
fieldSize = gsPos == std::string_view::npos ? Size(rem) : narrow_cast<int>(gsPos);
|
||||
#endif
|
||||
}
|
||||
if (fieldSize == 0 || Size(rem) < fieldSize)
|
||||
return {};
|
||||
|
||||
res += rem.substr(0, fieldSize);
|
||||
rem.remove_prefix(fieldSize);
|
||||
|
||||
// See General Specification v22.0 Section 7.8.6.3: "...the processing routine SHALL tolerate a single separator character
|
||||
// immediately following any element string, whether necessary or not..."
|
||||
if (Size(rem) && rem.front() == GS)
|
||||
rem.remove_prefix(1);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string HRIFromISO15434(std::string_view str)
|
||||
{
|
||||
// Use available unicode symbols to simulate sub- and superscript letters as specified in
|
||||
// ISO/IEC 15434:2019(E) 6. Human readable representation
|
||||
|
||||
std::string res;
|
||||
res.reserve(str.size());
|
||||
|
||||
for (char c : str) {
|
||||
#if 1
|
||||
if (!((c) & ~0x1f) || c == 0x20)
|
||||
(res += "\xe2\x90") += char(0x80 + c); // Unicode Block “Control Pictures”: 0x2400
|
||||
else
|
||||
res += c;
|
||||
#else
|
||||
switch (c) {
|
||||
case 4: oss << u8"\u1d31\u1d52\u209c"; break; // EOT
|
||||
case 28: oss << u8"\ua7f3\u209b"; break; // FS
|
||||
case 29: oss << u8"\u1d33\u209b"; break; // GS
|
||||
case 30: oss << u8"\u1d3f\u209b"; break; // RS
|
||||
case 31: oss << u8"\u1d41\u209b"; break; // US
|
||||
default: oss << c;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::string HRIFromGS1(std::string_view gs1);
|
||||
std::string HRIFromISO15434(std::string_view str);
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "HybridBinarizer.h"
|
||||
|
||||
#include "BitMatrix.h"
|
||||
#include "Matrix.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
#define USE_NEW_ALGORITHM
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
// This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels.
|
||||
// So this is the smallest dimension in each axis we can accept.
|
||||
static constexpr int BLOCK_SIZE = 8;
|
||||
static constexpr int WINDOW_SIZE = BLOCK_SIZE * (1 + 2 * 2);
|
||||
static constexpr int MIN_DYNAMIC_RANGE = 24;
|
||||
|
||||
HybridBinarizer::HybridBinarizer(const ImageView& iv) : GlobalHistogramBinarizer(iv) {}
|
||||
|
||||
HybridBinarizer::~HybridBinarizer() = default;
|
||||
|
||||
bool HybridBinarizer::getPatternRow(int row, int rotation, PatternRow& res) const
|
||||
{
|
||||
#if 1
|
||||
// This is the original "hybrid" behavior: use GlobalHistogram for the 1D case
|
||||
return GlobalHistogramBinarizer::getPatternRow(row, rotation, res);
|
||||
#else
|
||||
// This is an alternative that can be faster in general and perform better in unevenly lit situations like
|
||||
// https://github.com/zxing-cpp/zxing-cpp/blob/master/test/samples/ean13-2/21.png. That said, it fairs
|
||||
// worse in borderline low resolution situations. With the current black box sample set we'd loose 94
|
||||
// test cases while gaining 53 others.
|
||||
auto bits = getBitMatrix();
|
||||
if (bits)
|
||||
GetPatternRow(*bits, row, res, rotation % 180 != 0);
|
||||
return bits != nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
using T_t = uint8_t;
|
||||
|
||||
/**
|
||||
* Applies a single threshold to a block of pixels.
|
||||
*/
|
||||
static void ThresholdBlock(const uint8_t* __restrict luminances, int xoffset, int yoffset, T_t threshold, int rowStride,
|
||||
BitMatrix& matrix)
|
||||
{
|
||||
for (int y = yoffset; y < yoffset + BLOCK_SIZE; ++y) {
|
||||
auto* src = luminances + y * rowStride + xoffset;
|
||||
auto* const dstBegin = matrix.row(y).begin() + xoffset;
|
||||
// TODO: fix pixelStride > 1 case
|
||||
for (auto* dst = dstBegin; dst < dstBegin + BLOCK_SIZE; ++dst, ++src)
|
||||
*dst = (*src <= threshold) * BitMatrix::SET_V;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef USE_NEW_ALGORITHM
|
||||
|
||||
/**
|
||||
* Calculates a single black point for each block of pixels and saves it away.
|
||||
* See the following thread for a discussion of this algorithm:
|
||||
* http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0
|
||||
*/
|
||||
static Matrix<T_t> CalculateBlackPoints(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, int height,
|
||||
int rowStride)
|
||||
{
|
||||
Matrix<T_t> blackPoints(subWidth, subHeight);
|
||||
|
||||
for (int y = 0; y < subHeight; y++) {
|
||||
int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE);
|
||||
for (int x = 0; x < subWidth; x++) {
|
||||
int xoffset = std::min(x * BLOCK_SIZE, width - BLOCK_SIZE);
|
||||
int sum = 0;
|
||||
uint8_t min = luminances[yoffset * rowStride + xoffset];
|
||||
uint8_t max = min;
|
||||
for (int yy = 0, offset = yoffset * rowStride + xoffset; yy < BLOCK_SIZE; yy++, offset += rowStride) {
|
||||
for (int xx = 0; xx < BLOCK_SIZE; xx++) {
|
||||
auto pixel = luminances[offset + xx];
|
||||
sum += pixel;
|
||||
if (pixel < min)
|
||||
min = pixel;
|
||||
if (pixel > max)
|
||||
max = pixel;
|
||||
}
|
||||
// short-circuit min/max tests once dynamic range is met
|
||||
if (max - min > MIN_DYNAMIC_RANGE) {
|
||||
// finish the rest of the rows quickly
|
||||
for (yy++, offset += rowStride; yy < BLOCK_SIZE; yy++, offset += rowStride) {
|
||||
for (int xx = 0; xx < BLOCK_SIZE; xx++) {
|
||||
sum += luminances[offset + xx];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The default estimate is the average of the values in the block.
|
||||
int average = sum / (BLOCK_SIZE * BLOCK_SIZE);
|
||||
if (max - min <= MIN_DYNAMIC_RANGE) {
|
||||
// If variation within the block is low, assume this is a block with only light or only
|
||||
// dark pixels. In that case we do not want to use the average, as it would divide this
|
||||
// low contrast area into black and white pixels, essentially creating data out of noise.
|
||||
//
|
||||
// The default assumption is that the block is light/background. Since no estimate for
|
||||
// the level of dark pixels exists locally, use half the min for the block.
|
||||
average = min / 2;
|
||||
|
||||
if (y > 0 && x > 0) {
|
||||
// Correct the "white background" assumption for blocks that have neighbors by comparing
|
||||
// the pixels in this block to the previously calculated black points. This is based on
|
||||
// the fact that dark barcode symbology is always surrounded by some amount of light
|
||||
// background for which reasonable black point estimates were made. The bp estimated at
|
||||
// the boundaries is used for the interior.
|
||||
|
||||
// The (min < bp) is arbitrary but works better than other heuristics that were tried.
|
||||
int averageNeighborBlackPoint =
|
||||
(blackPoints(x, y - 1) + (2 * blackPoints(x - 1, y)) + blackPoints(x - 1, y - 1)) / 4;
|
||||
if (min < averageNeighborBlackPoint) {
|
||||
average = averageNeighborBlackPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
blackPoints(x, y) = average;
|
||||
}
|
||||
}
|
||||
return blackPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each block in the image, calculate the average black point using a 5x5 grid
|
||||
* of the blocks around it. Also handles the corner cases (fractional blocks are computed based
|
||||
* on the last pixels in the row/column which are also used in the previous block).
|
||||
*/
|
||||
static std::shared_ptr<BitMatrix> CalculateMatrix(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width,
|
||||
int height, int rowStride, const Matrix<T_t>& blackPoints)
|
||||
{
|
||||
auto matrix = std::make_shared<BitMatrix>(width, height);
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
Matrix<uint8_t> out(width, height);
|
||||
Matrix<uint8_t> out2(width, height);
|
||||
#endif
|
||||
|
||||
for (int y = 0; y < subHeight; y++) {
|
||||
int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE);
|
||||
for (int x = 0; x < subWidth; x++) {
|
||||
int xoffset = std::min(x * BLOCK_SIZE, width - BLOCK_SIZE);
|
||||
int left = std::clamp(x, 2, subWidth - 3);
|
||||
int top = std::clamp(y, 2, subHeight - 3);
|
||||
int sum = 0;
|
||||
for (int dy = -2; dy <= 2; ++dy) {
|
||||
for (int dx = -2; dx <= 2; ++dx) {
|
||||
sum += blackPoints(left + dx, top + dy);
|
||||
}
|
||||
}
|
||||
int average = sum / 25;
|
||||
ThresholdBlock(luminances, xoffset, yoffset, average, rowStride, *matrix);
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
for (int yy = 0; yy < 8; ++yy)
|
||||
for (int xx = 0; xx < 8; ++xx) {
|
||||
out.set(xoffset + xx, yoffset + yy, blackPoints(x, y));
|
||||
out2.set(xoffset + xx, yoffset + yy, average);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
std::ofstream file("thresholds.pnm");
|
||||
file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n";
|
||||
file.write(reinterpret_cast<const char*>(out.data()), out.size());
|
||||
std::ofstream file2("thresholds_avg.pnm");
|
||||
file2 << "P5\n" << out.width() << ' ' << out.height() << "\n255\n";
|
||||
file2.write(reinterpret_cast<const char*>(out2.data()), out2.size());
|
||||
#endif
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// Subdivide the image in blocks of BLOCK_SIZE and calculate one threshold value per block as
|
||||
// (max - min > MIN_DYNAMIC_RANGE) ? (max + min) / 2 : 0
|
||||
static Matrix<T_t> BlockThresholds(const ImageView iv)
|
||||
{
|
||||
int subWidth = (iv.width() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(width/BS)
|
||||
int subHeight = (iv.height() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(height/BS)
|
||||
|
||||
Matrix<T_t> thresholds(subWidth, subHeight);
|
||||
|
||||
for (int y = 0; y < subHeight; y++) {
|
||||
int y0 = std::min(y * BLOCK_SIZE, iv.height() - BLOCK_SIZE);
|
||||
for (int x = 0; x < subWidth; x++) {
|
||||
int x0 = std::min(x * BLOCK_SIZE, iv.width() - BLOCK_SIZE);
|
||||
uint8_t min = 255;
|
||||
uint8_t max = 0;
|
||||
for (int yy = 0; yy < BLOCK_SIZE; yy++) {
|
||||
auto line = iv.data(x0, y0 + yy);
|
||||
for (int xx = 0; xx < BLOCK_SIZE; xx++)
|
||||
UpdateMinMax(min, max, line[xx]);
|
||||
}
|
||||
|
||||
thresholds(x, y) = (max - min > MIN_DYNAMIC_RANGE) ? (int(max) + min) / 2 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
return thresholds;
|
||||
}
|
||||
|
||||
// Apply gaussian-like smoothing filter over all non-zero thresholds and fill any remaining gaps with nearest neighbor
|
||||
static Matrix<T_t> SmoothThresholds(Matrix<T_t>&& in)
|
||||
{
|
||||
Matrix<T_t> out(in.width(), in.height());
|
||||
|
||||
constexpr int R = WINDOW_SIZE / BLOCK_SIZE / 2;
|
||||
for (int y = 0; y < in.height(); y++) {
|
||||
for (int x = 0; x < in.width(); x++) {
|
||||
int left = std::clamp(x, R, in.width() - R - 1);
|
||||
int top = std::clamp(y, R, in.height() - R - 1);
|
||||
|
||||
int sum = in(x, y) * 2;
|
||||
int n = (sum > 0) * 2;
|
||||
auto add = [&](int x, int y) {
|
||||
int t = in(x, y);
|
||||
sum += t;
|
||||
n += t > 0;
|
||||
};
|
||||
|
||||
for (int dy = -R; dy <= R; ++dy)
|
||||
for (int dx = -R; dx <= R; ++dx)
|
||||
add(left + dx, top + dy);
|
||||
|
||||
out(x, y) = n > 0 ? sum / n : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// flood fill any remaining gaps of (very large) no-contrast regions
|
||||
auto last = out.begin() - 1;
|
||||
for (auto* i = out.begin(); i != out.end(); ++i) {
|
||||
if (*i) {
|
||||
if (last != i - 1)
|
||||
std::fill(last + 1, i, *i);
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
std::fill(last + 1, out.end(), *(std::max(last, out.begin())));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::shared_ptr<BitMatrix> ThresholdImage(const ImageView iv, const Matrix<T_t>& thresholds)
|
||||
{
|
||||
auto matrix = std::make_shared<BitMatrix>(iv.width(), iv.height());
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
Matrix<uint8_t> out(iv.width(), iv.height());
|
||||
#endif
|
||||
|
||||
for (int y = 0; y < thresholds.height(); y++) {
|
||||
int yoffset = std::min(y * BLOCK_SIZE, iv.height() - BLOCK_SIZE);
|
||||
for (int x = 0; x < thresholds.width(); x++) {
|
||||
int xoffset = std::min(x * BLOCK_SIZE, iv.width() - BLOCK_SIZE);
|
||||
ThresholdBlock(iv.data(), xoffset, yoffset, thresholds(x, y), iv.rowStride(), *matrix);
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
for (int yy = 0; yy < 8; ++yy)
|
||||
for (int xx = 0; xx < 8; ++xx)
|
||||
out.set(xoffset + xx, yoffset + yy, thresholds(x, y));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
std::ofstream file("thresholds_new.pnm");
|
||||
file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n";
|
||||
file.write(reinterpret_cast<const char*>(out.data()), out.size());
|
||||
#endif
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::shared_ptr<const BitMatrix> HybridBinarizer::getBlackMatrix() const
|
||||
{
|
||||
if (width() >= WINDOW_SIZE && height() >= WINDOW_SIZE) {
|
||||
#ifdef USE_NEW_ALGORITHM
|
||||
auto thrs = SmoothThresholds(BlockThresholds(_buffer));
|
||||
if (std::ranges::max(thrs) == 0)
|
||||
return GlobalHistogramBinarizer::getBlackMatrix();
|
||||
return ThresholdImage(_buffer, thrs);
|
||||
#else
|
||||
const uint8_t* luminances = _buffer.data();
|
||||
int subWidth = (width() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(width/BS)
|
||||
int subHeight = (height() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(height/BS)
|
||||
auto blackPoints =
|
||||
CalculateBlackPoints(luminances, subWidth, subHeight, width(), height(), _buffer.rowStride());
|
||||
|
||||
return CalculateMatrix(luminances, subWidth, subHeight, width(), height(), _buffer.rowStride(), blackPoints);
|
||||
#endif
|
||||
} else {
|
||||
// If the image is too small, fall back to the global histogram approach.
|
||||
return GlobalHistogramBinarizer::getBlackMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GlobalHistogramBinarizer.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* This class implements a local thresholding algorithm, which while slower than the
|
||||
* GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for
|
||||
* high frequency images of barcodes with black data on white backgrounds. For this application,
|
||||
* it does a much better job than a global blackpoint with severe shadows and gradients.
|
||||
* However it tends to produce artifacts on lower frequency images and is therefore not
|
||||
* a good general purpose binarizer for uses outside ZXing.
|
||||
*
|
||||
* This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers,
|
||||
* and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already
|
||||
* inherently local, and only fails for horizontal gradients. We can revisit that problem later,
|
||||
* but for now it was not a win to use local blocks for 1D.
|
||||
*
|
||||
* This Binarizer is the default for the unit tests and the recommended class for library users.
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
*/
|
||||
class HybridBinarizer : public GlobalHistogramBinarizer
|
||||
{
|
||||
public:
|
||||
explicit HybridBinarizer(const ImageView& iv);
|
||||
~HybridBinarizer() override;
|
||||
|
||||
bool getPatternRow(int row, int rotation, PatternRow &res) const override;
|
||||
std::shared_ptr<const BitMatrix> getBlackMatrix() const override;
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2019 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/// @brief Supported image formats for ImageView. The format encodes the pixel format and layout information.
|
||||
enum class ImageFormat : uint32_t
|
||||
{
|
||||
None = 0,
|
||||
Lum = 0x01000000,
|
||||
LumA = 0x02000000,
|
||||
RGB = 0x03000102,
|
||||
BGR = 0x03020100,
|
||||
RGBA = 0x04000102,
|
||||
ARGB = 0x04010203,
|
||||
BGRA = 0x04020100,
|
||||
ABGR = 0x04030201,
|
||||
RGBX [[deprecated("use RGBA")]] = RGBA,
|
||||
XRGB [[deprecated("use ARGB")]] = ARGB,
|
||||
BGRX [[deprecated("use BGRA")]] = BGRA,
|
||||
XBGR [[deprecated("use ABGR")]] = ABGR,
|
||||
};
|
||||
|
||||
/// @cond INTERNAL
|
||||
constexpr inline int PixStride(ImageFormat format) { return (static_cast<uint32_t>(format) >> 3*8) & 0xFF; }
|
||||
constexpr inline int RedIndex(ImageFormat format) { return (static_cast<uint32_t>(format) >> 2*8) & 0xFF; }
|
||||
constexpr inline int GreenIndex(ImageFormat format) { return (static_cast<uint32_t>(format) >> 1*8) & 0xFF; }
|
||||
constexpr inline int BlueIndex(ImageFormat format) { return (static_cast<uint32_t>(format) >> 0*8) & 0xFF; }
|
||||
|
||||
constexpr inline uint8_t RGBToLum(unsigned r, unsigned g, unsigned b)
|
||||
{
|
||||
// .299R + 0.587G + 0.114B (YUV/YIQ for PAL and NTSC),
|
||||
// (306*R) >> 10 is approximately equal to R*0.299, and so on.
|
||||
// 0x200 >> 10 is 0.5, it implements rounding.
|
||||
return static_cast<uint8_t>((306 * r + 601 * g + 117 * b + 0x200) >> 10);
|
||||
}
|
||||
/// @endcond
|
||||
|
||||
/**
|
||||
* @brief Simple class that stores a non-owning const pointer to image data plus layout and format information.
|
||||
*/
|
||||
class ImageView
|
||||
{
|
||||
/// @cond INTERNAL
|
||||
protected:
|
||||
const uint8_t* _data = nullptr;
|
||||
ImageFormat _format = ImageFormat::None;
|
||||
int _width = 0, _height = 0, _pixStride = 0, _rowStride = 0;
|
||||
/// @endcond
|
||||
|
||||
public:
|
||||
/**
|
||||
* ImageView default constructor creates a 'null' image view
|
||||
*/
|
||||
ImageView() = default;
|
||||
|
||||
/**
|
||||
* ImageView constructor
|
||||
*
|
||||
* @param data pointer to image buffer
|
||||
* @param width image width in pixels
|
||||
* @param height image height in pixels
|
||||
* @param format image/pixel format
|
||||
* @param rowStride optional row stride in bytes, default is width * pixStride
|
||||
* @param pixStride optional pixel stride in bytes, default is calculated from format
|
||||
*/
|
||||
ImageView(const uint8_t* data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0)
|
||||
: _data(data),
|
||||
_format(format),
|
||||
_width(width),
|
||||
_height(height),
|
||||
_pixStride(pixStride ? pixStride : PixStride(format)),
|
||||
_rowStride(rowStride ? rowStride : width * _pixStride)
|
||||
{
|
||||
if (_data == nullptr)
|
||||
throw std::invalid_argument("Can not construct an ImageView from a NULL pointer");
|
||||
|
||||
if (_width <= 0 || _height <= 0)
|
||||
throw std::invalid_argument("Neither width nor height of ImageView can be less or equal to 0");
|
||||
}
|
||||
|
||||
/**
|
||||
* ImageView constructor with bounds checking.
|
||||
*
|
||||
* This constructor checks if the provided size is consistent with the width, height, row stride and pixel stride
|
||||
* parameters to prevent out of bounds access when using the ImageView.
|
||||
*
|
||||
* @param data pointer to image buffer
|
||||
* @param size size of the image buffer in bytes
|
||||
* @param width image width in pixels
|
||||
* @param height image height in pixels
|
||||
* @param format image/pixel format
|
||||
* @param rowStride optional row stride in bytes, default is width * pixStride
|
||||
* @param pixStride optional pixel stride in bytes, default is calculated from format
|
||||
*/
|
||||
ImageView(const uint8_t* data, int size, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0)
|
||||
: ImageView(data, width, height, format, rowStride, pixStride)
|
||||
{
|
||||
if (_rowStride < 0 || _pixStride < 0 || size < _height * _rowStride)
|
||||
throw std::invalid_argument("ImageView parameters are inconsistent (out of bounds)");
|
||||
}
|
||||
|
||||
int width() const { return _width; }
|
||||
int height() const { return _height; }
|
||||
int pixStride() const { return _pixStride; }
|
||||
int rowStride() const { return _rowStride; }
|
||||
ImageFormat format() const { return _format; }
|
||||
|
||||
const uint8_t* data() const { return _data; }
|
||||
const uint8_t* data(int x, int y) const { return _data + y * _rowStride + x * _pixStride; }
|
||||
|
||||
/// Create a new ImageView that is a cropped version of this one.
|
||||
/// Negative width/height will be interpreted as "until the end of the line/image".
|
||||
ImageView cropped(int left, int top, int width, int height) const
|
||||
{
|
||||
left = std::clamp(left, 0, _width - 1);
|
||||
top = std::clamp(top, 0, _height - 1);
|
||||
width = width <= 0 ? (_width - left) : std::min(_width - left, width);
|
||||
height = height <= 0 ? (_height - top) : std::min(_height - top, height);
|
||||
return {data(left, top), width, height, _format, _rowStride, _pixStride};
|
||||
}
|
||||
|
||||
/// Create a new ImageView that is a rotated version of this one.
|
||||
/// Rotation is clockwise and only supports 90 degree steps.
|
||||
ImageView rotated(int degree) const
|
||||
{
|
||||
switch ((degree + 360) % 360) {
|
||||
case 90: return {data(0, _height - 1), _height, _width, _format, _pixStride, -_rowStride};
|
||||
case 180: return {data(_width - 1, _height - 1), _width, _height, _format, -_rowStride, -_pixStride};
|
||||
case 270: return {data(_width - 1, 0), _height, _width, _format, -_pixStride, _rowStride};
|
||||
default: return *this;
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new ImageView that is a subsampled version of this one.
|
||||
/// The subsampled image will have width/height of original divided by scale.
|
||||
ImageView subsampled(int scale) const
|
||||
{
|
||||
return {_data, _width / scale, _height / scale, _format, _rowStride * scale, _pixStride * scale};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Simple class that inherits ImageView but owns the image data and frees it on destruction.
|
||||
*/
|
||||
class Image : public ImageView
|
||||
{
|
||||
std::unique_ptr<uint8_t[]> _memory;
|
||||
Image(std::unique_ptr<uint8_t[]>&& data, int w, int h, ImageFormat f) : ImageView(data.get(), w, h, f), _memory(std::move(data)) {}
|
||||
|
||||
public:
|
||||
Image() = default;
|
||||
Image(int w, int h, ImageFormat f = ImageFormat::Lum) : Image(std::make_unique<uint8_t[]>(w * h * PixStride(f)), w, h, f) {}
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2025 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "JSON.h"
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
// This code is trying to find the value of a key-value pair in a string of those.
|
||||
// The input could be valid JSON, like '{"key": "val"}' or a stripped down version like
|
||||
// 'key:val'. This is also compatible with the string serialization of a python dictionary.
|
||||
// For convenience, also 'key=val' is supported, key is checked case insensitive and '_' is ignored.
|
||||
// This could easily be done with the following regex (see below).
|
||||
// But using std::regex adds 140k+ to the binary size. ctre is _very_ nice to use and has
|
||||
// the same binary footprint as the hand-roled code below! But I don't want to add
|
||||
// 5k lines of c++ code for that.
|
||||
|
||||
// #define ZXING_USE_CTRE
|
||||
#ifdef ZXING_USE_CTRE
|
||||
#pragma GCC diagnostic ignored "-Wundef" // see https://github.com/hanickadot/compile-time-regular-expressions/issues/340
|
||||
#include "ctre.hpp"
|
||||
#pragma GCC diagnostic error "-Wundef"
|
||||
|
||||
static constexpr auto PATTERN =
|
||||
ctll::fixed_string{R"(["']?([[:alpha:]][[:alnum:]]*)["']?\s*(?:[:]\s*["']?([[:alnum:]]+)["']?)?(?:,|\}|$))"};
|
||||
#endif
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
// Trim whitespace and quotes/braces from both ends
|
||||
inline std::string_view Trim(std::string_view sv)
|
||||
{
|
||||
return TrimWS(sv, " \t\n\r\"'{}");
|
||||
}
|
||||
|
||||
inline bool IsEqualIgnoreCaseAndUnderscore(std::string_view a, std::string_view b)
|
||||
{
|
||||
return IsEqualIgnoreCaseAnd(a, b, "_");
|
||||
}
|
||||
|
||||
std::string_view JsonFind(std::string_view json, std::string_view key)
|
||||
{
|
||||
#ifdef ZXING_USE_CTRE
|
||||
for (auto [ma, mk, mv] : ctre::search_all<PATTERN>(json))
|
||||
if (IsEqualCaseInsensitive(key, mk))
|
||||
return mv.size() ? mv.to_view() : std::string_view(mk.data(), 0);
|
||||
|
||||
return {};
|
||||
#else
|
||||
json = Trim(json);
|
||||
|
||||
// TODO: use something like ForEachToken here?
|
||||
while (!json.empty()) {
|
||||
auto posComma = json.find(',');
|
||||
auto pair = Trim(json.substr(0, posComma));
|
||||
|
||||
if (IsEqualIgnoreCaseAndUnderscore(pair, key))
|
||||
return {pair.data(), 0}; // return non-null data to signal that we found the key
|
||||
|
||||
auto posSep = pair.find_first_of(":=");
|
||||
|
||||
if (posSep != std::string_view::npos && IsEqualIgnoreCaseAndUnderscore(Trim(pair.substr(0, posSep)), key))
|
||||
return Trim(pair.substr(posSep + 1));
|
||||
|
||||
json = (posComma == std::string_view::npos) ? std::string_view{} : json.substr(posComma + 1);
|
||||
}
|
||||
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string JsonEscapeStr(std::string_view str)
|
||||
{
|
||||
std::string res;
|
||||
res.reserve(str.size() + 10);
|
||||
|
||||
for (unsigned char c : str) {
|
||||
switch (c) {
|
||||
case '\"': res += "\\\""; break;
|
||||
case '\\': res += "\\\\"; break;
|
||||
case '\b': res += "\\b"; break;
|
||||
case '\f': res += "\\f"; break;
|
||||
case '\n': res += "\\n"; break;
|
||||
case '\r': res += "\\r"; break;
|
||||
case '\t': res += "\\t"; break;
|
||||
default:
|
||||
if (c <= 0x1F) {
|
||||
char buf[7];
|
||||
std::snprintf(buf, sizeof(buf), "\\u%04X", c);
|
||||
res += buf;
|
||||
} else {
|
||||
res += c;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string JsonUnEscapeStr(std::string_view str)
|
||||
{
|
||||
std::string res;
|
||||
res.reserve(str.size());
|
||||
|
||||
for (size_t i = 0; i < str.size(); ++i) {
|
||||
char c = str[i];
|
||||
if (c == '\\') {
|
||||
if (++i >= str.size())
|
||||
throw std::runtime_error("Invalid escape sequence");
|
||||
|
||||
char esc = str[i];
|
||||
switch (esc) {
|
||||
case '"': res += '\"'; break;
|
||||
case '\\': res += '\\'; break;
|
||||
case '/': res += '/'; break;
|
||||
case 'b': res += '\b'; break;
|
||||
case 'f': res += '\f'; break;
|
||||
case 'n': res += '\n'; break;
|
||||
case 'r': res += '\r'; break;
|
||||
case 't': res += '\t'; break;
|
||||
case 'u': {
|
||||
if (i + 4 >= str.size())
|
||||
throw std::runtime_error("Incomplete \\u escape");
|
||||
|
||||
uint32_t code = 0;
|
||||
auto first = str.data() + i + 1;
|
||||
auto last = first + 4;
|
||||
|
||||
auto [ptr, ec] = std::from_chars(first, last, code, 16);
|
||||
if (ec != std::errc() || ptr != last)
|
||||
throw std::runtime_error("Failed to parse hex code");
|
||||
|
||||
if (code > 0x1F)
|
||||
throw std::runtime_error("Unexpected code point in \\u escape");
|
||||
|
||||
res += static_cast<char>(code);
|
||||
i += 4;
|
||||
break;
|
||||
}
|
||||
default: throw std::runtime_error("Unknown escape sequence");
|
||||
}
|
||||
} else {
|
||||
res += c;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2025 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <cstring>
|
||||
#ifdef __cpp_lib_format // not available on gcc 12
|
||||
#include <format>
|
||||
#endif
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::string JsonEscapeStr(std::string_view str);
|
||||
std::string JsonUnEscapeStr(std::string_view str);
|
||||
|
||||
template<typename T>
|
||||
inline std::string JsonProp(std::string_view key, const T& val, const T& ignore = {})
|
||||
{
|
||||
#define ZX_JSON_KEY_VAL(...) StrCat("\"", key, "\":", __VA_ARGS__, ',')
|
||||
#if !defined(__cpp_lib_to_chars) || !defined(__cpp_lib_format) // not available on older macOS / gcc 12
|
||||
#define ZX_JSON_FMT_FLOAT(val) (std::to_string(int(val)) + '.' + std::to_string(int(val * 100) % 100))
|
||||
#else
|
||||
#define ZX_JSON_FMT_FLOAT(val) std::format("{:.2f}", val)
|
||||
#endif
|
||||
|
||||
if constexpr (std::is_same_v<T, bool>)
|
||||
return val != ignore ? ZX_JSON_KEY_VAL(val ? "true" : "false") : "";
|
||||
else if constexpr (std::is_floating_point_v<T>)
|
||||
return val != ignore ? ZX_JSON_KEY_VAL(ZX_JSON_FMT_FLOAT(val)) : "";
|
||||
else if constexpr (std::is_integral_v<T>)
|
||||
return val != ignore ? ZX_JSON_KEY_VAL(std::to_string(val)) : "";
|
||||
else if constexpr (std::is_convertible_v<T, std::string_view>)
|
||||
return std::string_view(val) != std::string_view(ignore) ? ZX_JSON_KEY_VAL("\"", JsonEscapeStr(val), "\"") : "";
|
||||
else
|
||||
static_assert("unsupported JSON value type");
|
||||
#undef ZX_JSON_KEY_VAL
|
||||
}
|
||||
|
||||
// Finds the (potentially escaped / raw) value of the provided key
|
||||
std::string_view JsonFind(std::string_view json, std::string_view key);
|
||||
|
||||
template <typename T>
|
||||
inline std::optional<T> JsonGet(std::string_view json, std::string_view key)
|
||||
{
|
||||
auto str = JsonFind(json, key);
|
||||
if (!str.data())
|
||||
return std::nullopt;
|
||||
|
||||
if constexpr (std::is_same_v<bool, T>)
|
||||
return str.empty() || Contains("1tT", str.front()) ? std::optional(true) : std::nullopt;
|
||||
else if constexpr (std::is_arithmetic_v<T>)
|
||||
return str.empty() ? std::nullopt : std::optional(FromString<T>(str));
|
||||
else if constexpr (std::is_same_v<std::string, T>)
|
||||
return JsonUnEscapeStr(str);
|
||||
else
|
||||
static_assert("unsupported JSON value type");
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,242 @@
|
||||
// Copyright 2026 Axel Waggershauser
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "LocalGrid.h"
|
||||
|
||||
#include "LogMatrix.h"
|
||||
#include "StdGenerator.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#ifndef PRINT_DEBUG
|
||||
#define printf(...){}
|
||||
#endif
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
// returns the average of the largest cluster of values where cluster is defined as values that are within threshold of each other
|
||||
double clusterAvg(std::ranges::range auto& v, double threshold)
|
||||
{
|
||||
std::ranges::sort(v);
|
||||
size_t bestStart = 0, bestLen = 0, start = 0;
|
||||
for (size_t end = 0; end < v.size(); ++end) {
|
||||
while (v[end] - v[start] >= threshold)
|
||||
++start;
|
||||
if (end - start + 1 > bestLen) {
|
||||
bestLen = end - start + 1;
|
||||
bestStart = start;
|
||||
}
|
||||
}
|
||||
double sum = 0;
|
||||
for (size_t i = bestStart; i < bestStart + bestLen; ++i)
|
||||
sum += v[i];
|
||||
sum /= bestLen;
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("ds: ");
|
||||
for (auto d : v)
|
||||
printf("%5.2f ", d);
|
||||
printf(" -> len: %zu, avg: %5.2f\n", bestLen, sum);
|
||||
#endif
|
||||
return sum;
|
||||
};
|
||||
|
||||
void LocalGrid::adjustOriginAndStep(PointF& step, int radius, const std::span<const PointF> offsets)
|
||||
{
|
||||
auto modSize = length(step);
|
||||
int limit = int(6 * modSize);
|
||||
auto dir = bresenhamDirection(step);
|
||||
modSize /= length(dir); // mod size in terms of steps in the given direction
|
||||
printf("step: (%.2f, %.2f) %.2f: ", step.x, step.y, modSize);
|
||||
|
||||
auto nearestHalfResidual = [](double n, double s) { return n - (std::round((n - s / 2) / s) * s + s / 2); };
|
||||
|
||||
struct DistMod {
|
||||
double dist, modSize;
|
||||
};
|
||||
thread_local std::vector<DistMod> distMod;
|
||||
distMod.clear();
|
||||
distMod.reserve(offsets.size() * radius + 1);
|
||||
for (int r = 0; r <= radius; ++r)
|
||||
for (auto offset : offsets) {
|
||||
auto start = origin + r * offset;
|
||||
auto startC = centered(start);
|
||||
int stepsPos = BitMatrixCursorF(*img, startC, dir).stepToEdge(1, limit);
|
||||
int stepsNeg = BitMatrixCursorF(*img, startC, -dir).stepToEdge(1, limit);
|
||||
auto distPos = stepsPos - dot(start - startC, dir) - 0.5; // +0.5 because the center of the pixel is at .5, .5
|
||||
auto distNeg = stepsNeg - dot(start - startC, -dir) - 0.5;
|
||||
|
||||
if (stepsPos && stepsNeg) {
|
||||
int blockSize = stepsPos + stepsNeg - 1;
|
||||
auto localModSize = blockSize / std::max(1.0, std::round(blockSize / modSize));
|
||||
distMod.emplace_back(distPos, blockSize / modSize < 5 ? localModSize : 0.0);
|
||||
// if (r == 0 && (std::min(distNeg, distPos) < modSize / 4)) {
|
||||
// origin += (distNeg < distPos ? 1 : -1) * modSize / 4 * dir;
|
||||
// continue;
|
||||
// }
|
||||
}
|
||||
else if (stepsPos)
|
||||
distMod.emplace_back(distPos, 0.0);
|
||||
else if (stepsNeg)
|
||||
distMod.emplace_back(-distNeg, 0.0);
|
||||
|
||||
printf("+%.2f -%.2f (%.1f) | ", distPos, distNeg, distMod.empty() ? 0.0 : distMod.back().modSize);
|
||||
|
||||
if (r == 0)
|
||||
break; // only do the center point once
|
||||
}
|
||||
|
||||
// calcuate the average local module size from the points where we found one...
|
||||
double localModSize = 0.0;
|
||||
int n = 0;
|
||||
for (const auto& t : distMod | std::views::filter([](const DistMod& t) { return t.modSize > 0; }))
|
||||
localModSize += t.modSize, ++n;
|
||||
if (n == 0)
|
||||
return;
|
||||
localModSize /= n;
|
||||
printf("\nlocal mod size: %.2f\n", localModSize);
|
||||
|
||||
// ... and use it for the points where we didn't find one
|
||||
thread_local std::vector<double> d;
|
||||
d.clear();
|
||||
d.reserve(distMod.size());
|
||||
for (auto& t : distMod) {
|
||||
if (t.modSize == 0.0)
|
||||
t.modSize = localModSize;
|
||||
d.push_back(t.dist < 0 ? -nearestHalfResidual(-t.dist, t.modSize) : nearestHalfResidual(t.dist, t.modSize));
|
||||
printf("%.2f (%.1f) -> %.2f | ", t.dist, t.modSize, d.back());
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
origin += clusterAvg(d, modSize / 2) * dir;
|
||||
|
||||
// if we found local mod sizes for each point (hopefully at the timing pattern crosses), we update the step size
|
||||
if (n == Size(distMod) && std::abs(localModSize - modSize) > modSize * 0.1) {
|
||||
step = localModSize / modSize * step;
|
||||
printf(" adjusted mod size from %.2f to %.2f\n", modSize, localModSize);
|
||||
}
|
||||
}
|
||||
|
||||
LocalGrid::LocalGrid(const BitMatrix& image, const PerspectiveTransform& mod2Pix, PointI p, PointI dim, PointI offset)
|
||||
: img(&image), dim(dim), center(p)
|
||||
{
|
||||
origin = mod2Pix(centered(p));
|
||||
stepX = mod2Pix(centered(p) + PointF{1, 0}) - origin;
|
||||
stepY = mod2Pix(centered(p) + PointF{0, 1}) - origin;
|
||||
|
||||
printf("LocalGrid @ (%d, %d), initial origin: (%.2f, %.2f), offset: (%d, %d), stepX: (%.2f, %.2f), stepY: (%.2f, %.2f)\n",
|
||||
center.x, center.y, origin.x, origin.y, offset.x, offset.y, stepX.x, stepX.y, stepY.x, stepY.y);
|
||||
log(origin, 3);
|
||||
|
||||
auto offsets = std::array{-stepX, -stepY, stepX, stepY}; // works better for DataMatrix (especially near the symbol edges)
|
||||
// auto offsets = std::array{-stepX - stepY, stepX - stepY, stepX + stepY, -stepX + stepY};
|
||||
// auto offsets = std::array{-stepX, -stepY, stepX, stepY, -stepX - stepY, stepX - stepY, stepX + stepY, -stepX + stepY};
|
||||
|
||||
// evaluate the image at origin + offset (allows to effectively work near the border of the symbol)
|
||||
origin = getPos(PointF(offset));
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
adjustOriginAndStep(stepX, 2, offsets);
|
||||
adjustOriginAndStep(stepY, 2, offsets);
|
||||
printf("\n");
|
||||
}
|
||||
origin = getPos(PointF(-offset));
|
||||
}
|
||||
|
||||
bool LocalGrid::isTimingPatternCross(PointI p, bool isBlack, int radius, int errorThreshold)
|
||||
{
|
||||
// TODO: look into replacing this with something along the lines of CheckSymmetricAztecCenterPattern
|
||||
auto wrapOffset = [&](int center, int offset, int dim) {
|
||||
int pos = center + offset;
|
||||
return pos < 0 ? radius - pos : (pos >= dim ? -(radius + (pos - dim) + 1) : offset);
|
||||
};
|
||||
|
||||
int errors = 0;
|
||||
for (int r = 0; r <= radius; ++r) {
|
||||
auto check = [&](int x, int y) {
|
||||
x = wrapOffset(center.x, x, dim.x);
|
||||
y = wrapOffset(center.y, y, dim.y);
|
||||
auto d = PointI(x, y);
|
||||
errors += !findValue(p + d, d, Value((x + y) % 2 == (isBlack ? 0 : 1)));
|
||||
};
|
||||
check(-r, 0);
|
||||
check(r, 0);
|
||||
check(0, -r);
|
||||
check(0, r);
|
||||
if (errors > errorThreshold)
|
||||
return false;
|
||||
}
|
||||
return errors <= errorThreshold;
|
||||
};
|
||||
|
||||
std::optional<PointF> LocalGrid::findTimingPatternCross(bool isBlack, int radius)
|
||||
{
|
||||
for (auto p : Spiral(3)) {
|
||||
// check if there is a timing pattern cross candidate centered at p with half the radius
|
||||
if (isTimingPatternCross(p, isBlack, radius / 2)) {
|
||||
auto original = origin;
|
||||
origin = getPos(p);
|
||||
// adjust origin and step with full radius and only in the direction of the timing pattern
|
||||
printf("timing pattern:\n");
|
||||
adjustOriginAndStep(stepX, radius, std::array{-stepX, stepX});
|
||||
adjustOriginAndStep(stepY, radius, std::array{-stepY, stepY});
|
||||
|
||||
// check again, now with the full radius, to make sure we are correctly aligned to the timing pattern
|
||||
if (isTimingPatternCross(PointI{0, 0}, isBlack, radius))
|
||||
return origin;
|
||||
|
||||
origin = original;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool LocalGrid::findPattern(int radius, PointI timingStart, Directions timingDirs, PointI blackStart, Directions blackDirs,
|
||||
PointI whiteStart, Directions whiteDirs)
|
||||
{
|
||||
auto isPatternAt = [&](PointI p) {
|
||||
for (int r = 0; r <= radius; ++r) {
|
||||
for (auto d : timingDirs)
|
||||
if (!findValue(p + timingStart + r * d, d, Value(r % 2 == 1)))
|
||||
return false;
|
||||
for (auto d : blackDirs)
|
||||
if (!findValue(p + blackStart + r * d, d, Value(true)))
|
||||
return false;
|
||||
for (auto d : whiteDirs)
|
||||
if (!findValue(p + whiteStart + r * d, d, Value(false)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
for (auto p : Spiral(3)) {
|
||||
if (isPatternAt(p)) {
|
||||
auto original = origin;
|
||||
origin = getPos(PointF(p));
|
||||
// adjust origin and step with full radius and only in the direction of the timing pattern
|
||||
printf("found pattern at (%.2f, %.2f)\n", origin.x, origin.y);
|
||||
std::vector<PointF> stepsX, stepsY;
|
||||
for (auto d : timingDirs)
|
||||
d.y == 0 ? stepsX.push_back(d.x * stepX) : stepsY.push_back(d.y * stepY);
|
||||
|
||||
if (!stepsX.empty())
|
||||
adjustOriginAndStep(stepX, radius, stepsX);
|
||||
if (!stepsY.empty())
|
||||
adjustOriginAndStep(stepY, radius, stepsY);
|
||||
if ((!stepsX.empty() || !stepsY.empty()) && !isPatternAt(PointI{0, 0})) {
|
||||
origin = original;
|
||||
printf("pattern lost after adjusting for timing pattern, reverting origin\n");
|
||||
}
|
||||
printf("\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2026 Axel Waggershauser
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BitMatrixCursor.h"
|
||||
#include "BitMatrix.h"
|
||||
#include "LogMatrix.h"
|
||||
#include "Point.h"
|
||||
#include "PerspectiveTransform.h"
|
||||
|
||||
#include <optional>
|
||||
#include <span>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* @brief Represents a local grid for sampling and analyzing regions within a BitMatrix image.
|
||||
*
|
||||
* The LocalGrid class tries to find the center of a module and step vectors that point to the next module in x and y direction.
|
||||
* It provides methods to get the position of a point in the grid and to sample values from the image at those positions.
|
||||
* It supports searching for timing pattern crosses and specific (alignment) patterns within the grid, which updates the origin and
|
||||
* step vectors accordingly.
|
||||
*/
|
||||
class LocalGrid
|
||||
{
|
||||
const BitMatrix* img;
|
||||
PointF origin, stepX, stepY;
|
||||
PointI dim, center;
|
||||
|
||||
using Value = BitMatrixCursorF::Value;
|
||||
|
||||
void adjustOriginAndStep(PointF& dir, int radius, std::span<const PointF> offsets);
|
||||
bool isTimingPatternCross(PointI p, bool isBlack, int radius, int errorThreshold = 0);
|
||||
bool findValue(PointI p, PointI dir, Value v)
|
||||
{
|
||||
// check if there is a value v at p or a quarter module before or after p or p is outside the image
|
||||
auto d = PointF(dir.x ? 0.25 : 0.0, dir.y ? 0.25 : 0.0);
|
||||
return !img->isIn(getPos(p)) || get(p) == v || get(p + d) == v || get(p - d) == v;
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Constructs a LocalGrid object for mapping a region of a BitMatrix using a perspective transform.
|
||||
*
|
||||
* @param image The source BitMatrix representing the image data.
|
||||
* @param mod2Pix The (global) PerspectiveTransform used to map module coordinates to pixel coordinates.
|
||||
* @param p The logical position of the grid origin in module coordinates.
|
||||
* @param dim The dimensions (width and height) of the (global) grid in modules.
|
||||
* @param offset Optional offset in modules to specify the starting position of the search for the module center.
|
||||
*/
|
||||
LocalGrid(const BitMatrix& image, const PerspectiveTransform& mod2Pix, PointI p, PointI dim, PointI offset = {});
|
||||
|
||||
inline PointF getPos(PointF p = {}) const { return origin + p.x * stepX + p.y * stepY; }
|
||||
inline PointF getPos(PointI p) const { return getPos(PointF(p.x, p.y)); }
|
||||
|
||||
inline Value get(PointF p) const
|
||||
{
|
||||
auto q = getPos(p);
|
||||
log(q, 4);
|
||||
return img->isIn(q) ? Value{img->get(q)} : Value{};
|
||||
}
|
||||
|
||||
inline Value get(PointI p) const { return get(PointF(p.x, p.y)); }
|
||||
|
||||
/**
|
||||
* @brief Finds the center of a timing pattern cross in the local grid.
|
||||
*
|
||||
* This function searches for the center point of a timing pattern cross, which is a key feature in Aztec code detection, within a
|
||||
* specified radius.
|
||||
*
|
||||
* @param isBlack Indicates whether the central module of the timing pattern cross is expected to be black or white.
|
||||
* @param radius The length of the timing pattern cross arms.
|
||||
* @return An optional PointF representing the center of the timing pattern cross if found; std::nullopt otherwise.
|
||||
*/
|
||||
std::optional<PointF> findTimingPatternCross(bool isBlack, int radius);
|
||||
|
||||
using Directions = std::span<const PointI>;
|
||||
|
||||
/**
|
||||
* @brief Searches for a specific pattern within a grid based on provided parameters.
|
||||
*
|
||||
* This function attempts to locate a pattern in the grid by starting from given points
|
||||
* and following specified directions for timing, black, and white modules.
|
||||
*/
|
||||
bool findPattern(int radius, PointI timingStart, Directions timingDirs, PointI blackStart, Directions blackDirs, PointI whiteStart,
|
||||
Directions whiteDirs);
|
||||
};
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BitMatrix.h"
|
||||
#include "Matrix.h"
|
||||
#include "Point.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
|
||||
class LogMatrix
|
||||
{
|
||||
using LogBuffer = Matrix<uint8_t>;
|
||||
LogBuffer _log;
|
||||
const BitMatrix* _image = nullptr;
|
||||
int _scale = 1;
|
||||
|
||||
public:
|
||||
void init(const BitMatrix* image, int scale = 1)
|
||||
{
|
||||
_image = image;
|
||||
_scale = scale;
|
||||
_log = LogBuffer(_image->width() * _scale, _image->height() * _scale);
|
||||
}
|
||||
|
||||
void write(const char* fn)
|
||||
{
|
||||
assert(_image);
|
||||
FILE* f = fopen(fn, "wb");
|
||||
|
||||
// Write PPM header, P5 == grey, P6 == rgb
|
||||
fprintf(f, "P6\n%d %d\n255\n", _log.width(), _log.height());
|
||||
|
||||
// Write pixels
|
||||
for (int y = 0; y < _log.height(); ++y)
|
||||
for (int x = 0; x < _log.width(); ++x) {
|
||||
unsigned char pix[3];
|
||||
unsigned char &r = pix[0], &g = pix[1], &b = pix[2];
|
||||
r = g = b = _image->get(x / _scale, y / _scale) ? 0 : 255;
|
||||
if (_scale > 1 && x % _scale == _scale / 2 && y % _scale == _scale / 2)
|
||||
r = g = b = r ? 230 : 50;
|
||||
switch (_log.get(x, y)) {
|
||||
case 1: r = g = b = _scale > 1 ? 128 : (r ? 230 : 50); break;
|
||||
case 2: r = b = 50, g = 220; break;
|
||||
case 3: g = r = 100, b = 250; break;
|
||||
case 4: g = b = 100, r = 250; break;
|
||||
}
|
||||
fwrite(&pix, 3, 1, f);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void operator()(const PointT<T>& p, int color = 1)
|
||||
{
|
||||
if (_image && _image->isIn(p))
|
||||
_log.set(static_cast<int>(p.x * _scale), static_cast<int>(p.y * _scale), color);
|
||||
}
|
||||
|
||||
void operator()(const PointT<int>& p, int color)
|
||||
{
|
||||
operator()(centered(p), color);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void operator()(const std::vector<PointT<T>>& points, int color = 2)
|
||||
{
|
||||
for (auto p : points)
|
||||
operator()(p, color);
|
||||
}
|
||||
};
|
||||
|
||||
extern LogMatrix log;
|
||||
|
||||
class LogMatrixWriter
|
||||
{
|
||||
LogMatrix &log;
|
||||
std::string fn;
|
||||
|
||||
public:
|
||||
LogMatrixWriter(LogMatrix& log, const BitMatrix& image, int scale, std::string fn) : log(log), fn(fn)
|
||||
{
|
||||
log.init(&image, scale);
|
||||
}
|
||||
~LogMatrixWriter() { log.write(fn.c_str()); }
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
template<typename T> void log(PointT<T>, int = 0) {}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2016 Huy Cuong Nguyen
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Point.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
template <class T>
|
||||
class Matrix
|
||||
{
|
||||
public:
|
||||
using value_t = T;
|
||||
|
||||
private:
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
std::vector<value_t> _data;
|
||||
|
||||
// Nothing wrong to support it, just to make it explicit, instead of by mistake.
|
||||
// Use copy() below.
|
||||
Matrix(const Matrix &) = default;
|
||||
|
||||
public:
|
||||
Matrix() = default;
|
||||
|
||||
#if defined(__llvm__) || (defined(__GNUC__) && (__GNUC__ > 7))
|
||||
__attribute__((no_sanitize("signed-integer-overflow")))
|
||||
#endif
|
||||
Matrix(int width, int height, value_t val = {}) : _width(width), _height(height), _data(_width * _height, val) {
|
||||
if (width != 0 && int(_data.size()) / width != height)
|
||||
throw std::invalid_argument("Invalid size: width * height is too big");
|
||||
}
|
||||
|
||||
Matrix(Matrix&&) noexcept = default;
|
||||
Matrix& operator=(Matrix&&) noexcept = default;
|
||||
Matrix& operator=(const Matrix &) = delete;
|
||||
|
||||
Matrix copy() const {
|
||||
return *this;
|
||||
}
|
||||
|
||||
int height() const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
int width() const {
|
||||
return _width;
|
||||
}
|
||||
|
||||
int size() const {
|
||||
return int(_data.size());
|
||||
}
|
||||
|
||||
value_t& operator()(int x, int y)
|
||||
{
|
||||
assert(x >= 0 && x < _width && y >= 0 && y < _height);
|
||||
return _data[y * _width + x];
|
||||
}
|
||||
|
||||
const T& operator()(int x, int y) const
|
||||
{
|
||||
assert(x >= 0 && x < _width && y >= 0 && y < _height);
|
||||
return _data[y * _width + x];
|
||||
}
|
||||
|
||||
value_t& operator()(PointI p) {
|
||||
return operator()(p.x, p.y);
|
||||
}
|
||||
|
||||
const T& operator()(PointI p) const {
|
||||
return operator()(p.x, p.y);
|
||||
}
|
||||
|
||||
const value_t& get(int x, int y) const {
|
||||
return operator()(x, y);
|
||||
}
|
||||
|
||||
value_t& set(int x, int y, value_t value) {
|
||||
return operator()(x, y) = value;
|
||||
}
|
||||
|
||||
const value_t& get(PointI p) const {
|
||||
return operator()(p.x, p.y);
|
||||
}
|
||||
|
||||
value_t& set(PointI p, value_t value) {
|
||||
return operator()(p.x, p.y) = value;
|
||||
}
|
||||
|
||||
const value_t* data() const {
|
||||
return _data.data();
|
||||
}
|
||||
|
||||
const value_t* begin() const {
|
||||
return _data.data();
|
||||
}
|
||||
|
||||
const value_t* end() const {
|
||||
return _data.data() + _width * _height;
|
||||
}
|
||||
|
||||
value_t* begin() {
|
||||
return _data.data();
|
||||
}
|
||||
|
||||
value_t* end() {
|
||||
return _data.data() + _width * _height;
|
||||
}
|
||||
|
||||
void clear(value_t value = {}) {
|
||||
std::fill(_data.begin(), _data.end(), value);
|
||||
}
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "MultiFormatReader.h"
|
||||
|
||||
#include "BarcodeFormat.h"
|
||||
#include "BinaryBitmap.h"
|
||||
#include "Reader.h"
|
||||
#include "ReaderOptions.h"
|
||||
#include "Version.h"
|
||||
|
||||
#if ZXING_ENABLE_AZTEC
|
||||
#include "aztec/AZReader.h"
|
||||
#endif
|
||||
#if ZXING_ENABLE_DATAMATRIX
|
||||
#include "datamatrix/DMReader.h"
|
||||
#endif
|
||||
#if ZXING_ENABLE_MAXICODE
|
||||
#include "maxicode/MCReader.h"
|
||||
#endif
|
||||
#if ZXING_ENABLE_1D
|
||||
#include "oned/ODReader.h"
|
||||
#endif
|
||||
#if ZXING_ENABLE_PDF417
|
||||
#include "pdf417/PDFReader.h"
|
||||
#include "pdf417/MicroPDFReader.h"
|
||||
#endif
|
||||
#if ZXING_ENABLE_QRCODE
|
||||
#include "qrcode/QRReader.h"
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
MultiFormatReader::MultiFormatReader(const ReaderOptions& opts) : _opts(opts)
|
||||
{
|
||||
using enum BarcodeFormat;
|
||||
|
||||
// Put linear readers upfront in "normal" mode
|
||||
#if ZXING_ENABLE_1D
|
||||
if (!opts.tryHarder() && opts.hasAnyFormat(AllLinear))
|
||||
_readers.emplace_back(new OneD::Reader(opts));
|
||||
#endif
|
||||
|
||||
#if ZXING_ENABLE_QRCODE
|
||||
if (opts.hasAnyFormat(QRCode))
|
||||
_readers.emplace_back(new QRCode::Reader(opts, true));
|
||||
#endif
|
||||
#if ZXING_ENABLE_DATAMATRIX
|
||||
if (opts.hasAnyFormat(DataMatrix))
|
||||
_readers.emplace_back(new DataMatrix::Reader(opts, true));
|
||||
#endif
|
||||
#if ZXING_ENABLE_AZTEC
|
||||
if (opts.hasAnyFormat(Aztec))
|
||||
_readers.emplace_back(new Aztec::Reader(opts, true));
|
||||
#endif
|
||||
#if ZXING_ENABLE_PDF417
|
||||
if (opts.hasFormat(PDF417 | CompactPDF417))
|
||||
_readers.emplace_back(new Pdf417::Reader(opts));
|
||||
if (opts.hasFormat(MicroPDF417))
|
||||
_readers.emplace_back(new MicroPdf417::Reader(opts));
|
||||
#endif
|
||||
#if ZXING_ENABLE_MAXICODE
|
||||
if (opts.hasAnyFormat(MaxiCode))
|
||||
_readers.emplace_back(new MaxiCode::Reader(opts));
|
||||
#endif
|
||||
|
||||
// At end in "try harder" mode
|
||||
#if ZXING_ENABLE_1D
|
||||
if (opts.tryHarder() && opts.hasAnyFormat(AllLinear))
|
||||
_readers.emplace_back(new OneD::Reader(opts));
|
||||
#endif
|
||||
}
|
||||
|
||||
MultiFormatReader::~MultiFormatReader() = default;
|
||||
|
||||
Barcodes MultiFormatReader::read(const BinaryBitmap& image, int maxSymbols) const
|
||||
{
|
||||
Barcodes res;
|
||||
|
||||
for (const auto& reader : _readers) {
|
||||
if (image.inverted() && !reader->supportsInversion)
|
||||
continue;
|
||||
auto r = reader->read(image, maxSymbols);
|
||||
if (!_opts.returnErrors())
|
||||
std::erase_if(r, [](auto&& s) { return !s.isValid(); });
|
||||
maxSymbols -= Size(r);
|
||||
res.insert(res.end(), std::move_iterator(r.begin()), std::move_iterator(r.end()));
|
||||
if (maxSymbols <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// sort barcodes based on their position on the image
|
||||
std::sort(res.begin(), res.end(), [](const Barcode& l, const Barcode& r) {
|
||||
auto lp = l.position().topLeft();
|
||||
auto rp = r.position().topLeft();
|
||||
return lp.y < rp.y || (lp.y == rp.y && lp.x < rp.x);
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Barcode.h"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class Reader;
|
||||
class BinaryBitmap;
|
||||
class ReaderOptions;
|
||||
|
||||
class MultiFormatReader
|
||||
{
|
||||
public:
|
||||
explicit MultiFormatReader(const ReaderOptions& opts);
|
||||
explicit MultiFormatReader(ReaderOptions&& opts) = delete;
|
||||
~MultiFormatReader();
|
||||
|
||||
Barcodes read(const BinaryBitmap& image, int maxSymbols = 0xFF) const;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<Reader>> _readers;
|
||||
const ReaderOptions& _opts;
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "MultiFormatWriter.h"
|
||||
|
||||
#include "BitMatrix.h"
|
||||
#include "Utf.h"
|
||||
#include "Version.h"
|
||||
|
||||
#if ZXING_ENABLE_AZTEC
|
||||
#include "aztec/AZWriter.h"
|
||||
#endif
|
||||
#if ZXING_ENABLE_DATAMATRIX
|
||||
#include "datamatrix/DMWriter.h"
|
||||
#endif
|
||||
#if ZXING_ENABLE_1D
|
||||
#include "oned/ODCodabarWriter.h"
|
||||
#include "oned/ODCode128Writer.h"
|
||||
#include "oned/ODCode39Writer.h"
|
||||
#include "oned/ODCode93Writer.h"
|
||||
#include "oned/ODEAN13Writer.h"
|
||||
#include "oned/ODEAN8Writer.h"
|
||||
#include "oned/ODITFWriter.h"
|
||||
#include "oned/ODUPCAWriter.h"
|
||||
#include "oned/ODUPCEWriter.h"
|
||||
#endif
|
||||
#if ZXING_ENABLE_PDF417
|
||||
#include "pdf417/PDFWriter.h"
|
||||
#endif
|
||||
#if ZXING_ENABLE_QRCODE
|
||||
#include "qrcode/QRErrorCorrectionLevel.h"
|
||||
#include "qrcode/QRWriter.h"
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
BitMatrix
|
||||
MultiFormatWriter::encode(const std::wstring& contents, int width, int height) const
|
||||
{
|
||||
[[maybe_unused]] auto exec0 = [&](auto&& writer) {
|
||||
if (_margin >=0)
|
||||
writer.setMargin(_margin);
|
||||
return writer.encode(contents, width, height);
|
||||
};
|
||||
|
||||
#if ZXING_ENABLE_AZTEC
|
||||
auto AztecEccLevel = [&](Aztec::Writer& writer, int eccLevel) { writer.setEccPercent(eccLevel * 100 / 8); };
|
||||
#endif
|
||||
#if ZXING_ENABLE_PDF417
|
||||
auto Pdf417EccLevel = [&](Pdf417::Writer& writer, int eccLevel) { writer.setErrorCorrectionLevel(eccLevel); };
|
||||
#endif
|
||||
#if ZXING_ENABLE_QRCODE
|
||||
auto QRCodeEccLevel = [&](QRCode::Writer& writer, int eccLevel) {
|
||||
writer.setErrorCorrectionLevel(static_cast<QRCode::ErrorCorrectionLevel>(--eccLevel / 2));
|
||||
};
|
||||
#endif
|
||||
|
||||
[[maybe_unused]] auto exec1 = [&](auto&& writer, auto setEccLevel) {
|
||||
if (_encoding != CharacterSet::Unknown)
|
||||
writer.setEncoding(_encoding);
|
||||
if (_eccLevel >= 0 && _eccLevel <= 8)
|
||||
setEccLevel(writer, _eccLevel);
|
||||
return exec0(std::forward<decltype(writer)>(writer));
|
||||
};
|
||||
|
||||
[[maybe_unused]] auto exec2 = [&](auto&& writer) {
|
||||
if (_encoding != CharacterSet::Unknown)
|
||||
writer.setEncoding(_encoding);
|
||||
return exec0(std::forward<decltype(writer)>(writer));
|
||||
};
|
||||
|
||||
switch (_format) {
|
||||
#if ZXING_ENABLE_AZTEC
|
||||
case BarcodeFormat::AztecCode: return exec1(Aztec::Writer(), AztecEccLevel);
|
||||
#endif
|
||||
#if ZXING_ENABLE_DATAMATRIX
|
||||
case BarcodeFormat::DataMatrix: return exec2(DataMatrix::Writer());
|
||||
#endif
|
||||
#if ZXING_ENABLE_PDF417
|
||||
case BarcodeFormat::PDF417: return exec1(Pdf417::Writer(), Pdf417EccLevel);
|
||||
#endif
|
||||
#if ZXING_ENABLE_QRCODE
|
||||
case BarcodeFormat::QRCode: return exec1(QRCode::Writer(), QRCodeEccLevel);
|
||||
#endif
|
||||
#if ZXING_ENABLE_1D
|
||||
case BarcodeFormat::Codabar: return exec0(OneD::CodabarWriter());
|
||||
case BarcodeFormat::Code39: return exec0(OneD::Code39Writer());
|
||||
case BarcodeFormat::Code93: return exec0(OneD::Code93Writer());
|
||||
case BarcodeFormat::Code128: return exec0(OneD::Code128Writer());
|
||||
case BarcodeFormat::EAN8: return exec0(OneD::EAN8Writer());
|
||||
case BarcodeFormat::EAN13: return exec0(OneD::EAN13Writer());
|
||||
case BarcodeFormat::ITF: return exec0(OneD::ITFWriter());
|
||||
case BarcodeFormat::UPCA: return exec0(OneD::UPCAWriter());
|
||||
case BarcodeFormat::UPCE: return exec0(OneD::UPCEWriter());
|
||||
#endif
|
||||
default: throw std::invalid_argument("Unsupported format: " + ToString(_format));
|
||||
}
|
||||
}
|
||||
|
||||
BitMatrix MultiFormatWriter::encode(const std::string& contents, int width, int height) const
|
||||
{
|
||||
return encode(FromUtf8(contents), width, height);
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BarcodeFormat.h"
|
||||
#include "CharacterSet.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class BitMatrix;
|
||||
|
||||
/**
|
||||
* This class is here just for convenience as it offers single-point service
|
||||
* to generate barcodes for all supported formats. As a result, this class
|
||||
* offer very limited customization compared to what are available in each
|
||||
* individual encoder.
|
||||
*/
|
||||
class MultiFormatWriter
|
||||
{
|
||||
public:
|
||||
[[deprecated("use CreateBarcode()")]] explicit MultiFormatWriter(BarcodeFormat format) : _format(format) {}
|
||||
|
||||
/**
|
||||
* Used for Aztec, PDF417, and QRCode only.
|
||||
*/
|
||||
MultiFormatWriter& setEncoding(CharacterSet encoding) {
|
||||
_encoding = encoding;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for Aztec, PDF417, and QRCode only, [0-8].
|
||||
*/
|
||||
MultiFormatWriter& setEccLevel(int level) {
|
||||
_eccLevel = level;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for all formats, sets the minimum number of quiet zone pixels.
|
||||
*/
|
||||
MultiFormatWriter& setMargin(int margin) {
|
||||
_margin = margin;
|
||||
return *this;
|
||||
}
|
||||
|
||||
BitMatrix encode(const std::wstring& contents, int width, int height) const;
|
||||
BitMatrix encode(const std::string& contents, int width, int height) const;
|
||||
|
||||
private:
|
||||
BarcodeFormat _format;
|
||||
CharacterSet _encoding = CharacterSet::Unknown;
|
||||
int _margin = -1;
|
||||
int _eccLevel = -1;
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Range.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
using PatternType = uint16_t;
|
||||
template<int N> using Pattern = std::array<PatternType, N>;
|
||||
using PatternRow = std::vector<PatternType>;
|
||||
|
||||
class PatternView
|
||||
{
|
||||
using Iterator = PatternRow::const_pointer;
|
||||
Iterator _data = nullptr;
|
||||
int _size = 0;
|
||||
Iterator _base = nullptr;
|
||||
Iterator _end = nullptr;
|
||||
|
||||
public:
|
||||
using value_type = PatternRow::value_type;
|
||||
|
||||
PatternView() = default;
|
||||
|
||||
// A PatternRow always starts with the width of whitespace in front of the first black bar.
|
||||
// The first element of the PatternView is the first bar.
|
||||
PatternView(const PatternRow& bars)
|
||||
: _data(bars.data() + 1), _size(Size(bars) - 1), _base(bars.data()), _end(bars.data() + bars.size())
|
||||
{}
|
||||
|
||||
PatternView(Iterator data, int size, Iterator base, Iterator end) : _data(data), _size(size), _base(base), _end(end) {}
|
||||
|
||||
template <size_t N>
|
||||
PatternView(const Pattern<N>& row) : _data(row.data()), _size(N)
|
||||
{}
|
||||
|
||||
Iterator data() const { return _data; }
|
||||
Iterator begin() const { return _data; }
|
||||
Iterator end() const { return _data + _size; }
|
||||
|
||||
value_type operator[](int i) const
|
||||
{
|
||||
// assert(i < _count);
|
||||
return _data[i];
|
||||
}
|
||||
|
||||
int sum(int n = 0) const { return Reduce(_data, _data + (n == 0 ? _size : n)); }
|
||||
int size() const { return _size; }
|
||||
|
||||
// index is the number of bars and spaces from the first bar to the current position
|
||||
int index() const { return narrow_cast<int>(_data - _base) - 1; }
|
||||
int pixelsInFront() const { return Reduce(_base, _data); }
|
||||
int pixelsTillEnd() const { return Reduce(_base, _data + _size) - 1; }
|
||||
bool isAtFirstBar() const { return _data == _base + 1; }
|
||||
bool isAtLastBar() const { return _data + _size == _end - 1; }
|
||||
bool isValid(int n) const { return _data && _data >= _base && _data + n <= _end; }
|
||||
bool isValid() const { return isValid(size()); }
|
||||
int spaceInFront() const { return isAtFirstBar() ? INT_MAX : _data[-1]; }
|
||||
|
||||
template<bool acceptIfAtFirstBar = false>
|
||||
bool hasQuietZoneBefore(float scale) const
|
||||
{
|
||||
return (acceptIfAtFirstBar && isAtFirstBar()) || _data[-1] >= sum() * scale;
|
||||
}
|
||||
|
||||
template<bool acceptIfAtLastBar = true>
|
||||
bool hasQuietZoneAfter(float scale) const
|
||||
{
|
||||
return (acceptIfAtLastBar && isAtLastBar()) || _data[_size] >= sum() * scale;
|
||||
}
|
||||
|
||||
PatternView subView(int offset, int size = 0) const
|
||||
{
|
||||
// if(std::abs(size) > count())
|
||||
// printf("%d > %d\n", std::abs(size), _count);
|
||||
// assert(std::abs(size) <= count());
|
||||
if (size == 0)
|
||||
size = _size - offset;
|
||||
else if (size < 0)
|
||||
size = _size - offset + size;
|
||||
return {begin() + offset, std::max(size, 0), _base, _end};
|
||||
}
|
||||
|
||||
bool shift(int n)
|
||||
{
|
||||
return _data && ((_data += n) + _size <= _end);
|
||||
}
|
||||
|
||||
bool skipPair()
|
||||
{
|
||||
return shift(2);
|
||||
}
|
||||
|
||||
bool skipSymbol()
|
||||
{
|
||||
return shift(_size);
|
||||
}
|
||||
|
||||
bool skipSingle(int maxWidth)
|
||||
{
|
||||
return shift(1) && _data[-1] <= maxWidth;
|
||||
}
|
||||
|
||||
void extend()
|
||||
{
|
||||
_size = std::max(0, narrow_cast<int>(_end - _data));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The BarAndSpace struct is a simple 2 element data structure to hold information about bar(s) and space(s).
|
||||
*
|
||||
* The operator[](int) can be used in combination with a PatternView
|
||||
*/
|
||||
template <typename T>
|
||||
struct BarAndSpace
|
||||
{
|
||||
using value_type = T;
|
||||
T bar = {}, space = {};
|
||||
// even index -> bar, odd index -> space
|
||||
constexpr T& operator[](int i) noexcept { return reinterpret_cast<T*>(this)[i & 1]; }
|
||||
constexpr T operator[](int i) const noexcept { return reinterpret_cast<const T*>(this)[i & 1]; }
|
||||
bool isValid() const { return bar != T{} && space != T{}; }
|
||||
};
|
||||
|
||||
using BarAndSpaceI = BarAndSpace<PatternType>;
|
||||
|
||||
template <int LEN, typename RT, typename T>
|
||||
constexpr auto BarAndSpaceSum(const T* view) noexcept
|
||||
{
|
||||
BarAndSpace<RT> res;
|
||||
for (int i = 0; i < LEN; ++i)
|
||||
res[i] += view[i];
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief FixedPattern describes a compile-time constant (start/stop) pattern.
|
||||
*
|
||||
* N = number of bars/spaces
|
||||
* SUM = sum over all N elements (size of pattern in modules)
|
||||
* IS_SPARSE = whether or not the pattern contains '0's denoting 'wide' bars/spaces
|
||||
*/
|
||||
template <int N, int SUM, bool IS_SPARSE = false>
|
||||
struct FixedPattern
|
||||
{
|
||||
using value_type = PatternRow::value_type;
|
||||
value_type _data[N];
|
||||
constexpr value_type operator[](int i) const noexcept { return _data[i]; }
|
||||
constexpr const value_type* data() const noexcept { return _data; }
|
||||
constexpr int size() const noexcept { return N; }
|
||||
constexpr const value_type* begin() const noexcept { return _data; }
|
||||
constexpr const value_type* end() const noexcept { return _data + N; }
|
||||
constexpr BarAndSpace<value_type> sums() const noexcept { return BarAndSpaceSum<N, value_type>(_data); }
|
||||
};
|
||||
|
||||
template <int N, int SUM>
|
||||
using FixedSparsePattern = FixedPattern<N, SUM, true>;
|
||||
|
||||
template <bool E2E = false, int LEN, int SUM>
|
||||
double IsPattern(const PatternView& view, const FixedPattern<LEN, SUM, false>& pattern, int spaceInPixel = 0,
|
||||
double minQuietZone = 0, double moduleSizeRef = 0)
|
||||
{
|
||||
if constexpr (E2E) {
|
||||
auto widths = BarAndSpaceSum<LEN, double>(view.data());
|
||||
auto sums = pattern.sums();
|
||||
BarAndSpace<double> modSize = {widths[0] / sums[0], widths[1] / sums[1]};
|
||||
|
||||
auto [m, M] = std::minmax(modSize[0], modSize[1]);
|
||||
if (M > 4 * m) // make sure module sizes of bars and spaces are not too far away from each other
|
||||
return 0;
|
||||
|
||||
if (minQuietZone && spaceInPixel < minQuietZone * modSize.space)
|
||||
return 0;
|
||||
|
||||
const BarAndSpace<double> thr = {modSize[0] * .75 + .5, modSize[1] * .6 + .5};
|
||||
|
||||
for (int x = 0; x < LEN; ++x)
|
||||
if (std::abs(view[x] - pattern[x] * modSize[x]) > thr[x])
|
||||
return 0;
|
||||
|
||||
return (modSize[0] + modSize[1]) / 2;
|
||||
}
|
||||
|
||||
double width = view.sum(LEN);
|
||||
if (SUM > LEN && width < SUM)
|
||||
return 0;
|
||||
|
||||
const auto moduleSize = width / SUM;
|
||||
|
||||
if (minQuietZone && spaceInPixel < minQuietZone * moduleSize - 1)
|
||||
return 0;
|
||||
|
||||
if (!moduleSizeRef)
|
||||
moduleSizeRef = moduleSize;
|
||||
|
||||
// the offset of 0.5 is to make the code less sensitive to quantization errors for small (near 1) module sizes.
|
||||
// TODO: review once we have upsampling in the binarizer in place.
|
||||
const auto threshold = moduleSizeRef * (0.5 + E2E * 0.25) + 0.5;
|
||||
|
||||
for (int x = 0; x < LEN; ++x)
|
||||
if (std::abs(view[x] - pattern[x] * moduleSizeRef) > threshold)
|
||||
return 0;
|
||||
|
||||
return moduleSize;
|
||||
}
|
||||
|
||||
template <bool RELAXED_THRESHOLD = false, int N, int SUM>
|
||||
double IsPattern(const PatternView& view, const FixedPattern<N, SUM, true>& pattern, int spaceInPixel = 0,
|
||||
double minQuietZone = 0, double moduleSizeRef = 0)
|
||||
{
|
||||
// pattern contains the indices with the bars/spaces that need to be equally wide
|
||||
double width = 0;
|
||||
for (int x = 0; x < SUM; ++x)
|
||||
width += view[pattern[x]];
|
||||
|
||||
const auto moduleSize = width / SUM;
|
||||
|
||||
if (minQuietZone && spaceInPixel < minQuietZone * moduleSize - 1)
|
||||
return 0;
|
||||
|
||||
if (!moduleSizeRef)
|
||||
moduleSizeRef = moduleSize;
|
||||
|
||||
// the offset of 0.5 is to make the code less sensitive to quantization errors for small (near 1) module sizes.
|
||||
// TODO: review once we have upsampling in the binarizer in place.
|
||||
const auto threshold = moduleSizeRef * (0.5 + RELAXED_THRESHOLD * 0.25) + 0.5;
|
||||
|
||||
for (int x = 0; x < SUM; ++x)
|
||||
if (std::abs(view[pattern[x]] - moduleSizeRef) > threshold)
|
||||
return 0;
|
||||
|
||||
return moduleSize;
|
||||
}
|
||||
|
||||
template <bool E2E = false, int N, int SUM, bool IS_SPARSE>
|
||||
bool IsRightGuard(const PatternView& view, const FixedPattern<N, SUM, IS_SPARSE>& pattern, double minQuietZone,
|
||||
double moduleSizeRef = 0)
|
||||
{
|
||||
assert(view.size() == pattern.size());
|
||||
if (!view.isValid())
|
||||
return false;
|
||||
int spaceInPixel = view.isAtLastBar() ? std::numeric_limits<int>::max() : *view.end();
|
||||
return IsPattern<E2E>(view, pattern, spaceInPixel, minQuietZone, moduleSizeRef) != 0;
|
||||
}
|
||||
|
||||
template<int LEN, typename Pred>
|
||||
PatternView FindLeftGuard(const PatternView& view, int minSize, Pred isGuard)
|
||||
{
|
||||
if (view.size() < minSize)
|
||||
return {};
|
||||
|
||||
auto window = view.subView(0, LEN);
|
||||
if (window.isAtFirstBar() && isGuard(window, std::numeric_limits<int>::max()))
|
||||
return window;
|
||||
for (auto end = view.end() - minSize; window.data() < end; window.skipPair())
|
||||
if (isGuard(window, window[-1]))
|
||||
return window;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template <bool E2E = false, int LEN, int SUM, bool IS_SPARSE>
|
||||
PatternView FindLeftGuard(const PatternView& view, int minSize, const FixedPattern<LEN, SUM, IS_SPARSE>& pattern,
|
||||
double minQuietZone)
|
||||
{
|
||||
return FindLeftGuard<LEN>(view, std::max(minSize, LEN),
|
||||
[&pattern, minQuietZone](const PatternView& window, int spaceInPixel) {
|
||||
return IsPattern<E2E>(window, pattern, spaceInPixel, minQuietZone);
|
||||
});
|
||||
}
|
||||
|
||||
template <int LEN>
|
||||
std::array<int, LEN - 2> NormalizedE2EPattern(const PatternView& view, int mods, bool reverse = false)
|
||||
{
|
||||
double moduleSize = static_cast<double>(view.sum(LEN)) / mods;
|
||||
std::array<int, LEN - 2> e2e;
|
||||
|
||||
for (int i = 0; i < LEN - 2; i++) {
|
||||
int i_v = reverse ? LEN - 2 - i : i;
|
||||
double v = (view[i_v] + view[i_v + 1]) / moduleSize;
|
||||
e2e[i] = int(v + .5);
|
||||
}
|
||||
|
||||
return e2e;
|
||||
}
|
||||
|
||||
template <int LEN, int SUM>
|
||||
std::array<int, LEN> NormalizedPattern(const PatternView& view)
|
||||
{
|
||||
double moduleSize = static_cast<double>(view.sum(LEN)) / SUM;
|
||||
#if 1
|
||||
int err = SUM;
|
||||
std::array<int, LEN> is;
|
||||
std::array<double, LEN> rs;
|
||||
for (int i = 0; i < LEN; i++) {
|
||||
double v = view[i] / moduleSize;
|
||||
is[i] = int(v + .5);
|
||||
rs[i] = v - is[i];
|
||||
err -= is[i];
|
||||
}
|
||||
|
||||
if (std::abs(err) > 1)
|
||||
return {};
|
||||
|
||||
if (err) {
|
||||
auto mi = err > 0 ? std::ranges::max_element(rs) - rs.begin()
|
||||
: std::ranges::min_element(rs) - rs.begin();
|
||||
is[mi] += err;
|
||||
rs[mi] -= err;
|
||||
}
|
||||
#else
|
||||
std::array<int, LEN> is, e2e;
|
||||
int min_v = view[0], min_i = 0;
|
||||
|
||||
for (int i = 1; i < LEN; i++) {
|
||||
double v = (view[i - 1] + view[i]) / moduleSize;
|
||||
e2e[i] = int(v + .5);
|
||||
if (view[i] < min_v) {
|
||||
min_v = view[i];
|
||||
min_i = i;
|
||||
}
|
||||
}
|
||||
|
||||
is[min_i] = 1;
|
||||
for (int i = min_i + 1; i < LEN; ++i)
|
||||
is[i] = e2e[i] - is[i - 1];
|
||||
for (int i = min_i - 1; i >= 0; --i)
|
||||
is[i] = e2e[i + 1] - is[i + 1];
|
||||
#endif
|
||||
return is;
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
void GetPatternRow(Range<I> b_row, PatternRow& p_row)
|
||||
{
|
||||
// TODO: if reactivating the bit-packed array (!ZX_FAST_BIT_STORAGE) should be of interest then the following code could be
|
||||
// considerably speed up by using a specialized variant along the lines of the old BitArray::getNextSetTo() function that
|
||||
// was removed between 1.4 and 2.0.
|
||||
|
||||
#if 0
|
||||
p_row.reserve(64);
|
||||
p_row.clear();
|
||||
|
||||
auto lastPos = b_row.begin();
|
||||
if (*lastPos)
|
||||
p_row.push_back(0); // first value is number of white pixels, here 0
|
||||
|
||||
for (auto p = b_row.begin() + 1; p != b_row.end(); ++p)
|
||||
if (bool(*p) != bool(*lastPos))
|
||||
p_row.push_back(p - std::exchange(lastPos, p));
|
||||
|
||||
p_row.push_back(b_row.end() - lastPos);
|
||||
|
||||
if (*lastPos)
|
||||
p_row.push_back(0); // last value is number of white pixels, here 0
|
||||
#else
|
||||
p_row.resize(b_row.size() + 2);
|
||||
std::fill(p_row.begin(), p_row.end(), 0);
|
||||
|
||||
auto bitPos = b_row.begin();
|
||||
const auto bitPosEnd = b_row.end();
|
||||
auto intPos = p_row.data();
|
||||
|
||||
if (*bitPos)
|
||||
intPos++; // first value is number of white pixels, here 0
|
||||
|
||||
// The following code has been observed to cause a speedup of up to 30% on large images on an AVX cpu
|
||||
// and on an a Google Pixel 3 Android phone. Your mileage may vary.
|
||||
if constexpr (std::contiguous_iterator<I> && sizeof(std::remove_cv_t<std::iter_value_t<I>>) == 1) {
|
||||
using simd_t = uint64_t;
|
||||
const auto* const bitPtrBegin = std::to_address(bitPos);
|
||||
const auto* const bitPtrEnd = std::to_address(bitPosEnd);
|
||||
auto* bitPtr = bitPtrBegin;
|
||||
|
||||
while (bitPtr < bitPtrEnd - sizeof(simd_t)) {
|
||||
auto asSimd0 = LoadU<simd_t>(bitPtr);
|
||||
auto asSimd1 = LoadU<simd_t>(bitPtr + 1);
|
||||
auto z = asSimd0 ^ asSimd1;
|
||||
if (z) {
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
int step = std::countr_zero(z) / 8 + 1;
|
||||
#else
|
||||
int step = std::countl_zero(z) / 8 + 1;
|
||||
#endif
|
||||
(*intPos++) += step;
|
||||
bitPtr += step;
|
||||
} else {
|
||||
(*intPos) += sizeof(simd_t);
|
||||
bitPtr += sizeof(simd_t);
|
||||
}
|
||||
}
|
||||
|
||||
bitPos += bitPtr - bitPtrBegin;
|
||||
}
|
||||
|
||||
while (++bitPos != bitPosEnd) {
|
||||
++(*intPos);
|
||||
intPos += bitPos[0] != bitPos[-1];
|
||||
}
|
||||
++(*intPos);
|
||||
|
||||
if (bitPos[-1])
|
||||
intPos++;
|
||||
|
||||
p_row.resize(intPos - p_row.data() + 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "PerspectiveTransform.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
PerspectiveTransform PerspectiveTransform::inverse() const
|
||||
{
|
||||
// Here, the adjoint serves as the inverse:
|
||||
// Adjoint is the transpose of the cofactor matrix:
|
||||
return {
|
||||
a22 * a33 - a23 * a32,
|
||||
a23 * a31 - a21 * a33,
|
||||
a21 * a32 - a22 * a31,
|
||||
a13 * a32 - a12 * a33,
|
||||
a11 * a33 - a13 * a31,
|
||||
a12 * a31 - a11 * a32,
|
||||
a12 * a23 - a13 * a22,
|
||||
a13 * a21 - a11 * a23,
|
||||
a11 * a22 - a12 * a21
|
||||
};
|
||||
}
|
||||
|
||||
PerspectiveTransform PerspectiveTransform::times(const PerspectiveTransform& other) const
|
||||
{
|
||||
return {
|
||||
a11 * other.a11 + a21 * other.a12 + a31 * other.a13,
|
||||
a11 * other.a21 + a21 * other.a22 + a31 * other.a23,
|
||||
a11 * other.a31 + a21 * other.a32 + a31 * other.a33,
|
||||
a12 * other.a11 + a22 * other.a12 + a32 * other.a13,
|
||||
a12 * other.a21 + a22 * other.a22 + a32 * other.a23,
|
||||
a12 * other.a31 + a22 * other.a32 + a32 * other.a33,
|
||||
a13 * other.a11 + a23 * other.a12 + a33 * other.a13,
|
||||
a13 * other.a21 + a23 * other.a22 + a33 * other.a23,
|
||||
a13 * other.a31 + a23 * other.a32 + a33 * other.a33
|
||||
};
|
||||
}
|
||||
|
||||
PerspectiveTransform PerspectiveTransform::UnitSquareTo(const QuadrilateralF& q)
|
||||
{
|
||||
auto [x0, y0, x1, y1, x2, y2, x3, y3] = reinterpret_cast<const std::array<PointF::value_t, 8>&>(q);
|
||||
|
||||
auto d3 = q[0] - q[1] + q[2] - q[3];
|
||||
if (d3 == PointF(0, 0)) {
|
||||
// Affine
|
||||
return {x1 - x0, x2 - x1, x0,
|
||||
y1 - y0, y2 - y1, y0,
|
||||
0.0f, 0.0f, 1.0f};
|
||||
} else {
|
||||
auto d1 = q[1] - q[2];
|
||||
auto d2 = q[3] - q[2];
|
||||
auto denominator = cross(d1, d2);
|
||||
auto a13 = cross(d3, d2) / denominator;
|
||||
auto a23 = cross(d1, d3) / denominator;
|
||||
return {x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0,
|
||||
y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0,
|
||||
a13, a23, 1.0f};
|
||||
}
|
||||
}
|
||||
|
||||
PerspectiveTransform::PerspectiveTransform(const QuadrilateralF& src, const QuadrilateralF& dst)
|
||||
{
|
||||
if (!IsConvex(src) || !IsConvex(dst))
|
||||
return;
|
||||
*this = UnitSquareTo(dst).times(UnitSquareTo(src).inverse());
|
||||
}
|
||||
|
||||
PointF PerspectiveTransform::operator()(PointF p) const
|
||||
{
|
||||
auto denominator = a13 * p.x + a23 * p.y + a33;
|
||||
return {(a11 * p.x + a21 * p.y + a31) / denominator, (a12 * p.x + a22 * p.y + a32) / denominator};
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Point.h"
|
||||
#include "Quadrilateral.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* This class implements a perspective transform in two dimensions. Given four source and four
|
||||
* destination points, it will compute the transformation implied between them. The code is based
|
||||
* directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.
|
||||
*
|
||||
* See also e.g. https://math.stackexchange.com/a/339033
|
||||
*/
|
||||
class PerspectiveTransform
|
||||
{
|
||||
using value_t = PointF::value_t;
|
||||
value_t a11, a12, a13, a21, a22, a23, a31, a32, a33 = NAN;
|
||||
|
||||
PerspectiveTransform(value_t a11, value_t a21, value_t a31, value_t a12, value_t a22, value_t a32, value_t a13,
|
||||
value_t a23, value_t a33)
|
||||
: a11(a11), a12(a12), a13(a13), a21(a21), a22(a22), a23(a23), a31(a31), a32(a32), a33(a33)
|
||||
{}
|
||||
|
||||
PerspectiveTransform inverse() const;
|
||||
PerspectiveTransform times(const PerspectiveTransform& other) const;
|
||||
|
||||
static PerspectiveTransform UnitSquareTo(const QuadrilateralF& q);
|
||||
|
||||
public:
|
||||
PerspectiveTransform() = default;
|
||||
PerspectiveTransform(const QuadrilateralF& src, const QuadrilateralF& dst);
|
||||
|
||||
/// Project from the destination space (grid of modules) into the image space (bit matrix)
|
||||
PointF operator()(PointF p) const;
|
||||
|
||||
bool isValid() const { return !std::isnan(a33); }
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* @brief A simple 2D point class template.
|
||||
*
|
||||
* PointT is a simple 2D point class template with direct access to its x and y coordinate members.
|
||||
*/
|
||||
template <typename T>
|
||||
struct PointT
|
||||
{
|
||||
using value_t = T;
|
||||
T x = 0, y = 0;
|
||||
|
||||
constexpr PointT() = default;
|
||||
constexpr PointT(T xx, T yy) : x(xx), y(yy) {}
|
||||
|
||||
template <typename U>
|
||||
constexpr explicit PointT(const PointT<U>& p) : x(static_cast<T>(p.x)), y(static_cast<T>(p.y))
|
||||
{}
|
||||
|
||||
template <typename U>
|
||||
PointT& operator+=(const PointT<U>& b)
|
||||
{
|
||||
x += b.x;
|
||||
y += b.y;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
using PointI = PointT<int>;
|
||||
using PointF = PointT<double>;
|
||||
|
||||
/// @cond INTERNAL
|
||||
|
||||
template <typename T>
|
||||
bool operator==(const PointT<T>& a, const PointT<T>& b)
|
||||
{
|
||||
return a.x == b.x && a.y == b.y;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool operator!=(const PointT<T>& a, const PointT<T>& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto operator-(const PointT<T>& a) -> PointT<T>
|
||||
{
|
||||
return {-a.x, -a.y};
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
auto operator+(const PointT<T>& a, const PointT<U>& b) -> PointT<decltype(a.x + b.x)>
|
||||
{
|
||||
return {a.x + b.x, a.y + b.y};
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
auto operator-(const PointT<T>& a, const PointT<U>& b) -> PointT<decltype(a.x - b.x)>
|
||||
{
|
||||
return {a.x - b.x, a.y - b.y};
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
auto operator*(const PointT<T>& a, const PointT<U>& b) -> PointT<decltype(a.x * b.x)>
|
||||
{
|
||||
return {a.x * b.x, a.y * b.y};
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
PointT<T> operator*(U s, const PointT<T>& a)
|
||||
{
|
||||
return {s * a.x, s * a.y};
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
PointT<T> operator/(const PointT<T>& a, U d)
|
||||
{
|
||||
return {a.x / d, a.y / d};
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
auto dot(const PointT<T>& a, const PointT<U>& b) -> decltype (a.x * b.x)
|
||||
{
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto cross(PointT<T> a, PointT<T> b) -> decltype(a.x * b.x)
|
||||
{
|
||||
return a.x * b.y - b.x * a.y;
|
||||
}
|
||||
|
||||
/// L1 norm
|
||||
template <typename T>
|
||||
T sumAbsComponent(PointT<T> p)
|
||||
{
|
||||
return std::abs(p.x) + std::abs(p.y);
|
||||
}
|
||||
|
||||
/// L2 norm
|
||||
template <typename T>
|
||||
auto length(PointT<T> p) -> decltype(std::sqrt(dot(p, p)))
|
||||
{
|
||||
return std::sqrt(dot(p, p));
|
||||
}
|
||||
|
||||
/// L-inf norm
|
||||
template <typename T>
|
||||
T maxAbsComponent(PointT<T> p)
|
||||
{
|
||||
return std::max(std::abs(p.x), std::abs(p.y));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto distance(PointT<T> a, PointT<T> b) -> decltype(length(a - b))
|
||||
{
|
||||
return length(a - b);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
PointF normalized(PointT<T> d)
|
||||
{
|
||||
return PointF(d) / length(PointF(d));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
PointT<T> bresenhamDirection(PointT<T> d)
|
||||
{
|
||||
return d / maxAbsComponent(d);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
PointT<T> mainDirection(PointT<T> d)
|
||||
{
|
||||
return std::abs(d.x) > std::abs(d.y) ? PointT<T>(d.x, 0) : PointT<T>(0, d.y);
|
||||
}
|
||||
|
||||
/// Calculate a floating point pixel coordinate representing the 'center' of the pixel.
|
||||
/// This is sort of the inverse operation of the PointI(PointF) conversion constructor.
|
||||
/// See also the documentation of the GridSampler API.
|
||||
inline PointF centered(PointI p)
|
||||
{
|
||||
return p + PointF(0.5f, 0.5f);
|
||||
}
|
||||
|
||||
inline PointF centered(PointF p)
|
||||
{
|
||||
return {std::floor(p.x) + 0.5f, std::floor(p.y) + 0.5f};
|
||||
}
|
||||
|
||||
/// @endcond
|
||||
|
||||
template <typename T>
|
||||
std::string ToString(const PointT<T>& p, bool swap = false, char delim = 'x')
|
||||
{
|
||||
return std::to_string(swap ? p.y : p.x) + delim + std::to_string(swap ? p.x : p.y);
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Point.h"
|
||||
|
||||
#ifdef ZXING_INTERNAL
|
||||
#include "ZXAlgorithms.h"
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* @brief A simple class representing a quadrilateral defined by its four corner points.
|
||||
*
|
||||
* It represents a quadrilateral defined by its four corner points:
|
||||
* top-left, top-right, bottom-right, and bottom-left. Those points are relative to the detected symbol
|
||||
* i.e. topLeft() will return the coordinates of the top-left corner of the symbol. If e.g. the symbol
|
||||
* is rotated 180 degrees, the top-left corner will be the one that is at the bottom-right position in the image.
|
||||
*/
|
||||
template <typename T>
|
||||
class Quadrilateral : public std::array<T, 4>
|
||||
{
|
||||
using Base = std::array<T, 4>;
|
||||
using Base::at;
|
||||
public:
|
||||
using Point = T;
|
||||
|
||||
Quadrilateral() = default;
|
||||
Quadrilateral(T tl, T tr, T br, T bl) : Base{tl, tr, br, bl} {}
|
||||
template <typename U>
|
||||
Quadrilateral(PointT<U> tl, PointT<U> tr, PointT<U> br, PointT<U> bl)
|
||||
: Quadrilateral(Point(tl), Point(tr), Point(br), Point(bl))
|
||||
{}
|
||||
|
||||
constexpr Point topLeft() const noexcept { return at(0); }
|
||||
constexpr Point topRight() const noexcept { return at(1); }
|
||||
constexpr Point bottomRight() const noexcept { return at(2); }
|
||||
constexpr Point bottomLeft() const noexcept { return at(3); }
|
||||
|
||||
/// Return the orientation of the quadrilateral in radians, where 0 means the horizontal center line
|
||||
/// is parallel to the x-axis and positive values mean a clockwise rotation.
|
||||
double orientation() const
|
||||
{
|
||||
auto centerLine = (topRight() + bottomRight()) - (topLeft() + bottomLeft());
|
||||
if (centerLine == Point{})
|
||||
return 0.;
|
||||
auto centerLineF = normalized(centerLine);
|
||||
return std::atan2(centerLineF.y, centerLineF.x);
|
||||
}
|
||||
};
|
||||
|
||||
using QuadrilateralF = Quadrilateral<PointF>;
|
||||
using QuadrilateralI = Quadrilateral<PointI>;
|
||||
|
||||
template <typename T>
|
||||
std::string ToString(const Quadrilateral<PointT<T>>& points)
|
||||
{
|
||||
std::string res;
|
||||
for (const auto& p : points)
|
||||
res += std::to_string(p.x) + "x" + std::to_string(p.y) + (&p == &points.back() ? "" : " ");
|
||||
return res;
|
||||
}
|
||||
|
||||
#ifdef ZXING_INTERNAL
|
||||
|
||||
template <typename PointT = PointF>
|
||||
Quadrilateral<PointT> Rectangle(int width, int height, typename PointT::value_t margin = 0)
|
||||
{
|
||||
return {
|
||||
PointT{margin, margin}, {width - margin, margin}, {width - margin, height - margin}, {margin, height - margin}};
|
||||
}
|
||||
|
||||
template <typename PointT = PointF>
|
||||
Quadrilateral<PointT> Rectangle(int x0, int x1, int y0, int y1, typename PointT::value_t o)
|
||||
{
|
||||
return {PointT{x0 + o, y0 + o}, {x1 + o, y0 + o}, {x1 + o, y1 + o}, {x0 + o, y1 + o}};
|
||||
}
|
||||
|
||||
template <typename PointT = PointF>
|
||||
Quadrilateral<PointT> Rectangle(int left, int top, int width, int height)
|
||||
{
|
||||
int right = left + width - 1;
|
||||
int bottom = top + height - 1;
|
||||
|
||||
return {PointT{left, top}, {right, top}, {right, bottom}, {left, bottom}};
|
||||
}
|
||||
|
||||
template <typename PointT = PointF>
|
||||
Quadrilateral<PointT> CenteredSquare(int size)
|
||||
{
|
||||
return Scale(Quadrilateral(PointT{-1, -1}, {1, -1}, {1, 1}, {-1, 1}), size / 2);
|
||||
}
|
||||
|
||||
template <typename PointT = PointI>
|
||||
Quadrilateral<PointT> Line(int y, int xStart, int xStop)
|
||||
{
|
||||
return {PointT{xStart, y}, {xStop, y}, {xStop, y}, {xStart, y}};
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
bool IsConvex(const Quadrilateral<PointT>& poly)
|
||||
{
|
||||
const int N = Size(poly);
|
||||
bool sign = false;
|
||||
|
||||
typename PointT::value_t m = INFINITY, M = 0;
|
||||
|
||||
for(int i = 0; i < N; i++)
|
||||
{
|
||||
auto d1 = poly[(i + 2) % N] - poly[(i + 1) % N];
|
||||
auto d2 = poly[i] - poly[(i + 1) % N];
|
||||
auto cp = cross(d1, d2);
|
||||
|
||||
// TODO: see if the isInside check for all boundary points in GridSampler is still required after fixing the wrong fabs()
|
||||
// application in the following line
|
||||
UpdateMinMax(m, M, std::fabs(cp));
|
||||
|
||||
if (i == 0)
|
||||
sign = cp > 0;
|
||||
else if (sign != (cp > 0))
|
||||
return false;
|
||||
}
|
||||
|
||||
// It turns out being convex is not enough to prevent a "numerical instability"
|
||||
// that can cause the corners being projected inside the image boundaries but
|
||||
// some points near the corners being projected outside. This has been observed
|
||||
// where one corner is almost in line with two others. The M/m ratio is below 2
|
||||
// for the complete existing sample set. For very "skewed" QRCodes a value of
|
||||
// around 3 is realistic. A value of 14 has been observed to trigger the
|
||||
// instability.
|
||||
return M / m < 4.0;
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
Quadrilateral<PointT> Scale(const Quadrilateral<PointT>& q, int factor)
|
||||
{
|
||||
return {factor * q[0], factor * q[1], factor * q[2], factor * q[3]};
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
Quadrilateral<PointT> Move(const Quadrilateral<PointT>& q, PointT offset)
|
||||
{
|
||||
return {q[0] + offset, q[1] + offset, q[2] + offset, q[3] + offset};
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
PointT Center(const Quadrilateral<PointT>& q)
|
||||
{
|
||||
return Reduce(q) / Size(q);
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
Quadrilateral<PointT> RotatedCorners(const Quadrilateral<PointT>& q, int n = 1, bool mirror = false)
|
||||
{
|
||||
Quadrilateral<PointT> res;
|
||||
std::rotate_copy(q.begin(), q.begin() + ((n + 4) % 4), q.end(), res.begin());
|
||||
if (mirror)
|
||||
std::swap(res[1], res[3]);
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
bool IsInside(const PointT& p, const Quadrilateral<PointT>& q)
|
||||
{
|
||||
// Test if p is on the same side (right or left) of all polygon segments
|
||||
int pos = 0, neg = 0;
|
||||
for (int i = 0; i < Size(q); ++i)
|
||||
(cross(p - q[i], q[(i + 1) % Size(q)] - q[i]) < 0 ? neg : pos)++;
|
||||
return pos == 0 || neg == 0;
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
Quadrilateral<PointT> BoundingBox(const Quadrilateral<PointT>& q)
|
||||
{
|
||||
auto [minX, maxX] = std::minmax({q[0].x, q[1].x, q[2].x, q[3].x});
|
||||
auto [minY, maxY] = std::minmax({q[0].y, q[1].y, q[2].y, q[3].y});
|
||||
return {PointT{minX, minY}, {maxX, minY}, {maxX, maxY}, {minX, maxY}};
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
bool HaveIntersectingBoundingBoxes(const Quadrilateral<PointT>& a, const Quadrilateral<PointT>& b)
|
||||
{
|
||||
auto bba = BoundingBox(a), bbb = BoundingBox(b);
|
||||
|
||||
bool x = bbb.topRight().x < bba.topLeft().x || bbb.topLeft().x > bba.topRight().x;
|
||||
bool y = bbb.bottomLeft().y < bba.topLeft().y || bbb.topLeft().y > bba.bottomLeft().y;
|
||||
return !(x || y);
|
||||
}
|
||||
|
||||
template <typename PointT>
|
||||
Quadrilateral<PointT> Blend(const Quadrilateral<PointT>& a, const Quadrilateral<PointT>& b)
|
||||
{
|
||||
auto dist2First = [r = a[0]](auto s, auto t) { return distance(s, r) < distance(t, r); };
|
||||
// rotate points such that the the two topLeft points are closest to each other
|
||||
auto offset = std::min_element(b.begin(), b.end(), dist2First) - b.begin();
|
||||
|
||||
Quadrilateral<PointT> res;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
res[i] = (a[i] + b[(i + offset) % 4]) / 2;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // ZXing
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
template <typename Iterator>
|
||||
struct StrideIter
|
||||
{
|
||||
Iterator pos;
|
||||
int stride;
|
||||
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
using difference_type = typename std::iterator_traits<Iterator>::difference_type;
|
||||
using value_type = typename std::iterator_traits<Iterator>::value_type;
|
||||
using pointer = Iterator;
|
||||
using reference = typename std::iterator_traits<Iterator>::reference;
|
||||
|
||||
auto operator*() const { return *pos; }
|
||||
auto operator[](int i) const { return *(pos + i * stride); }
|
||||
StrideIter<Iterator>& operator++() { return pos += stride, *this; }
|
||||
StrideIter<Iterator> operator++(int) { auto temp = *this; ++*this; return temp; }
|
||||
bool operator==(const StrideIter<Iterator>& rhs) const { return pos == rhs.pos; }
|
||||
StrideIter<Iterator> operator+(int i) const { return {pos + i * stride, stride}; }
|
||||
StrideIter<Iterator> operator-(int i) const { return {pos - i * stride, stride}; }
|
||||
int operator-(const StrideIter<Iterator>& rhs) const { return narrow_cast<int>((pos - rhs.pos) / stride); }
|
||||
};
|
||||
|
||||
template <typename Iterator>
|
||||
StrideIter(const Iterator&, int) -> StrideIter<Iterator>;
|
||||
|
||||
|
||||
template <typename Iterator>
|
||||
struct Range
|
||||
{
|
||||
Iterator _begin, _end;
|
||||
|
||||
Range(Iterator b, Iterator e) : _begin(b), _end(e) {}
|
||||
|
||||
template <typename C>
|
||||
Range(const C& c) : _begin(std::begin(c)), _end(std::end(c)) {}
|
||||
|
||||
Iterator begin() const noexcept { return _begin; }
|
||||
Iterator end() const noexcept { return _end; }
|
||||
explicit operator bool() const { return begin() < end(); }
|
||||
int size() const { return narrow_cast<int>(end() - begin()); }
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
Range(const C&) -> Range<typename C::const_iterator>;
|
||||
|
||||
/**
|
||||
* ArrayView is a lightweight, non-owning, non-mutable view over a contiguous sequence of elements.
|
||||
* Similar to std::span<const T>. See also Range template for general iterator use case.
|
||||
*/
|
||||
template <typename T>
|
||||
class ArrayView
|
||||
{
|
||||
const T* _data = nullptr;
|
||||
std::size_t _size = 0;
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
using pointer = const value_type*;
|
||||
using const_pointer = const value_type*;
|
||||
using reference = const value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using size_type = std::size_t;
|
||||
|
||||
constexpr ArrayView() noexcept = default;
|
||||
|
||||
constexpr ArrayView(pointer data, size_type size) noexcept : _data(data), _size(size) {}
|
||||
|
||||
template <typename P, typename U = T,
|
||||
typename = std::enable_if_t<
|
||||
sizeof(U) == 1 && std::is_same_v<void, std::remove_cv_t<std::remove_reference_t<std::remove_pointer_t<P>>>>>>
|
||||
constexpr ArrayView(P data, size_type size) noexcept : _data(reinterpret_cast<pointer>(data)), _size(size)
|
||||
{}
|
||||
|
||||
template <typename Container,
|
||||
typename = std::enable_if_t<std::is_convertible_v<decltype(std::data(std::declval<Container&>())), const_pointer>>>
|
||||
constexpr ArrayView(const Container& c) noexcept : _data(std::data(c)), _size(std::size(c))
|
||||
{}
|
||||
|
||||
constexpr pointer data() const noexcept { return _data; }
|
||||
constexpr size_type size() const noexcept { return _size; }
|
||||
constexpr bool empty() const noexcept { return _size == 0; }
|
||||
|
||||
constexpr const_reference operator[](size_type index) const noexcept { return _data[index]; }
|
||||
|
||||
constexpr pointer begin() const noexcept { return _data; }
|
||||
constexpr pointer end() const noexcept { return _data + _size; }
|
||||
|
||||
constexpr ArrayView<T> subview(size_type pos, size_type len = size_type(-1)) const noexcept
|
||||
{
|
||||
if (pos > _size)
|
||||
return {};
|
||||
return {_data + pos, std::min(len, _size - pos)};
|
||||
}
|
||||
};
|
||||
|
||||
using ByteView = ArrayView<uint8_t>;
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright 2019 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ReadBarcode.h"
|
||||
#include "ReaderOptions.h"
|
||||
#include "BarcodeData.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
|
||||
#ifdef ZXING_READERS
|
||||
#include "GlobalHistogramBinarizer.h"
|
||||
#include "HybridBinarizer.h"
|
||||
#include "MultiFormatReader.h"
|
||||
#include "Pattern.h"
|
||||
#include "ThresholdBinarizer.h"
|
||||
#endif
|
||||
|
||||
#include <climits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
// ==============================================================================
|
||||
// ReaderOptions implementation
|
||||
// ==============================================================================
|
||||
|
||||
struct ReaderOptions::Data
|
||||
{
|
||||
bool tryHarder : 1 = true;
|
||||
bool tryRotate : 1 = true;
|
||||
bool tryInvert : 1 = true;
|
||||
bool tryDownscale : 1 = true;
|
||||
#ifdef ZXING_EXPERIMENTAL_API
|
||||
bool tryDenoise : 1 = false;
|
||||
#endif
|
||||
bool isPure : 1 = false;
|
||||
bool validateOptionalChecksum : 1 = false;
|
||||
bool returnErrors : 1 = false;
|
||||
uint8_t downscaleFactor : 3 = 3; // values 2, 3, 4
|
||||
EanAddOnSymbol eanAddOnSymbol : 2 = EanAddOnSymbol::Ignore;
|
||||
Binarizer binarizer : 2 = Binarizer::LocalAverage;
|
||||
TextMode textMode : 3 = TextMode::HRI;
|
||||
CharacterSet characterSet : 6 = CharacterSet::Unknown;
|
||||
|
||||
uint8_t minLineCount = 2;
|
||||
uint8_t maxNumberOfSymbols = 0xff;
|
||||
uint16_t downscaleThreshold = 500;
|
||||
BarcodeFormats formats = {};
|
||||
};
|
||||
|
||||
ReaderOptions::ReaderOptions() : d(std::make_unique<Data>()) {}
|
||||
ReaderOptions::~ReaderOptions() = default;
|
||||
|
||||
// copy
|
||||
ReaderOptions::ReaderOptions(const ReaderOptions& other) : d(std::make_unique<Data>(*other.d)) {}
|
||||
ReaderOptions& ReaderOptions::operator=(const ReaderOptions& other)
|
||||
{
|
||||
if (this != &other)
|
||||
d = std::make_unique<Data>(*other.d);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// move
|
||||
ReaderOptions::ReaderOptions(ReaderOptions&&) noexcept = default;
|
||||
ReaderOptions& ReaderOptions::operator=(ReaderOptions&&) noexcept = default;
|
||||
|
||||
const BarcodeFormats& ReaderOptions::formats() const noexcept { return d->formats; }
|
||||
ReaderOptions& ReaderOptions::formats(BarcodeFormats&& v) & { return (void)(d->formats = std::move(v)), *this; }
|
||||
ReaderOptions&& ReaderOptions::formats(BarcodeFormats&& v) && { return (void)(d->formats = std::move(v)), std::move(*this); }
|
||||
|
||||
#define ZX_PROPERTY(TYPE, NAME, SETTER) \
|
||||
TYPE ReaderOptions::NAME() const noexcept { return d->NAME; } \
|
||||
ReaderOptions& ReaderOptions::NAME(TYPE v) & { return (void)(d->NAME = std::move(v)), *this; } \
|
||||
ReaderOptions&& ReaderOptions::NAME(TYPE v) && { return (void)(d->NAME = std::move(v)), std::move(*this); }
|
||||
|
||||
ZX_PROPERTY(bool, tryHarder, setTryHarder)
|
||||
ZX_PROPERTY(bool, tryRotate, setTryRotate)
|
||||
ZX_PROPERTY(bool, tryInvert, setTryInvert)
|
||||
ZX_PROPERTY(bool, tryDownscale, setTryDownscale)
|
||||
#ifdef ZXING_EXPERIMENTAL_API
|
||||
ZX_PROPERTY(bool, tryDenoise, setTryDenoise)
|
||||
#endif
|
||||
ZX_PROPERTY(Binarizer, binarizer, setBinarizer)
|
||||
ZX_PROPERTY(bool, isPure, setIsPure)
|
||||
ZX_PROPERTY(uint16_t, downscaleThreshold, setDownscaleThreshold)
|
||||
ZX_PROPERTY(uint8_t, downscaleFactor, setDownscaleFactor)
|
||||
ZX_PROPERTY(uint8_t, minLineCount, setMinLineCount)
|
||||
ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols)
|
||||
ZX_PROPERTY(bool, validateOptionalChecksum, setValidateOptionalChecksum)
|
||||
ZX_PROPERTY(bool, returnErrors, setReturnErrors)
|
||||
ZX_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, setEanAddOnSymbol)
|
||||
ZX_PROPERTY(TextMode, textMode, setTextMode)
|
||||
ZX_PROPERTY(CharacterSet, characterSet, setCharacterSet)
|
||||
|
||||
#undef ZX_PROPERTY
|
||||
|
||||
ReaderOptions& ReaderOptions::characterSet(std::string_view v) &
|
||||
{
|
||||
d->characterSet = CharacterSetFromString(v);
|
||||
return *this;
|
||||
}
|
||||
ReaderOptions&& ReaderOptions::characterSet(std::string_view v) &&
|
||||
{
|
||||
d->characterSet = CharacterSetFromString(v);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
bool ReaderOptions::hasFormat(const BarcodeFormats& formats) const noexcept
|
||||
{
|
||||
return d->formats.empty() || std::any_of(formats.begin(), formats.end(), [this](BarcodeFormat bt) { return bt <= d->formats; });
|
||||
}
|
||||
|
||||
bool ReaderOptions::hasAnyFormat(const BarcodeFormats& formats) const noexcept
|
||||
{
|
||||
return d->formats.empty() || std::any_of(formats.begin(), formats.end(), [this](BarcodeFormat bt) { return bt & d->formats; });
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
// ReadBarcode implementation
|
||||
// ==============================================================================
|
||||
|
||||
#ifdef ZXING_READERS
|
||||
|
||||
class LumImage : public Image
|
||||
{
|
||||
public:
|
||||
using Image::Image;
|
||||
|
||||
uint8_t* data() { return const_cast<uint8_t*>(Image::data()); }
|
||||
};
|
||||
|
||||
template<typename P>
|
||||
static LumImage ExtractLum(const ImageView& iv, P projection)
|
||||
{
|
||||
LumImage res(iv.width(), iv.height());
|
||||
|
||||
auto* dst = res.data();
|
||||
for(int y = 0; y < iv.height(); ++y)
|
||||
for(int x = 0, w = iv.width(); x < w; ++x)
|
||||
*dst++ = projection(iv.data(x, y));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
class LumImagePyramid
|
||||
{
|
||||
std::vector<LumImage> buffers;
|
||||
|
||||
template<int N>
|
||||
void addLayer()
|
||||
{
|
||||
auto siv = layers.back();
|
||||
buffers.emplace_back(siv.width() / N, siv.height() / N);
|
||||
layers.push_back(buffers.back());
|
||||
auto& div = buffers.back();
|
||||
auto* d = div.data();
|
||||
|
||||
for (int dy = 0; dy < div.height(); ++dy)
|
||||
for (int dx = 0; dx < div.width(); ++dx) {
|
||||
int sum = (N * N) / 2;
|
||||
for (int ty = 0; ty < N; ++ty)
|
||||
for (int tx = 0; tx < N; ++tx)
|
||||
sum += *siv.data(dx * N + tx, dy * N + ty);
|
||||
*d++ = sum / (N * N);
|
||||
}
|
||||
}
|
||||
|
||||
void addLayer(int factor)
|
||||
{
|
||||
// help the compiler's auto-vectorizer by hard-coding the scale factor
|
||||
switch (factor) {
|
||||
case 2: addLayer<2>(); break;
|
||||
case 3: addLayer<3>(); break;
|
||||
case 4: addLayer<4>(); break;
|
||||
default: throw std::invalid_argument("Invalid ReaderOptions::downscaleFactor"); break;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<ImageView> layers;
|
||||
|
||||
LumImagePyramid(const ImageView& iv, int threshold, int factor)
|
||||
{
|
||||
if (factor < 2)
|
||||
throw std::invalid_argument("Invalid ReaderOptions::downscaleFactor");
|
||||
|
||||
layers.push_back(iv);
|
||||
// TODO: if only matrix codes were considered, then using std::min would be sufficient (see #425)
|
||||
while (threshold > 0 && std::max(layers.back().width(), layers.back().height()) > threshold &&
|
||||
std::min(layers.back().width(), layers.back().height()) >= factor)
|
||||
addLayer(factor);
|
||||
#if 0
|
||||
// Reversing the layers means we'd start with the smallest. that can make sense if we are only looking for a
|
||||
// single symbol. If we start with the higher resolution, we get better (high res) position information.
|
||||
// TODO: see if masking out higher res layers based on found symbols in lower res helps overall performance.
|
||||
std::reverse(layers.begin(), layers.end());
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
ImageView SetupLumImageView(ImageView iv, LumImage& lum, const ReaderOptions& opts)
|
||||
{
|
||||
if (iv.format() == ImageFormat::None)
|
||||
throw std::invalid_argument("Invalid image format");
|
||||
|
||||
if (opts.binarizer() == Binarizer::GlobalHistogram || opts.binarizer() == Binarizer::LocalAverage) {
|
||||
// manually spell out the 3 most common pixel formats to get at least gcc to vectorize the code
|
||||
if (iv.format() == ImageFormat::RGB && iv.pixStride() == 3) {
|
||||
lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[0], src[1], src[2]); });
|
||||
} else if (iv.format() == ImageFormat::RGBA && iv.pixStride() == 4) {
|
||||
lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[0], src[1], src[2]); });
|
||||
} else if (iv.format() == ImageFormat::BGR && iv.pixStride() == 3) {
|
||||
lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[2], src[1], src[0]); });
|
||||
} else if (iv.format() != ImageFormat::Lum) {
|
||||
lum = ExtractLum(iv, [r = RedIndex(iv.format()), g = GreenIndex(iv.format()), b = BlueIndex(iv.format())](
|
||||
const uint8_t* src) { return RGBToLum(src[r], src[g], src[b]); });
|
||||
} else if (iv.pixStride() != 1) {
|
||||
// GlobalHistogram and LocalAverage need dense line memory layout
|
||||
lum = ExtractLum(iv, [](const uint8_t* src) { return *src; });
|
||||
}
|
||||
if (lum.data())
|
||||
return lum;
|
||||
}
|
||||
return iv;
|
||||
}
|
||||
|
||||
std::unique_ptr<BinaryBitmap> CreateBitmap(ZXing::Binarizer binarizer, const ImageView& iv)
|
||||
{
|
||||
switch (binarizer) {
|
||||
case Binarizer::BoolCast: return std::make_unique<ThresholdBinarizer>(iv, 0);
|
||||
case Binarizer::FixedThreshold: return std::make_unique<ThresholdBinarizer>(iv, 127);
|
||||
case Binarizer::GlobalHistogram: return std::make_unique<GlobalHistogramBinarizer>(iv);
|
||||
case Binarizer::LocalAverage: return std::make_unique<HybridBinarizer>(iv);
|
||||
}
|
||||
return {}; // silence gcc warning
|
||||
}
|
||||
|
||||
Barcode ReadBarcode(const ImageView& _iv, const ReaderOptions& opts)
|
||||
{
|
||||
return FirstOrDefault(ReadBarcodes(_iv, ReaderOptions(opts).maxNumberOfSymbols(1)));
|
||||
}
|
||||
|
||||
Barcodes ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts)
|
||||
{
|
||||
if (sizeof(PatternType) < 4 && (_iv.width() > 0xffff || _iv.height() > 0xffff))
|
||||
throw std::invalid_argument("Maximum image width/height is 65535");
|
||||
|
||||
if (!_iv.data() || _iv.width() * _iv.height() == 0)
|
||||
throw std::invalid_argument("ImageView is null/empty");
|
||||
|
||||
LumImage lum;
|
||||
ImageView iv = SetupLumImageView(_iv, lum, opts);
|
||||
MultiFormatReader reader(opts);
|
||||
|
||||
if (opts.isPure())
|
||||
return {FirstOrDefault(reader.read(*CreateBitmap(opts.binarizer(), iv), 1)).setReaderOptions(opts)};
|
||||
|
||||
std::unique_ptr<MultiFormatReader> closedReader;
|
||||
#ifdef ZXING_EXPERIMENTAL_API
|
||||
using enum BarcodeFormat;
|
||||
BarcodeFormats formatsBenefittingFromClosing = Aztec | DataMatrix | QRCode;
|
||||
ReaderOptions closedOptions = opts;
|
||||
if (opts.tryDenoise() && opts.hasAnyFormat(formatsBenefittingFromClosing) && _iv.height() >= 3) {
|
||||
closedOptions.formats(opts.formats().empty() ? formatsBenefittingFromClosing : formatsBenefittingFromClosing & opts.formats());
|
||||
closedReader = std::make_unique<MultiFormatReader>(closedOptions);
|
||||
}
|
||||
#endif
|
||||
LumImagePyramid pyramid(iv, opts.downscaleThreshold() * opts.tryDownscale(), opts.downscaleFactor());
|
||||
|
||||
Barcodes res;
|
||||
int maxSymbols = opts.maxNumberOfSymbols() ? opts.maxNumberOfSymbols() : INT_MAX;
|
||||
for (auto&& iv : pyramid.layers) {
|
||||
auto bitmap = CreateBitmap(opts.binarizer(), iv);
|
||||
for (int close = 0; close <= (closedReader ? 1 : 0); ++close) {
|
||||
if (close) {
|
||||
// if we already inverted the image in the first round, we need to undo that first
|
||||
if (bitmap->inverted())
|
||||
bitmap->invert();
|
||||
bitmap->close();
|
||||
}
|
||||
|
||||
// TODO: check if closing after invert would be beneficial
|
||||
for (int invert = 0; invert <= static_cast<int>(opts.tryInvert() && !close); ++invert) {
|
||||
if (invert)
|
||||
bitmap->invert();
|
||||
auto rs = (close ? *closedReader : reader).read(*bitmap, maxSymbols);
|
||||
for (auto& r : rs) {
|
||||
if (iv.width() != _iv.width())
|
||||
r.d->position = Scale(r.position(), _iv.width() / iv.width());
|
||||
if (!Contains(res, r)) {
|
||||
r.setReaderOptions(opts);
|
||||
r.d->isInverted = bitmap->inverted();
|
||||
res.push_back(std::move(r));
|
||||
--maxSymbols;
|
||||
}
|
||||
}
|
||||
if (maxSymbols <= 0)
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.returnErrors()) {
|
||||
// if symbols overlap and one is in error remove it
|
||||
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->error() != b->error()))
|
||||
*(a->error() ? a : b) = BarcodeData();
|
||||
|
||||
std::erase_if(res, [](auto&& r) { return r.format() == BarcodeFormat::None; });
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#else // ZXING_READERS
|
||||
|
||||
Barcode ReadBarcode(const ImageView&, const ReaderOptions&)
|
||||
{
|
||||
throw std::runtime_error("This build of zxing-cpp does not support reading barcodes.");
|
||||
}
|
||||
|
||||
Barcodes ReadBarcodes(const ImageView&, const ReaderOptions&)
|
||||
{
|
||||
throw std::runtime_error("This build of zxing-cpp does not support reading barcodes.");
|
||||
}
|
||||
|
||||
#endif // ZXING_READERS
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2019 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ReaderOptions.h"
|
||||
#include "ImageView.h"
|
||||
#include "Barcode.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* Read barcode from an ImageView
|
||||
*
|
||||
* @param image view of the image data including layout and format
|
||||
* @param options optional ReaderOptions to parameterize / speed up detection
|
||||
* @return Barcode found, if any, otherwise a Barcode with empty content and format ZXing::BarcodeFormat::None
|
||||
*/
|
||||
Barcode ReadBarcode(const ImageView& image, const ReaderOptions& options = {});
|
||||
|
||||
/**
|
||||
* Read barcodes from an ImageView
|
||||
*
|
||||
* @param image view of the image data including layout and format
|
||||
* @param options optional ReaderOptions to parameterize / speed up detection
|
||||
* @return List of Barcode found, may be empty
|
||||
*/
|
||||
Barcodes ReadBarcodes(const ImageView& image, const ReaderOptions& options = {});
|
||||
|
||||
} // ZXing
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ReaderOptions.h"
|
||||
#include "BarcodeData.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class BinaryBitmap;
|
||||
class ReaderOptions;
|
||||
|
||||
class Reader
|
||||
{
|
||||
protected:
|
||||
const ReaderOptions& _opts;
|
||||
|
||||
public:
|
||||
const bool supportsInversion;
|
||||
|
||||
explicit Reader(const ReaderOptions& opts, bool supportsInversion = false) : _opts(opts), supportsInversion(supportsInversion) {}
|
||||
explicit Reader(ReaderOptions&& opts) = delete;
|
||||
virtual ~Reader() = default;
|
||||
|
||||
virtual BarcodesData read(const BinaryBitmap& image, int maxSymbols) const = 0;
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BarcodeFormat.h"
|
||||
#include "CharacterSet.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* @brief Specify which algorithm to use for the grayscale to binary transformation.
|
||||
*
|
||||
* The difference is how to get to a threshold value T which results in a bit value R(esult) = L(uminance) <= T(hreshold).
|
||||
*/
|
||||
enum class Binarizer : unsigned char // needs to be unsigned for the bitfield below to work, uint8_t fails as well
|
||||
{
|
||||
LocalAverage, ///< T = average of neighboring pixels for matrix and GlobalHistogram for linear (HybridBinarizer)
|
||||
GlobalHistogram, ///< T = valley between the 2 largest peaks in the histogram (per line in linear case)
|
||||
FixedThreshold, ///< T = 127
|
||||
BoolCast, ///< T = 0, fastest possible
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Specify whether to ignore, read or require EAN-2/5 add-on symbols while scanning EAN/UPC codes.
|
||||
*/
|
||||
enum class EanAddOnSymbol : unsigned char // see above
|
||||
{
|
||||
Ignore, ///< Ignore any Add-On symbol during read/scan
|
||||
Read, ///< Read EAN-2/EAN-5 Add-On symbol if found
|
||||
Require, ///< Require EAN-2/EAN-5 Add-On symbol to be present
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Specify how the decoded byte content of a barcode should be transcoded to text.
|
||||
*
|
||||
* @see Barcode::text(), ReaderOptions::textMode().
|
||||
*/
|
||||
enum class TextMode : unsigned char // see above
|
||||
{
|
||||
Plain, ///< bytes() transcoded to unicode based on ECI info or guessed charset (the default mode prior to 2.0)
|
||||
ECI, ///< standard content following the ECI protocol with every character set ECI segment transcoded to unicode
|
||||
HRI, ///< Human Readable Interpretation (dependent on the ContentType)
|
||||
Escaped, ///< Use the EscapeNonGraphical() function (e.g. ASCII 29 will be transcoded to "<GS>")
|
||||
Hex, ///< bytes() transcoded to ASCII string of HEX values
|
||||
HexECI, ///< bytesECI() transcoded to ASCII string of HEX values
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Configuration options for barcode reading and decoding behavior.
|
||||
*
|
||||
* ReaderOptions encapsulates a set of flags and parameters that control
|
||||
* how barcode detection and decoding is performed. It provides
|
||||
* fluent setters that support chaining. Both `name(val)` and `setName(val)`
|
||||
* forms are available for convenience and compatibility.
|
||||
*
|
||||
* The class is intended to be passed to the ReadBarcodes function to
|
||||
* influence scanning heuristics, performance vs. accuracy trade-offs, output
|
||||
* formatting, and symbol filtering. Instances can be reused across multiple
|
||||
* read operations.
|
||||
*
|
||||
* The default settings are optimized for detection rate and can be tuned
|
||||
* for speed or specific use-cases.
|
||||
*
|
||||
* @see BarcodeFormats, Binarizer, TextMode, CharacterSet, ReadBarcodes
|
||||
*/
|
||||
class ReaderOptions
|
||||
{
|
||||
struct Data;
|
||||
std::unique_ptr<Data> d;
|
||||
|
||||
public:
|
||||
ReaderOptions();
|
||||
~ReaderOptions();
|
||||
ReaderOptions(const ReaderOptions&);
|
||||
ReaderOptions& operator=(const ReaderOptions&);
|
||||
ReaderOptions(ReaderOptions&&) noexcept;
|
||||
ReaderOptions& operator=(ReaderOptions&&) noexcept;
|
||||
|
||||
#define ZX_PROPERTY(TYPE, NAME, SETTER, ...) \
|
||||
TYPE NAME() const noexcept; \
|
||||
__VA_ARGS__ ReaderOptions& NAME(TYPE v) &; \
|
||||
__VA_ARGS__ ReaderOptions&& NAME(TYPE v) &&; \
|
||||
__VA_ARGS__ inline ReaderOptions& SETTER(TYPE v) & { return NAME(v); } \
|
||||
__VA_ARGS__ inline ReaderOptions&& SETTER(TYPE v) && { return std::move(*this).NAME(v); }
|
||||
|
||||
/// Specify a set of BarcodeFormats that should be searched for, the default is all supported formats.
|
||||
const BarcodeFormats& formats() const noexcept;
|
||||
ReaderOptions& formats(BarcodeFormats&& v) &;
|
||||
ReaderOptions&& formats(BarcodeFormats&& v) &&;
|
||||
ReaderOptions& formats(const BarcodeFormats& v) & { return formats(BarcodeFormats(v)); }
|
||||
ReaderOptions&& formats(const BarcodeFormats& v) && { return std::move(*this).formats(BarcodeFormats(v)); }
|
||||
inline ReaderOptions& setFormats(BarcodeFormats&& v) & { return formats(std::move(v)); }
|
||||
inline ReaderOptions&& setFormats(BarcodeFormats&& v) && { return std::move(*this).formats(std::move(v)); }
|
||||
inline ReaderOptions& setFormats(const BarcodeFormats& v) & { return formats(BarcodeFormats(v)); }
|
||||
inline ReaderOptions&& setFormats(const BarcodeFormats& v) && { return std::move(*this).formats(BarcodeFormats(v)); }
|
||||
|
||||
/// Spend more time to try to find a barcode; optimize for accuracy instead of not speed (default: true).
|
||||
ZX_PROPERTY(bool, tryHarder, setTryHarder)
|
||||
|
||||
/// Try detecting codes in 90, 180 and 270 degree rotated images (default: true).
|
||||
ZX_PROPERTY(bool, tryRotate, setTryRotate)
|
||||
|
||||
/// Try detecting inverted ("reversed reflectance") codes if the format allows for those (default: true).
|
||||
ZX_PROPERTY(bool, tryInvert, setTryInvert)
|
||||
|
||||
/// Try detecting code in downscaled images (depending on image size) (default: true).
|
||||
ZX_PROPERTY(bool, tryDownscale, setTryDownscale)
|
||||
|
||||
#ifdef ZXING_EXPERIMENTAL_API
|
||||
/// Also try detecting code after denoising (currently morphological closing filter for 2D formats only).
|
||||
ZX_PROPERTY(bool, tryDenoise, setTryDenoise)
|
||||
#endif
|
||||
|
||||
/// Binarizer to use for grayscale to binary transformation (default: Binarizer::LocalAverage).
|
||||
ZX_PROPERTY(Binarizer, binarizer, setBinarizer)
|
||||
|
||||
/// Set to true if the input contains nothing but a single perfectly aligned barcode (generated image).
|
||||
ZX_PROPERTY(bool, isPure, setIsPure)
|
||||
|
||||
/// Image size ( min(width, height) ) threshold at which to start downscaled scanning.
|
||||
ZX_PROPERTY(uint16_t, downscaleThreshold, setDownscaleThreshold)
|
||||
|
||||
/// Scale factor used during downscaling, meaningful values are 2, 3 and 4.
|
||||
ZX_PROPERTY(uint8_t, downscaleFactor, setDownscaleFactor)
|
||||
|
||||
/// The number of scan lines in a linear barcode that have to be equal to accept the result (default: 2).
|
||||
ZX_PROPERTY(uint8_t, minLineCount, setMinLineCount)
|
||||
|
||||
/// The maximum number of symbols (barcodes) to detect / look for with ReadBarcodes().
|
||||
ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols)
|
||||
|
||||
/// Validate optional checksums where applicable (e.g. Code39, ITF) (default: false).
|
||||
ZX_PROPERTY(bool, validateOptionalChecksum, setValidateOptionalChecksum)
|
||||
|
||||
/// If true, return the barcodes with errors as well (e.g. checksum errors, see Barcode::error()) (default: false).
|
||||
ZX_PROPERTY(bool, returnErrors, setReturnErrors)
|
||||
|
||||
/// Specify whether to ignore, read or require EAN-2/5 add-on symbols while scanning EAN/UPC codes.
|
||||
ZX_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, setEanAddOnSymbol)
|
||||
|
||||
/// Specifies the TextMode that controls the return of the Barcode::text() function (default: TextMode::HRI).
|
||||
ZX_PROPERTY(TextMode, textMode, setTextMode)
|
||||
|
||||
/// Specifies fallback character set to use instead of auto-detecting it (when applicable).
|
||||
ZX_PROPERTY(CharacterSet, characterSet, setCharacterSet)
|
||||
ReaderOptions& characterSet(std::string_view v) &;
|
||||
ReaderOptions&& characterSet(std::string_view v) &&;
|
||||
inline ReaderOptions& setCharacterSet(std::string_view v) & { return characterSet(v); }
|
||||
inline ReaderOptions&& setCharacterSet(std::string_view v) && { return std::move(*this).characterSet(v); }
|
||||
|
||||
#undef ZX_PROPERTY
|
||||
|
||||
/// @cond DEPRECATED
|
||||
|
||||
// Silence deprecated-declarations warnings, only happening here for deprecated inline functions
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
|
||||
#define ZX_DEPRECATED_PROPERTY(TYPE, NAME, SETTER, GET_IMPL, SET_IMPL) \
|
||||
[[deprecated]] inline TYPE NAME() const noexcept { return GET_IMPL; } \
|
||||
[[deprecated]] ReaderOptions& NAME(TYPE v) & { SET_IMPL; return *this; } \
|
||||
[[deprecated]] ReaderOptions&& NAME(TYPE v) && { SET_IMPL; return std::move(*this); } \
|
||||
[[deprecated]] inline ReaderOptions& SETTER(TYPE v) & { return NAME(v); } \
|
||||
[[deprecated]] inline ReaderOptions&& SETTER(TYPE v) && { return std::move(*this).NAME(v); }
|
||||
|
||||
/// @deprecated (does nothing). See BarcodeFormat::Code39Ext and ::Code39Std to select full ASCII or standard Code39 mode.
|
||||
ZX_DEPRECATED_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode, true, (void)v)
|
||||
|
||||
/// @deprecated (use validateOptionalChecksum). The Code39 symbol has a valid checksum iff symbologyIdentifier()[2] is an odd digit
|
||||
ZX_DEPRECATED_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum, validateOptionalChecksum(),
|
||||
validateOptionalChecksum(v))
|
||||
|
||||
/// @deprecated (use validateOptionalChecksum). The ITF symbol has a valid checksum iff symbologyIdentifier()[2] == '1'.
|
||||
ZX_DEPRECATED_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum, validateOptionalChecksum(), validateOptionalChecksum(v))
|
||||
|
||||
#undef ZX_DEPRECATED_PROPERTY
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#elif defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
/// @endcond
|
||||
|
||||
#ifdef ZXING_INTERNAL
|
||||
/// Check if a specific format is explicitly enabled in the formats set
|
||||
bool hasFormat(const BarcodeFormats& formats) const noexcept;
|
||||
|
||||
/// Check if any format is explicitly or implicitly enabled in the formats set
|
||||
bool hasAnyFormat(const BarcodeFormats& formats) const noexcept;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2026 Axel Waggershauser
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ReedSolomon.h"
|
||||
#include "Version.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include "librscpp/encode.h"
|
||||
#include "librscpp/decode.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
namespace rs = librscpp;
|
||||
|
||||
using GF2nI = rs::GF2n<>;
|
||||
|
||||
const GF2nI& GetGF2n(RSField field)
|
||||
{
|
||||
static GF2nI gf2n_4_1(0x0013, 1); // x^4 + x + 1
|
||||
static GF2nI gf2n_6_1(0x0043, 1); // x^6 + x + 1
|
||||
static GF2nI gf2n_8_5_3_2(0x012D, 1); // x^8 + x^5 + x^3 + x^2 + 1 : DataMatrix
|
||||
static GF2nI gf2n_8_4_3_2(0x011D, 0); // x^8 + x^4 + x^3 + x^2 + 1 : QRCode
|
||||
static GF2nI gf2n_10_3(0x0409, 1); // x^10 + x^3 + 1
|
||||
static GF2nI gf2n_12_6_5_3(0x1069, 1); // x^12 + x^6 + x^5 + x^3 + 1
|
||||
|
||||
switch (field) {
|
||||
case RSField::Aztec4: return gf2n_4_1;
|
||||
case RSField::Aztec6: return gf2n_6_1;
|
||||
case RSField::Aztec8: return gf2n_8_5_3_2;
|
||||
case RSField::Aztec10: return gf2n_10_3;
|
||||
case RSField::Aztec12: return gf2n_12_6_5_3;
|
||||
case RSField::DataMatrix: return gf2n_8_5_3_2;
|
||||
case RSField::MaxiCode: return gf2n_6_1;
|
||||
case RSField::QRCode: return gf2n_8_4_3_2;
|
||||
default: throw std::invalid_argument("Unsupported RSField");
|
||||
}
|
||||
}
|
||||
|
||||
const rs::GFp<>& GetGFPDF417()
|
||||
{
|
||||
static rs::GFp<> gf929_3(929, 3, 1);
|
||||
return gf929_3;
|
||||
}
|
||||
|
||||
// See ISO/IEC 15415:2024 7.5.9
|
||||
static std::optional<double> UEC(std::optional<int> usedECC, int numECC)
|
||||
{
|
||||
return usedECC ? std::optional(std::clamp(1.0 - static_cast<double>(*usedECC) / numECC, 0.0, 1.0)) : std::nullopt;
|
||||
}
|
||||
|
||||
#ifdef ZXING_READERS
|
||||
std::optional<double> ReedSolomonDecode(RSField field, std::span<uint8_t> codeword, int numECC, std::span<const int> erasures)
|
||||
{
|
||||
return UEC(rs::decode(GetGF2n(field), codeword, numECC, erasures), numECC);
|
||||
}
|
||||
|
||||
std::optional<double> ReedSolomonDecode(RSField field, std::span<int> codeword, int numECC, std::span<const int> erasures)
|
||||
{
|
||||
// The number of erasures may at most be numECC.
|
||||
// Note that leaves 0 parity symbols to error correction (or even detection) but the final syndrome verification in rs::decode will
|
||||
// still catch any errors in that case.
|
||||
if (Size(erasures) > numECC)
|
||||
return {};
|
||||
|
||||
#if ZXING_ENABLE_PDF417
|
||||
if (field == RSField::PDF417)
|
||||
return UEC(rs::decode(GetGFPDF417(), codeword, numECC, erasures), numECC);
|
||||
else
|
||||
#endif
|
||||
return UEC(rs::decode(GetGF2n(field), codeword, numECC, erasures), numECC);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ZXING_WRITERS
|
||||
void ReedSolomonEncode(RSField field, std::span<const uint8_t> data, std::span<uint8_t> parity)
|
||||
{
|
||||
rs::encode(GetGF2n(field), data, parity);
|
||||
}
|
||||
|
||||
void ReedSolomonEncode(RSField field, std::span<uint8_t> codeword, int numECC)
|
||||
{
|
||||
rs::encode_inplace(GetGF2n(field), codeword, numECC);
|
||||
}
|
||||
|
||||
void ReedSolomonEncode(RSField field, std::span<int> codeword, int numECC)
|
||||
{
|
||||
#if ZXING_ENABLE_PDF417
|
||||
if (field == RSField::PDF417)
|
||||
return rs::encode_inplace(GetGFPDF417(), codeword, numECC);
|
||||
else
|
||||
#endif
|
||||
rs::encode_inplace(GetGF2n(field), codeword, numECC);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2026 Axel Waggershauser
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
enum class RSField
|
||||
{
|
||||
Aztec4,
|
||||
Aztec6,
|
||||
Aztec8,
|
||||
Aztec10,
|
||||
Aztec12,
|
||||
DataMatrix,
|
||||
MaxiCode,
|
||||
PDF417,
|
||||
QRCode
|
||||
};
|
||||
|
||||
inline RSField GF2nAztec(int wordSize)
|
||||
{
|
||||
switch (wordSize) {
|
||||
case 4: return RSField::Aztec4;
|
||||
case 6: return RSField::Aztec6;
|
||||
case 8: return RSField::Aztec8;
|
||||
case 10: return RSField::Aztec10;
|
||||
case 12: return RSField::Aztec12;
|
||||
default: throw std::invalid_argument("Unsupported word size " + std::to_string(wordSize));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ReedSolomonDecode fixes errors in a codeword containing both data and parity symbols.
|
||||
*
|
||||
* @param codeword data and error-correction/parity symbols; corrected in place on success
|
||||
* @param numECC number of error-correction/parity symbols
|
||||
* @param erasures positions of known erasures in the codeword
|
||||
* @return optional unused error correction in the range [0, 1] if codeword errors could successfully be fixed (or there have not been
|
||||
* any), std::nullopt otherwise
|
||||
*/
|
||||
std::optional<double> ReedSolomonDecode(RSField field, std::span<int> codeword, int numECC, std::span<const int> erasures = {});
|
||||
|
||||
std::optional<double> ReedSolomonDecode(RSField field, std::span<uint8_t> codeword, int numECC,
|
||||
std::span<const int> erasures = {});
|
||||
|
||||
/**
|
||||
* @brief ReedSolomonEncode generates error correction symbols for the given data symbols.
|
||||
*
|
||||
* @param field The Galois field to use for encoding.
|
||||
* @param data The input data symbols.
|
||||
* @param parity The output buffer for the generated error correction symbols.
|
||||
*/
|
||||
void ReedSolomonEncode(RSField field, std::span<const uint8_t> data, std::span<uint8_t> parity);
|
||||
|
||||
/// @brief ReedSolomonEncode replaces the last numECC symbols in codeword with parity symbols
|
||||
void ReedSolomonEncode(RSField field, std::span<uint8_t> codeword, int numECC);
|
||||
void ReedSolomonEncode(RSField field, std::span<int> codeword, int numECC);
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Point.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
#include <cstdio>
|
||||
#endif
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class RegressionLine
|
||||
{
|
||||
protected:
|
||||
std::vector<PointF> _points;
|
||||
PointF _directionInward;
|
||||
PointF::value_t a = NAN, b = NAN, c = NAN;
|
||||
|
||||
friend PointF intersect(const RegressionLine& l1, const RegressionLine& l2);
|
||||
|
||||
template<typename T> bool evaluate(const PointT<T>* begin, const PointT<T>* end)
|
||||
{
|
||||
auto mean = Reduce(begin, end, PointF()) / std::distance(begin, end);
|
||||
PointF::value_t sumXX = 0, sumYY = 0, sumXY = 0;
|
||||
for (auto p = begin; p != end; ++p) {
|
||||
auto d = *p - mean;
|
||||
sumXX += d.x * d.x;
|
||||
sumYY += d.y * d.y;
|
||||
sumXY += d.x * d.y;
|
||||
}
|
||||
if (sumYY >= sumXX) {
|
||||
auto l = std::sqrt(sumYY * sumYY + sumXY * sumXY);
|
||||
a = +sumYY / l;
|
||||
b = -sumXY / l;
|
||||
} else {
|
||||
auto l = std::sqrt(sumXX * sumXX + sumXY * sumXY);
|
||||
a = +sumXY / l;
|
||||
b = -sumXX / l;
|
||||
}
|
||||
if (dot(_directionInward, normal()) < 0) {
|
||||
a = -a;
|
||||
b = -b;
|
||||
}
|
||||
c = dot(normal(), mean); // (a*mean.x + b*mean.y);
|
||||
return dot(_directionInward, normal()) > 0.5f; // angle between original and new direction is at most 60 degree
|
||||
}
|
||||
|
||||
template <typename T> bool evaluate(const std::vector<PointT<T>>& points) { return evaluate(&points.front(), &points.back() + 1); }
|
||||
|
||||
template <typename T> static auto distance(PointT<T> a, PointT<T> b) { return ZXing::distance(a, b); }
|
||||
|
||||
public:
|
||||
RegressionLine() { _points.reserve(16); } // arbitrary but plausible start size (tiny performance improvement)
|
||||
|
||||
template<typename T> RegressionLine(PointT<T> a, PointT<T> b)
|
||||
{
|
||||
evaluate(std::vector{a, b});
|
||||
}
|
||||
|
||||
template<typename T> RegressionLine(const PointT<T>* b, const PointT<T>* e)
|
||||
{
|
||||
evaluate(b, e);
|
||||
}
|
||||
|
||||
const auto& points() const { return _points; }
|
||||
int length() const { return _points.size() >= 2 ? int(distance(_points.front(), _points.back())) : 0; }
|
||||
bool isValid() const { return !std::isnan(a); }
|
||||
PointF normal() const { return isValid() ? PointF(a, b) : _directionInward; }
|
||||
auto signedDistance(PointF p) const { return dot(normal(), p) - c; }
|
||||
template <typename T> auto distance(PointT<T> p) const { return std::abs(signedDistance(PointF(p))); }
|
||||
PointF project(PointF p) const { return p - signedDistance(p) * normal(); }
|
||||
PointF centroid() const { return Reduce(_points) / _points.size(); }
|
||||
|
||||
void reset()
|
||||
{
|
||||
_points.clear();
|
||||
_directionInward = {};
|
||||
a = b = c = NAN;
|
||||
}
|
||||
|
||||
void add(PointF p) {
|
||||
assert(_directionInward != PointF());
|
||||
_points.push_back(p);
|
||||
if (_points.size() == 1)
|
||||
c = dot(normal(), p);
|
||||
}
|
||||
|
||||
void pop_back() { _points.pop_back(); }
|
||||
void pop_front()
|
||||
{
|
||||
std::rotate(_points.begin(), _points.begin() + 1, _points.end());
|
||||
_points.pop_back();
|
||||
}
|
||||
|
||||
void setDirectionInward(PointF d) { _directionInward = normalized(d); }
|
||||
|
||||
bool evaluate(double maxSignedDist = -1, bool updatePoints = false)
|
||||
{
|
||||
bool ret = evaluate(_points);
|
||||
if (maxSignedDist > 0) {
|
||||
auto points = _points;
|
||||
while (true) {
|
||||
auto old_points_size = points.size();
|
||||
// remove points that are further 'inside' than maxSignedDist or further 'outside' than 2 x maxSignedDist
|
||||
std::erase_if(points, [this, maxSignedDist](auto p) {
|
||||
auto sd = this->signedDistance(p);
|
||||
return sd > maxSignedDist || sd < -2 * maxSignedDist;
|
||||
});
|
||||
// if we threw away too many points, something is off with the line to begin with
|
||||
if (points.size() < old_points_size / 2 || points.size() < 2)
|
||||
return false;
|
||||
if (old_points_size == points.size())
|
||||
break;
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("removed %zu points -> %zu remaining\n", old_points_size - points.size(), points.size());
|
||||
fflush(stdout);
|
||||
#endif
|
||||
ret = evaluate(points);
|
||||
}
|
||||
|
||||
if (updatePoints)
|
||||
_points = std::move(points);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool isHighRes() const
|
||||
{
|
||||
PointF min = _points.front(), max = _points.front();
|
||||
for (auto p : _points) {
|
||||
UpdateMinMax(min.x, max.x, p.x);
|
||||
UpdateMinMax(min.y, max.y, p.y);
|
||||
}
|
||||
auto diff = max - min;
|
||||
auto len = maxAbsComponent(diff);
|
||||
auto steps = std::min(std::abs(diff.x), std::abs(diff.y));
|
||||
// due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal
|
||||
return steps > 2 || len > 50;
|
||||
}
|
||||
};
|
||||
|
||||
inline PointF intersect(const RegressionLine& l1, const RegressionLine& l2)
|
||||
{
|
||||
assert(l1.isValid() && l2.isValid());
|
||||
auto d = l1.a * l2.b - l1.b * l2.a;
|
||||
auto x = (l1.c * l2.b - l1.b * l2.c) / d;
|
||||
auto y = (l1.a * l2.c - l1.c * l2.a) / d;
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ResultPoint.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
float ResultPoint::Distance(int aX, int aY, int bX, int bY)
|
||||
{
|
||||
auto dx = static_cast<float>(aX - bX);
|
||||
auto dy = static_cast<float>(aY - bY);
|
||||
return std::sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Point.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* <p>Encapsulates a point of interest in an image containing a barcode. Typically, this
|
||||
* would be the location of a finder pattern or the corner of the barcode, for example.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
class ResultPoint : public PointF
|
||||
{
|
||||
public:
|
||||
ResultPoint() = default;
|
||||
ResultPoint(float x, float y) : PointF(x, y) {}
|
||||
ResultPoint(int x, int y) : PointF(x, y) {}
|
||||
template <typename T> ResultPoint(PointT<T> p) : PointF(p) {}
|
||||
|
||||
float x() const { return static_cast<float>(PointF::x); }
|
||||
float y() const { return static_cast<float>(PointF::y); }
|
||||
|
||||
void set(float x, float y) { *this = PointF(x, y); }
|
||||
|
||||
static float Distance(int aX, int aY, int bX, int bY);
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <version>
|
||||
|
||||
#if defined(__cpp_lib_generator)
|
||||
|
||||
#include <generator>
|
||||
|
||||
#else
|
||||
|
||||
#include <concepts>
|
||||
#include <coroutine>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <iterator>
|
||||
|
||||
namespace std {
|
||||
|
||||
template <std::movable T>
|
||||
class generator
|
||||
{
|
||||
public:
|
||||
struct promise_type
|
||||
{
|
||||
std::optional<T> value_;
|
||||
|
||||
generator get_return_object() noexcept { return generator{handle_type::from_promise(*this)}; }
|
||||
|
||||
static std::suspend_always initial_suspend() noexcept { return {}; }
|
||||
static std::suspend_always final_suspend() noexcept { return {}; }
|
||||
|
||||
std::suspend_always yield_value(T&& value) noexcept
|
||||
{
|
||||
value_ = std::move(value);
|
||||
return {};
|
||||
}
|
||||
|
||||
static void return_void() noexcept {}
|
||||
// Disallow co_await in generator coroutines.
|
||||
void await_transform() = delete;
|
||||
[[noreturn]] static void unhandled_exception() { throw; }
|
||||
};
|
||||
|
||||
using handle_type = std::coroutine_handle<promise_type>;
|
||||
|
||||
generator() noexcept = default;
|
||||
explicit generator(handle_type h) noexcept : h_(h) {}
|
||||
~generator()
|
||||
{
|
||||
if (h_)
|
||||
h_.destroy();
|
||||
}
|
||||
|
||||
generator(generator&& other) noexcept : h_(other.h_) { other.h_ = nullptr; }
|
||||
|
||||
generator& operator=(generator&& other) noexcept
|
||||
{
|
||||
if (this != &other) {
|
||||
if (h_)
|
||||
h_.destroy();
|
||||
h_ = other.h_;
|
||||
other.h_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
generator(const generator&) = delete;
|
||||
generator& operator=(const generator&) = delete;
|
||||
|
||||
class iterator
|
||||
{
|
||||
public:
|
||||
void operator++() { h_.resume(); }
|
||||
T&& operator*() const noexcept { return std::move(*h_.promise().value_); }
|
||||
bool operator==(std::default_sentinel_t) const noexcept { return !h_ || h_.done(); }
|
||||
|
||||
explicit iterator(handle_type h) : h_(h) {}
|
||||
|
||||
private:
|
||||
handle_type h_ = nullptr;
|
||||
};
|
||||
|
||||
iterator begin()
|
||||
{
|
||||
if (h_)
|
||||
h_.resume();
|
||||
return iterator{h_};
|
||||
}
|
||||
|
||||
std::default_sentinel_t end() noexcept { return {}; }
|
||||
|
||||
private:
|
||||
handle_type h_;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif // __cpp_lib_generator
|
||||
|
||||
/*
|
||||
usage example:
|
||||
|
||||
template <std::integral T>
|
||||
std::generator<T> range(T first, const T last)
|
||||
{
|
||||
while (first < last)
|
||||
co_yield first++;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
for (const char i : range(65, 91))
|
||||
std::print("{} ", i);
|
||||
std::println("");
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <version>
|
||||
|
||||
#if defined(__cpp_lib_print) && __cpp_lib_print >= 202207L
|
||||
|
||||
#include <print>
|
||||
|
||||
#else
|
||||
|
||||
#include <format>
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
|
||||
namespace std {
|
||||
|
||||
template <class... Args>
|
||||
inline void print(std::format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
auto s = std::format(fmt, std::forward<Args>(args)...);
|
||||
std::fwrite(s.data(), 1, s.size(), stdout);
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
inline void println(std::format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
std::print(fmt, std::forward<Args>(args)...);
|
||||
std::fputc('\n', stdout);
|
||||
}
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
// idea from <experimental/scope> (library fundamentals TS v3)
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace std {
|
||||
|
||||
template <class F>
|
||||
class scope_exit
|
||||
{
|
||||
public:
|
||||
explicit scope_exit(F&& f) noexcept(std::is_nothrow_move_constructible_v<F>) : func_(std::forward<F>(f)), active_(true) {}
|
||||
|
||||
scope_exit(scope_exit&& other) noexcept(std::is_nothrow_move_constructible_v<F>)
|
||||
: func_(std::move(other.func_)), active_(std::exchange(other.active_, false))
|
||||
{}
|
||||
|
||||
scope_exit(const scope_exit&) = delete;
|
||||
scope_exit& operator=(const scope_exit&) = delete;
|
||||
scope_exit& operator=(scope_exit&&) = delete;
|
||||
|
||||
~scope_exit() noexcept(noexcept(std::declval<F&>()()))
|
||||
{
|
||||
if (active_)
|
||||
func_();
|
||||
}
|
||||
|
||||
void release() noexcept { active_ = false; }
|
||||
|
||||
private:
|
||||
F func_;
|
||||
bool active_;
|
||||
};
|
||||
|
||||
template <class F>
|
||||
scope_exit(F) -> scope_exit<F>;
|
||||
|
||||
} // namespace std
|
||||
|
||||
/**
|
||||
* The SCOPE_EXIT macro is eliminating the need to give the object a name.
|
||||
* Example usage:
|
||||
* SCOPE_EXIT([]{ printf("exiting scope"); });
|
||||
*/
|
||||
|
||||
#define SCOPE_EXIT_CAT2(x, y) x##y
|
||||
#define SCOPE_EXIT_CAT(x, y) SCOPE_EXIT_CAT2(x, y)
|
||||
#define SCOPE_EXIT const auto SCOPE_EXIT_CAT(scopeExit_, __COUNTER__) = std::scope_exit
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2021 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
struct StructuredAppendInfo
|
||||
{
|
||||
int index = -1;
|
||||
int count = -1;
|
||||
std::string id;
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
enum class AIFlag : char { None, GS1, AIM };
|
||||
|
||||
struct SymbologyIdentifier
|
||||
{
|
||||
char code = 0, modifier = 0;
|
||||
signed char eciModifierOffset = 0;
|
||||
AIFlag aiFlag = AIFlag::None;
|
||||
|
||||
std::string toString(bool hasECI = false) const
|
||||
{
|
||||
int modVal = (modifier >= 'A' ? modifier - 'A' + 10 : modifier - '0') + eciModifierOffset * hasECI;
|
||||
return code ? StrCat(']', code, static_cast<char>((modVal >= 10 ? 'A' - 10 : '0') + modVal)) : std::string();
|
||||
}
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2022 gitlost
|
||||
* Copyright 2025 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "TextDecoder.h"
|
||||
|
||||
#include "ZXAlgorithms.h"
|
||||
#include "libzueci/zueci.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include <cstdio>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::string BytesToUtf8(ByteView bytes, ECI eci)
|
||||
{
|
||||
constexpr unsigned int replacement = 0xFFFD;
|
||||
constexpr unsigned int flags = ZUECI_FLAG_SB_STRAIGHT_THRU | ZUECI_FLAG_SJIS_STRAIGHT_THRU;
|
||||
int utf8_len;
|
||||
|
||||
if (eci == ECI::Unknown)
|
||||
eci = ECI::Binary;
|
||||
|
||||
int error_number = zueci_dest_len_utf8(ToInt(eci), bytes.data(), Size(bytes), replacement, flags, &utf8_len);
|
||||
if (error_number >= ZUECI_ERROR) {
|
||||
#ifdef PRINT_DEBUG
|
||||
fprintf(stderr, "zueci_dest_len_utf8 error: %d\n", error_number);
|
||||
#endif
|
||||
// throw FormatError("Invalid UTF-8 data");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string utf8(utf8_len, 0);
|
||||
|
||||
error_number = zueci_eci_to_utf8(ToInt(eci), bytes.data(), Size(bytes), replacement, flags,
|
||||
reinterpret_cast<uint8_t*>(utf8.data()), &utf8_len);
|
||||
if (error_number >= ZUECI_ERROR) {
|
||||
#ifdef PRINT_DEBUG
|
||||
fprintf(stderr, "zueci_eci_to_utf8 error: %d\n", error_number);
|
||||
#endif
|
||||
// throw FormatError("Invalid UTF-8 data");
|
||||
return {};
|
||||
}
|
||||
|
||||
assert(Size(utf8) == utf8_len);
|
||||
|
||||
return utf8;
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2025 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CharacterSet.h"
|
||||
#include "ECI.h"
|
||||
#include "Range.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
std::string BytesToUtf8(ByteView bytes, ECI eci);
|
||||
|
||||
inline std::string BytesToUtf8(ByteView bytes, CharacterSet cs)
|
||||
{
|
||||
return BytesToUtf8(bytes, ToECI(cs));
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "TextEncoder.h"
|
||||
|
||||
#include "CharacterSet.h"
|
||||
#include "ECI.h"
|
||||
#include "Utf.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
#include "Version.h"
|
||||
|
||||
#ifdef ZXING_USE_ZINT
|
||||
#include <zint.h>
|
||||
#else
|
||||
#include "libzueci/zueci.h"
|
||||
#endif
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
void TextEncoder::GetBytes(const std::string& str, CharacterSet charset, std::string& bytes)
|
||||
{
|
||||
int eci = ToInt(ToECI(charset));
|
||||
const int str_len = narrow_cast<int>(str.length());
|
||||
int eci_len;
|
||||
|
||||
if (eci == -1)
|
||||
eci = 899; // Binary
|
||||
|
||||
bytes.clear();
|
||||
|
||||
#ifdef ZXING_USE_ZINT
|
||||
int error_number = ZBarcode_Dest_Len_ECI(eci, reinterpret_cast<const unsigned char *>(str.data()), str_len, &eci_len);
|
||||
if (error_number >= ZINT_ERROR) // Shouldn't happen
|
||||
throw std::logic_error("Internal error `ZBarcode_Dest_Len_ECI()`");
|
||||
|
||||
bytes.resize(eci_len); // Sufficient but approximate length
|
||||
|
||||
error_number = ZBarcode_UTF8_To_ECI(eci,reinterpret_cast<const unsigned char *>(str.data()), str_len,
|
||||
reinterpret_cast<unsigned char *>(bytes.data()), &eci_len);
|
||||
if (error_number >= ZINT_ERROR) {
|
||||
bytes.clear();
|
||||
throw std::invalid_argument("Unexpected charcode");
|
||||
}
|
||||
#else
|
||||
int error_number = zueci_dest_len_eci(eci, reinterpret_cast<const unsigned char *>(str.data()), str_len, &eci_len);
|
||||
if (error_number >= ZUECI_ERROR) // Shouldn't happen
|
||||
throw std::logic_error("Internal error `zueci_dest_len_eci()`");
|
||||
|
||||
bytes.resize(eci_len); // Sufficient but approximate length
|
||||
|
||||
error_number = zueci_utf8_to_eci(eci, reinterpret_cast<const unsigned char *>(str.data()), str_len,
|
||||
reinterpret_cast<unsigned char *>(bytes.data()), &eci_len);
|
||||
if (error_number >= ZUECI_ERROR) {
|
||||
bytes.clear();
|
||||
throw std::invalid_argument("Unexpected charcode");
|
||||
}
|
||||
#endif // ZXING_USE_ZINT
|
||||
|
||||
bytes.resize(eci_len); // Actual length
|
||||
}
|
||||
|
||||
void TextEncoder::GetBytes(const std::wstring& str, CharacterSet charset, std::string& bytes)
|
||||
{
|
||||
GetBytes(ToUtf8(str), charset, bytes);
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CharacterSet.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class TextEncoder
|
||||
{
|
||||
static void GetBytes(const std::string& str, CharacterSet charset, std::string& bytes);
|
||||
static void GetBytes(const std::wstring& str, CharacterSet charset, std::string& bytes);
|
||||
public:
|
||||
static std::string FromUnicode(const std::string& str, CharacterSet charset) {
|
||||
std::string r;
|
||||
GetBytes(str, charset, r);
|
||||
return r;
|
||||
}
|
||||
static std::string FromUnicode(const std::wstring& str, CharacterSet charset) {
|
||||
std::string r;
|
||||
GetBytes(str, charset, r);
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BinaryBitmap.h"
|
||||
#include "BitMatrix.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class ThresholdBinarizer : public BinaryBitmap
|
||||
{
|
||||
const uint8_t _threshold = 0;
|
||||
|
||||
public:
|
||||
ThresholdBinarizer(const ImageView& buffer, uint8_t threshold = 128) : BinaryBitmap(buffer), _threshold(threshold) {}
|
||||
|
||||
bool getPatternRow(int row, int rotation, PatternRow& res) const override
|
||||
{
|
||||
auto buffer = _buffer.rotated(rotation);
|
||||
|
||||
const int stride = buffer.pixStride();
|
||||
const uint8_t* begin = buffer.data(0, row) + GreenIndex(buffer.format());
|
||||
const uint8_t* end = begin + buffer.width() * stride;
|
||||
|
||||
auto* lastPos = begin;
|
||||
bool lastVal = false;
|
||||
|
||||
res.clear();
|
||||
|
||||
for (const uint8_t* p = begin; p != end; p += stride) {
|
||||
bool val = *p <= _threshold;
|
||||
if (val != lastVal) {
|
||||
res.push_back(narrow_cast<PatternRow::value_type>((p - lastPos) / stride));
|
||||
lastVal = val;
|
||||
lastPos = p;
|
||||
}
|
||||
}
|
||||
|
||||
res.push_back(narrow_cast<PatternRow::value_type>((end - lastPos) / stride));
|
||||
|
||||
if (*(end - stride) <= _threshold)
|
||||
res.push_back(0); // last value is number of white pixels, here 0
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<const BitMatrix> getBlackMatrix() const override
|
||||
{
|
||||
return std::make_shared<const BitMatrix>(binarize(_threshold));
|
||||
}
|
||||
};
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Matrix.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* @brief Represent a tri-state value false/true/empty
|
||||
*/
|
||||
class Trit
|
||||
{
|
||||
public:
|
||||
enum value_t : uint8_t {false_v, true_v, empty_v} value = empty_v;
|
||||
Trit() = default;
|
||||
Trit(bool v) : value(static_cast<value_t>(v)) {}
|
||||
operator bool() const { return value == true_v; }
|
||||
bool isEmpty() const { return value == empty_v; }
|
||||
};
|
||||
|
||||
using TritMatrix = Matrix<Trit>;
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2021 gitlost
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "Utf.h"
|
||||
|
||||
#include "ZXTestSupport.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
|
||||
using utf8_t = std::u8string_view;
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
using state_t = uint8_t;
|
||||
constexpr state_t kAccepted = 0;
|
||||
constexpr state_t kRejected [[maybe_unused]] = 12;
|
||||
|
||||
inline char32_t Utf8Decode(char8_t byte, state_t& state, char32_t& codep)
|
||||
{
|
||||
// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||
// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
||||
static constexpr const state_t kUtf8Data[] = {
|
||||
/* The first part of the table maps bytes to character classes that
|
||||
* reduce the size of the transition table and create bitmasks. */
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
|
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
|
||||
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
|
||||
10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
|
||||
|
||||
/* The second part is a transition table that maps a combination
|
||||
* of a state of the automaton and a character class to a state. */
|
||||
0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
|
||||
12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
|
||||
12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
|
||||
12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
|
||||
12,36,12,12,12,12,12,12,12,12,12,12,
|
||||
};
|
||||
|
||||
state_t type = kUtf8Data[byte];
|
||||
codep = (state != kAccepted) ? (byte & 0x3fu) | (codep << 6) : (0xff >> type) & (byte);
|
||||
state = kUtf8Data[256 + state + type];
|
||||
return state;
|
||||
}
|
||||
|
||||
static_assert(sizeof(wchar_t) == 4 || sizeof(wchar_t) == 2, "wchar_t needs to be 2 or 4 bytes wide");
|
||||
|
||||
inline bool IsUtf16SurrogatePair(std::wstring_view str)
|
||||
{
|
||||
return sizeof(wchar_t) == 2 && str.size() >= 2 && (str[0] & 0xfc00) == 0xd800 && (str[1] & 0xfc00) == 0xdc00;
|
||||
}
|
||||
|
||||
inline char32_t Utf32FromUtf16Surrogates(std::wstring_view str)
|
||||
{
|
||||
return (static_cast<char32_t>(str[0]) << 10) + str[1] - 0x35fdc00;
|
||||
}
|
||||
|
||||
static size_t Utf8CountCodePoints(utf8_t utf8)
|
||||
{
|
||||
size_t count = 0;
|
||||
|
||||
for (size_t i = 0; i < utf8.size();) {
|
||||
if (utf8[i] < 128) {
|
||||
++i;
|
||||
} else {
|
||||
switch (utf8[i] & 0xf0) {
|
||||
case 0xc0: [[fallthrough]];
|
||||
case 0xd0: i += 2; break;
|
||||
case 0xe0: i += 3; break;
|
||||
case 0xf0: i += 4; break;
|
||||
default: // we are in middle of a sequence
|
||||
++i;
|
||||
while (i < utf8.size() && (utf8[i] & 0xc0) == 0x80)
|
||||
++i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void AppendFromUtf8(utf8_t utf8, std::wstring& buffer)
|
||||
{
|
||||
buffer.reserve(buffer.size() + Utf8CountCodePoints(utf8));
|
||||
|
||||
char32_t codePoint = 0;
|
||||
state_t state = kAccepted;
|
||||
|
||||
for (auto b : utf8) {
|
||||
if (Utf8Decode(b, state, codePoint) != kAccepted)
|
||||
continue;
|
||||
|
||||
if (sizeof(wchar_t) == 2 && codePoint > 0xffff) { // surrogate pair
|
||||
buffer.push_back(narrow_cast<wchar_t>(0xd7c0 + (codePoint >> 10)));
|
||||
buffer.push_back(narrow_cast<wchar_t>(0xdc00 + (codePoint & 0x3ff)));
|
||||
} else {
|
||||
buffer.push_back(narrow_cast<wchar_t>(codePoint));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValidUtf8(ByteView bytes)
|
||||
{
|
||||
state_t state = kAccepted;
|
||||
char32_t codepoint = 0;
|
||||
for (int value : bytes) {
|
||||
Utf8Decode(value, state, codepoint);
|
||||
if (state == kRejected)
|
||||
return false;
|
||||
}
|
||||
return state == kAccepted;
|
||||
}
|
||||
|
||||
std::wstring FromUtf8(std::string_view utf8)
|
||||
{
|
||||
std::wstring str;
|
||||
AppendFromUtf8({reinterpret_cast<const char8_t*>(utf8.data()), utf8.size()}, str);
|
||||
return str;
|
||||
}
|
||||
|
||||
std::wstring FromUtf8(std::u8string_view utf8)
|
||||
{
|
||||
std::wstring str;
|
||||
AppendFromUtf8(utf8, str);
|
||||
return str;
|
||||
}
|
||||
|
||||
// Count the number of bytes required to store given code points in UTF-8.
|
||||
static size_t Utf8CountBytes(std::wstring_view str)
|
||||
{
|
||||
int result = 0;
|
||||
for (; str.size(); str.remove_prefix(1)) {
|
||||
if (str.front() < 0x80)
|
||||
result += 1;
|
||||
else if (str.front() < 0x800)
|
||||
result += 2;
|
||||
else if (sizeof(wchar_t) == 4) {
|
||||
if (str.front() < 0x10000)
|
||||
result += 3;
|
||||
else
|
||||
result += 4;
|
||||
} else {
|
||||
if (IsUtf16SurrogatePair(str)) {
|
||||
result += 4;
|
||||
str.remove_prefix(1);
|
||||
} else
|
||||
result += 3;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ZXING_EXPORT_TEST_ONLY
|
||||
int Utf32ToUtf8(char32_t utf32, char* out)
|
||||
{
|
||||
if (utf32 < 0x80) {
|
||||
*out++ = narrow_cast<char8_t>(utf32);
|
||||
return 1;
|
||||
}
|
||||
if (utf32 < 0x800) {
|
||||
*out++ = narrow_cast<char8_t>((utf32 >> 6) | 0xc0);
|
||||
*out++ = narrow_cast<char8_t>((utf32 & 0x3f) | 0x80);
|
||||
return 2;
|
||||
}
|
||||
if (utf32 < 0x10000) {
|
||||
*out++ = narrow_cast<char8_t>((utf32 >> 12) | 0xe0);
|
||||
*out++ = narrow_cast<char8_t>(((utf32 >> 6) & 0x3f) | 0x80);
|
||||
*out++ = narrow_cast<char8_t>((utf32 & 0x3f) | 0x80);
|
||||
return 3;
|
||||
}
|
||||
|
||||
*out++ = narrow_cast<char8_t>((utf32 >> 18) | 0xf0);
|
||||
*out++ = narrow_cast<char8_t>(((utf32 >> 12) & 0x3f) | 0x80);
|
||||
*out++ = narrow_cast<char8_t>(((utf32 >> 6) & 0x3f) | 0x80);
|
||||
*out++ = narrow_cast<char8_t>((utf32 & 0x3f) | 0x80);
|
||||
return 4;
|
||||
}
|
||||
|
||||
static void AppendToUtf8(std::wstring_view str, std::string& utf8)
|
||||
{
|
||||
utf8.reserve(utf8.size() + Utf8CountBytes(str));
|
||||
|
||||
char buffer[4];
|
||||
for (; str.size(); str.remove_prefix(1))
|
||||
{
|
||||
uint32_t cp;
|
||||
if (IsUtf16SurrogatePair(str)) {
|
||||
cp = Utf32FromUtf16Surrogates(str);
|
||||
str.remove_prefix(1);
|
||||
} else
|
||||
cp = str.front();
|
||||
|
||||
auto bufLength = Utf32ToUtf8(cp, buffer);
|
||||
utf8.append(buffer, bufLength);
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToUtf8(std::wstring_view str)
|
||||
{
|
||||
std::string utf8;
|
||||
AppendToUtf8(str, utf8);
|
||||
return utf8;
|
||||
}
|
||||
|
||||
static bool iswgraph(wchar_t wc)
|
||||
{
|
||||
/* Consider all legal codepoints as graphical except for:
|
||||
* - whitespace
|
||||
* - C0 and C1 control characters
|
||||
* - U+2028 and U+2029 (line/para break)
|
||||
* - U+FFF9 through U+FFFB (interlinear annotation controls)
|
||||
* The following code is based on libmusls implementation */
|
||||
|
||||
if (wc == ' ' || (unsigned)wc - '\t' < 5)
|
||||
return false;
|
||||
if (wc < 0xff)
|
||||
return ((wc + 1) & 0x7f) >= 0x21;
|
||||
if (wc < 0x2028 || wc - 0x202a < 0xd800 - 0x202a || wc - 0xe000 < 0xfff9 - 0xe000)
|
||||
return true;
|
||||
if (wc - 0xfffc > 0x10ffff - 0xfffc || (wc & 0xfffe) == 0xfffe)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::wstring EscapeNonGraphical(std::wstring_view str)
|
||||
{
|
||||
static const char* const ascii_nongraphs[33] = {
|
||||
"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
|
||||
"BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
|
||||
"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
|
||||
"CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US",
|
||||
"DEL",
|
||||
};
|
||||
|
||||
std::wostringstream ws;
|
||||
ws.fill(L'0');
|
||||
|
||||
for (; str.size(); str.remove_prefix(1)) {
|
||||
wchar_t wc = str.front();
|
||||
if (wc < 32 || wc == 127) // Non-graphical ASCII, excluding space
|
||||
ws << "<" << ascii_nongraphs[wc == 127 ? 32 : wc] << ">";
|
||||
else if (wc < 128) // ASCII
|
||||
ws << wc;
|
||||
else if (IsUtf16SurrogatePair(str)) {
|
||||
ws.write(str.data(), 2);
|
||||
str.remove_prefix(1);
|
||||
}
|
||||
// Exclude unpaired surrogates and NO-BREAK spaces NBSP and NUMSP
|
||||
else if ((wc < 0xd800 || wc >= 0xe000) && (iswgraph(wc) && wc != 0xA0 && wc != 0x2007 && wc != 0x2000 && wc != 0xfffd))
|
||||
ws << wc;
|
||||
else // Non-graphical Unicode
|
||||
ws << "<U+" << std::setw(wc < 256 ? 2 : 4) << std::uppercase << std::hex << static_cast<uint32_t>(wc) << ">";
|
||||
}
|
||||
|
||||
return ws.str();
|
||||
}
|
||||
|
||||
std::string EscapeNonGraphical(std::string_view utf8)
|
||||
{
|
||||
return ToUtf8(EscapeNonGraphical(FromUtf8(utf8)));
|
||||
}
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Range.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
bool IsValidUtf8(ByteView bytes);
|
||||
|
||||
std::string ToUtf8(std::wstring_view str);
|
||||
std::wstring FromUtf8(std::string_view utf8);
|
||||
#ifdef __cpp_lib_char8_t
|
||||
std::wstring FromUtf8(std::u8string_view utf8);
|
||||
#endif
|
||||
|
||||
std::wstring EscapeNonGraphical(std::wstring_view str);
|
||||
std::string EscapeNonGraphical(std::string_view utf8);
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "WhiteRectDetector.h"
|
||||
|
||||
#include "BitMatrix.h"
|
||||
#include "BitMatrixCursor.h"
|
||||
#include "ResultPoint.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
static const int INIT_SIZE = 10;
|
||||
static const int CORR = 1;
|
||||
|
||||
bool DetectWhiteRect(const BitMatrix& image, ResultPoint& p0, ResultPoint& p1, ResultPoint& p2, ResultPoint& p3)
|
||||
{
|
||||
return DetectWhiteRect(image, INIT_SIZE, image.width() / 2, image.height() / 2, p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a segment contains a black point
|
||||
*
|
||||
* @param a min value of the scanned coordinate
|
||||
* @param b max value of the scanned coordinate
|
||||
* @param fixed value of fixed coordinate
|
||||
* @param horizontal set to true if scan must be horizontal, false if vertical
|
||||
* @return true if a black point has been found, else false.
|
||||
*/
|
||||
static bool ContainsBlackPoint(const BitMatrix& image, int a, int b, int fixed, bool horizontal) {
|
||||
|
||||
a = std::max(a, 0);
|
||||
if (horizontal) {
|
||||
if (fixed < 0 || fixed >= image.height())
|
||||
return false;
|
||||
b = std::min(b, image.width() - 1);
|
||||
for (int x = a; x <= b; x++) {
|
||||
if (image.get(x, fixed)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (fixed < 0 || fixed >= image.width())
|
||||
return false;
|
||||
b = std::min(b, image.height() - 1);
|
||||
for (int y = a; y <= b; y++) {
|
||||
if (image.get(fixed, y)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool GetBlackPointOnSegment(const BitMatrix& image, int aX, int aY, int bX, int bY, ResultPoint& result)
|
||||
{
|
||||
PointF a(aX, aY), b(bX, bY);
|
||||
BitMatrixCursorF cur(image, a, b - a);
|
||||
|
||||
auto dist = std::lround(distance(a, b) / length(cur.d));
|
||||
|
||||
for (int i = 0; i < dist; i++) {
|
||||
if (cur.isBlack()) {
|
||||
result = cur.p;
|
||||
return true;
|
||||
}
|
||||
cur.step();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* recenters the points of a constant distance towards the center
|
||||
*
|
||||
* p0 to p3 describing the corners of the rectangular
|
||||
* region. The first and last points are opposed on the diagonal, as
|
||||
* are the second and third. The first point will be the topmost
|
||||
* point and the last, the bottommost. The second point will be
|
||||
* leftmost and the third, the rightmost
|
||||
*
|
||||
* @param y bottom most point
|
||||
* @param z left most point
|
||||
* @param x right most point
|
||||
* @param t top most point
|
||||
*/
|
||||
static void CenterEdges(const ResultPoint& y, const ResultPoint& z, const ResultPoint& x, const ResultPoint& t, int width, ResultPoint& p0, ResultPoint& p1, ResultPoint& p2, ResultPoint& p3)
|
||||
{
|
||||
//
|
||||
// t t
|
||||
// z x
|
||||
// x OR z
|
||||
// y y
|
||||
//
|
||||
|
||||
float yi = y.x();
|
||||
float yj = y.y();
|
||||
float zi = z.x();
|
||||
float zj = z.y();
|
||||
float xi = x.x();
|
||||
float xj = x.y();
|
||||
float ti = t.x();
|
||||
float tj = t.y();
|
||||
|
||||
if (yi < width / 2.0f) {
|
||||
p0 = ResultPoint(ti - CORR, tj + CORR);
|
||||
p1 = ResultPoint(zi + CORR, zj + CORR);
|
||||
p2 = ResultPoint(xi - CORR, xj - CORR);
|
||||
p3 = ResultPoint(yi + CORR, yj - CORR);
|
||||
}
|
||||
else {
|
||||
p0 = ResultPoint(ti + CORR, tj + CORR);
|
||||
p1 = ResultPoint(zi + CORR, zj - CORR);
|
||||
p2 = ResultPoint(xi - CORR, xj + CORR);
|
||||
p3 = ResultPoint(yi - CORR, yj - CORR);
|
||||
}
|
||||
}
|
||||
|
||||
bool DetectWhiteRect(const BitMatrix& image, int initSize, int x, int y, ResultPoint& p0, ResultPoint& p1,
|
||||
ResultPoint& p2, ResultPoint& p3)
|
||||
{
|
||||
int height = image.height();
|
||||
int width = image.width();
|
||||
int halfsize = initSize / 2;
|
||||
int left = x - halfsize;
|
||||
int right = x + halfsize;
|
||||
int up = y - halfsize;
|
||||
int down = y + halfsize;
|
||||
if (up < 0 || left < 0 || down >= height || right >= width) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool aBlackPointFoundOnBorder = true;
|
||||
bool atLeastOneBlackPointFoundOnBorder = false;
|
||||
|
||||
bool atLeastOneBlackPointFoundOnRight = false;
|
||||
bool atLeastOneBlackPointFoundOnBottom = false;
|
||||
bool atLeastOneBlackPointFoundOnLeft = false;
|
||||
bool atLeastOneBlackPointFoundOnTop = false;
|
||||
|
||||
while (aBlackPointFoundOnBorder) {
|
||||
|
||||
aBlackPointFoundOnBorder = false;
|
||||
|
||||
// .....
|
||||
// . |
|
||||
// .....
|
||||
bool rightBorderNotWhite = true;
|
||||
while ((rightBorderNotWhite || !atLeastOneBlackPointFoundOnRight) && right < width) {
|
||||
rightBorderNotWhite = ContainsBlackPoint(image, up, down, right, false);
|
||||
if (rightBorderNotWhite) {
|
||||
right++;
|
||||
aBlackPointFoundOnBorder = true;
|
||||
atLeastOneBlackPointFoundOnRight = true;
|
||||
}
|
||||
else if (!atLeastOneBlackPointFoundOnRight) {
|
||||
right++;
|
||||
}
|
||||
}
|
||||
|
||||
// .....
|
||||
// . .
|
||||
// .___.
|
||||
bool bottomBorderNotWhite = true;
|
||||
while ((bottomBorderNotWhite || !atLeastOneBlackPointFoundOnBottom) && down < height) {
|
||||
bottomBorderNotWhite = ContainsBlackPoint(image, left, right, down, true);
|
||||
if (bottomBorderNotWhite) {
|
||||
down++;
|
||||
aBlackPointFoundOnBorder = true;
|
||||
atLeastOneBlackPointFoundOnBottom = true;
|
||||
}
|
||||
else if (!atLeastOneBlackPointFoundOnBottom) {
|
||||
down++;
|
||||
}
|
||||
}
|
||||
|
||||
// .....
|
||||
// | .
|
||||
// .....
|
||||
bool leftBorderNotWhite = true;
|
||||
while ((leftBorderNotWhite || !atLeastOneBlackPointFoundOnLeft) && left >= 0) {
|
||||
leftBorderNotWhite = ContainsBlackPoint(image, up, down, left, false);
|
||||
if (leftBorderNotWhite) {
|
||||
left--;
|
||||
aBlackPointFoundOnBorder = true;
|
||||
atLeastOneBlackPointFoundOnLeft = true;
|
||||
}
|
||||
else if (!atLeastOneBlackPointFoundOnLeft) {
|
||||
left--;
|
||||
}
|
||||
}
|
||||
|
||||
// .___.
|
||||
// . .
|
||||
// .....
|
||||
bool topBorderNotWhite = true;
|
||||
while ((topBorderNotWhite || !atLeastOneBlackPointFoundOnTop) && up >= 0) {
|
||||
topBorderNotWhite = ContainsBlackPoint(image, left, right, up, true);
|
||||
if (topBorderNotWhite) {
|
||||
up--;
|
||||
aBlackPointFoundOnBorder = true;
|
||||
atLeastOneBlackPointFoundOnTop = true;
|
||||
}
|
||||
else if (!atLeastOneBlackPointFoundOnTop) {
|
||||
up--;
|
||||
}
|
||||
}
|
||||
|
||||
if (aBlackPointFoundOnBorder) {
|
||||
atLeastOneBlackPointFoundOnBorder = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (up < 0 || left < 0 || down >= height || right >= width)
|
||||
return false;
|
||||
|
||||
if (atLeastOneBlackPointFoundOnBorder) {
|
||||
|
||||
int maxSize = right - left;
|
||||
|
||||
ResultPoint z;
|
||||
bool found = false;
|
||||
for (int i = 1; !found && i < maxSize; i++) {
|
||||
found = GetBlackPointOnSegment(image, left, down - i, left + i, down, z);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResultPoint t;
|
||||
found = false;
|
||||
//go down right
|
||||
for (int i = 1; !found && i < maxSize; i++) {
|
||||
found = GetBlackPointOnSegment(image, left, up + i, left + i, up, t);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResultPoint x;
|
||||
found = false;
|
||||
//go down left
|
||||
for (int i = 1; !found && i < maxSize; i++) {
|
||||
found = GetBlackPointOnSegment(image, right, up + i, right - i, up, x);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResultPoint y;
|
||||
found = false;
|
||||
//go up left
|
||||
for (int i = 1; !found && i < maxSize; i++) {
|
||||
found = GetBlackPointOnSegment(image, right, down - i, right - i, down, y);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CenterEdges(y, z, x, t, width, p0, p1, p2, p3);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class BitMatrix;
|
||||
class ResultPoint;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Detects a candidate barcode-like rectangular region within an image. It
|
||||
* starts around the center of the image, increases the size of the candidate
|
||||
* region until it finds a white rectangular region.
|
||||
* </p>
|
||||
*
|
||||
* @param image barcode image to find a rectangle in
|
||||
* @param initSize initial size of search area around center
|
||||
* @param x x position of search center
|
||||
* @param y y position of search center
|
||||
* @return {@link ResultPoint}[] describing the corners of the rectangular
|
||||
* region. The first and last points are opposed on the diagonal, as
|
||||
* are the second and third. The first point will be the topmost
|
||||
* point and the last, the bottommost. The second point will be
|
||||
* leftmost and the third, the rightmost
|
||||
* @return true iff white rect was found
|
||||
*/
|
||||
bool DetectWhiteRect(const BitMatrix& image, int initSize, int x, int y, ResultPoint& p0, ResultPoint& p1,
|
||||
ResultPoint& p2, ResultPoint& p3);
|
||||
bool DetectWhiteRect(const BitMatrix& image, ResultPoint& p0, ResultPoint& p1, ResultPoint& p2, ResultPoint& p3);
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2024 Axel Waggershauser
|
||||
* Copyright 2025 gitlost
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "WriteBarcode.h"
|
||||
|
||||
#include "BarcodeData.h"
|
||||
#include "BitMatrix.h"
|
||||
#include "CreateBarcode.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#ifdef ZXING_USE_ZINT
|
||||
#include <zint.h>
|
||||
#endif // ZXING_USE_ZINT
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
struct WriterOptions::Data
|
||||
{
|
||||
int scale = 1;
|
||||
int rotate = 0;
|
||||
bool invert = false;
|
||||
bool addHRT = false;
|
||||
bool addQuietZones = true;
|
||||
};
|
||||
|
||||
#define ZX_PROPERTY(TYPE, NAME) \
|
||||
TYPE WriterOptions::NAME() const noexcept { return d->NAME; } \
|
||||
WriterOptions& WriterOptions::NAME(TYPE v)& { return d->NAME = std::move(v), *this; } \
|
||||
WriterOptions&& WriterOptions::NAME(TYPE v)&& { return d->NAME = std::move(v), std::move(*this); }
|
||||
|
||||
ZX_PROPERTY(int, scale)
|
||||
ZX_PROPERTY(int, rotate)
|
||||
ZX_PROPERTY(bool, invert)
|
||||
ZX_PROPERTY(bool, addHRT)
|
||||
ZX_PROPERTY(bool, addQuietZones)
|
||||
|
||||
#undef ZX_PROPERTY
|
||||
|
||||
WriterOptions::WriterOptions() : d(std::make_unique<Data>()) {}
|
||||
WriterOptions::~WriterOptions() = default;
|
||||
WriterOptions::WriterOptions(WriterOptions&&) noexcept = default;
|
||||
WriterOptions& WriterOptions::operator=(WriterOptions&&) noexcept = default;
|
||||
|
||||
#ifdef ZXING_USE_ZINT
|
||||
|
||||
struct SetCommonWriterOptions
|
||||
{
|
||||
zint_symbol* zint;
|
||||
|
||||
SetCommonWriterOptions(zint_symbol* zint, const WriterOptions& opts) : zint(zint)
|
||||
{
|
||||
zint->show_hrt = opts.addHRT();
|
||||
|
||||
zint->output_options &= ~(OUT_BUFFER_INTERMEDIATE | BARCODE_NO_QUIET_ZONES);
|
||||
zint->output_options |= opts.addQuietZones() ? BARCODE_QUIET_ZONES : BARCODE_NO_QUIET_ZONES;
|
||||
|
||||
if (opts.scale() > 0)
|
||||
zint->scale = opts.scale() / 2.f;
|
||||
else if (opts.scale() < 0) {
|
||||
int size = std::max(zint->width, zint->rows);
|
||||
zint->scale = std::max(1, int(float(-opts.scale()) / size)) / 2.f;
|
||||
}
|
||||
|
||||
if (opts.invert()) {
|
||||
strcpy(zint->bgcolour, "000000");
|
||||
strcpy(zint->fgcolour, "ffffff");
|
||||
}
|
||||
}
|
||||
|
||||
// reset the defaults such that consecutive write calls don't influence each other
|
||||
~SetCommonWriterOptions()
|
||||
{
|
||||
zint->scale = 0.5f;
|
||||
strcpy(zint->fgcolour, "000000");
|
||||
strcpy(zint->bgcolour, "ffffff");
|
||||
}
|
||||
};
|
||||
|
||||
#define CHECK(ZINT_CALL) \
|
||||
if (int err = (ZINT_CALL); err >= ZINT_ERROR) \
|
||||
throw std::invalid_argument(StrCat(zint->errtxt, " (retval: ", std::to_string(err), ")"));
|
||||
|
||||
#endif // ZXING_USE_ZINT
|
||||
|
||||
static std::string ToSVG(ImageView iv)
|
||||
{
|
||||
if (!iv.data())
|
||||
return {};
|
||||
|
||||
// see https://stackoverflow.com/questions/10789059/create-qr-code-in-vector-image/60638350#60638350
|
||||
|
||||
std::ostringstream res;
|
||||
|
||||
res << R"(<?xml version="1.0" encoding="UTF-8"?>)" << "\n"
|
||||
<< R"(<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 )" << iv.width() << ' ' << iv.height()
|
||||
<< R"(" stroke="none">)" << "\n"
|
||||
<< "<path d=\"";
|
||||
|
||||
for (int y = 0; y < iv.height(); ++y)
|
||||
for (int x = 0; x < iv.width(); ++x)
|
||||
if (*iv.data(x, y) == 0)
|
||||
res << "M" << x << "," << y << "h1v1h-1z";
|
||||
|
||||
res << "\"/>\n</svg>";
|
||||
|
||||
return res.str();
|
||||
}
|
||||
|
||||
static Image ToImage(BitMatrix bits, bool isLinearCode, const WriterOptions& opts)
|
||||
{
|
||||
bits.flipAll();
|
||||
int width = opts.scale() > 0 ? bits.width() * opts.scale() : -opts.scale();
|
||||
int height = isLinearCode ? std::clamp(width / 2, 50, 300) : opts.scale() > 0 ? bits.height() * opts.scale() : -opts.scale();
|
||||
auto symbol = Inflate(std::move(bits), width, height, opts.addQuietZones() ? 10 : 0);
|
||||
auto bitmap = ToMatrix<uint8_t>(symbol);
|
||||
auto iv = Image(symbol.width(), symbol.height());
|
||||
std::memcpy(const_cast<uint8_t*>(iv.data()), bitmap.data(), iv.width() * iv.height());
|
||||
return iv;
|
||||
}
|
||||
|
||||
std::string WriteBarcodeToSVG(const Barcode& barcode, [[maybe_unused]] const WriterOptions& options)
|
||||
{
|
||||
#if defined(ZXING_WRITERS) && defined(ZXING_USE_ZINT)
|
||||
auto* zint = barcode.d->zint.get();
|
||||
|
||||
if (!zint)
|
||||
#endif
|
||||
return ToSVG(barcode.symbol());
|
||||
|
||||
#if defined(ZXING_WRITERS) && defined(ZXING_USE_ZINT)
|
||||
auto zintLock = std::lock_guard(*barcode.d->zintMutex);
|
||||
auto resetOnExit = SetCommonWriterOptions(zint, options);
|
||||
|
||||
zint->output_options |= BARCODE_MEMORY_FILE;// | EMBED_VECTOR_FONT;
|
||||
strcpy(zint->outfile, "null.svg");
|
||||
|
||||
CHECK(ZBarcode_Print(zint, options.rotate()));
|
||||
|
||||
return std::string(reinterpret_cast<const char*>(zint->memfile), zint->memfile_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
Image WriteBarcodeToImage(const Barcode& barcode, [[maybe_unused]] const WriterOptions& options)
|
||||
{
|
||||
#if defined(ZXING_WRITERS) && defined(ZXING_USE_ZINT)
|
||||
auto* zint = barcode.d->zint.get();
|
||||
|
||||
if (!zint)
|
||||
#endif
|
||||
return ToImage(barcode.d->symbol.copy(), barcode.format() & BarcodeFormat::AllLinear, options);
|
||||
|
||||
#if defined(ZXING_WRITERS) && defined(ZXING_USE_ZINT)
|
||||
auto zintLock = std::lock_guard(*barcode.d->zintMutex);
|
||||
auto resetOnExit = SetCommonWriterOptions(zint, options);
|
||||
|
||||
CHECK(ZBarcode_Buffer(zint, options.rotate()));
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("write symbol with size: %dx%d\n", zint->bitmap_width, zint->bitmap_height);
|
||||
#endif
|
||||
auto iv = Image(zint->bitmap_width, zint->bitmap_height);
|
||||
auto* src = zint->bitmap;
|
||||
auto* dst = const_cast<uint8_t*>(iv.data());
|
||||
for(int y = 0; y < iv.height(); ++y)
|
||||
for(int x = 0, w = iv.width(); x < w; ++x, src += 3)
|
||||
*dst++ = RGBToLum(src[0], src[1], src[2]);
|
||||
|
||||
return iv;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string WriteBarcodeToUtf8(const Barcode& barcode, [[maybe_unused]] const WriterOptions& options)
|
||||
{
|
||||
auto iv = barcode.symbol();
|
||||
if (!iv.data())
|
||||
return {};
|
||||
|
||||
constexpr auto map = std::array{" ", "▀", "▄", "█"};
|
||||
std::ostringstream res;
|
||||
bool invert = !options.invert();
|
||||
|
||||
Image buffer;
|
||||
if (options.addQuietZones()) {
|
||||
buffer = Image(iv.width() + 2, iv.height() + 2);
|
||||
memset(const_cast<uint8_t*>(buffer.data()), 0xff, buffer.rowStride() * buffer.height());
|
||||
for (int y = 0; y < iv.height(); y++)
|
||||
memcpy(const_cast<uint8_t*>(buffer.data(1, y + 1)), iv.data(0, y), iv.width());
|
||||
iv = barcode.format() & BarcodeFormat::AllLinear ? iv.cropped(0, 1, buffer.width(), buffer.height() - 2) : buffer;
|
||||
}
|
||||
|
||||
for (int y = 0; y < iv.height(); y += 2) {
|
||||
// for linear barcodes, only print line pairs that are distinct from the previous one
|
||||
if (barcode.format() & BarcodeFormat::AllLinear && y > 1 && y < iv.height() - 1
|
||||
&& memcmp(iv.data(0, y), iv.data(0, y - 2), 2 * iv.rowStride()) == 0)
|
||||
continue;
|
||||
|
||||
for (int x = 0; x < iv.width(); ++x) {
|
||||
int tp = bool(*iv.data(x, y)) ^ invert;
|
||||
int bt = (iv.height() == 1 && tp) || (y + 1 < iv.height() && (bool(*iv.data(x, y + 1)) ^ invert));
|
||||
res << map[tp | (bt << 1)];
|
||||
}
|
||||
res << '\n';
|
||||
}
|
||||
|
||||
return res.str();
|
||||
}
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2024 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Barcode.h"
|
||||
#include "ImageView.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
/**
|
||||
* @brief Configuration options for barcode writing/generation.
|
||||
*
|
||||
* WriterOptions provides a fluent interface for setting various parameters
|
||||
* that control how barcodes are generated.
|
||||
*
|
||||
* This class supports method chaining for convenient option configuration:
|
||||
* ```cpp
|
||||
* auto opts = WriterOptions().scale(5).addHRT(true);
|
||||
* ```
|
||||
*/
|
||||
class WriterOptions
|
||||
{
|
||||
struct Data;
|
||||
|
||||
std::unique_ptr<Data> d;
|
||||
|
||||
public:
|
||||
WriterOptions();
|
||||
~WriterOptions();
|
||||
WriterOptions(WriterOptions&&) noexcept;
|
||||
WriterOptions& operator=(WriterOptions&&) noexcept;
|
||||
|
||||
#define ZX_PROPERTY(TYPE, NAME) \
|
||||
TYPE NAME() const noexcept; \
|
||||
WriterOptions& NAME(TYPE v)&; \
|
||||
WriterOptions&& NAME(TYPE v)&&;
|
||||
|
||||
/// scale factor for rendering, ie the module size (default: 1). Passing a negative value will choose the scale
|
||||
/// automatically to fit the size of the barcode to abs(scale) as close as possible.
|
||||
ZX_PROPERTY(int, scale)
|
||||
|
||||
/// rotate the barcode by given degrees (0, 90, 180, 270)
|
||||
ZX_PROPERTY(int, rotate)
|
||||
|
||||
/// invert the colors of the barcode
|
||||
ZX_PROPERTY(bool, invert)
|
||||
|
||||
/// add human readable text (HRI) to the barcode
|
||||
ZX_PROPERTY(bool, addHRT)
|
||||
|
||||
/// add quiet zones around the barcode (default: true)
|
||||
ZX_PROPERTY(bool, addQuietZones)
|
||||
|
||||
#undef ZX_PROPERTY
|
||||
};
|
||||
|
||||
|
||||
/// Write barcode symbol to SVG
|
||||
std::string WriteBarcodeToSVG(const Barcode& barcode, const WriterOptions& options = {});
|
||||
|
||||
/// Write barcode symbol to a utf8 string using graphical characters (e.g. '▀')
|
||||
std::string WriteBarcodeToUtf8(const Barcode& barcode, const WriterOptions& options = {});
|
||||
|
||||
/// Write barcode symbol to Image (Bitmap)
|
||||
Image WriteBarcodeToImage(const Barcode& barcode, const WriterOptions& options = {});
|
||||
|
||||
} // ZXing
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Copyright 2017 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Error.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
#include <concepts>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
template <class T, class U>
|
||||
constexpr T narrow_cast(U&& u) noexcept {
|
||||
return static_cast<T>(std::forward<U>(u));
|
||||
}
|
||||
|
||||
inline constexpr auto& Find = std::ranges::find;
|
||||
inline constexpr auto& FindIf = std::ranges::find_if;
|
||||
|
||||
template <typename Container, typename Value> requires std::ranges::input_range<const Container>
|
||||
auto Contains(const Container& c, const Value& v) {
|
||||
return std::ranges::find(c, v) != std::ranges::end(c);
|
||||
}
|
||||
|
||||
template <typename ListType, typename Value>
|
||||
auto Contains(const std::initializer_list<ListType>& c, const Value& v) {
|
||||
return std::ranges::find(c, v) != std::ranges::end(c);
|
||||
}
|
||||
|
||||
inline bool Contains(const char* str, char c) {
|
||||
return str && strchr(str, c) != nullptr;
|
||||
}
|
||||
|
||||
inline bool Contains(std::string_view str, std::string_view substr) {
|
||||
return str.find(substr) != std::string_view::npos;
|
||||
}
|
||||
|
||||
template <template <typename...> typename C, typename... Ts>
|
||||
auto FirstOrDefault(C<Ts...>&& container)
|
||||
{
|
||||
return container.empty() ? typename C<Ts...>::value_type() : std::move(container.front());
|
||||
}
|
||||
|
||||
template <typename Iterator, typename Value = typename std::iterator_traits<Iterator>::value_type, typename Op = std::plus<Value>>
|
||||
constexpr Value Reduce(Iterator b, Iterator e, Value v = Value{}, Op op = {}) {
|
||||
// std::reduce() first sounded like a better implementation because it is not implemented as a strict left-fold
|
||||
// operation, meaning the order of the op-application is not specified. This sounded like an optimization opportunity
|
||||
// but it turns out that for this use case it actually does not make a difference (falsepositives runtime). And
|
||||
// when tested with a large std::vector<uint16_t> and proper autovectorization (e.g. clang++ -O2) it turns out that
|
||||
// std::accumulate can be twice as fast as std::reduce.
|
||||
return std::accumulate(b, e, v, op);
|
||||
}
|
||||
|
||||
template <typename Container, typename Value = typename Container::value_type, typename Op = std::plus<Value>>
|
||||
constexpr Value Reduce(const Container& c, Value v = Value{}, Op op = {}) {
|
||||
return Reduce(std::begin(c), std::end(c), v, op);
|
||||
}
|
||||
|
||||
// see C++20 ssize
|
||||
template <class Container>
|
||||
constexpr auto Size(const Container& c) -> decltype(c.size(), int()) {
|
||||
return narrow_cast<int>(c.size());
|
||||
}
|
||||
|
||||
template <class T, std::size_t N>
|
||||
constexpr int Size(T const (&)[N]) noexcept {
|
||||
return narrow_cast<int>(N);
|
||||
}
|
||||
|
||||
inline constexpr int Size(const char* s) noexcept {
|
||||
return narrow_cast<int>(std::char_traits<char>::length(s));
|
||||
}
|
||||
|
||||
inline constexpr int Size(char) noexcept { return 1; }
|
||||
|
||||
template <typename... Args>
|
||||
std::string StrCat(Args&&... args)
|
||||
{
|
||||
std::string res;
|
||||
res.reserve((Size(args) + ...));
|
||||
(res += ... += args);
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename Container, typename Value>
|
||||
int IndexOf(const Container& c, const Value& v) {
|
||||
auto i = Find(c, v);
|
||||
return i == std::end(c) ? -1 : narrow_cast<int>(std::distance(std::begin(c), i));
|
||||
}
|
||||
|
||||
inline int IndexOf(const char* str, char c) {
|
||||
auto s = strchr(str, c);
|
||||
return s != nullptr ? narrow_cast<int>(s - str) : -1;
|
||||
}
|
||||
|
||||
template <typename Container, typename Value, class UnaryOp>
|
||||
Value TransformReduce(const Container& c, Value s, UnaryOp op) {
|
||||
for (const auto& v : c)
|
||||
s += op(v);
|
||||
return s;
|
||||
}
|
||||
|
||||
template <std::integral T>
|
||||
constexpr bool IsUpper(T v) noexcept
|
||||
{
|
||||
return 'A' <= v && v <= 'Z';
|
||||
}
|
||||
|
||||
template <std::integral T>
|
||||
constexpr bool IsLower(T v) noexcept
|
||||
{
|
||||
return 'a' <= v && v <= 'z';
|
||||
}
|
||||
|
||||
template <std::integral T>
|
||||
constexpr bool IsAlpha(T v) noexcept
|
||||
{
|
||||
return IsUpper(v) || IsLower(v);
|
||||
}
|
||||
|
||||
template <std::integral T>
|
||||
constexpr bool IsDigit(T v) noexcept
|
||||
{
|
||||
return '0' <= v && v <= '9';
|
||||
}
|
||||
|
||||
template <std::integral T>
|
||||
constexpr bool IsSpace(T v) noexcept
|
||||
{
|
||||
// Matches the standard ASCII whitespace characters:
|
||||
// ' ', '\t', '\n', '\v', '\f', '\r'
|
||||
return v == ' ' || ('\t' <= v && v <= '\r');
|
||||
}
|
||||
|
||||
template <typename T = char>
|
||||
T ToDigit(int i)
|
||||
{
|
||||
if (i < 0 || i > 9)
|
||||
throw FormatError("Invalid digit value");
|
||||
return static_cast<T>('0' + i);
|
||||
}
|
||||
|
||||
template <std::integral T>
|
||||
std::string ToString(T val, int len)
|
||||
{
|
||||
std::string result(len--, '0');
|
||||
if (val < 0)
|
||||
throw FormatError("Invalid value");
|
||||
for (; len >= 0 && val != 0; --len, val /= 10)
|
||||
result[len] = '0' + val % 10;
|
||||
if (val)
|
||||
throw FormatError("Invalid value");
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename C> requires (sizeof(typename C::value_type) == 1)
|
||||
std::string ToHex(const C& c)
|
||||
{
|
||||
static constexpr char hex[] = "0123456789ABCDEF";
|
||||
|
||||
const auto data = reinterpret_cast<const uint8_t*>(c.data());
|
||||
const auto size = Size(c);
|
||||
if (size == 0)
|
||||
return {};
|
||||
|
||||
std::string res(size * 3 - 1, ' ');
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
res[i * 3 + 0] = hex[data[i] >> 4];
|
||||
res[i * 3 + 1] = hex[data[i] & 0x0F];
|
||||
if (i + 1 < size)
|
||||
res[i * 3 + 2] = ' ';
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> ToVector(T&& v)
|
||||
{
|
||||
// simply constructing a vector via initializer_list does not work with move-only types
|
||||
std::vector<T> res;
|
||||
res.emplace_back(std::forward<T>(v));
|
||||
return res;
|
||||
}
|
||||
|
||||
template <std::integral T>
|
||||
constexpr inline auto ToUnsigned(T v) noexcept
|
||||
{
|
||||
return static_cast<std::make_unsigned_t<T>>(v);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
constexpr std::string_view TypeName()
|
||||
{
|
||||
#ifdef __clang__
|
||||
std::string_view p = __PRETTY_FUNCTION__;
|
||||
return p.substr(40, p.size() - 40 - 1);
|
||||
#elif defined(__GNUC__)
|
||||
std::string_view p = __PRETTY_FUNCTION__;
|
||||
return p.substr(55, p.find(';', 55) - 55);
|
||||
#elif defined(_MSC_VER)
|
||||
std::string_view p = __FUNCSIG__;
|
||||
return p.substr(90, p.size() - 90 - 7);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T> requires std::is_arithmetic_v<T>
|
||||
inline T FromString(std::string_view sv)
|
||||
{
|
||||
T val = {};
|
||||
auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), val);
|
||||
if (ec != std::errc() || ptr != sv.data() + sv.size())
|
||||
throw std::invalid_argument(StrCat("failed to parse '", TypeName<T>(), "' from '", sv, "'"));
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
// Trim whitespace from both ends
|
||||
inline std::string_view TrimWS(std::string_view sv, const char* ws = " \t\n\r")
|
||||
{
|
||||
while (sv.size() && Contains(ws, sv.back()))
|
||||
sv.remove_suffix(1);
|
||||
while (sv.size() && Contains(ws, sv.front()))
|
||||
sv.remove_prefix(1);
|
||||
return sv.empty() ? std::string_view() : sv;
|
||||
}
|
||||
|
||||
// Split string into tokens based on delimiters and call callback for each token
|
||||
template <typename FUNC>
|
||||
inline void ForEachToken(std::string_view str, std::string_view delimiters, FUNC&& callback)
|
||||
{
|
||||
std::size_t pos = 0;
|
||||
while (pos < str.size()) {
|
||||
auto const next_pos = str.find_first_of(delimiters, pos);
|
||||
callback(str.substr(pos, next_pos - pos));
|
||||
pos = next_pos == std::string_view::npos ? str.size() : next_pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool IsEqualIgnoreCase(std::string_view a, std::string_view b)
|
||||
{
|
||||
return std::ranges::equal(a, b, [](uint8_t a, uint8_t b) { return std::tolower(a) == std::tolower(b); });
|
||||
}
|
||||
|
||||
// Compare two strings ignoring case and specified whitespace characters
|
||||
inline bool IsEqualIgnoreCaseAnd(std::string_view a, std::string_view b, const char* ws)
|
||||
{
|
||||
auto i = a.begin(), j = b.begin();
|
||||
while (i != a.end() && j != b.end()) {
|
||||
if (Contains(ws, *i))
|
||||
++i;
|
||||
else if (Contains(ws, *j))
|
||||
++j;
|
||||
else if (std::tolower(static_cast<uint8_t>(*i)) != std::tolower(static_cast<uint8_t>(*j)))
|
||||
return false;
|
||||
else
|
||||
++i, ++j;
|
||||
}
|
||||
return i == a.end() && j == b.end();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void UpdateMin(T& min, T val)
|
||||
{
|
||||
min = std::min(min, val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void UpdateMax(T& max, T val)
|
||||
{
|
||||
max = std::max(max, val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void UpdateMinMax(T& min, T& max, T val)
|
||||
{
|
||||
min = std::min(min, val);
|
||||
max = std::max(max, val);
|
||||
|
||||
// Note: the above code is not equivalent to
|
||||
// if (val < min) min = val;
|
||||
// else if (val > max) max = val;
|
||||
// It is basically the same but without the 'else'. For the 'else'-variant to work,
|
||||
// both min and max have to be initialized with a value that is part of the sequence.
|
||||
// Also it turns out clang and gcc can vectorize the code above but not the code below.
|
||||
}
|
||||
|
||||
inline uint32_t ReverseBits32(uint32_t v)
|
||||
{
|
||||
v = ((v >> 1) & 0x55555555) | ((v & 0x55555555) << 1);
|
||||
// swap consecutive pairs
|
||||
v = ((v >> 2) & 0x33333333) | ((v & 0x33333333) << 2);
|
||||
// swap nibbles ...
|
||||
v = ((v >> 4) & 0x0F0F0F0F) | ((v & 0x0F0F0F0F) << 4);
|
||||
// swap bytes
|
||||
v = ((v >> 8) & 0x00FF00FF) | ((v & 0x00FF00FF) << 8);
|
||||
// swap 2-byte long pairs
|
||||
v = (v >> 16) | (v << 16);
|
||||
return v;
|
||||
}
|
||||
|
||||
// use to avoid "load of misaligned address" when using a simple type cast
|
||||
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
T LoadU(const void* ptr)
|
||||
{
|
||||
T res;
|
||||
memcpy(&res, ptr, sizeof(T));
|
||||
return res;
|
||||
}
|
||||
|
||||
} // ZXing
|
||||
|
||||
#ifndef __cpp_lib_to_underlying
|
||||
|
||||
namespace std {
|
||||
|
||||
template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>>
|
||||
constexpr std::underlying_type_t<E> to_underlying(E e) noexcept
|
||||
{
|
||||
return static_cast<std::underlying_type_t<E>>(e);
|
||||
}
|
||||
|
||||
} // namespace std
|
||||
|
||||
#endif // __cpp_lib_to_underlying
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2018 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
// Thread local or static memory may be used to reduce the number of (re-)allocations of temporary variables
|
||||
// in e.g. the HistogramBinarizer. The default is thread_local, which is the best option for performance and safety in most cases. If
|
||||
// your platform doesn't support thread_local, you can switch to static, but be aware that this makes the code not thread safe.
|
||||
// For Windows in Visual Studio 2019 on Intel 64-bit using thread_local causes a dependency to VCRUNTIME140_1.dll, so you need 2019
|
||||
// runtime DLLs instead of only 2015 version.
|
||||
#define ZX_THREAD_LOCAL thread_local // '' (nothing), 'thread_local' or 'static'
|
||||
|
||||
// The Galois Field abstractions used in Reed-Solomon error correction code use more memory than required to improve performance (20% - 100%).
|
||||
// If RAM is scarce, you can define LIBRSCPP_SAVE_MEMORY to reduce memory usage. The effect is a few kB big.
|
||||
// #define LIBRSCPP_SAVE_MEMORY
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ZXING_BUILD_FOR_TEST
|
||||
#define ZXING_EXPORT_TEST_ONLY
|
||||
#define ZXING_IF_NOT_TEST(x)
|
||||
#else
|
||||
#define ZXING_EXPORT_TEST_ONLY static
|
||||
#define ZXING_IF_NOT_TEST(x) x
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2026 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Version.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma message("<ZXing/ZXingVersion.h> is deprecated, include ZXing/Version.h instead")
|
||||
#else
|
||||
#warning "<ZXing/ZXingVersion.h> is deprecated, include ZXing/Version.h instead"
|
||||
#endif
|
||||
@@ -0,0 +1,460 @@
|
||||
/*
|
||||
* Copyright 2023 siiky
|
||||
* Copyright 2023 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ZXingC.h"
|
||||
|
||||
#include "ZXingCpp.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
#include "ZXConfig.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include <bit>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
using namespace ZXing;
|
||||
|
||||
static ZX_THREAD_LOCAL std::string lastErrorMsg;
|
||||
static Barcodes emptyBarcodes{}; // used to prevent new heap allocation for each empty result
|
||||
|
||||
template<typename R, typename T> R transmute_cast(const T& v) noexcept
|
||||
{
|
||||
static_assert(sizeof(T) == sizeof(R));
|
||||
return *(const R*)(&v);
|
||||
}
|
||||
|
||||
template<typename C, typename P = typename C::pointer>
|
||||
P copy(const C& c) noexcept
|
||||
{
|
||||
auto ret = (P)malloc((c.size() + 1) * sizeof(typename C::value_type));
|
||||
if (ret) {
|
||||
memcpy(ret, c.data(), c.size() * sizeof(typename C::value_type));
|
||||
ret[c.size()] = typename C::value_type(0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename C, typename P = typename C::pointer>
|
||||
P copy(const C& c, int* len) noexcept
|
||||
{
|
||||
// for convenience and as a safety measure, we NULL terminate even byte arrays
|
||||
auto ret = copy(c);
|
||||
if (len)
|
||||
*len = ret ? Size(c) : 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define ZX_CHECK( GOOD, MSG ) \
|
||||
if (!(GOOD)) { \
|
||||
lastErrorMsg = MSG; \
|
||||
return {}; \
|
||||
}
|
||||
|
||||
#define ZX_CATCH(...) \
|
||||
catch (std::exception & e) { \
|
||||
lastErrorMsg = e.what(); \
|
||||
} catch (...) { \
|
||||
lastErrorMsg = "Unknown error"; \
|
||||
} \
|
||||
return __VA_ARGS__;
|
||||
|
||||
#define ZX_TRY(...) \
|
||||
try { \
|
||||
return __VA_ARGS__; \
|
||||
} \
|
||||
ZX_CATCH({})
|
||||
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
* MARK: - ImageView.h
|
||||
*/
|
||||
|
||||
ZXing_ImageView* ZXing_ImageView_new(const uint8_t* data, int width, int height, ZXing_ImageFormat format, int rowStride,
|
||||
int pixStride)
|
||||
{
|
||||
auto cppformat = static_cast<ImageFormat>(format);
|
||||
ZX_TRY(new ImageView(data, width, height, cppformat, rowStride, pixStride))
|
||||
}
|
||||
|
||||
ZXing_ImageView* ZXing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, ZXing_ImageFormat format,
|
||||
int rowStride, int pixStride)
|
||||
{
|
||||
auto cppformat = static_cast<ImageFormat>(format);
|
||||
ZX_TRY(new ImageView(data, size, width, height, cppformat, rowStride, pixStride))
|
||||
}
|
||||
|
||||
void ZXing_ImageView_delete(ZXing_ImageView* iv)
|
||||
{
|
||||
delete iv;
|
||||
}
|
||||
|
||||
void ZXing_ImageView_crop(ZXing_ImageView* iv, int left, int top, int width, int height)
|
||||
{
|
||||
*iv = iv->cropped(left, top, width, height);
|
||||
}
|
||||
|
||||
void ZXing_ImageView_rotate(ZXing_ImageView* iv, int degree)
|
||||
{
|
||||
*iv = iv->rotated(degree);
|
||||
}
|
||||
|
||||
void ZXing_Image_delete(ZXing_Image* img)
|
||||
{
|
||||
delete img;
|
||||
}
|
||||
|
||||
const uint8_t* ZXing_Image_data(const ZXing_Image* img)
|
||||
{
|
||||
return img->data();
|
||||
}
|
||||
|
||||
int ZXing_Image_width(const ZXing_Image* img)
|
||||
{
|
||||
return img->width();
|
||||
}
|
||||
|
||||
int ZXing_Image_height(const ZXing_Image* img)
|
||||
{
|
||||
return img->height();
|
||||
}
|
||||
|
||||
ZXing_ImageFormat ZXing_Image_format(const ZXing_Image* img)
|
||||
{
|
||||
return transmute_cast<ZXing_ImageFormat>(img->format());
|
||||
}
|
||||
|
||||
/*
|
||||
* MARK: - BarcodeFormat.h
|
||||
*/
|
||||
|
||||
ZXing_BarcodeFormat ZXing_BarcodeFormatSymbology(ZXing_BarcodeFormat format)
|
||||
{
|
||||
return transmute_cast<ZXing_BarcodeFormat>(Symbology(transmute_cast<BarcodeFormat>(format)));
|
||||
}
|
||||
|
||||
ZXing_BarcodeFormat ZXing_BarcodeFormatFromString(const char* str)
|
||||
{
|
||||
try {
|
||||
return transmute_cast<ZXing_BarcodeFormat>(BarcodeFormatFromString(str));
|
||||
}
|
||||
ZX_CATCH(ZXing_BarcodeFormat_Invalid)
|
||||
}
|
||||
|
||||
char* ZXing_BarcodeFormatToString(ZXing_BarcodeFormat format)
|
||||
{
|
||||
ZX_TRY(copy(ToString(transmute_cast<BarcodeFormat>(format))));
|
||||
}
|
||||
|
||||
ZXing_BarcodeFormat* ZXing_BarcodeFormatsList(ZXing_BarcodeFormat filter, int* outCount)
|
||||
{
|
||||
ZX_TRY((ZXing_BarcodeFormat*)copy(BarcodeFormats::list(transmute_cast<BarcodeFormat>(filter)), outCount));
|
||||
}
|
||||
|
||||
ZXing_BarcodeFormat* ZXing_BarcodeFormatsFromString(const char* str, int* outCount)
|
||||
{
|
||||
if (!str) {
|
||||
if (outCount)
|
||||
*outCount = 0;
|
||||
return NULL;
|
||||
}
|
||||
ZX_TRY((ZXing_BarcodeFormat*)copy(BarcodeFormats(str), outCount));
|
||||
}
|
||||
|
||||
char* ZXing_BarcodeFormatsToString(const ZXing_BarcodeFormat* formats, int count)
|
||||
{
|
||||
if (!formats || count == 0)
|
||||
return copy(std::string{});
|
||||
ZX_TRY(copy(ToString(BarcodeFormats(std::vector((BarcodeFormat*)formats, (BarcodeFormat*)formats + count)))));
|
||||
}
|
||||
|
||||
/*
|
||||
* MARK: - Barcode.h
|
||||
*/
|
||||
|
||||
char* ZXing_ContentTypeToString(ZXing_ContentType type)
|
||||
{
|
||||
return copy(ToString(static_cast<ContentType>(type)));
|
||||
}
|
||||
|
||||
char* ZXing_PositionToString(ZXing_Position position)
|
||||
{
|
||||
return copy(ToString(transmute_cast<Position>(position)));
|
||||
}
|
||||
|
||||
|
||||
bool ZXing_Barcode_isValid(const ZXing_Barcode* barcode)
|
||||
{
|
||||
return barcode != NULL && barcode->isValid();
|
||||
}
|
||||
|
||||
ZXing_ErrorType ZXing_Barcode_errorType(const ZXing_Barcode* barcode)
|
||||
{
|
||||
return static_cast<ZXing_ErrorType>(barcode->error().type());
|
||||
}
|
||||
|
||||
char* ZXing_Barcode_errorMsg(const ZXing_Barcode* barcode)
|
||||
{
|
||||
return copy(ToString(barcode->error()));
|
||||
}
|
||||
|
||||
uint8_t* ZXing_Barcode_bytes(const ZXing_Barcode* barcode, int* len)
|
||||
{
|
||||
return copy(barcode->bytes(), len);
|
||||
}
|
||||
|
||||
uint8_t* ZXing_Barcode_bytesECI(const ZXing_Barcode* barcode, int* len)
|
||||
{
|
||||
return copy(barcode->bytesECI(), len);
|
||||
}
|
||||
|
||||
char* ZXing_Barcode_extra(const ZXing_Barcode* barcode, const char* key)
|
||||
{
|
||||
return copy(barcode->extra(key ? key : ""));
|
||||
}
|
||||
|
||||
#define ZX_GETTER(TYPE, NAME, TRANS) \
|
||||
TYPE ZXing_Barcode_##NAME(const ZXing_Barcode* barcode) { return TRANS(barcode->NAME()); }
|
||||
|
||||
ZX_GETTER(ZXing_BarcodeFormat, format, static_cast<ZXing_BarcodeFormat>)
|
||||
ZX_GETTER(ZXing_BarcodeFormat, symbology, static_cast<ZXing_BarcodeFormat>)
|
||||
ZX_GETTER(ZXing_ContentType, contentType, static_cast<ZXing_ContentType>)
|
||||
ZX_GETTER(char*, text, copy)
|
||||
ZX_GETTER(char*, ecLevel, copy)
|
||||
ZX_GETTER(char*, symbologyIdentifier, copy)
|
||||
ZX_GETTER(char*, sequenceId, copy)
|
||||
ZX_GETTER(ZXing_Position, position, transmute_cast<ZXing_Position>)
|
||||
|
||||
ZX_GETTER(int, orientation,)
|
||||
ZX_GETTER(bool, hasECI,)
|
||||
ZX_GETTER(bool, isInverted,)
|
||||
ZX_GETTER(bool, isMirrored,)
|
||||
ZX_GETTER(int, lineCount,)
|
||||
ZX_GETTER(int, sequenceIndex,)
|
||||
ZX_GETTER(int, sequenceSize,)
|
||||
|
||||
void ZXing_Barcode_delete(ZXing_Barcode* barcode)
|
||||
{
|
||||
delete barcode;
|
||||
}
|
||||
|
||||
void ZXing_Barcodes_delete(ZXing_Barcodes* barcodes)
|
||||
{
|
||||
if (barcodes != &emptyBarcodes)
|
||||
delete barcodes;
|
||||
}
|
||||
|
||||
int ZXing_Barcodes_size(const ZXing_Barcodes* barcodes)
|
||||
{
|
||||
return barcodes ? Size(*barcodes) : 0;
|
||||
}
|
||||
|
||||
const ZXing_Barcode* ZXing_Barcodes_at(const ZXing_Barcodes* barcodes, int i)
|
||||
{
|
||||
if (!barcodes || i < 0 || i >= Size(*barcodes))
|
||||
return NULL;
|
||||
return &(*barcodes)[i];
|
||||
}
|
||||
|
||||
ZXing_Barcode* ZXing_Barcodes_move(ZXing_Barcodes* barcodes, int i)
|
||||
{
|
||||
if (!barcodes || i < 0 || i >= Size(*barcodes))
|
||||
return NULL;
|
||||
|
||||
ZX_TRY(new Barcode(std::move((*barcodes)[i])));
|
||||
}
|
||||
|
||||
/*
|
||||
* MARK: - ReaderOptions.h
|
||||
*/
|
||||
|
||||
ZXing_ReaderOptions* ZXing_ReaderOptions_new()
|
||||
{
|
||||
ZX_TRY(new ReaderOptions());
|
||||
}
|
||||
|
||||
void ZXing_ReaderOptions_delete(ZXing_ReaderOptions* opts)
|
||||
{
|
||||
delete opts;
|
||||
}
|
||||
|
||||
#define ZX_PROPERTY(TYPE, NAME, CAP_NAME) \
|
||||
TYPE ZXing_ReaderOptions_get##CAP_NAME(const ZXing_ReaderOptions* opts) { return opts->NAME(); } \
|
||||
void ZXing_ReaderOptions_set##CAP_NAME(ZXing_ReaderOptions* opts, TYPE NAME) { opts->NAME(NAME); }
|
||||
|
||||
ZX_PROPERTY(bool, tryHarder, TryHarder)
|
||||
ZX_PROPERTY(bool, tryRotate, TryRotate)
|
||||
ZX_PROPERTY(bool, tryInvert, TryInvert)
|
||||
ZX_PROPERTY(bool, tryDownscale, TryDownscale)
|
||||
#ifdef ZXING_EXPERIMENTAL_API
|
||||
ZX_PROPERTY(bool, tryDenoise, TryDenoise)
|
||||
#endif
|
||||
ZX_PROPERTY(bool, isPure, IsPure)
|
||||
ZX_PROPERTY(bool, validateOptionalChecksum, ValidateOptionalChecksum)
|
||||
ZX_PROPERTY(bool, returnErrors, ReturnErrors)
|
||||
ZX_PROPERTY(int, minLineCount, MinLineCount)
|
||||
ZX_PROPERTY(int, maxNumberOfSymbols, MaxNumberOfSymbols)
|
||||
|
||||
#undef ZX_PROPERTY
|
||||
|
||||
void ZXing_ReaderOptions_setFormats(ZXing_ReaderOptions* opts, const ZXing_BarcodeFormat* formats, int count)
|
||||
{
|
||||
if (!formats || !count)
|
||||
return;
|
||||
if (count == -1) // determine count by looking for null terminator
|
||||
for (count = 0; formats[count] != ZXing_BarcodeFormat_None; ++count)
|
||||
;
|
||||
std::vector<BarcodeFormat> v((BarcodeFormat*)formats, (BarcodeFormat*)formats + count);
|
||||
opts->formats(std::move(v));
|
||||
}
|
||||
|
||||
ZXing_BarcodeFormat* ZXing_ReaderOptions_getFormats(const ZXing_ReaderOptions* opts, int* outCount)
|
||||
{
|
||||
return (ZXing_BarcodeFormat*)copy(opts->formats(), outCount);
|
||||
}
|
||||
|
||||
#define ZX_ENUM_PROPERTY(TYPE, NAME, CAP_NAME) \
|
||||
ZXing_##TYPE ZXing_ReaderOptions_get##CAP_NAME(const ZXing_ReaderOptions* opts) { return static_cast<ZXing_##TYPE>(opts->NAME()); } \
|
||||
void ZXing_ReaderOptions_set##CAP_NAME(ZXing_ReaderOptions* opts, ZXing_##TYPE NAME) { opts->NAME(static_cast<TYPE>(NAME)); }
|
||||
|
||||
ZX_ENUM_PROPERTY(Binarizer, binarizer, Binarizer)
|
||||
ZX_ENUM_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, EanAddOnSymbol)
|
||||
ZX_ENUM_PROPERTY(TextMode, textMode, TextMode)
|
||||
|
||||
#undef ZX_ENUM_PROPERTY
|
||||
|
||||
/*
|
||||
* MARK: - ReadBarcode.h
|
||||
*/
|
||||
|
||||
ZXing_Barcodes* ZXing_ReadBarcodes(const ZXing_ImageView* iv, const ZXing_ReaderOptions* opts)
|
||||
{
|
||||
ZX_CHECK(iv, "ImageView param is NULL")
|
||||
try {
|
||||
auto res = ReadBarcodes(*iv, opts ? *opts : ReaderOptions{});
|
||||
return res.empty() ? &emptyBarcodes : new Barcodes(std::move(res));
|
||||
}
|
||||
ZX_CATCH(NULL);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* MARK: - CreateBarcode.h
|
||||
*/
|
||||
|
||||
ZXing_CreatorOptions* ZXing_CreatorOptions_new(ZXing_BarcodeFormat format)
|
||||
{
|
||||
ZX_TRY(new CreatorOptions(static_cast<BarcodeFormat>(format)));
|
||||
}
|
||||
|
||||
void ZXing_CreatorOptions_delete(ZXing_CreatorOptions* opts)
|
||||
{
|
||||
delete opts;
|
||||
}
|
||||
|
||||
#define ZX_ENUM_PROPERTY(TYPE, NAME, CAP_NAME) \
|
||||
ZXing_##TYPE ZXing_CreatorOptions_get##CAP_NAME(const ZXing_CreatorOptions* opts) { return transmute_cast<ZXing_##TYPE>(opts->NAME()); } \
|
||||
void ZXing_CreatorOptions_set##CAP_NAME(ZXing_CreatorOptions* opts, ZXing_##TYPE NAME) { opts->NAME(transmute_cast<TYPE>(NAME)); }
|
||||
|
||||
ZX_ENUM_PROPERTY(BarcodeFormat, format, Format)
|
||||
|
||||
|
||||
#undef ZX_ENUM_PROPERTY
|
||||
|
||||
#define ZX_PROPERTY(TYPE, NAME, CAP_NAME) \
|
||||
TYPE ZXing_CreatorOptions_get##CAP_NAME(const ZXing_CreatorOptions* opts) { return opts->NAME(); } \
|
||||
void ZXing_CreatorOptions_set##CAP_NAME(ZXing_CreatorOptions* opts, TYPE NAME) { opts->NAME(NAME); }
|
||||
|
||||
#undef ZX_PROPERTY
|
||||
|
||||
char* ZXing_CreatorOptions_getOptions(const ZXing_CreatorOptions* opts)
|
||||
{
|
||||
return copy(opts->options());
|
||||
}
|
||||
|
||||
void ZXing_CreatorOptions_setOptions(ZXing_CreatorOptions* opts, const char* val)
|
||||
{
|
||||
opts->options(val);
|
||||
}
|
||||
|
||||
ZXing_Barcode* ZXing_CreateBarcodeFromText(const char* data, int size, const ZXing_CreatorOptions* opts)
|
||||
{
|
||||
ZX_CHECK(opts, "CreatorOptions param is NULL")
|
||||
ZX_TRY(new Barcode(CreateBarcodeFromText({data, size ? static_cast<size_t>(size) : data ? strlen(data) : 0}, *opts));)
|
||||
}
|
||||
|
||||
ZXing_Barcode* ZXing_CreateBarcodeFromBytes(const void* data, int size, const ZXing_CreatorOptions* opts)
|
||||
{
|
||||
ZX_CHECK(opts, "CreatorOptions param is NULL")
|
||||
ZX_TRY(new Barcode(CreateBarcodeFromBytes(data, size, *opts)))
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* MARK: - WriteBarcode.h
|
||||
*/
|
||||
|
||||
ZXing_WriterOptions* ZXing_WriterOptions_new()
|
||||
{
|
||||
ZX_TRY(new ZXing_WriterOptions());
|
||||
}
|
||||
|
||||
void ZXing_WriterOptions_delete(ZXing_WriterOptions* opts)
|
||||
{
|
||||
delete opts;
|
||||
}
|
||||
|
||||
#define ZX_PROPERTY(TYPE, NAME, CAP_NAME) \
|
||||
TYPE ZXing_WriterOptions_get##CAP_NAME(const ZXing_WriterOptions* opts) { return opts->NAME(); } \
|
||||
void ZXing_WriterOptions_set##CAP_NAME(ZXing_WriterOptions* opts, TYPE NAME) { opts->NAME(NAME); }
|
||||
|
||||
ZX_PROPERTY(int, scale, Scale)
|
||||
ZX_PROPERTY(int, rotate, Rotate)
|
||||
ZX_PROPERTY(bool, addHRT, AddHRT)
|
||||
ZX_PROPERTY(bool, addQuietZones, AddQuietZones)
|
||||
|
||||
#undef ZX_PROPERTY
|
||||
|
||||
char* ZXing_WriteBarcodeToSVG(const ZXing_Barcode* barcode, const ZXing_WriterOptions* opts)
|
||||
{
|
||||
ZX_CHECK(barcode, "Barcode param is NULL")
|
||||
ZX_TRY(copy(opts ? WriteBarcodeToSVG(*barcode, *opts) : WriteBarcodeToSVG(*barcode)))
|
||||
}
|
||||
|
||||
ZXing_Image* ZXing_WriteBarcodeToImage(const ZXing_Barcode* barcode, const ZXing_WriterOptions* opts)
|
||||
{
|
||||
ZX_CHECK(barcode, "Barcode param is NULL")
|
||||
ZX_TRY(new Image(opts ? WriteBarcodeToImage(*barcode, *opts) : WriteBarcodeToImage(*barcode)))
|
||||
}
|
||||
|
||||
/*
|
||||
* MARK: - ZXingC.h
|
||||
*/
|
||||
|
||||
char* ZXing_LastErrorMsg()
|
||||
{
|
||||
if (lastErrorMsg.empty())
|
||||
return NULL;
|
||||
|
||||
return copy(std::exchange(lastErrorMsg, {}));
|
||||
}
|
||||
|
||||
const char* ZXing_Version()
|
||||
{
|
||||
return ZXING_VERSION_STR;
|
||||
}
|
||||
|
||||
void ZXing_free(void* ptr)
|
||||
{
|
||||
if (ptr != ZXing_Version())
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Copyright 2023 siiky
|
||||
* Copyright 2023 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#ifndef _ZXING_C_H
|
||||
#define _ZXING_C_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include "ZXingCpp.h"
|
||||
|
||||
typedef ZXing::Barcode ZXing_Barcode;
|
||||
typedef ZXing::Barcodes ZXing_Barcodes;
|
||||
typedef ZXing::ImageView ZXing_ImageView;
|
||||
typedef ZXing::Image ZXing_Image;
|
||||
typedef ZXing::ReaderOptions ZXing_ReaderOptions;
|
||||
typedef ZXing::CreatorOptions ZXing_CreatorOptions;
|
||||
typedef ZXing::WriterOptions ZXing_WriterOptions;
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#else
|
||||
|
||||
typedef struct ZXing_Barcode ZXing_Barcode;
|
||||
typedef struct ZXing_Barcodes ZXing_Barcodes;
|
||||
typedef struct ZXing_ImageView ZXing_ImageView;
|
||||
typedef struct ZXing_Image ZXing_Image;
|
||||
typedef struct ZXing_ReaderOptions ZXing_ReaderOptions;
|
||||
typedef struct ZXing_CreatorOptions ZXing_CreatorOptions;
|
||||
typedef struct ZXing_WriterOptions ZXing_WriterOptions;
|
||||
|
||||
#include "BarcodeFormat.h"
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Memory management: All functions that return a non-const pointer (e.g. ZXing_Barcode_text(), ZXing_ImageView_new() or
|
||||
* ZXing_ReadBarcodes()) transfer the memory ownership to the caller, who is responsible for freeing it.
|
||||
* If the returned type is some ZXing_<X>* pointer and a corresponding ZXing_<X>_delete() function is available (e.g.
|
||||
* ZXing_Barcode_delete() for ZXing_Barcode*), that function must be used to free the memory. For all other types (e.g. cha* or
|
||||
* uint8_t*), ZXing_free() must be used. For functions that return a const pointer (e.g. ZXing_Barcodes_at()), the caller must not
|
||||
* attempt to free the returned pointer, as it is static memory or the memory is owned by the parent object.
|
||||
*/
|
||||
|
||||
/*
|
||||
* MARK: - ImageView.h
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
ZXing_ImageFormat_None = 0,
|
||||
ZXing_ImageFormat_Lum = 0x01000000,
|
||||
ZXing_ImageFormat_LumA = 0x02000000,
|
||||
ZXing_ImageFormat_RGB = 0x03000102,
|
||||
ZXing_ImageFormat_BGR = 0x03020100,
|
||||
ZXing_ImageFormat_RGBA = 0x04000102,
|
||||
ZXing_ImageFormat_ARGB = 0x04010203,
|
||||
ZXing_ImageFormat_BGRA = 0x04020100,
|
||||
ZXing_ImageFormat_ABGR = 0x04030201,
|
||||
} ZXing_ImageFormat;
|
||||
|
||||
ZXing_ImageView* ZXing_ImageView_new(const uint8_t* data, int width, int height, ZXing_ImageFormat format, int rowStride,
|
||||
int pixStride);
|
||||
ZXing_ImageView* ZXing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, ZXing_ImageFormat format,
|
||||
int rowStride, int pixStride);
|
||||
void ZXing_ImageView_delete(ZXing_ImageView* iv);
|
||||
|
||||
void ZXing_ImageView_crop(ZXing_ImageView* iv, int left, int top, int width, int height);
|
||||
void ZXing_ImageView_rotate(ZXing_ImageView* iv, int degree);
|
||||
|
||||
void ZXing_Image_delete(ZXing_Image* img);
|
||||
|
||||
const uint8_t* ZXing_Image_data(const ZXing_Image* img);
|
||||
int ZXing_Image_width(const ZXing_Image* img);
|
||||
int ZXing_Image_height(const ZXing_Image* img);
|
||||
ZXing_ImageFormat ZXing_Image_format(const ZXing_Image* img);
|
||||
|
||||
/*
|
||||
* MARK: - BarcodeFormat.h
|
||||
*/
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ZXing_BarcodeFormat_Invalid = ZX_BCF_ID(0xff, 0xff), /* return value when BarcodeFormatsFromString() throws */
|
||||
#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) ZXing_BarcodeFormat_##NAME = ZX_BCF_ID(SYM, VAR),
|
||||
ZX_BCF_LIST(X)
|
||||
#undef X
|
||||
} ZXing_BarcodeFormat;
|
||||
|
||||
ZXing_BarcodeFormat ZXing_BarcodeFormatSymbology(ZXing_BarcodeFormat format);
|
||||
ZXing_BarcodeFormat ZXing_BarcodeFormatFromString(const char* str);
|
||||
char* ZXing_BarcodeFormatToString(ZXing_BarcodeFormat format);
|
||||
|
||||
ZXing_BarcodeFormat* ZXing_BarcodeFormatsList(ZXing_BarcodeFormat filter, int* outCount);
|
||||
ZXing_BarcodeFormat* ZXing_BarcodeFormatsFromString(const char* str, int* outCount);
|
||||
char* ZXing_BarcodeFormatsToString(const ZXing_BarcodeFormat* formats, int count);
|
||||
|
||||
|
||||
/*
|
||||
* MARK: - Barcode.h
|
||||
*/
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ZXing_ContentType_Text,
|
||||
ZXing_ContentType_Binary,
|
||||
ZXing_ContentType_Mixed,
|
||||
ZXing_ContentType_GS1,
|
||||
ZXing_ContentType_ISO15434,
|
||||
ZXing_ContentType_UnknownECI
|
||||
} ZXing_ContentType;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ZXing_ErrorType_None,
|
||||
ZXing_ErrorType_Format,
|
||||
ZXing_ErrorType_Checksum,
|
||||
ZXing_ErrorType_Unsupported
|
||||
} ZXing_ErrorType;
|
||||
|
||||
char* ZXing_ContentTypeToString(ZXing_ContentType type);
|
||||
|
||||
typedef struct ZXing_PointI
|
||||
{
|
||||
int x, y;
|
||||
} ZXing_PointI;
|
||||
|
||||
typedef struct ZXing_Position
|
||||
{
|
||||
ZXing_PointI topLeft, topRight, bottomRight, bottomLeft;
|
||||
} ZXing_Position;
|
||||
|
||||
char* ZXing_PositionToString(ZXing_Position position);
|
||||
|
||||
bool ZXing_Barcode_isValid(const ZXing_Barcode* barcode);
|
||||
ZXing_ErrorType ZXing_Barcode_errorType(const ZXing_Barcode* barcode);
|
||||
char* ZXing_Barcode_errorMsg(const ZXing_Barcode* barcode);
|
||||
ZXing_BarcodeFormat ZXing_Barcode_format(const ZXing_Barcode* barcode);
|
||||
ZXing_BarcodeFormat ZXing_Barcode_symbology(const ZXing_Barcode* barcode);
|
||||
ZXing_ContentType ZXing_Barcode_contentType(const ZXing_Barcode* barcode);
|
||||
uint8_t* ZXing_Barcode_bytes(const ZXing_Barcode* barcode, int* len);
|
||||
uint8_t* ZXing_Barcode_bytesECI(const ZXing_Barcode* barcode, int* len);
|
||||
char* ZXing_Barcode_text(const ZXing_Barcode* barcode);
|
||||
char* ZXing_Barcode_symbologyIdentifier(const ZXing_Barcode* barcode);
|
||||
ZXing_Position ZXing_Barcode_position(const ZXing_Barcode* barcode);
|
||||
int ZXing_Barcode_orientation(const ZXing_Barcode* barcode);
|
||||
bool ZXing_Barcode_hasECI(const ZXing_Barcode* barcode);
|
||||
bool ZXing_Barcode_isInverted(const ZXing_Barcode* barcode);
|
||||
bool ZXing_Barcode_isMirrored(const ZXing_Barcode* barcode);
|
||||
int ZXing_Barcode_lineCount(const ZXing_Barcode* barcode);
|
||||
int ZXing_Barcode_sequenceIndex(const ZXing_Barcode* barcode);
|
||||
int ZXing_Barcode_sequenceSize(const ZXing_Barcode* barcode);
|
||||
char* ZXing_Barcode_sequenceId(const ZXing_Barcode* barcode);
|
||||
char* ZXing_Barcode_extra(const ZXing_Barcode* barcode, const char* key); /* key can be NULL */
|
||||
|
||||
void ZXing_Barcode_delete(ZXing_Barcode* barcode);
|
||||
void ZXing_Barcodes_delete(ZXing_Barcodes* barcodes);
|
||||
|
||||
int ZXing_Barcodes_size(const ZXing_Barcodes* barcodes);
|
||||
const ZXing_Barcode* ZXing_Barcodes_at(const ZXing_Barcodes* barcodes, int i);
|
||||
ZXing_Barcode* ZXing_Barcodes_move(ZXing_Barcodes* barcodes, int i);
|
||||
|
||||
/*
|
||||
* MARK: - ReaderOptions.h
|
||||
*/
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ZXing_Binarizer_LocalAverage,
|
||||
ZXing_Binarizer_GlobalHistogram,
|
||||
ZXing_Binarizer_FixedThreshold,
|
||||
ZXing_Binarizer_BoolCast,
|
||||
} ZXing_Binarizer;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ZXing_EanAddOnSymbol_Ignore,
|
||||
ZXing_EanAddOnSymbol_Read,
|
||||
ZXing_EanAddOnSymbol_Require,
|
||||
} ZXing_EanAddOnSymbol;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ZXing_TextMode_Plain,
|
||||
ZXing_TextMode_ECI,
|
||||
ZXing_TextMode_HRI,
|
||||
ZXing_TextMode_Escaped,
|
||||
ZXing_TextMode_Hex,
|
||||
ZXing_TextMode_HexECI,
|
||||
} ZXing_TextMode;
|
||||
|
||||
ZXing_ReaderOptions* ZXing_ReaderOptions_new();
|
||||
void ZXing_ReaderOptions_delete(ZXing_ReaderOptions* opts);
|
||||
|
||||
void ZXing_ReaderOptions_setTryHarder(ZXing_ReaderOptions* opts, bool tryHarder);
|
||||
void ZXing_ReaderOptions_setTryRotate(ZXing_ReaderOptions* opts, bool tryRotate);
|
||||
void ZXing_ReaderOptions_setTryInvert(ZXing_ReaderOptions* opts, bool tryInvert);
|
||||
void ZXing_ReaderOptions_setTryDownscale(ZXing_ReaderOptions* opts, bool tryDownscale);
|
||||
#ifdef ZXING_EXPERIMENTAL_API
|
||||
void ZXing_ReaderOptions_setTryDenoise(ZXing_ReaderOptions* opts, bool tryDenoise);
|
||||
#endif
|
||||
void ZXing_ReaderOptions_setIsPure(ZXing_ReaderOptions* opts, bool isPure);
|
||||
void ZXing_ReaderOptions_setValidateOptionalChecksum(ZXing_ReaderOptions* opts, bool validateOptionalChecksum);
|
||||
void ZXing_ReaderOptions_setReturnErrors(ZXing_ReaderOptions* opts, bool returnErrors);
|
||||
void ZXing_ReaderOptions_setFormats(ZXing_ReaderOptions* opts, const ZXing_BarcodeFormat* formats, int count);
|
||||
void ZXing_ReaderOptions_setBinarizer(ZXing_ReaderOptions* opts, ZXing_Binarizer binarizer);
|
||||
void ZXing_ReaderOptions_setEanAddOnSymbol(ZXing_ReaderOptions* opts, ZXing_EanAddOnSymbol eanAddOnSymbol);
|
||||
void ZXing_ReaderOptions_setTextMode(ZXing_ReaderOptions* opts, ZXing_TextMode textMode);
|
||||
void ZXing_ReaderOptions_setMinLineCount(ZXing_ReaderOptions* opts, int n);
|
||||
void ZXing_ReaderOptions_setMaxNumberOfSymbols(ZXing_ReaderOptions* opts, int n);
|
||||
|
||||
bool ZXing_ReaderOptions_getTryHarder(const ZXing_ReaderOptions* opts);
|
||||
bool ZXing_ReaderOptions_getTryRotate(const ZXing_ReaderOptions* opts);
|
||||
bool ZXing_ReaderOptions_getTryInvert(const ZXing_ReaderOptions* opts);
|
||||
bool ZXing_ReaderOptions_getTryDownscale(const ZXing_ReaderOptions* opts);
|
||||
#ifdef ZXING_EXPERIMENTAL_API
|
||||
bool ZXing_ReaderOptions_getTryDenoise(const ZXing_ReaderOptions* opts);
|
||||
#endif
|
||||
bool ZXing_ReaderOptions_getIsPure(const ZXing_ReaderOptions* opts);
|
||||
bool ZXing_ReaderOptions_getValidateOptionalChecksum(const ZXing_ReaderOptions* opts);
|
||||
bool ZXing_ReaderOptions_getReturnErrors(const ZXing_ReaderOptions* opts);
|
||||
ZXing_BarcodeFormat* ZXing_ReaderOptions_getFormats(const ZXing_ReaderOptions* opts, int* outCount);
|
||||
ZXing_Binarizer ZXing_ReaderOptions_getBinarizer(const ZXing_ReaderOptions* opts);
|
||||
ZXing_EanAddOnSymbol ZXing_ReaderOptions_getEanAddOnSymbol(const ZXing_ReaderOptions* opts);
|
||||
ZXing_TextMode ZXing_ReaderOptions_getTextMode(const ZXing_ReaderOptions* opts);
|
||||
int ZXing_ReaderOptions_getMinLineCount(const ZXing_ReaderOptions* opts);
|
||||
int ZXing_ReaderOptions_getMaxNumberOfSymbols(const ZXing_ReaderOptions* opts);
|
||||
|
||||
/*
|
||||
* MARK: - ReadBarcode.h
|
||||
*/
|
||||
|
||||
/**
|
||||
* Note: opts is optional, i.e. it can be NULL, which will imply default settings.
|
||||
* @return NULL in case of an error (e.g. invalid image or out of memory), otherwise a pointer to a ZXing_Barcodes object, which may
|
||||
* contain zero or more barcodes. Freeing the empty ZXing_Barcodes object is optional.
|
||||
*/
|
||||
ZXing_Barcodes* ZXing_ReadBarcodes(const ZXing_ImageView* iv, const ZXing_ReaderOptions* opts);
|
||||
|
||||
|
||||
/*
|
||||
* MARK: - CreateBarcode.h
|
||||
*/
|
||||
|
||||
ZXing_CreatorOptions* ZXing_CreatorOptions_new(ZXing_BarcodeFormat format);
|
||||
void ZXing_CreatorOptions_delete(ZXing_CreatorOptions* opts);
|
||||
|
||||
void ZXing_CreatorOptions_setFormat(ZXing_CreatorOptions* opts, ZXing_BarcodeFormat format);
|
||||
ZXing_BarcodeFormat ZXing_CreatorOptions_getFormat(const ZXing_CreatorOptions* opts);
|
||||
void ZXing_CreatorOptions_setOptions(ZXing_CreatorOptions* opts, const char* options);
|
||||
char* ZXing_CreatorOptions_getOptions(const ZXing_CreatorOptions* opts);
|
||||
|
||||
ZXing_Barcode* ZXing_CreateBarcodeFromText(const char* data, int size, const ZXing_CreatorOptions* opts);
|
||||
ZXing_Barcode* ZXing_CreateBarcodeFromBytes(const void* data, int size, const ZXing_CreatorOptions* opts);
|
||||
|
||||
/*
|
||||
* MARK: - WriteBarcode.h
|
||||
*/
|
||||
|
||||
ZXing_WriterOptions* ZXing_WriterOptions_new();
|
||||
void ZXing_WriterOptions_delete(ZXing_WriterOptions* opts);
|
||||
|
||||
void ZXing_WriterOptions_setScale(ZXing_WriterOptions* opts, int scale);
|
||||
int ZXing_WriterOptions_getScale(const ZXing_WriterOptions* opts);
|
||||
|
||||
void ZXing_WriterOptions_setRotate(ZXing_WriterOptions* opts, int rotate);
|
||||
int ZXing_WriterOptions_getRotate(const ZXing_WriterOptions* opts);
|
||||
|
||||
void ZXing_WriterOptions_setAddHRT(ZXing_WriterOptions* opts, bool addHRT);
|
||||
bool ZXing_WriterOptions_getAddHRT(const ZXing_WriterOptions* opts);
|
||||
|
||||
void ZXing_WriterOptions_setAddQuietZones(ZXing_WriterOptions* opts, bool addQuietZones);
|
||||
bool ZXing_WriterOptions_getAddQuietZones(const ZXing_WriterOptions* opts);
|
||||
|
||||
|
||||
/** Note: opts is optional, i.e. it can be NULL, which will imply default settings. */
|
||||
char* ZXing_WriteBarcodeToSVG(const ZXing_Barcode* barcode, const ZXing_WriterOptions* opts);
|
||||
ZXing_Image* ZXing_WriteBarcodeToImage(const ZXing_Barcode* barcode, const ZXing_WriterOptions* opts);
|
||||
|
||||
|
||||
/* ZXing_LastErrorMsg() returns NULL in case there is no last error and a copy of the string otherwise. */
|
||||
char* ZXing_LastErrorMsg();
|
||||
|
||||
const char* ZXing_Version();
|
||||
|
||||
void ZXing_free(void* ptr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _ZXING_C_H */
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2024 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "ZXingCpp.h"
|
||||
|
||||
#include "Version.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
const std::string& Version()
|
||||
{
|
||||
static std::string res = ZXING_VERSION_STR;
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace ZXing
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2024 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BarcodeFormat.h"
|
||||
#include "CreateBarcode.h"
|
||||
#include "ReadBarcode.h"
|
||||
#include "WriteBarcode.h"
|
||||
#include "Version.h"
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
const std::string& Version();
|
||||
|
||||
} // namespace ZXing
|
||||
|
||||
/**
|
||||
\mainpage Introduction
|
||||
|
||||
ZXing-C++ ("zebra crossing") is an open-source, multi-format linear/matrix barcode image processing library implemented in C++.
|
||||
|
||||
The central class in the library is ZXing::Barcode, which represents a decoded or created barcode symbol, providing access to its
|
||||
content, format, position, and other metadata. It serves as the primary interface for working with barcodes in the library.
|
||||
|
||||
\section reader_sec Reading barcodes
|
||||
|
||||
1. Load your image into memory (3rd-party library required).
|
||||
2. Call ZXing::ReadBarcodes(), the simplest API to get a list of ZXing::Barcode objects.
|
||||
|
||||
A very simple example looks like this:
|
||||
```c++
|
||||
#include "ZXing/ZXingCpp.h"
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int width, height;
|
||||
unsigned char* data;
|
||||
// load your image data from somewhere. ImageFormat::Lum assumes grey scale image data.
|
||||
|
||||
auto image = ZXing::ImageView(data, width, height, ZXing::ImageFormat::Lum);
|
||||
auto options = ZXing::ReaderOptions().formats(ZXing::BarcodeFormat::QRCode);
|
||||
auto barcodes = ZXing::ReadBarcodes(image, options);
|
||||
|
||||
for (const auto& b : barcodes)
|
||||
std::cout << ZXing::ToString(b.format()) << ": " << b.text() << "\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
\section writer_sec Writing barcodes
|
||||
|
||||
1. Create a ZXing::Barcode object using either ZXing::CreateBarcodeFromText() or ZXing::CreateBarcodeFromBytes().
|
||||
2. Call ZXing::WriteBarcodeToImage() or ZXing::WriteBarcodeToSVG() to get the output.
|
||||
|
||||
A very simple example looks like this:
|
||||
```c++
|
||||
#include "ZXing/ZXingCpp.h"
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
auto barcode = ZXing::CreateBarcodeFromText("Hello, World!", ZXing::BarcodeFormat::QRCode);
|
||||
auto options = ZXing::WriterOptions().scale(4).addQuietZones(false);
|
||||
auto svg = ZXing::WriteBarcodeToSVG(barcode, options);
|
||||
|
||||
std::cout << svg << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
*/
|
||||
@@ -0,0 +1,606 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ZXingCpp.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QDebug>
|
||||
#include <QMetaType>
|
||||
#include <QPoint>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QScopeGuard>
|
||||
#include <QThreadPool>
|
||||
|
||||
#ifdef QT_MULTIMEDIA_LIB
|
||||
#include <QVideoFrame>
|
||||
#include <QVideoSink>
|
||||
#include <QElapsedTimer>
|
||||
#endif
|
||||
|
||||
// This is some sample code to start a discussion about how a minimal and header-only Qt wrapper/helper could look like.
|
||||
|
||||
namespace ZXingQt {
|
||||
|
||||
Q_NAMESPACE
|
||||
|
||||
inline QString Version()
|
||||
{
|
||||
return QString::fromStdString(ZXing::Version());
|
||||
}
|
||||
|
||||
namespace Detail {
|
||||
|
||||
template<typename Cout, typename Cin>
|
||||
inline Cout transcode(Cin&& in)
|
||||
{
|
||||
Cout out;
|
||||
out.reserve(in.size());
|
||||
for (auto&& v : in)
|
||||
out.push_back(static_cast<typename Cout::value_type>(std::move(v)));
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::string_view qba2sv(const QByteArray& ba) noexcept
|
||||
{
|
||||
return std::string_view(ba.constData(), static_cast<size_t>(ba.size()));
|
||||
}
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
// MARK: - Enums and Types
|
||||
|
||||
enum class BarcodeFormat : unsigned int
|
||||
{
|
||||
#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) NAME = ZX_BCF_ID(SYM, VAR),
|
||||
ZX_BCF_LIST(X)
|
||||
#undef X
|
||||
};
|
||||
|
||||
enum class ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI };
|
||||
enum class TextMode { Plain, ECI, HRI, Escaped, Hex, HexECI };
|
||||
enum class Binarizer { LocalAverage, GlobalHistogram, FixedThreshold, BoolCast };
|
||||
|
||||
Q_ENUM_NS(BarcodeFormat)
|
||||
Q_ENUM_NS(ContentType)
|
||||
Q_ENUM_NS(TextMode)
|
||||
Q_ENUM_NS(Binarizer)
|
||||
|
||||
using BarcodeFormats = QList<BarcodeFormat>;
|
||||
|
||||
using ZXing::ReaderOptions;
|
||||
using ZXing::WriterOptions;
|
||||
|
||||
inline BarcodeFormat BarcodeFormatFromString(QStringView str)
|
||||
{
|
||||
return static_cast<BarcodeFormat>(ZXing::BarcodeFormatFromString(Detail::qba2sv(str.toUtf8())));
|
||||
}
|
||||
|
||||
inline BarcodeFormats BarcodeFormatsFromString(QStringView str)
|
||||
{
|
||||
return Detail::transcode<BarcodeFormats>(ZXing::BarcodeFormatsFromString(Detail::qba2sv(str.toUtf8())));
|
||||
}
|
||||
|
||||
inline BarcodeFormats ListBarcodeFormats(BarcodeFormat filter = BarcodeFormat::None)
|
||||
{
|
||||
return Detail::transcode<BarcodeFormats>(ZXing::BarcodeFormats::list(
|
||||
filter == BarcodeFormat::None ? ZXing::BarcodeFormats() : static_cast<ZXing::BarcodeFormat>(filter)));
|
||||
}
|
||||
|
||||
#define ZQ_DECL_TO_STRING(TYPE) \
|
||||
inline QString ToString(::ZXingQt::TYPE v) \
|
||||
{ \
|
||||
return QString::fromStdString(::ZXing::ToString(static_cast<::ZXing::TYPE>(v))); \
|
||||
}
|
||||
|
||||
ZQ_DECL_TO_STRING(BarcodeFormat)
|
||||
ZQ_DECL_TO_STRING(ContentType)
|
||||
#undef ZQ_DECL_TO_STRING
|
||||
|
||||
// template <typename T, typename = decltype(ToString(T()))>
|
||||
// QDebug operator<<(QDebug dbg, const T& v)
|
||||
// {
|
||||
// return dbg.noquote() << ToString(v);
|
||||
// }
|
||||
|
||||
class Error : private ZXing::Error
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
Q_PROPERTY(Type type READ type)
|
||||
Q_PROPERTY(QString message READ message)
|
||||
Q_PROPERTY(QString location READ location)
|
||||
|
||||
friend QString ToString(const Error& err);
|
||||
|
||||
public:
|
||||
enum class Type { None, Format, Checksum, Unsupported };
|
||||
Q_ENUM(Type)
|
||||
|
||||
Error() = default;
|
||||
explicit Error(const ZXing::Error& e) : ZXing::Error(e) {}
|
||||
|
||||
Type type() const { return static_cast<Type>(ZXing::Error::type()); }
|
||||
QString message() const { return QString::fromStdString(ZXing::Error::msg()); }
|
||||
QString location() const { return QString::fromStdString(ZXing::Error::location()); }
|
||||
|
||||
using ZXing::Error::operator bool;
|
||||
|
||||
bool operator==(const Error& o) const { return ZXing::Error::operator==(o); }
|
||||
};
|
||||
|
||||
inline QString ToString(const Error& err)
|
||||
{
|
||||
return QString::fromStdString(ZXing::ToString(static_cast<ZXing::Error>(err)));
|
||||
}
|
||||
|
||||
class Position : public ZXing::Quadrilateral<QPoint>
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
Q_PROPERTY(QPoint topLeft READ topLeft)
|
||||
Q_PROPERTY(QPoint topRight READ topRight)
|
||||
Q_PROPERTY(QPoint bottomRight READ bottomRight)
|
||||
Q_PROPERTY(QPoint bottomLeft READ bottomLeft)
|
||||
Q_PROPERTY(QPoint center READ center)
|
||||
|
||||
using Base = ZXing::Quadrilateral<QPoint>;
|
||||
|
||||
public:
|
||||
using Base::Base;
|
||||
|
||||
QPoint center() const { return std::accumulate(this->begin(), this->end(), QPoint(0, 0)) / 4; }
|
||||
};
|
||||
|
||||
// MARK: - Barcode
|
||||
|
||||
class Barcode : private ZXing::Barcode
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
Q_PROPERTY(BarcodeFormat format READ format)
|
||||
Q_PROPERTY(BarcodeFormat symbology READ symbology)
|
||||
Q_PROPERTY(QString text READ text)
|
||||
Q_PROPERTY(QByteArray bytes READ bytes)
|
||||
Q_PROPERTY(QByteArray bytesECI READ bytesECI)
|
||||
Q_PROPERTY(bool isValid READ isValid)
|
||||
Q_PROPERTY(Error error READ error)
|
||||
Q_PROPERTY(ContentType contentType READ contentType)
|
||||
Q_PROPERTY(bool hasECI READ hasECI)
|
||||
Q_PROPERTY(Position position READ position)
|
||||
Q_PROPERTY(int orientation READ orientation)
|
||||
Q_PROPERTY(bool isMirrored READ isMirrored)
|
||||
Q_PROPERTY(bool isInverted READ isInverted)
|
||||
Q_PROPERTY(QString symbologyIdentifier READ symbologyIdentifier)
|
||||
Q_PROPERTY(int sequenceSize READ sequenceSize)
|
||||
Q_PROPERTY(int sequenceIndex READ sequenceIndex)
|
||||
Q_PROPERTY(QString sequenceId READ sequenceId)
|
||||
Q_PROPERTY(bool isLastInSequence READ isLastInSequence)
|
||||
Q_PROPERTY(bool isPartOfSequence READ isPartOfSequence)
|
||||
Q_PROPERTY(int lineCount READ lineCount)
|
||||
|
||||
public:
|
||||
Barcode() = default; // required for qmetatype machinery
|
||||
|
||||
explicit Barcode(ZXing::Barcode&& r) : ZXing::Barcode(std::move(r)) {}
|
||||
|
||||
using ZXing::Barcode::isValid;
|
||||
|
||||
BarcodeFormat format() const { return static_cast<BarcodeFormat>(ZXing::Barcode::format()); }
|
||||
BarcodeFormat symbology() const { return static_cast<BarcodeFormat>(ZXing::Barcode::symbology()); }
|
||||
ContentType contentType() const { return static_cast<ContentType>(ZXing::Barcode::contentType()); }
|
||||
|
||||
Error error() const { return Error(ZXing::Barcode::error()); }
|
||||
|
||||
QString text() const { return QString::fromStdString(ZXing::Barcode::text()); }
|
||||
QString text(TextMode mode) const { return QString::fromStdString(ZXing::Barcode::text(static_cast<ZXing::TextMode>(mode))); }
|
||||
|
||||
QByteArray bytes() const
|
||||
{
|
||||
return QByteArray(reinterpret_cast<const char*>(ZXing::Barcode::bytes().data()), std::size(ZXing::Barcode::bytes()));
|
||||
}
|
||||
|
||||
QByteArray bytesECI() const
|
||||
{
|
||||
auto b = ZXing::Barcode::bytesECI();
|
||||
return QByteArray(reinterpret_cast<const char*>(b.data()), b.size());
|
||||
}
|
||||
|
||||
using ZXing::Barcode::hasECI;
|
||||
|
||||
Position position() const
|
||||
{
|
||||
auto& pos = ZXing::Barcode::position();
|
||||
auto qp = [&pos](int i) { return QPoint(pos[i].x, pos[i].y); };
|
||||
return {qp(0), qp(1), qp(2), qp(3)};
|
||||
}
|
||||
|
||||
using ZXing::Barcode::orientation;
|
||||
using ZXing::Barcode::isMirrored;
|
||||
using ZXing::Barcode::isInverted;
|
||||
|
||||
QString symbologyIdentifier() const { return QString::fromStdString(ZXing::Barcode::symbologyIdentifier()); }
|
||||
|
||||
using ZXing::Barcode::sequenceSize;
|
||||
using ZXing::Barcode::sequenceIndex;
|
||||
using ZXing::Barcode::isLastInSequence;
|
||||
using ZXing::Barcode::isPartOfSequence;
|
||||
QString sequenceId() const { return QString::fromStdString(ZXing::Barcode::sequenceId()); }
|
||||
|
||||
QString extra(QStringView key = {}) const { return QString::fromStdString(ZXing::Barcode::extra(Detail::qba2sv(key.toUtf8()))); }
|
||||
|
||||
using ZXing::Barcode::lineCount;
|
||||
|
||||
QString toSVG(const WriterOptions& options = {}) const
|
||||
{
|
||||
return QString::fromStdString(ZXing::WriteBarcodeToSVG(*this, options));
|
||||
}
|
||||
|
||||
QImage toImage(const WriterOptions& options = {}) const
|
||||
{
|
||||
auto img = ZXing::WriteBarcodeToImage(*this, options);
|
||||
return QImage(img.data(), img.width(), img.height(), img.width(), QImage::Format::Format_Grayscale8).copy();
|
||||
}
|
||||
|
||||
QImage symbol() const
|
||||
{
|
||||
auto img = ZXing::Barcode::symbol();
|
||||
return img.data() ? QImage(img.data(), img.width(), img.height(), img.width(), QImage::Format::Format_Grayscale8).copy() : QImage();
|
||||
}
|
||||
|
||||
bool operator==(const Barcode& o) const { return ZXing::Barcode::operator==(o); }
|
||||
|
||||
static Barcode fromText(QStringView text, BarcodeFormat format, QStringView options = {})
|
||||
{
|
||||
auto opts = ZXing::CreatorOptions(static_cast<ZXing::BarcodeFormat>(format), options.toUtf8().toStdString());
|
||||
return Barcode(ZXing::CreateBarcodeFromText(Detail::qba2sv(text.toUtf8()), opts));
|
||||
}
|
||||
|
||||
static Barcode fromBytes(const QByteArray& bytes, BarcodeFormat format, QStringView options = {})
|
||||
{
|
||||
auto opts = ZXing::CreatorOptions(static_cast<ZXing::BarcodeFormat>(format), options.toUtf8().toStdString());
|
||||
return Barcode(ZXing::CreateBarcodeFromBytes(bytes, opts));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// MARK: - Read
|
||||
|
||||
inline QList<Barcode> ReadBarcodes(const QImage& img, const ReaderOptions& opts = {})
|
||||
{
|
||||
using namespace ZXing;
|
||||
|
||||
auto ImgFmtFromQImg = [](const QImage& img) {
|
||||
switch (img.format()) {
|
||||
case QImage::Format_ARGB32:
|
||||
case QImage::Format_RGB32:
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
return ImageFormat::BGRA;
|
||||
#else
|
||||
return ImageFormat::ARGB;
|
||||
#endif
|
||||
case QImage::Format_RGB888: return ImageFormat::RGB;
|
||||
case QImage::Format_RGBX8888:
|
||||
case QImage::Format_RGBA8888: return ImageFormat::RGBA;
|
||||
case QImage::Format_Grayscale8: return ImageFormat::Lum;
|
||||
default: return ImageFormat::None;
|
||||
}
|
||||
};
|
||||
|
||||
auto exec = [&](const QImage& img) {
|
||||
return Detail::transcode<QList<Barcode>>(ZXing::ReadBarcodes(
|
||||
{img.bits(), img.width(), img.height(), ImgFmtFromQImg(img), static_cast<int>(img.bytesPerLine())}, opts));
|
||||
};
|
||||
|
||||
return ImgFmtFromQImg(img) == ImageFormat::None ? exec(img.convertToFormat(QImage::Format_Grayscale8)) : exec(img);
|
||||
}
|
||||
|
||||
inline Barcode ReadBarcode(const QImage& img, const ReaderOptions& opts = {})
|
||||
{
|
||||
auto res = ReadBarcodes(img, ReaderOptions(opts).setMaxNumberOfSymbols(1));
|
||||
return !res.isEmpty() ? res.takeFirst() : Barcode();
|
||||
}
|
||||
|
||||
#ifdef QT_MULTIMEDIA_LIB
|
||||
inline QList<Barcode> ReadBarcodes(const QVideoFrame& frame, const ReaderOptions& opts = {})
|
||||
{
|
||||
using namespace ZXing;
|
||||
|
||||
ImageFormat fmt = ImageFormat::None;
|
||||
int pixStride = 0;
|
||||
int pixOffset = 0;
|
||||
|
||||
switch (frame.pixelFormat()) {
|
||||
case QVideoFrameFormat::Format_ARGB8888:
|
||||
case QVideoFrameFormat::Format_ARGB8888_Premultiplied:
|
||||
case QVideoFrameFormat::Format_XRGB8888: fmt = ImageFormat::ARGB; break;
|
||||
|
||||
case QVideoFrameFormat::Format_BGRA8888:
|
||||
case QVideoFrameFormat::Format_BGRA8888_Premultiplied:
|
||||
case QVideoFrameFormat::Format_BGRX8888: fmt = ImageFormat::BGRA; break;
|
||||
|
||||
case QVideoFrameFormat::Format_ABGR8888:
|
||||
case QVideoFrameFormat::Format_XBGR8888: fmt = ImageFormat::ABGR; break;
|
||||
|
||||
case QVideoFrameFormat::Format_RGBA8888:
|
||||
case QVideoFrameFormat::Format_RGBX8888: fmt = ImageFormat::RGBA; break;
|
||||
|
||||
case QVideoFrameFormat::Format_P010:
|
||||
case QVideoFrameFormat::Format_P016: fmt = ImageFormat::Lum, pixStride = 1; break;
|
||||
|
||||
case QVideoFrameFormat::Format_YUV420P:
|
||||
case QVideoFrameFormat::Format_YUV422P:
|
||||
case QVideoFrameFormat::Format_NV12:
|
||||
case QVideoFrameFormat::Format_NV21:
|
||||
case QVideoFrameFormat::Format_IMC1:
|
||||
case QVideoFrameFormat::Format_IMC2:
|
||||
case QVideoFrameFormat::Format_IMC3:
|
||||
case QVideoFrameFormat::Format_IMC4:
|
||||
case QVideoFrameFormat::Format_YV12: fmt = ImageFormat::Lum; break;
|
||||
|
||||
case QVideoFrameFormat::Format_AYUV_Premultiplied:
|
||||
case QVideoFrameFormat::Format_AYUV: fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 1; break;
|
||||
case QVideoFrameFormat::Format_UYVY: fmt = ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;
|
||||
case QVideoFrameFormat::Format_YUYV: fmt = ImageFormat::Lum, pixStride = 2; break;
|
||||
|
||||
case QVideoFrameFormat::Format_Y8: fmt = ImageFormat::Lum; break;
|
||||
case QVideoFrameFormat::Format_Y16: fmt = ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (fmt != ImageFormat::None) {
|
||||
auto img = frame; // shallow copy just get access to non-const map() function
|
||||
if (!img.isValid() || !img.map(QVideoFrame::ReadOnly)) {
|
||||
qWarning() << "invalid QVideoFrame: could not map memory";
|
||||
return {};
|
||||
}
|
||||
QScopeGuard unmap([&] { img.unmap(); });
|
||||
|
||||
return Detail::transcode<QList<Barcode>>(ZXing::ReadBarcodes(
|
||||
{img.bits(0) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(0), pixStride}, opts));
|
||||
}
|
||||
else {
|
||||
auto qimg = frame.toImage();
|
||||
if (qimg.format() != QImage::Format_Invalid)
|
||||
return ReadBarcodes(qimg, opts);
|
||||
qWarning() << "failed to convert QVideoFrame to QImage";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
inline Barcode ReadBarcode(const QVideoFrame& frame, const ReaderOptions& opts = {})
|
||||
{
|
||||
auto res = ReadBarcodes(frame, ReaderOptions(opts).setMaxNumberOfSymbols(1));
|
||||
return !res.isEmpty() ? res.takeFirst() : Barcode();
|
||||
}
|
||||
#endif // QT_MULTIMEDIA_LIB
|
||||
|
||||
#define ZQ_PROPERTY(Type, name, setter) \
|
||||
public: \
|
||||
Q_PROPERTY(Type name READ name WRITE setter NOTIFY name##Changed) \
|
||||
Type name() const noexcept { return ReaderOptions::name(); } \
|
||||
Q_SLOT void setter(const Type& newVal) \
|
||||
{ \
|
||||
if (name() != newVal) { \
|
||||
ReaderOptions::setter(newVal); \
|
||||
Q_EMIT name##Changed(); \
|
||||
} \
|
||||
} \
|
||||
Q_SIGNAL void name##Changed();
|
||||
|
||||
|
||||
// MARK: - BarcodeReader
|
||||
|
||||
class BarcodeReader : public QObject, private ReaderOptions
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
mutable QThreadPool _pool;
|
||||
|
||||
Q_PROPERTY(BarcodeFormats formats READ formats WRITE setFormats NOTIFY formatsChanged)
|
||||
Q_PROPERTY(TextMode textMode READ textMode WRITE setTextMode NOTIFY textModeChanged)
|
||||
|
||||
void emitFoundBarcodes(const QList<Barcode>& barcodes) const
|
||||
{
|
||||
if (!barcodes.isEmpty())
|
||||
Q_EMIT foundBarcodes(barcodes);
|
||||
else
|
||||
Q_EMIT foundNoBarcodes();
|
||||
}
|
||||
|
||||
public:
|
||||
BarcodeReader(QObject* parent = nullptr) : QObject(parent)
|
||||
{
|
||||
_pool.setMaxThreadCount(1);
|
||||
}
|
||||
|
||||
~BarcodeReader()
|
||||
{
|
||||
_pool.setMaxThreadCount(0);
|
||||
_pool.waitForDone(-1);
|
||||
}
|
||||
|
||||
Q_PROPERTY(int maxThreadCount READ maxThreadCount WRITE setMaxThreadCount)
|
||||
int maxThreadCount() const { return _pool.maxThreadCount(); }
|
||||
Q_SLOT void setMaxThreadCount(int maxThreadCount)
|
||||
{
|
||||
if (_pool.maxThreadCount() != maxThreadCount) {
|
||||
_pool.setMaxThreadCount(maxThreadCount);
|
||||
Q_EMIT maxThreadCountChanged();
|
||||
}
|
||||
}
|
||||
Q_SIGNAL void maxThreadCountChanged();
|
||||
|
||||
BarcodeFormats formats() const noexcept { return Detail::transcode<BarcodeFormats>(ReaderOptions::formats()); }
|
||||
Q_SLOT void setFormats(const BarcodeFormats& newVal)
|
||||
{
|
||||
if (formats() != newVal) {
|
||||
ReaderOptions::setFormats(Detail::transcode<std::vector<ZXing::BarcodeFormat>>(newVal));
|
||||
Q_EMIT formatsChanged();
|
||||
}
|
||||
}
|
||||
// Q_SLOT void setFormats(BarcodeFormat newVal) { setFormats(BarcodeFormats({newVal})); }
|
||||
Q_SIGNAL void formatsChanged();
|
||||
|
||||
TextMode textMode() const noexcept { return static_cast<TextMode>(ReaderOptions::textMode()); }
|
||||
Q_SLOT void setTextMode(TextMode newVal)
|
||||
{
|
||||
if (textMode() != newVal) {
|
||||
ReaderOptions::setTextMode(static_cast<ZXing::TextMode>(newVal));
|
||||
Q_EMIT textModeChanged();
|
||||
}
|
||||
}
|
||||
Q_SIGNAL void textModeChanged();
|
||||
|
||||
// ZQ_PROPERTY(BarcodeFormats, formats, setFormats)
|
||||
// ZQ_PROPERTY(TextMode, textMode, setTextMode)
|
||||
ZQ_PROPERTY(bool, tryRotate, setTryRotate)
|
||||
ZQ_PROPERTY(bool, tryHarder, setTryHarder)
|
||||
ZQ_PROPERTY(bool, tryInvert, setTryInvert)
|
||||
ZQ_PROPERTY(bool, tryDownscale, setTryDownscale)
|
||||
ZQ_PROPERTY(bool, isPure, setIsPure)
|
||||
ZQ_PROPERTY(bool, returnErrors, setReturnErrors)
|
||||
|
||||
// For debugging/development
|
||||
mutable QAtomicInt runTime = 0;
|
||||
Q_PROPERTY(int runTime MEMBER runTime)
|
||||
|
||||
Q_SLOT QList<Barcode> read(const QImage& image) const
|
||||
{
|
||||
auto barcodes = ReadBarcodes(image, *this);
|
||||
emitFoundBarcodes(barcodes);
|
||||
return barcodes;
|
||||
}
|
||||
|
||||
/// @brief Read barcodes from the given image asynchronously.
|
||||
/// @note The foundBarcodes() and foundNoBarcodes() signals are emitted from a worker thread.
|
||||
Q_SLOT void readAsync(const QImage& image) const
|
||||
{
|
||||
_pool.start([this, image]() { read(image); });
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
/// @note If an async read is called, the foundBarcodes() and foundNoBarcodes() signals are emitted from a worker thread.
|
||||
void foundNoBarcodes() const;
|
||||
void foundBarcodes(const QList<Barcode>& barcodes) const;
|
||||
|
||||
public:
|
||||
#ifdef QT_MULTIMEDIA_LIB
|
||||
Q_SLOT QList<Barcode> read(const QVideoFrame& image) const
|
||||
{
|
||||
QElapsedTimer t;
|
||||
t.start();
|
||||
auto barcodes = ReadBarcodes(image, *this);
|
||||
runTime = t.elapsed();
|
||||
emitFoundBarcodes(barcodes);
|
||||
return barcodes;
|
||||
}
|
||||
|
||||
/// @brief Try to read barcodes from the given video frame asynchronously.
|
||||
/// @return true iff a read task was started, false if all worker threads are busy.
|
||||
Q_SLOT bool tryReadAsync(const QVideoFrame& frame) const
|
||||
{
|
||||
return _pool.tryStart([this, frame]() { read(frame); });
|
||||
}
|
||||
|
||||
private:
|
||||
QVideoSink *_sink = nullptr;
|
||||
|
||||
public:
|
||||
void setVideoSink(QVideoSink* sink)
|
||||
{
|
||||
if (_sink == sink)
|
||||
return;
|
||||
|
||||
if (_sink)
|
||||
disconnect(_sink, nullptr, this, nullptr);
|
||||
|
||||
_sink = sink;
|
||||
if (_sink)
|
||||
connect(_sink, &QVideoSink::videoFrameChanged, this, &BarcodeReader::tryReadAsync, Qt::DirectConnection);
|
||||
}
|
||||
Q_PROPERTY(QVideoSink* videoSink MEMBER _sink WRITE setVideoSink)
|
||||
|
||||
#endif // QT_MULTIMEDIA_LIB
|
||||
|
||||
};
|
||||
|
||||
#undef ZQ_PROPERTY
|
||||
|
||||
} // namespace ZXingQt
|
||||
|
||||
|
||||
// Q_DECLARE_METATYPE: compile-time declaration required for QVariant storage and template instantiation
|
||||
Q_DECLARE_METATYPE(ZXingQt::Error)
|
||||
Q_DECLARE_METATYPE(ZXingQt::Position)
|
||||
Q_DECLARE_METATYPE(ZXingQt::Barcode)
|
||||
|
||||
#ifdef QT_QML_LIB
|
||||
|
||||
// MARK: - QML Integration
|
||||
|
||||
#include <QtQmlIntegration/qqmlintegration.h>
|
||||
#include <QQmlEngine>
|
||||
|
||||
namespace ZXingQt {
|
||||
|
||||
class ZXingQml : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE static QString FormatToString(BarcodeFormat format) { return ZXingQt::ToString(format); }
|
||||
Q_INVOKABLE static QString ContentTypeToString(ContentType type) { return ZXingQt::ToString(type); }
|
||||
Q_INVOKABLE static QList<BarcodeFormat> ListBarcodeFormats(BarcodeFormat filter = BarcodeFormat::None)
|
||||
{
|
||||
return ZXingQt::ListBarcodeFormats(filter);
|
||||
}
|
||||
};
|
||||
|
||||
namespace Detail {
|
||||
|
||||
inline void registerQmlAndMetaTypes()
|
||||
{
|
||||
// qRegisterMetaType: runtime registration required for queued signal/slot connections across threads
|
||||
qRegisterMetaType<ZXingQt::BarcodeFormat>("BarcodeFormat");
|
||||
qRegisterMetaType<ZXingQt::ContentType>("ContentType");
|
||||
qRegisterMetaType<ZXingQt::TextMode>("TextMode");
|
||||
qRegisterMetaType<ZXingQt::Error>("Error");
|
||||
qRegisterMetaType<ZXingQt::Error::Type>("Error::Type");
|
||||
qRegisterMetaType<ZXingQt::Position>("Position");
|
||||
qRegisterMetaType<ZXingQt::Barcode>("Barcode");
|
||||
qRegisterMetaType<QList<ZXingQt::BarcodeFormat>>("QList<BarcodeFormat>");
|
||||
qRegisterMetaType<QList<ZXingQt::Barcode>>("QList<Barcode>");
|
||||
|
||||
// Custom enum to string converters
|
||||
QMetaType::registerConverter<BarcodeFormat, QString>(
|
||||
[](BarcodeFormat format) { return QString::fromStdString(ZXing::ToString(static_cast<ZXing::BarcodeFormat>(format))); });
|
||||
|
||||
QMetaType::registerConverter<ContentType, QString>(
|
||||
[](ContentType type) { return QString::fromStdString(ZXing::ToString(static_cast<ZXing::ContentType>(type))); });
|
||||
|
||||
// qmlRegisterType allows us to store the position / barcode in a QML property, i.e. property barcode myBarcode: ...
|
||||
qmlRegisterType<ZXingQt::Error>("ZXing", 1, 0, "error");
|
||||
qmlRegisterType<ZXingQt::Position>("ZXing", 1, 0, "position");
|
||||
qmlRegisterType<ZXingQt::Barcode>("ZXing", 1, 0, "barcode");
|
||||
|
||||
qmlRegisterUncreatableMetaObject(ZXingQt::staticMetaObject, "ZXing", 1, 0, "ZXing", QStringLiteral("Access to enums only"));
|
||||
qmlRegisterType<ZXingQt::BarcodeReader>("ZXing", 1, 0, "BarcodeReader");
|
||||
|
||||
qmlRegisterSingletonType<ZXingQml>("ZXing", 1, 0, "ZXingQml", [](QQmlEngine*, QJSEngine*) -> QObject* { return new ZXingQml(); });
|
||||
}
|
||||
|
||||
struct ZXingQtInitializer
|
||||
{
|
||||
ZXingQtInitializer() { registerQmlAndMetaTypes(); }
|
||||
} inline zxingQtInitializer;
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
} // namespace ZXingQt
|
||||
|
||||
#endif // QT_QML_LIB
|
||||
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "AZDecoder.h"
|
||||
|
||||
#include "AZDetectorResult.h"
|
||||
#include "Barcode.h"
|
||||
#include "BitArray.h"
|
||||
#include "BitMatrix.h"
|
||||
#include "DecoderResult.h"
|
||||
#include "ReedSolomon.h"
|
||||
#include "ZXTestSupport.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing::Aztec {
|
||||
|
||||
enum class Table
|
||||
{
|
||||
UPPER,
|
||||
LOWER,
|
||||
MIXED,
|
||||
DIGIT,
|
||||
PUNCT,
|
||||
BINARY
|
||||
};
|
||||
|
||||
static const char* UPPER_TABLE[] = {
|
||||
"CTRL_PS", " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
|
||||
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "CTRL_LL", "CTRL_ML", "CTRL_DL", "CTRL_BS"
|
||||
};
|
||||
|
||||
static const char* LOWER_TABLE[] = {
|
||||
"CTRL_PS", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
|
||||
"q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "CTRL_US", "CTRL_ML", "CTRL_DL", "CTRL_BS"
|
||||
};
|
||||
|
||||
static const char* MIXED_TABLE[] = {
|
||||
"CTRL_PS", " ", "\1", "\2", "\3", "\4", "\5", "\6", "\7", "\b", "\t", "\n",
|
||||
"\13", "\f", "\r", "\33", "\34", "\35", "\36", "\37", "@", "\\", "^", "_",
|
||||
"`", "|", "~", "\177", "CTRL_LL", "CTRL_UL", "CTRL_PL", "CTRL_BS"
|
||||
};
|
||||
|
||||
static const char* PUNCT_TABLE[] = {
|
||||
"FLGN", "\r", "\r\n", ". ", ", ", ": ", "!", "\"", "#", "$", "%", "&", "'", "(", ")",
|
||||
"*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "[", "]", "{", "}", "CTRL_UL"
|
||||
};
|
||||
|
||||
static const char* DIGIT_TABLE[] = {
|
||||
"CTRL_PS", " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", ".", "CTRL_UL", "CTRL_US"
|
||||
};
|
||||
|
||||
static int TotalBitsInLayer(int layers, bool compact)
|
||||
{
|
||||
return ((compact ? 88 : 112) + 16 * layers) * layers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the array of bits from an Aztec Code matrix
|
||||
*
|
||||
* @return the array of bits
|
||||
*/
|
||||
static BitArray ExtractBits(const DetectorResult& ddata)
|
||||
{
|
||||
bool compact = ddata.isCompact();
|
||||
int layers = ddata.nbLayers();
|
||||
int baseMatrixSize = (compact ? 11 : 14) + layers * 4; // not including alignment lines
|
||||
std::vector<int> map(baseMatrixSize, 0);
|
||||
|
||||
if (compact) {
|
||||
std::iota(map.begin(), map.end(), 0);
|
||||
} else {
|
||||
int matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
|
||||
int origCenter = baseMatrixSize / 2;
|
||||
int center = matrixSize / 2;
|
||||
for (int i = 0; i < origCenter; i++) {
|
||||
int newOffset = i + i / 15;
|
||||
map[origCenter - i - 1] = center - newOffset - 1;
|
||||
map[origCenter + i] = center + newOffset + 1;
|
||||
}
|
||||
}
|
||||
auto& matrix = ddata.bits();
|
||||
BitArray rawbits(TotalBitsInLayer(layers, compact));
|
||||
for (int i = 0, rowOffset = 0; i < layers; i++) {
|
||||
int rowSize = (layers - i) * 4 + (compact ? 9 : 12);
|
||||
// The top-left most point of this layer is <low, low> (not including alignment lines)
|
||||
int low = i * 2;
|
||||
// The bottom-right most point of this layer is <high, high> (not including alignment lines)
|
||||
int high = baseMatrixSize - 1 - low;
|
||||
// We pull bits from the two 2 x rowSize columns and two rowSize x 2 rows
|
||||
for (int j = 0; j < rowSize; j++) {
|
||||
int colOffset = j * 2;
|
||||
for (int k = 0; k < 2; k++) {
|
||||
// left column
|
||||
rawbits.set(rowOffset + 0 * rowSize + colOffset + k, matrix.get(map[low + k], map[low + j]));
|
||||
// bottom row
|
||||
rawbits.set(rowOffset + 2 * rowSize + colOffset + k, matrix.get(map[low + j], map[high - k]));
|
||||
// right column
|
||||
rawbits.set(rowOffset + 4 * rowSize + colOffset + k, matrix.get(map[high - k], map[high - j]));
|
||||
// top row
|
||||
rawbits.set(rowOffset + 6 * rowSize + colOffset + k, matrix.get(map[high - j], map[low + k]));
|
||||
}
|
||||
}
|
||||
rowOffset += rowSize * 8;
|
||||
}
|
||||
return rawbits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Performs RS error correction on an array of bits.
|
||||
*/
|
||||
static std::tuple<BitArray, int, double> CorrectBits(const DetectorResult& ddata, const BitArray& rawbits)
|
||||
{
|
||||
int codewordSize = 0;
|
||||
|
||||
if (ddata.nbLayers() <= 2) {
|
||||
codewordSize = 6;
|
||||
} else if (ddata.nbLayers() <= 8) {
|
||||
codewordSize = 8;
|
||||
} else if (ddata.nbLayers() <= 22) {
|
||||
codewordSize = 10;
|
||||
} else {
|
||||
codewordSize = 12;
|
||||
}
|
||||
|
||||
int numCodewords = Size(rawbits) / codewordSize;
|
||||
int numDataCodewords = ddata.nbDatablocks();
|
||||
int numECCodewords = numCodewords - numDataCodewords;
|
||||
|
||||
if (numCodewords < numDataCodewords)
|
||||
throw FormatError("Invalid number of code words");
|
||||
|
||||
auto dataWords = ToInts<int>(rawbits, codewordSize, numCodewords, Size(rawbits) % codewordSize);
|
||||
auto uec = ReedSolomonDecode(GF2nAztec(codewordSize), dataWords, numECCodewords);
|
||||
|
||||
if (!uec)
|
||||
throw ChecksumError();
|
||||
|
||||
// drop the ECCodewords from the dataWords array
|
||||
dataWords.resize(numDataCodewords);
|
||||
|
||||
// Now perform the unstuffing operation.
|
||||
BitArray correctedBits;
|
||||
// correctedBits.reserve(numDataCodewords * codewordSize - stuffedBits);
|
||||
for (int dataWord : dataWords) {
|
||||
if (dataWord == 0 || dataWord == (1 << codewordSize) - 1)
|
||||
return {};
|
||||
else if (dataWord == 1) // next codewordSize-1 bits are all zeros or all ones
|
||||
correctedBits.appendBits(0, codewordSize - 1);
|
||||
else if (dataWord == (1 << codewordSize) - 2)
|
||||
correctedBits.appendBits(0xffffffff, codewordSize - 1);
|
||||
else
|
||||
correctedBits.appendBits(dataWord, codewordSize);
|
||||
}
|
||||
|
||||
return {std::move(correctedBits), numECCodewords * 100 / numCodewords, *uec};
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the table corresponding to the char passed
|
||||
*/
|
||||
static Table GetTable(char t)
|
||||
{
|
||||
switch (t) {
|
||||
case 'L': return Table::LOWER;
|
||||
case 'P': return Table::PUNCT;
|
||||
case 'M': return Table::MIXED;
|
||||
case 'D': return Table::DIGIT;
|
||||
case 'B': return Table::BINARY;
|
||||
case 'U':
|
||||
default: return Table::UPPER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the character (or string) corresponding to the passed code in the given table
|
||||
*
|
||||
* @param table the table used
|
||||
* @param code the code of the character
|
||||
*/
|
||||
static const char* GetCharacter(Table table, int code)
|
||||
{
|
||||
switch (table) {
|
||||
case Table::UPPER: return UPPER_TABLE[code];
|
||||
case Table::LOWER: return LOWER_TABLE[code];
|
||||
case Table::MIXED: return MIXED_TABLE[code];
|
||||
case Table::PUNCT: return PUNCT_TABLE[code];
|
||||
case Table::DIGIT: return DIGIT_TABLE[code];
|
||||
case Table::BINARY: return nullptr; // should not happen
|
||||
}
|
||||
// silence gcc warning/error (this code can not be reached)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* See ISO/IEC 24778:2008 Section 10.1
|
||||
*/
|
||||
static ECI ParseECIValue(BitArrayView& bits, const int flg)
|
||||
{
|
||||
int eci = 0;
|
||||
for (int i = 0; i < flg; i++)
|
||||
eci = 10 * eci + bits.readBits(4) - 2;
|
||||
return ECI(eci);
|
||||
}
|
||||
|
||||
/**
|
||||
* See ISO/IEC 24778:2008 Section 8
|
||||
*/
|
||||
static StructuredAppendInfo ParseStructuredAppend(Content& res)
|
||||
{
|
||||
std::string text(res.bytes.begin(), res.bytes.end());
|
||||
StructuredAppendInfo sai;
|
||||
std::string::size_type i = 0;
|
||||
|
||||
if (text[0] == ' ') { // Space-delimited id
|
||||
std::string::size_type sp = text.find(' ', 1);
|
||||
if (sp == std::string::npos)
|
||||
return {};
|
||||
|
||||
sai.id = text.substr(1, sp - 1); // Strip space delimiters
|
||||
i = sp + 1;
|
||||
}
|
||||
if (i + 1 >= text.size() || !IsUpper(text[i]) || !IsUpper(text[i + 1]))
|
||||
return {};
|
||||
|
||||
sai.index = text[i] - 'A';
|
||||
sai.count = text[i + 1] - 'A' + 1;
|
||||
|
||||
if (sai.count == 1 || sai.count <= sai.index) // If info doesn't make sense
|
||||
sai.count = 0; // Choose to mark count as unknown
|
||||
|
||||
res.erase(0, narrow_cast<int>(i + 2)); // Remove
|
||||
|
||||
return sai;
|
||||
}
|
||||
|
||||
static void DecodeContent(const BitArray& bits, Content& res, bool &haveFNC1)
|
||||
{
|
||||
Table latchTable = Table::UPPER; // table most recently latched to
|
||||
Table shiftTable = Table::UPPER; // table to use for the next read
|
||||
|
||||
auto remBits = BitArrayView(bits);
|
||||
haveFNC1 = false;
|
||||
|
||||
while (remBits.size() >= (shiftTable == Table::DIGIT ? 4 : 5)) { // see ISO/IEC 24778:2008 7.3.1.2 regarding padding bits
|
||||
if (shiftTable == Table::BINARY) {
|
||||
if (remBits.size() <= 6) // padding bits
|
||||
break;
|
||||
int length = remBits.readBits(5);
|
||||
if (length == 0)
|
||||
length = remBits.readBits(11) + 31;
|
||||
for (int i = 0; i < length; i++)
|
||||
res.push_back(remBits.readBits(8));
|
||||
// Go back to whatever mode we had been in
|
||||
shiftTable = latchTable;
|
||||
} else {
|
||||
int size = shiftTable == Table::DIGIT ? 4 : 5;
|
||||
int code = remBits.readBits(size);
|
||||
const char* str = GetCharacter(shiftTable, code);
|
||||
if (std::strncmp(str, "CTRL_", 5) == 0) {
|
||||
// Table changes
|
||||
// ISO/IEC 24778:2008 prescribes ending a shift sequence in the mode from which it was invoked.
|
||||
// That's including when that mode is a shift.
|
||||
// Our test case dlusbs.png for issue #642 exercises that.
|
||||
latchTable = shiftTable; // Latch the current mode, so as to return to Upper after U/S B/S
|
||||
shiftTable = GetTable(str[5]);
|
||||
if (str[6] == 'L')
|
||||
latchTable = shiftTable;
|
||||
} else if (std::strcmp(str, "FLGN") == 0) {
|
||||
int flg = remBits.readBits(3);
|
||||
if (flg == 0) { // FNC1
|
||||
haveFNC1 = true;
|
||||
res.push_back(29); // May be removed at end if first/second FNC1
|
||||
} else if (flg <= 6) {
|
||||
// FLG(1) to FLG(6) ECI
|
||||
res.switchEncoding(ParseECIValue(remBits, flg));
|
||||
} else {
|
||||
// FLG(7) is invalid
|
||||
}
|
||||
shiftTable = latchTable;
|
||||
} else {
|
||||
res.append(str);
|
||||
// Go back to whatever mode we had been in
|
||||
shiftTable = latchTable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ZXING_EXPORT_TEST_ONLY
|
||||
DecoderResult Decode(const BitArray& bits)
|
||||
{
|
||||
Content res;
|
||||
res.symbology = {'z', '0', 3};
|
||||
bool haveFNC1;
|
||||
|
||||
try {
|
||||
DecodeContent(bits, res, haveFNC1);
|
||||
} catch (std::out_of_range&) { // see BitArrayView::readBits
|
||||
return FormatError("Truncated bit stream");
|
||||
}
|
||||
|
||||
if (res.bytes.empty())
|
||||
return FormatError("Empty symbol content");
|
||||
|
||||
// Check for Structured Append - need 4 5-bit words, beginning with ML UL, ending with index and count
|
||||
bool haveStructuredAppend = Size(bits) > 20 && ToInt(bits, 0, 5) == 29 // latch to MIXED (from UPPER)
|
||||
&& ToInt(bits, 5, 5) == 29; // latch back to UPPER (from MIXED)
|
||||
|
||||
StructuredAppendInfo sai = haveStructuredAppend ? ParseStructuredAppend(res) : StructuredAppendInfo();
|
||||
|
||||
if (res.bytes.empty())
|
||||
return FormatError("Empty symbol content after removing Structured Append info");
|
||||
|
||||
if (haveFNC1) {
|
||||
if (res.bytes[0] == 29) {
|
||||
res.symbology.modifier = '1'; // GS1
|
||||
res.symbology.aiFlag = AIFlag::GS1;
|
||||
res.erase(0, 1); // Remove FNC1
|
||||
} else if (res.bytes.size() > 1 && IsAlpha(res.bytes[0]) && res.bytes[1] == 29) {
|
||||
// FNC1 following single upper/lowercase letter (the AIM Application Indicator)
|
||||
res.symbology.modifier = '2'; // AIM
|
||||
res.symbology.aiFlag = AIFlag::AIM;
|
||||
res.erase(1, 1); // Remove FNC1,
|
||||
// The AIM Application Indicator character "A"-"Z"/"a"-"z" is left in the stream (ISO/IEC 24778:2008 16.2)
|
||||
} else if (res.bytes.size() > 2 && IsDigit(res.bytes[0]) && IsDigit(res.bytes[1]) && res.bytes[2] == 29) {
|
||||
// FNC1 following 2 digits (the AIM Application Indicator)
|
||||
res.symbology.modifier = '2'; // AIM
|
||||
res.symbology.aiFlag = AIFlag::AIM;
|
||||
res.erase(2, 1); // Remove FNC1
|
||||
// The AIM Application Indicator characters "00"-"99" are left in the stream (ISO/IEC 24778:2008 16.2)
|
||||
}
|
||||
}
|
||||
|
||||
if (sai.index != -1)
|
||||
res.symbology.modifier += 6; // TODO: this is wrong as long as we remove the sai info from the content in ParseStructuredAppend
|
||||
|
||||
return DecoderResult(std::move(res)).setStructuredAppend(sai);
|
||||
}
|
||||
|
||||
DecoderResult DecodeRune(const DetectorResult& detectorResult) {
|
||||
Content res;
|
||||
res.symbology = {'z', 'C', 0}; // Runes cannot have ECI
|
||||
|
||||
// Bizarrely, this is what it says to do in the spec
|
||||
auto runeString = ToString(detectorResult.runeValue(), 3);
|
||||
res.append(runeString);
|
||||
|
||||
return DecoderResult(std::move(res));
|
||||
}
|
||||
|
||||
DecoderResult Decode(const DetectorResult& detectorResult)
|
||||
{
|
||||
try {
|
||||
if (detectorResult.nbLayers() == 0) {
|
||||
// This is a rune - just return the rune value
|
||||
return DecodeRune(detectorResult);
|
||||
}
|
||||
auto [bits, ecLevel, uec] = CorrectBits(detectorResult, ExtractBits(detectorResult));
|
||||
return Decode(bits).setEcLevel(std::to_string(ecLevel) + "%").addExtra(BarcodeExtra::UEC, uec, -1.0);
|
||||
} catch (Error e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ZXing::Aztec
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class DecoderResult;
|
||||
|
||||
namespace Aztec {
|
||||
|
||||
class DetectorResult;
|
||||
|
||||
DecoderResult Decode(const DetectorResult& detectorResult);
|
||||
|
||||
} // Aztec
|
||||
} // ZXing
|
||||
@@ -0,0 +1,508 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "AZDetector.h"
|
||||
|
||||
#include "AZDetectorResult.h"
|
||||
#include "BitArray.h"
|
||||
#include "BitMatrix.h"
|
||||
#include "BitMatrixCursor.h"
|
||||
#include "ConcentricFinder.h"
|
||||
#include "GridSampler.h"
|
||||
#include "LocalGrid.h"
|
||||
#include "LogMatrix.h"
|
||||
#include "Pattern.h"
|
||||
#include "ReedSolomon.h"
|
||||
#include "ZXAlgorithms.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <vector>
|
||||
|
||||
#ifndef PRINT_DEBUG
|
||||
#define printf(...){}
|
||||
#endif
|
||||
|
||||
namespace ZXing::Aztec {
|
||||
|
||||
static bool IsAztecCenterPattern(const PatternView& view)
|
||||
{
|
||||
// find min/max of all subsequent black/white pairs and check that they 'close together'
|
||||
auto m = view[0] + view[1];
|
||||
auto M = m;
|
||||
for (int i = 1; i < Size(view) - 1; ++i) {
|
||||
int v = view[i] + view[i + 1];
|
||||
UpdateMinMax(m, M, v);
|
||||
}
|
||||
return M <= m * 4 / 3 + 1 && view[-1] >= view[Size(view) / 2] - 2 && view[Size(view)] >= view[Size(view) / 2] - 2;
|
||||
};
|
||||
|
||||
// specialized version of FindLeftGuard to find the '1,1,1,1,1,1,1' pattern of a compact Aztec center pattern
|
||||
static PatternView FindAztecCenterPattern(const PatternView& view)
|
||||
{
|
||||
constexpr int minSize = 8; // Aztec runes
|
||||
auto window = view.subView(0, 7);
|
||||
for (auto end = view.end() - minSize; window.data() < end; window.skipPair())
|
||||
if (IsAztecCenterPattern(window))
|
||||
return window;
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
static int CheckSymmetricAztecCenterPattern(BitMatrixCursorI& cur, int range, bool updatePosition)
|
||||
{
|
||||
range *= 2; // tilted symbols may have a larger vertical than horizontal range
|
||||
|
||||
FastEdgeToEdgeCounter curFwd(cur), curBwd(cur.turnedBack());
|
||||
|
||||
int centerFwd = curFwd.stepToNextEdge(range / 7);
|
||||
if (!centerFwd)
|
||||
return 0;
|
||||
int centerBwd = curBwd.stepToNextEdge(range / 7);
|
||||
if (!centerBwd)
|
||||
return 0;
|
||||
int center = centerFwd + centerBwd - 1; // -1 because the starting pixel is counted twice
|
||||
if (center > range / 7 || center < range / (4 * 7))
|
||||
return 0;
|
||||
|
||||
int spread = center;
|
||||
int m = 0;
|
||||
int M = 0;
|
||||
for (auto c : {&curFwd, &curBwd}) {
|
||||
int lastS = center;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
int s = c->stepToNextEdge(range - spread);
|
||||
if (s == 0)
|
||||
return 0;
|
||||
int v = s + lastS;
|
||||
if (m == 0)
|
||||
m = M = v;
|
||||
else
|
||||
UpdateMinMax(m, M, v);
|
||||
if (M > m * 4 / 3 + 1)
|
||||
return 0;
|
||||
spread += s;
|
||||
lastS = s;
|
||||
}
|
||||
}
|
||||
|
||||
if (updatePosition)
|
||||
cur.step(centerFwd - centerBwd);
|
||||
|
||||
return spread;
|
||||
}
|
||||
|
||||
static std::optional<ConcentricPattern> LocateAztecCenter(const BitMatrix& image, PointF center, int spreadH)
|
||||
{
|
||||
auto cur = BitMatrixCursor(image, PointI(center), {});
|
||||
int minSpread = spreadH, maxSpread = 0;
|
||||
for (auto d : {PointI{0, 1}, {1, 0}, {1, 1}, {1, -1}}) {
|
||||
int spread = CheckSymmetricAztecCenterPattern(cur.setDirection(d), spreadH, d.x == 0);
|
||||
if (!spread)
|
||||
return {};
|
||||
UpdateMinMax(minSpread, maxSpread, spread);
|
||||
}
|
||||
|
||||
return ConcentricPattern{centered(cur.p), (maxSpread + minSpread) / 2};
|
||||
}
|
||||
|
||||
static std::vector<ConcentricPattern> FindPureFinderPattern(const BitMatrix& image)
|
||||
{
|
||||
int left, top, width, height;
|
||||
if (!image.findBoundingBox(left, top, width, height, 11)) { // 11 is the size of an Aztec Rune, see ISO/IEC 24778:2008(E) Annex A
|
||||
// Runes 68 and 223 have none of their bits set on the bottom row
|
||||
if (image.findBoundingBox(left, top, width, height, 10) && (width == 11) && (height == 10))
|
||||
height = 11;
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
// Symbols can have a blank row or column at the edge (in particular, top and left)
|
||||
if (width < height && image.width() >= height) {
|
||||
left = std::max(0, left - (height - width + 1) / 2); // No real net effect if right edge blank
|
||||
width = height;
|
||||
} else if (height < width && image.height() >= width) {
|
||||
top = std::max(0, top - (width - height + 1) / 2); // No real net effect if bottom edge blank
|
||||
height = width;
|
||||
}
|
||||
|
||||
PointF p(left + width / 2, top + height / 2);
|
||||
constexpr auto PATTERN = FixedPattern<7, 7>{1, 1, 1, 1, 1, 1, 1};
|
||||
if (auto pattern = LocateConcentricPattern(image, PATTERN, p, width))
|
||||
return {*pattern};
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::vector<ConcentricPattern> FindFinderPatterns(const BitMatrix& image, bool tryHarder)
|
||||
{
|
||||
std::vector<ConcentricPattern> res;
|
||||
|
||||
[[maybe_unused]] int N = 0;
|
||||
|
||||
#if 0 // reference algorithm for finding aztec center candidates
|
||||
constexpr auto PATTERN = FixedPattern<7, 7>{1, 1, 1, 1, 1, 1, 1};
|
||||
auto l0 = image.row(0);
|
||||
std::vector<uint8_t> line(l0.begin(), l0.end());
|
||||
const int width = image.width();
|
||||
|
||||
int skip = tryHarder ? 1 : std::clamp(image.height() / 100, 1, 3);
|
||||
int margin = skip;
|
||||
for (int y = margin; y < image.height() - margin; y += skip) {
|
||||
auto lc = image.row(y).begin() + 1;
|
||||
auto lp = image.row(y - skip).begin() + 1;
|
||||
line.front() = image.get(0, y);
|
||||
// update line and swipe right
|
||||
for (int x = 1; x < image.width(); ++x) {
|
||||
line[x] += *lc++ != *lp++;
|
||||
while (line[x] > line[x - 1] + 1)
|
||||
line[x] -= 2;
|
||||
}
|
||||
// swipe left
|
||||
line.back() = image.get(width - 1, y);
|
||||
for (int x = width - 2; x > 0; --x)
|
||||
while (line[x] > line[x + 1] + 1)
|
||||
line[x] -= 2;
|
||||
|
||||
int first = 0, last = 0;
|
||||
for (int x = 1; x < width - 5; ++x) {
|
||||
if (line[x] > line[x - 1] && line[x] >= 5 && line[x] % 2 == 1)
|
||||
first = x;
|
||||
else
|
||||
continue;
|
||||
while (line[x] == line[x + 1])
|
||||
++x;
|
||||
last = x;
|
||||
if (line[last + 1] < line[last]) {
|
||||
auto p = centered(PointI((first + last) / 2, y));
|
||||
// make sure p is not 'inside' an already found pattern area
|
||||
if (FindIf(res, [p](const auto& old) { return distance(p, old) < old.size / 2; }) == res.end()) {
|
||||
++N;
|
||||
auto pattern = LocateConcentricPattern(image, PATTERN, p, image.width() / 3);
|
||||
if (pattern){
|
||||
log(*pattern, 2);
|
||||
res.push_back(*pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else // own algorithm based on PatternRow processing (between 0% and 100% faster than reference algo depending on input)
|
||||
int skip = tryHarder ? 1 : std::clamp(image.height() / 2 / 100, 1, 5);
|
||||
int margin = tryHarder ? 5 : image.height() / 4;
|
||||
|
||||
PatternRow row;
|
||||
|
||||
for (int y = margin; y < image.height() - margin; y += skip)
|
||||
{
|
||||
GetPatternRow(image, y, row, false);
|
||||
PatternView next = row;
|
||||
next.shift(1); // the center pattern we are looking for starts with white and is 7 wide (compact code)
|
||||
|
||||
#if 1
|
||||
while (next = FindAztecCenterPattern(next), next.isValid()) {
|
||||
#else
|
||||
constexpr auto PATTERN = FixedPattern<7, 7>{1, 1, 1, 1, 1, 1, 1};
|
||||
while (next = FindLeftGuard(next, 0, PATTERN, 0.5), next.isValid()) {
|
||||
#endif
|
||||
PointF p(next.pixelsInFront() + next[0] + next[1] + next[2] + next[3] / 2.0, y + 0.5);
|
||||
|
||||
// make sure p is not 'inside' an already found pattern area
|
||||
bool found = false;
|
||||
for (auto& old : std::ranges::reverse_view(res)) {
|
||||
// search from back to front, stop once we are out of range due to the y-coordinate
|
||||
if (p.y - old.y > old.size / 2)
|
||||
break;
|
||||
if (distance(p, old) < old.size / 2) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
++N;
|
||||
log(p, 1);
|
||||
|
||||
auto pattern = LocateAztecCenter(image, p, next.sum());
|
||||
if (pattern) {
|
||||
log(*pattern, 3);
|
||||
assert(image.get(*pattern));
|
||||
res.push_back(*pattern);
|
||||
}
|
||||
}
|
||||
|
||||
next.skipPair();
|
||||
next.extend();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PRINT_DEBUG
|
||||
printf("\n# checked centers: %d, # found centers: %d\n", N, Size(res));
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
static int FindRotation(uint32_t bits, bool mirror)
|
||||
{
|
||||
const uint32_t mask = mirror ? 0b111'000'001'110 : 0b111'011'100'000;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (std::popcount(mask ^ bits) <= 2) // at most 2 bits may be wrong (24778:2008(E) 14.3.3 says 3 but that is wrong)
|
||||
return i;
|
||||
bits = ((bits << 3) & 0xfff) | ((bits >> 9) & 0b111); // left shift/rotate, see RotatedCorners(Quadrilateral)
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// read 4*3=12 bits from the 4 corners of the finder pattern at radius
|
||||
static uint32_t SampleOrientationBits(const BitMatrix& image, const PerspectiveTransform& mod2Pix, int radius)
|
||||
{
|
||||
uint32_t bits = 0;
|
||||
for (auto d : {PointI{-1, -1}, {1, -1}, {1, 1}, {-1, 1}}) {
|
||||
auto corner = radius * d;
|
||||
auto cornerL = corner + PointI{0, -d.y};
|
||||
auto cornerR = corner + PointI{-d.x, 0};
|
||||
if (d.x != d.y)
|
||||
std::swap(cornerL, cornerR);
|
||||
for (auto ps : {cornerL, corner, cornerR}) {
|
||||
auto p = mod2Pix(PointF(ps));
|
||||
if (!image.isIn(p))
|
||||
return 0;
|
||||
log(p);
|
||||
AppendBit(bits, image.get(p));
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
static int ModeMessage(const BitMatrix& image, const PerspectiveTransform& mod2Pix, int radius, bool& isRune)
|
||||
{
|
||||
const bool compact = radius == 5;
|
||||
isRune = false;
|
||||
|
||||
// read the bits between the corner bits along the 4 edges
|
||||
uint64_t bits = 0;
|
||||
for (auto d : {PointI{-1, -1}, {1, -1}, {1, 1}, {-1, 1}}) {
|
||||
auto corner = radius * d;
|
||||
auto next = (d.x == d.y) ? PointI{-d.x, 0} : PointI{0, -d.y};
|
||||
for (int i = 2; i <= 2 * radius - 2; ++i) {
|
||||
if (!compact && i == 7)
|
||||
continue; // skip the timing pattern
|
||||
auto p = mod2Pix(PointF(corner + i * next));
|
||||
log(p);
|
||||
if (!image.isIn(p))
|
||||
return -1;
|
||||
AppendBit(bits, image.get(p));
|
||||
}
|
||||
}
|
||||
|
||||
// error correct bits
|
||||
int numCodewords = compact ? 7 : 10;
|
||||
int numDataCodewords = compact ? 2 : 4;
|
||||
int numECCodewords = numCodewords - numDataCodewords;
|
||||
|
||||
std::vector<int> words(numCodewords);
|
||||
for (int i = numCodewords - 1; i >= 0; --i) {
|
||||
words[i] = narrow_cast<int>(bits & 0xF);
|
||||
bits >>= 4;
|
||||
}
|
||||
|
||||
auto decodeResult = ReedSolomonDecode(GF2nAztec(4), words, numECCodewords);
|
||||
|
||||
if ((!decodeResult) && compact) {
|
||||
// Is this a Rune?
|
||||
for (auto& word : words)
|
||||
word ^= 0b1010;
|
||||
|
||||
decodeResult = ReedSolomonDecode(GF2nAztec(4), words, numECCodewords);
|
||||
|
||||
if (decodeResult)
|
||||
isRune = true;
|
||||
}
|
||||
|
||||
if (!decodeResult)
|
||||
return -1;
|
||||
|
||||
int res = 0;
|
||||
for (int i = 0; i < numDataCodewords; i++)
|
||||
res = (res << 4) + words[i];
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void ExtractParameters(int modeMessage, bool compact, int& nbLayers, int& nbDataBlocks, bool& readerInit)
|
||||
{
|
||||
readerInit = false;
|
||||
if (compact) {
|
||||
// 8 bits: 2 bits layers and 6 bits data blocks
|
||||
nbLayers = (modeMessage >> 6) + 1;
|
||||
if (nbLayers == 1 && (modeMessage & 0x20)) { // ISO/IEC 24778:2008 Section 9 MSB artificially set
|
||||
readerInit = true;
|
||||
modeMessage &= ~0x20;
|
||||
}
|
||||
nbDataBlocks = (modeMessage & 0x3F) + 1;
|
||||
} else {
|
||||
// 16 bits: 5 bits layers and 11 bits data blocks
|
||||
nbLayers = (modeMessage >> 11) + 1;
|
||||
if (nbLayers <= 22 && (modeMessage & 0x400)) { // ISO/IEC 24778:2008 Section 9 MSB artificially set
|
||||
readerInit = true;
|
||||
modeMessage &= ~0x400;
|
||||
}
|
||||
nbDataBlocks = (modeMessage & 0x7FF) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
DetectorResult Detect(const BitMatrix& image, bool isPure, bool tryHarder, bool standard, bool runes)
|
||||
{
|
||||
return FirstOrDefault(Detect(image, isPure, tryHarder, 1, standard, runes));
|
||||
}
|
||||
|
||||
DetectorResults Detect(const BitMatrix& image, bool isPure, bool tryHarder, int maxSymbols, bool standard, bool runes)
|
||||
{
|
||||
#ifdef PRINT_DEBUG
|
||||
LogMatrixWriter lmw(log, image, 5, "az-log.pnm");
|
||||
#endif
|
||||
|
||||
DetectorResults res;
|
||||
auto fps = isPure ? FindPureFinderPattern(image) : FindFinderPatterns(image, tryHarder);
|
||||
for (const auto& fp : fps) {
|
||||
auto fpQuad = FindConcentricPatternCorners(image, fp, fp.size, 3);
|
||||
if (!fpQuad)
|
||||
continue;
|
||||
|
||||
auto srcQuad = CenteredSquare(7);
|
||||
auto mod2Pix = PerspectiveTransform(srcQuad, *fpQuad);
|
||||
if (!mod2Pix.isValid())
|
||||
continue;
|
||||
|
||||
int radius; // 5 or 7 (compact vs. full)
|
||||
int mirror; // 0 or 1
|
||||
int rotate; // [0..3]
|
||||
int modeMessage = -1;
|
||||
bool isRune = false;
|
||||
|
||||
auto parseModeMessage = [&image, &radius, &mirror, &rotate, &modeMessage, &isRune](QuadrilateralF srcQuad, QuadrilateralF fpQuad) {
|
||||
// 24778:2008(E) 14.3.3 reads:
|
||||
// In the outer layer of the Core Symbol, the 12 orientation bits at the corners are bitwise compared against the specified
|
||||
// pattern in each of four possible orientations and their four mirror inverse orientations as well. If in any of the 8
|
||||
// cases checked as many as 9 of the 12 bits correctly match, that is deemed to be the correct orientation, otherwise
|
||||
// decoding fails.
|
||||
// Unfortunately, this seems to be wrong: there are 12-bit patterns in those 8 cases that differ only in 4 bits like
|
||||
// 011'100'000'111 (rot90 && !mirror) and 111'000'001'110 (rot0 && mirror), meaning if two of those are wrong, both cases
|
||||
// have a hamming distance of 2, meaning only 1 bit errors can be reliable recovered from. The following code therefore
|
||||
// incorporates the complete set of mode message bits to help determine the orientation of the symbol. This is still not
|
||||
// sufficient for the ErrorInModeMessageZero test case in AZDecoderTest.cpp but good enough for the author.
|
||||
for (radius = 5; radius <= 7; radius += 2) {
|
||||
uint32_t bits = SampleOrientationBits(image, PerspectiveTransform(srcQuad, fpQuad), radius);
|
||||
if (bits == 0)
|
||||
continue;
|
||||
for (mirror = 0; mirror <= 1; ++mirror) {
|
||||
rotate = FindRotation(bits, mirror);
|
||||
if (rotate == -1)
|
||||
continue;
|
||||
modeMessage = ModeMessage(image, PerspectiveTransform(srcQuad, RotatedCorners(fpQuad, rotate, mirror)), radius, isRune);
|
||||
if (modeMessage != -1)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
#if 1
|
||||
if (!parseModeMessage(srcQuad, *fpQuad) || radius == 7) {
|
||||
// improve prescision of sample grid by extrapolating from outer square of white pixels (5 edges away from center)
|
||||
if (auto fpQuad5 = FindConcentricPatternCorners(image, fp, fp.size * 5 / 3, 5)) {
|
||||
if (parseModeMessage(CenteredSquare(11), *fpQuad5) && radius == 7) {
|
||||
srcQuad = CenteredSquare(11);
|
||||
fpQuad = fpQuad5;
|
||||
}
|
||||
}
|
||||
if (modeMessage == -1)
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
if (!parseModeMessage(srcQuad, *fpQuad))
|
||||
continue;
|
||||
#endif
|
||||
|
||||
if ((!standard && !isRune) || (!runes && isRune))
|
||||
continue;
|
||||
|
||||
*fpQuad = RotatedCorners(*fpQuad, rotate, mirror);
|
||||
|
||||
int nbLayers = 0;
|
||||
int nbDataBlocks = 0;
|
||||
bool readerInit = false;
|
||||
if (!isRune) {
|
||||
ExtractParameters(modeMessage, radius == 5, nbLayers, nbDataBlocks, readerInit);
|
||||
}
|
||||
|
||||
int dim = radius == 5 ? 4 * nbLayers + 11 : 4 * nbLayers + 2 * ((2 * nbLayers + 6) / 15) + 15;
|
||||
|
||||
auto center = PointF(dim / 2.0, dim / 2.0);
|
||||
srcQuad = Move(srcQuad, center);
|
||||
mod2Pix = PerspectiveTransform{srcQuad, *fpQuad};
|
||||
|
||||
ZXing::DetectorResult bits;
|
||||
#if 1
|
||||
// for symbols with timing patterns, find those starting from the center and successively move outward
|
||||
if (dim >= 35) {
|
||||
int R = (dim / 2) / 16;
|
||||
int firstTimingPattern = dim / 2 - R * 16;
|
||||
|
||||
auto apM = std::vector<int>(); // alignment pattern positions in modules
|
||||
for (int i = firstTimingPattern; i < dim; i += 16)
|
||||
apM.push_back(i);//, printf("apM: %d\n", i);
|
||||
auto apP = Matrix<std::optional<PointF>>(Size(apM), Size(apM)); // found/guessed alignment pattern positions in pixels
|
||||
apP.set(Size(apM) / 2, Size(apM) / 2, mod2Pix(center)); // center point
|
||||
|
||||
for (int r = R-1; r >= 0; --r) {
|
||||
srcQuad = Move(CenteredSquare(32 * (R - r)), center);
|
||||
auto idxs = std::array<PointI, 4>{PointI{-1, -1}, {1, -1}, {1, 1}, {-1, 1}};
|
||||
QuadrilateralF dstQuad;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
auto pi = (R - r) * idxs[i] + Size(apM) / 2 * PointI{1, 1};
|
||||
printf("\nlocate %dx%d\n", pi.x, pi.y);
|
||||
apP.set(pi.x, pi.y, LocalGrid(image, mod2Pix, PointI(srcQuad[i]), {dim, dim}).findTimingPatternCross(true, 4));
|
||||
dstQuad[i] = apP(pi.x, pi.y).value_or(mod2Pix(srcQuad[i]));
|
||||
log(dstQuad[i], 2);
|
||||
}
|
||||
mod2Pix = PerspectiveTransform(srcQuad, dstQuad);
|
||||
}
|
||||
|
||||
#if 1
|
||||
// find the remaining (non-corner) alignment patterns
|
||||
for (int y = 0; y < Size(apM); ++y)
|
||||
for (int x = 0; x < Size(apM); ++x) {
|
||||
if (!apP(x, y)) {
|
||||
printf("\nlocate %dx%d\n", x, y);
|
||||
apP.set(x, y, LocalGrid(image, mod2Pix, {apM[x], apM[y]}, {dim, dim}).findTimingPatternCross(true, 4));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bits = SampleGrid(image, dim, dim, mod2Pix, std::move(apP), apM, apM);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
bits = SampleGrid(image, dim, dim, mod2Pix);
|
||||
|
||||
if (!bits.isValid())
|
||||
continue;
|
||||
|
||||
res.emplace_back(std::move(bits), radius == 5, nbDataBlocks, nbLayers, readerInit, mirror != 0, isRune ? modeMessage : -1);
|
||||
|
||||
if (Size(res) == maxSymbols)
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace ZXing::Aztec
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace ZXing {
|
||||
|
||||
class BitMatrix;
|
||||
|
||||
namespace Aztec {
|
||||
|
||||
class DetectorResult;
|
||||
|
||||
DetectorResult Detect(const BitMatrix& image, bool isPure, bool tryHarder = true, bool standard = true, bool runes = true);
|
||||
|
||||
using DetectorResults = std::vector<DetectorResult>;
|
||||
DetectorResults Detect(const BitMatrix& image, bool isPure, bool tryHarder, int maxSymbols, bool standard = true, bool runes = true);
|
||||
|
||||
} // Aztec
|
||||
} // ZXing
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DetectorResult.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace ZXing::Aztec {
|
||||
|
||||
class DetectorResult : public ZXing::DetectorResult
|
||||
{
|
||||
bool _compact = false;
|
||||
int _nbDatablocks = 0;
|
||||
int _nbLayers = 0;
|
||||
bool _readerInit = false;
|
||||
bool _isMirrored = false;
|
||||
int _runeValue = -1;
|
||||
|
||||
public:
|
||||
DetectorResult() = default;
|
||||
DetectorResult(DetectorResult&&) noexcept = default;
|
||||
DetectorResult& operator=(DetectorResult&&) noexcept = default;
|
||||
DetectorResult(const DetectorResult&) = delete;
|
||||
DetectorResult& operator=(const DetectorResult&) = delete;
|
||||
|
||||
DetectorResult(ZXing::DetectorResult&& result, bool isCompact, int nbDatablocks, int nbLayers, bool readerInit, bool isMirrored, int runeValue)
|
||||
: ZXing::DetectorResult{std::move(result)},
|
||||
_compact(isCompact),
|
||||
_nbDatablocks(nbDatablocks),
|
||||
_nbLayers(nbLayers),
|
||||
_readerInit(readerInit),
|
||||
_isMirrored(isMirrored),
|
||||
_runeValue(runeValue)
|
||||
{}
|
||||
|
||||
bool isCompact() const { return _compact; }
|
||||
int nbDatablocks() const { return _nbDatablocks; }
|
||||
int nbLayers() const { return _nbLayers; }
|
||||
bool readerInit() const { return _readerInit; }
|
||||
bool isMirrored() const { return _isMirrored; }
|
||||
|
||||
// Only meaningful is nbDatablocks == 0
|
||||
int runeValue() const { return _runeValue; }
|
||||
};
|
||||
|
||||
} // namespace ZXing::Aztec
|
||||
Loaded 100 of 1272 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user