Skip to content

Commit

Permalink
zint: add libzint backend for experimental new WriteBarcode API
Browse files Browse the repository at this point in the history
Set cmake option `ZXING_USE_ZINT` to `ON` to enable this. libzint needs
to be from current HEAD (post 2.12 release) to include support for
zint_symbol->memfile. Use `cmake -DBUILD_DEPENDENCIES=GITHUB` to force
download directly from github.
  • Loading branch information
axxel committed Mar 3, 2024
1 parent 3a79c38 commit 0ee68df
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ option (BUILD_UNIT_TESTS "Build the unit tests (don't enable for production buil
option (BUILD_PYTHON_MODULE "Build the python module" OFF)
option (BUILD_C_API "Build the C-API" OFF)
option (BUILD_EXPERIMENTAL_API "Build with experimental API" OFF)
option (ZXING_USE_ZINT "Use libzint for barcode creation/generation" OFF)
set(BUILD_DEPENDENCIES "AUTO" CACHE STRING "Fetch from github or use locally installed (AUTO/GITHUB/LOCAL)")

if (WIN32)
Expand Down Expand Up @@ -47,6 +48,11 @@ if (BUILD_UNIT_TESTS AND (NOT BUILD_WRITERS OR NOT BUILD_READERS))
set (BUILD_READERS ON)
endif()

if (ZXING_USE_ZINT)
set (BUILD_EXPERIMENTAL_API ON)
add_definitions (-DZXING_USE_ZINT)
endif()

if (BUILD_EXPERIMENTAL_API)
set (CMAKE_CXX_STANDARD 20)
add_definitions (-DZXING_BUILD_EXPERIMENTAL_API)
Expand Down
6 changes: 6 additions & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,12 @@ target_compile_features(ZXing PUBLIC cxx_std_17)

target_link_libraries (ZXing PRIVATE Threads::Threads)

if (ZXING_USE_ZINT)
include(../zxing.cmake)
zxing_add_package(zint zint https://github.com/zint/zint.git 11b3c18aed29cf9eb2a31debd66c8fe8a1d77604)
target_link_libraries (ZXing PRIVATE zint)
endif()

add_library(ZXing::ZXing ALIAS ZXing)
# add the old alias as well, to keep old clients compiling
# note: this only affects client code that includes ZXing via sub_directory.
Expand Down
11 changes: 11 additions & 0 deletions core/src/Barcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
#include "DetectorResult.h"
#include "ZXAlgorithms.h"

#ifdef ZXING_USE_ZINT
#include <zint.h>
#endif

#include <cmath>
#include <list>
#include <map>
Expand Down Expand Up @@ -128,6 +132,13 @@ Result& Result::setReaderOptions(const ReaderOptions& opts)
return *this;
}

#ifdef ZXING_USE_ZINT
void Result::zint(std::unique_ptr<zint_symbol>&& z)
{
_zint = std::shared_ptr(std::move(z));
}
#endif

bool Result::operator==(const Result& o) const
{
// handle case where both are MatrixCodes first
Expand Down
8 changes: 8 additions & 0 deletions core/src/Barcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#ifdef ZXING_BUILD_EXPERIMENTAL_API
#include "BitMatrix.h"
#include <memory>
extern "C" struct zint_symbol;
#endif

#include <string>
Expand Down Expand Up @@ -167,6 +168,10 @@ class Result
void symbol(BitMatrix&& bits) { _symbol = std::make_shared<BitMatrix>(std::move(bits)); }
const BitMatrix& symbol() const { return *_symbol; }
#endif
#ifdef ZXING_USE_ZINT
void zint(std::unique_ptr<zint_symbol>&& z);
zint_symbol* zint() const { return _zint.get(); }
#endif

bool operator==(const Result& o) const;

Expand All @@ -186,6 +191,9 @@ class Result
#ifdef ZXING_BUILD_EXPERIMENTAL_API
std::shared_ptr<BitMatrix> _symbol;
#endif
#ifdef ZXING_USE_ZINT
std::shared_ptr<zint_symbol> _zint;
#endif
};

/**
Expand Down
227 changes: 227 additions & 0 deletions core/src/WriteBarcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,18 @@

#include "WriteBarcode.h"

#ifdef ZXING_USE_ZINT
#include <zint.h>

template <>
struct std::default_delete<zint_symbol>
{
void operator()(zint_symbol* p) const noexcept { ZBarcode_Delete(p); }
};

#else
struct zint_symbol {};
#endif

namespace ZXing {

Expand Down Expand Up @@ -71,6 +82,220 @@ WriterOptions& WriterOptions::operator=(WriterOptions&&) = default;

} // namespace ZXing

#ifdef ZXING_USE_ZINT
#include "ECI.h"
#include "ReadBarcode.h"

#include <zint.h>
#include <charconv>

namespace ZXing {

struct BarcodeFormatZXing2Zint
{
BarcodeFormat zxing;
int zint;
};

static constexpr BarcodeFormatZXing2Zint barcodeFormatZXing2Zint[] = {
{BarcodeFormat::Aztec, BARCODE_AZTEC},
{BarcodeFormat::Codabar, BARCODE_CODABAR},
{BarcodeFormat::Code39, BARCODE_CODE39},
{BarcodeFormat::Code93, BARCODE_CODE93},
{BarcodeFormat::Code128, BARCODE_CODE128},
{BarcodeFormat::DataBar, BARCODE_DBAR_OMN},
{BarcodeFormat::DataBarExpanded, BARCODE_DBAR_EXP},
{BarcodeFormat::DataMatrix, BARCODE_DATAMATRIX},
{BarcodeFormat::DXFilmEdge, -1},
{BarcodeFormat::EAN8, BARCODE_EANX},
{BarcodeFormat::EAN13, BARCODE_EANX},
{BarcodeFormat::ITF, BARCODE_ITF14},
{BarcodeFormat::MaxiCode, BARCODE_MAXICODE},
{BarcodeFormat::MicroQRCode, BARCODE_MICROQR},
{BarcodeFormat::PDF417, BARCODE_PDF417},
{BarcodeFormat::QRCode, BARCODE_QRCODE},
{BarcodeFormat::RMQRCode, BARCODE_RMQR},
{BarcodeFormat::UPCA, BARCODE_UPCA},
{BarcodeFormat::UPCE, BARCODE_UPCE},
};

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;
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("Invalid ecLevel: '" + std::string(s) + "'");

auto findClosestECLevel = [](const std::vector<int>& list, int val) {
int mIdx, 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;
};

if (s.back()=='%'){
switch (symbology) {
case BARCODE_QRCODE:
case BARCODE_MICROQR:
case BARCODE_RMQR:
return findClosestECLevel({20, 37, 55, 65}, res);
case BARCODE_AZTEC:
return findClosestECLevel({10, 23, 26, 50}, res);
case BARCODE_PDF417:
// TODO: do something sensible with PDF417?
default:
return -1;
}
}

return res;
};

zint_symbol* CreatorOptions::zint() const
{
auto& zint = d->zint;

if (!zint) {
printf("zint version: %d, sizeof(zint_symbol): %ld\n", ZBarcode_Version(), sizeof(zint_symbol));

zint.reset(ZBarcode_Create());

auto i = FindIf(barcodeFormatZXing2Zint, [zxing = format()](auto& v) { return v.zxing == zxing; });
if (i == std::end(barcodeFormatZXing2Zint))
throw std::invalid_argument("unsupported barcode format: " + ToString(format()));
zint->symbology = i->zint;

zint->scale = 0.5f;

if (!ecLevel().empty())
zint->option_1 = ParseECLevel(zint->symbology, ecLevel());
}

return zint.get();
}

Barcode CreateBarcode(const void* data, int size, int mode, const CreatorOptions& opts)
{
auto zint = opts.zint();

zint->input_mode = mode;
zint->output_options |= OUT_BUFFER_INTERMEDIATE | BARCODE_QUIET_ZONES;

if (mode == DATA_MODE && ZBarcode_Cap(zint->symbology, ZINT_CAP_ECI))
zint->eci = static_cast<int>(ECI::Binary);

int err = ZBarcode_Encode_and_Buffer(zint, (uint8_t*)data, size, 0);

if (err || *zint->errtxt)
printf("Error %d: %s\n", err, zint->errtxt);

printf("symbol size: %dx%d\n", zint->width, zint->rows);

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 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 == '0') * BitMatrix::SET_V; });

auto res = ReadBarcode({buffer.data(), zint->bitmap_width, zint->bitmap_height, ImageFormat::Lum},
ReaderOptions().setFormats(opts.format()).setIsPure(true).setBinarizer(Binarizer::BoolCast));
res.symbol(std::move(bits));
res.zint(std::move(opts.d->zint));

return res;
}

Barcode CreateBarcodeFromText(std::string_view contents, const CreatorOptions& opts)
{
return CreateBarcode(contents.data(), contents.size(), UNICODE_MODE, opts);
}

Barcode CreateBarcodeFromText(std::u8string_view contents, const CreatorOptions& opts)
{
return CreateBarcode(contents.data(), contents.size(), UNICODE_MODE, opts);
}

Barcode CreateBarcodeFromBytes(const void* data, int size, const CreatorOptions& opts)
{
return CreateBarcode(data, size, DATA_MODE, opts);
}

// Writer ========================================================================

static void SetCommonWriterOptions(zint_symbol* zint, const WriterOptions& opts)
{
zint->show_hrt = opts.withHRT();

zint->output_options &= ~OUT_BUFFER_INTERMEDIATE;
zint->output_options |= opts.withQuietZones() ? BARCODE_QUIET_ZONES: BARCODE_NO_QUIET_ZONES;

if (opts.scale())
zint->scale = opts.scale();
else if (opts.sizeHint()) {
int size = std::max(zint->width, zint->rows);
zint->scale = std::max(1, int(float(opts.sizeHint()) / size)) / 2.f;
}
}

std::string WriteBarcodeToSVG(const Barcode& barcode, const WriterOptions& opts)
{
auto zs = barcode.zint();

if (!zs)
return ToSVG(barcode.symbol());

SetCommonWriterOptions(zs, opts);

zs->output_options |= BARCODE_MEMORY_FILE;// | EMBED_VECTOR_FONT;
strcpy(zs->outfile, "null.svg");

int err = ZBarcode_Print(zs, opts.rotate());
if (err || *zs->errtxt)
printf("Error %d: %s\n", err, zs->errtxt);

return std::string(reinterpret_cast<const char*>(zs->memfile), zs->memfile_size);
}

Image WriteBarcodeToImage(const Barcode& barcode, const WriterOptions& opts)
{
auto zs = barcode.zint();

SetCommonWriterOptions(zs, opts);

int err = ZBarcode_Buffer(zs, opts.rotate());
if (err || *zs->errtxt)
printf("Error %d: %s\n", err, zs->errtxt);

printf("symbol size: %dx%d\n", zs->bitmap_width, zs->bitmap_height);
auto iv = Image(zs->bitmap_width, zs->bitmap_height);
auto* src = zs->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;
}

} // ZXing


#else

#include "BitMatrix.h"
#include "MultiFormatWriter.h"
Expand Down Expand Up @@ -139,4 +364,6 @@ Image WriteBarcodeToImage(const Barcode& barcode, [[maybe_unused]] const WriterO

} // namespace ZXing

#endif

#endif // ZXING_BUILD_EXPERIMENTAL_API

0 comments on commit 0ee68df

Please sign in to comment.