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:
evgeny committed 2026-08-07 00:48:27 +03:00
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
+191
View File
@@ -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
+201
View File
@@ -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
+81
View File
@@ -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
+78
View File
@@ -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;
}
}
+79
View File
@@ -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);
}
+165
View File
@@ -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
+253
View File
@@ -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
+58
View File
@@ -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
+343
View File
@@ -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
+16
View File
@@ -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
+154
View File
@@ -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
+66
View File
@@ -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
+125
View File
@@ -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
+427
View File
@@ -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
+173
View File
@@ -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
+112
View File
@@ -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
+32
View File
@@ -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
+276
View File
@@ -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
+26
View File
@@ -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
+460
View File
@@ -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"
+296
View File
@@ -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;
}
```
*/
+606
View File
@@ -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