You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
683 lines
29 KiB
683 lines
29 KiB
/* |
|
* Copyright 2019 Tim Rae |
|
* Copyright 2021 Antoine Humbert |
|
* Copyright 2021 Axel Waggershauser |
|
*/ |
|
// SPDX-License-Identifier: Apache-2.0 |
|
|
|
#include "ZXingCpp.h" |
|
#include "ZXAlgorithms.h" |
|
|
|
#include <nanobind/make_iterator.h> |
|
#include <nanobind/nanobind.h> |
|
#include <nanobind/ndarray.h> |
|
#include <nanobind/stl/optional.h> |
|
#include <nanobind/stl/string.h> |
|
#include <nanobind/stl/string_view.h> |
|
#include <nanobind/stl/vector.h> |
|
#include <bit> |
|
#include <cstdint> |
|
#include <optional> |
|
#include <vector> |
|
|
|
using namespace ZXing; |
|
namespace nb = nanobind; |
|
using namespace nb::literals; // to bring in the `_a` literal |
|
|
|
static void deprecation_warning(std::string_view msg) |
|
{ |
|
auto warnings = nb::module_::import_("warnings"); |
|
auto builtins = nb::module_::import_("builtins"); |
|
warnings.attr("warn")(msg, builtins.attr("DeprecationWarning")); |
|
} |
|
|
|
std::string ToString(nb::dlpack::dtype dt) |
|
{ |
|
std::string res; |
|
if (dt.code == (uint8_t)nb::dlpack::dtype_code::Bool) { |
|
res = "bool"; |
|
} else { |
|
switch (dt.code) { |
|
case (uint8_t)nb::dlpack::dtype_code::Int: res = "int"; break; |
|
case (uint8_t)nb::dlpack::dtype_code::UInt: res = "uint"; break; |
|
case (uint8_t)nb::dlpack::dtype_code::Float: res = "float"; break; |
|
case (uint8_t)nb::dlpack::dtype_code::Complex: res = "complex"; break; |
|
} |
|
res += std::to_string(dt.bits); |
|
} |
|
return res; |
|
} |
|
|
|
// MARK: - Buffer protocol |
|
|
|
struct ImageViewPyBufferInfo { |
|
Py_ssize_t shape[3]; |
|
Py_ssize_t strides[3]; |
|
}; |
|
|
|
static int ImageView_getbuffer(PyObject* obj, Py_buffer* view, int /*flags*/) |
|
{ |
|
const ImageView& iv = nb::isinstance<ImageView>(nb::handle(obj)) ? nb::cast<const ImageView&>(nb::handle(obj)) |
|
: nb::cast<const Image&>(nb::handle(obj)); |
|
auto* info = new ImageViewPyBufferInfo{{iv.height(), iv.width(), PixStride(iv.format())}, {iv.rowStride(), iv.pixStride(), 1}}; |
|
view->obj = obj; |
|
view->buf = const_cast<uint8_t*>(iv.data()); |
|
view->len = iv.rowStride() * iv.height(); |
|
view->itemsize = sizeof(uint8_t); |
|
view->readonly = true; |
|
view->ndim = iv.format() == ImageFormat::Lum ? 2 : 3; |
|
view->format = const_cast<char*>("B"); |
|
view->shape = info->shape; |
|
view->strides = info->strides; |
|
view->suboffsets = nullptr; |
|
view->internal = info; |
|
|
|
Py_INCREF(obj); |
|
|
|
return 0; |
|
} |
|
|
|
static void ImageView_releasebuffer(PyObject*, Py_buffer* view) |
|
{ |
|
delete static_cast<ImageViewPyBufferInfo*>(view->internal); |
|
} |
|
|
|
static PyType_Slot ImageView_slots[] = { |
|
{Py_bf_getbuffer, (void*)ImageView_getbuffer}, |
|
{Py_bf_releasebuffer, (void*)ImageView_releasebuffer}, |
|
{0, nullptr} |
|
}; |
|
|
|
// MARK: - Reader |
|
|
|
auto read_barcodes_impl(nb::object _image, const BarcodeFormats& formats, bool try_rotate, bool try_downscale, bool try_invert, |
|
TextMode text_mode, Binarizer binarizer, bool is_pure, EanAddOnSymbol ean_add_on_symbol, bool return_errors, |
|
uint8_t max_number_of_symbols = 0xff) |
|
{ |
|
const auto opts = ReaderOptions() |
|
.formats(formats) |
|
.tryRotate(try_rotate) |
|
.tryDownscale(try_downscale) |
|
.tryInvert(try_invert) |
|
.textMode(text_mode) |
|
.binarizer(binarizer) |
|
.isPure(is_pure) |
|
.maxNumberOfSymbols(max_number_of_symbols) |
|
.eanAddOnSymbol(ean_add_on_symbol) |
|
.returnErrors(return_errors); |
|
|
|
if (nb::isinstance<ImageView>(_image)) { |
|
// Disables the GIL during zxing processing (restored automatically upon completion) |
|
nb::gil_scoped_release release; |
|
return ReadBarcodes(nb::cast<ImageView>(_image), opts); |
|
} |
|
|
|
const auto _type = nb::str(_image.type()); |
|
nb::ndarray<nb::ro> arr; |
|
ImageFormat imgfmt = ImageFormat::None; |
|
try { |
|
if (nb::hasattr(_image, "__array_interface__")) { |
|
if (Contains(_type.c_str(), "PIL.")) { |
|
// TODO: make sure we don't leak memory (anymore), see https://github.com/zxing-cpp/zxing-cpp/discussions/762 |
|
// TODO: make sure we don't make unnecessary copies of the data, see https://uploadcare.com/blog/fast-import-of-pillow-images-to-numpy-opencv-arrays/ |
|
_image.attr("load")(); |
|
const auto mode = nb::cast<std::string>(_image.attr("mode")); |
|
if (mode == "L") |
|
imgfmt = ImageFormat::Lum; |
|
else if (mode == "RGB") |
|
imgfmt = ImageFormat::RGB; |
|
else if (mode == "RGBA") |
|
imgfmt = ImageFormat::RGBA; |
|
else { |
|
// Unsupported mode in ImageFormat. Let's do conversion to L mode with PIL. |
|
_image = _image.attr("convert")("L"); |
|
imgfmt = ImageFormat::Lum; |
|
} |
|
} |
|
|
|
auto ai = nb::cast<nb::dict>(_image.attr("__array_interface__")); |
|
auto shape = nb::cast<std::vector<size_t>>(ai["shape"]); |
|
auto typestr = nb::cast<std::string>(ai["typestr"]); |
|
|
|
if (typestr != "|u1") |
|
nb::raise_type_error("Incompatible __array_interface__ data type (%s): expected a uint8_t array (|u1).", |
|
typestr.c_str()); |
|
|
|
if (ai.contains("data")) { |
|
auto adata = ai["data"]; |
|
|
|
if (nb::try_cast(adata, arr)) { |
|
// PIL and our own __array_interface__ passes data as a buffer/bytes object |
|
// PIL's bytes object has wrong dim/shape/strides info |
|
if (arr.ndim() != shape.size()) |
|
arr = |
|
nb::ndarray<nb::ro>{arr.data(), shape.size(), shape.data(), nb::find(arr), nullptr, nb::dtype<uint8_t>()}; |
|
} else if (nb::isinstance<nb::tuple>(adata)) { |
|
auto data_tuple = nb::cast<nb::tuple>(adata); |
|
// numpy data is passed as a tuple |
|
auto strides = std::vector<int64_t>{}; |
|
if (ai.contains("strides") && !ai["strides"].is_none()) |
|
strides = nb::cast<std::vector<int64_t>>(ai["strides"]); |
|
auto data_ptr = reinterpret_cast<const void*>(nb::cast<uintptr_t>(nb::cast<nb::tuple>(adata)[0])); |
|
arr = nb::ndarray<nb::ro>{data_ptr, |
|
shape.size(), |
|
shape.data(), |
|
nb::find(_image), |
|
strides.empty() ? nullptr : strides.data(), |
|
nb::dtype<uint8_t>()}; |
|
} else if (!nb::try_cast(_image, arr)) { |
|
nb::raise_type_error("No way to get data from __array_interface__"); |
|
} |
|
} else { |
|
arr = nb::cast<nb::ndarray<nb::ro>>(_image); |
|
} |
|
} else if (Contains(_type.c_str(), "QtGui.QImage")) { |
|
const std::string format = nb::cast<std::string>(nb::str(_image.attr("format")())); |
|
if (format.ends_with("Format_ARGB32") || format.ends_with("Format_RGB32")) { |
|
if constexpr (std::endian::native == std::endian::little) |
|
imgfmt = ImageFormat::BGRA; |
|
else |
|
imgfmt = ImageFormat::ARGB; |
|
} else if (format.ends_with("Format_RGBA8888")) |
|
imgfmt = ImageFormat::RGBA; |
|
else if (format.ends_with("Format_RGB888")) |
|
imgfmt = ImageFormat::RGB; |
|
else if (format.ends_with("Format_BGR888")) |
|
imgfmt = ImageFormat::BGR; |
|
else if (format.ends_with("Format_Grayscale8")) |
|
imgfmt = ImageFormat::Lum; |
|
else { |
|
_image = _image.attr("convertToFormat")(24); // 24 is Format_Greyscale8 |
|
imgfmt = ImageFormat::Lum; |
|
} |
|
arr = nb::cast<nb::ndarray<nb::ro>>(_image.attr("constBits")()); |
|
arr = nb::ndarray<nb::ro>{arr.data(), |
|
{nb::cast<size_t>(_image.attr("height")()), nb::cast<size_t>(_image.attr("width")()), |
|
static_cast<size_t>(PixStride(imgfmt))}, |
|
nb::find(arr), |
|
{nb::cast<int64_t>(_image.attr("bytesPerLine")()), PixStride(imgfmt), 1}, |
|
nb::dtype<uint8_t>()}; |
|
} else { |
|
arr = nb::cast<nb::ndarray<nb::ro>>(_image); |
|
} |
|
} catch (nb::python_error& e) { |
|
nb::raise_from(e, PyExc_TypeError, "Invalid input: %s does not support the buffer protocol.", _type.c_str()); |
|
} catch (...) { |
|
nb::raise_type_error("Invalid input: %s does not support the buffer protocol.", _type.c_str()); |
|
} |
|
if (arr.dtype() != nb::dtype<uint8_t>()) |
|
nb::raise_type_error("Incompatible buffer format '%s': expected a uint8_t array.", ToString(arr.dtype()).c_str()); |
|
|
|
if (arr.ndim() != 2 && arr.ndim() != 3) |
|
nb::raise_type_error("Incompatible buffer dimension %s (needs to be 2 or 3).", std::to_string(arr.ndim()).c_str()); |
|
|
|
const auto height = narrow_cast<int>(arr.shape(0)); |
|
const auto width = narrow_cast<int>(arr.shape(1)); |
|
const auto channels = arr.ndim() == 2 ? 1 : narrow_cast<int>(arr.shape(2)); |
|
const auto rowStride = narrow_cast<int>(arr.stride(0)); |
|
const auto pixStride = narrow_cast<int>(arr.stride(1)); |
|
|
|
if (imgfmt == ImageFormat::None) { |
|
// Assume grayscale or BGR image depending on channels number |
|
if (channels == 1) |
|
imgfmt = ImageFormat::Lum; |
|
else if (channels == 3) |
|
imgfmt = ImageFormat::BGR; |
|
else |
|
throw nb::value_error(("Unsupported number of channels for buffer: " + std::to_string(channels)).c_str()); |
|
} |
|
|
|
const auto bytes = static_cast<const uint8_t*>(arr.data()); |
|
// Disables the GIL during zxing processing (restored automatically upon completion) |
|
nb::gil_scoped_release release; |
|
return ReadBarcodes({bytes, width, height, imgfmt, rowStride, pixStride}, opts); |
|
} |
|
|
|
std::optional<Barcode> read_barcode(nb::object _image, const BarcodeFormats& formats, bool try_rotate, bool try_downscale, |
|
bool try_invert, TextMode text_mode, Binarizer binarizer, bool is_pure, |
|
EanAddOnSymbol ean_add_on_symbol, bool return_errors) |
|
{ |
|
auto res = read_barcodes_impl(_image, formats, try_rotate, try_downscale, try_invert, text_mode, binarizer, is_pure, |
|
ean_add_on_symbol, return_errors, 1); |
|
return res.empty() ? std::nullopt : std::optional(res.front()); |
|
} |
|
|
|
Barcodes read_barcodes(nb::object _image, const BarcodeFormats& formats, bool try_rotate, bool try_downscale, bool try_invert, |
|
TextMode text_mode, Binarizer binarizer, bool is_pure, EanAddOnSymbol ean_add_on_symbol, bool return_errors) |
|
{ |
|
return read_barcodes_impl(_image, formats, try_rotate, try_downscale, try_invert, text_mode, binarizer, is_pure, ean_add_on_symbol, |
|
return_errors); |
|
} |
|
|
|
// MARK: - Writer |
|
|
|
Barcode create_barcode(nb::object content, BarcodeFormat format, const nb::kwargs& kwargs) |
|
{ |
|
auto cOpts = CreatorOptions(format, nb::cast<std::string>(nb::str(kwargs))); // see https://github.com/pybind/pybind11/issues/5938 |
|
|
|
if (nb::isinstance<nb::str>(content)) |
|
return CreateBarcodeFromText(nb::cast<std::string_view>(content), cOpts); |
|
else if (nb::isinstance<nb::bytes>(content)) { |
|
// TODO: fix CreateBarcodeFromBytes overload |
|
auto content_bytes = nb::cast<nb::bytes>(content); |
|
return CreateBarcodeFromBytes(content_bytes.data(), content_bytes.size(), cOpts); |
|
} else |
|
nb::raise_type_error("Invalid input: only 'str' and 'bytes' supported."); |
|
} |
|
|
|
Image write_barcode_to_image(Barcode barcode, int scale, bool add_hrt, bool add_quiet_zones) |
|
{ |
|
return WriteBarcodeToImage(barcode, WriterOptions().scale(scale).addHRT(add_hrt).addQuietZones(add_quiet_zones)); |
|
} |
|
|
|
std::string write_barcode_to_svg(Barcode barcode, int scale, bool add_hrt, bool add_quiet_zones) |
|
{ |
|
return WriteBarcodeToSVG(barcode, WriterOptions().scale(scale).addHRT(add_hrt).addQuietZones(add_quiet_zones)); |
|
} |
|
|
|
Image write_barcode(BarcodeFormat format, nb::object content, int width, int height, int quiet_zone, int ec_level) |
|
{ |
|
deprecation_warning("write_barcode() is deprecated, use create_barcode() and write_barcode_to_image() instead."); |
|
|
|
#ifdef ZXING_USE_ZINT |
|
ec_level = format & BarcodeFormat::QRCode ? ec_level / 2 : ec_level; // Zint uses 0-4 for QR code EC level |
|
#endif |
|
|
|
nb::kwargs args; |
|
args["ec_level"] = ec_level; |
|
auto barcode = create_barcode(content, format, args); |
|
return write_barcode_to_image(barcode, -std::max(width, height), false, quiet_zone != 0); |
|
} |
|
|
|
// MARK: - Python |
|
|
|
NB_MODULE(zxingcpp, m) |
|
{ |
|
m.doc() = "python bindings for zxing-cpp"; |
|
|
|
// forward declaration of BarcodeFormats to fix BarcodeFormat function header typings |
|
// see https://github.com/zxing-cpp/zxing-cpp/pull/271 |
|
nb::class_<BarcodeFormats> pyBarcodeFormats(m, "BarcodeFormats"); |
|
|
|
// MARK: - Enums |
|
|
|
nb::enum_<BarcodeFormat>(m, "BarcodeFormat", nb::is_arithmetic{}, "Enumeration of zxing supported barcode formats") |
|
#define X(NAME, SYM, VAR, FLAGS, ZINT, ENABLED, HRI) .value(#NAME, BarcodeFormat::NAME) |
|
ZX_BCF_LIST(X) |
|
#undef X |
|
// use upper case 'NONE' because 'None' is a reserved identifier in python |
|
.value("NONE", BarcodeFormat::None) |
|
.value("DataBarExpanded", BarcodeFormat::DataBarExp) // backward compatibility alias |
|
.value("DataBarLimited", BarcodeFormat::DataBarLtd) // backward compatibility alias |
|
.value("LinearCodes", BarcodeFormat::AllLinear) // backward compatibility alias |
|
.value("MatrixCodes", BarcodeFormat::AllMatrix) // backward compatibility alias |
|
.export_values() |
|
// see https://github.com/pybind/pybind11/issues/2221 |
|
.def("__or__", [](BarcodeFormat f1, BarcodeFormat f2) { |
|
deprecation_warning("operator | is deprecated, pass array or tuple instead."); |
|
return BarcodeFormats(f1 | f2); |
|
}) |
|
.def("__str__", [](BarcodeFormat f) { return ToString(f); }) |
|
.def_prop_ro("symbology", [](BarcodeFormat f) { return Symbology(f); }); |
|
pyBarcodeFormats |
|
.def("__repr__", [](const BarcodeFormats& f) { return ToString(f); }) |
|
.def("__eq__", [](const BarcodeFormats& f1, const BarcodeFormats& f2) { return f1 == f2; }) |
|
.def("__or__", |
|
[](const BarcodeFormats& fs, BarcodeFormat f) { |
|
deprecation_warning("operator | is deprecated, pass array or tuple instead."); |
|
auto res = std::vector(fs.begin(), fs.end()); |
|
res.push_back(f); |
|
return BarcodeFormats(std::move(res)); |
|
}) |
|
.def("__len__", [](const BarcodeFormats& fs) { return static_cast<nb::ssize_t>(fs.size()); }) |
|
.def( |
|
"__iter__", [](const BarcodeFormats& fs) { return nb::make_iterator(nb::type<BarcodeFormats>(), "Iterator", fs); }, |
|
nb::keep_alive<0, 1>()) |
|
.def("__getitem__", |
|
[](const BarcodeFormats& fs, nb::ssize_t idx) { |
|
if (idx < 0) |
|
idx += static_cast<nb::ssize_t>(fs.size()); |
|
if (idx < 0 || idx >= static_cast<nb::ssize_t>(fs.size())) |
|
throw nb::index_error("BarcodeFormats index out of range"); |
|
return *(fs.begin() + idx); |
|
}) |
|
.def(nb::init_implicit<BarcodeFormat>()) |
|
.def("__init__", [](BarcodeFormats* self, std::vector<BarcodeFormat> vec) { |
|
new (self) BarcodeFormats(std::move(vec)); |
|
}); |
|
nb::implicitly_convertible<nb::list, BarcodeFormats>(); |
|
nb::implicitly_convertible<nb::tuple, BarcodeFormats>(); |
|
nb::enum_<Binarizer>(m, "Binarizer", "Enumeration of binarizers used before decoding images") |
|
.value("BoolCast", Binarizer::BoolCast) |
|
.value("FixedThreshold", Binarizer::FixedThreshold) |
|
.value("GlobalHistogram", Binarizer::GlobalHistogram) |
|
.value("LocalAverage", Binarizer::LocalAverage) |
|
.export_values(); |
|
nb::enum_<EanAddOnSymbol>(m, "EanAddOnSymbol", "Enumeration of options for EAN-2/5 add-on symbols check") |
|
.value("Ignore", EanAddOnSymbol::Ignore, "Ignore any Add-On symbol during read/scan") |
|
.value("Read", EanAddOnSymbol::Read, "Read EAN-2/EAN-5 Add-On symbol if found") |
|
.value("Require", EanAddOnSymbol::Require, "Require EAN-2/EAN-5 Add-On symbol to be present") |
|
.export_values(); |
|
nb::enum_<ContentType>(m, "ContentType", "Enumeration of content types") |
|
.value("Text", ContentType::Text) |
|
.value("Binary", ContentType::Binary) |
|
.value("Mixed", ContentType::Mixed) |
|
.value("GS1", ContentType::GS1) |
|
.value("ISO15434", ContentType::ISO15434) |
|
.value("UnknownECI", ContentType::UnknownECI) |
|
.export_values(); |
|
nb::enum_<TextMode>(m, "TextMode", "") |
|
.value("Plain", TextMode::Plain, "bytes() transcoded to unicode based on ECI info or guessed charset (the default mode prior to 2.0)") |
|
.value("ECI", TextMode::ECI, "standard content following the ECI protocol with every character set ECI segment transcoded to unicode") |
|
.value("HRI", TextMode::HRI, "Human Readable Interpretation (dependent on the ContentType)") |
|
.value("Escaped", TextMode::Escaped, "Use the EscapeNonGraphical() function (e.g. ASCII 29 will be transcoded to '<GS>'") |
|
.value("Hex", TextMode::Hex, "bytes() transcoded to ASCII string of HEX values") |
|
.value("HexECI", TextMode::HexECI, "bytesECI() transcoded to ASCII string of HEX values") |
|
.export_values(); |
|
nb::enum_<ImageFormat>(m, "ImageFormat", "Enumeration of image formats supported by read_barcodes") |
|
.value("Lum", ImageFormat::Lum) |
|
.value("LumA", ImageFormat::LumA) |
|
.value("RGB", ImageFormat::RGB) |
|
.value("BGR", ImageFormat::BGR) |
|
.value("RGBA", ImageFormat::RGBA) |
|
.value("ARGB", ImageFormat::ARGB) |
|
.value("BGRA", ImageFormat::BGRA) |
|
.value("ABGR", ImageFormat::ABGR) |
|
.export_values(); |
|
|
|
// MARK: - Classes |
|
|
|
nb::class_<PointI>(m, "Point", "Represents the coordinates of a point in an image", nb::is_final{}) |
|
.def_ro("x", &PointI::x, |
|
":return: horizontal coordinate of the point\n" |
|
":rtype: int") |
|
.def_ro("y", &PointI::y, |
|
":return: vertical coordinate of the point\n" |
|
":rtype: int"); |
|
nb::class_<Position>(m, "Position", "The position of a decoded symbol", nb::is_final{}) |
|
.def_prop_ro("top_left", &Position::topLeft, |
|
":return: coordinate of the symbol's top-left corner\n" |
|
":rtype: zxingcpp.Point") |
|
.def_prop_ro("top_right", &Position::topRight, |
|
":return: coordinate of the symbol's top-right corner\n" |
|
":rtype: zxingcpp.Point") |
|
.def_prop_ro("bottom_left", &Position::bottomLeft, |
|
":return: coordinate of the symbol's bottom-left corner\n" |
|
":rtype: zxingcpp.Point") |
|
.def_prop_ro("bottom_right", &Position::bottomRight, |
|
":return: coordinate of the symbol's bottom-right corner\n" |
|
":rtype: zxingcpp.Point") |
|
.def("__str__", [](Position pos) { return ToString(pos); }); |
|
nb::enum_<Error::Type>(m, "ErrorType", "") |
|
.value("None", Error::Type::None, "No error") |
|
.value("Format", Error::Type::Format, "Data format error") |
|
.value("Checksum", Error::Type::Checksum, "Checksum error") |
|
.value("Unsupported", Error::Type::Unsupported, "Unsupported content error") |
|
.export_values(); |
|
nb::class_<Error>(m, "Error", "Barcode reading error", nb::is_final{}) |
|
.def_prop_ro("type", &Error::type, |
|
":return: Error type\n" |
|
":rtype: zxingcpp.ErrorType") |
|
.def_prop_ro("message", &Error::msg, |
|
":return: Error message\n" |
|
":rtype: str") |
|
.def("__str__", [](Error e) { return ToString(e); }); |
|
nb::class_<Barcode>(m, "Barcode", "The Barcode class", nb::dynamic_attr{}, nb::is_final{}) |
|
.def_prop_ro("valid", &Barcode::isValid, |
|
":return: whether or not barcode is valid (i.e. a symbol was found and decoded)\n" |
|
":rtype: bool") |
|
.def_prop_ro("text", [](const Barcode& res) { return res.text(); }, |
|
":return: text of the decoded symbol (see also TextMode parameter)\n" |
|
":rtype: str") |
|
.def_prop_ro("bytes", [](const Barcode& res) { return nb::bytes(res.bytes().data(), res.bytes().size()); }, |
|
":return: uninterpreted bytes of the decoded symbol\n" |
|
":rtype: bytes") |
|
.def_prop_ro("format", &Barcode::format, |
|
":return: decoded symbol format\n" |
|
":rtype: zxingcpp.BarcodeFormat") |
|
.def_prop_ro("symbology", &Barcode::symbology, |
|
":return: decoded symbol symbology\n" |
|
":rtype: zxingcpp.BarcodeFormat") |
|
.def_prop_ro("symbology_identifier", &Barcode::symbologyIdentifier, |
|
":return: decoded symbology idendifier\n" |
|
":rtype: str") |
|
.def_prop_ro("ec_level", &Barcode::ecLevel, |
|
":return: error correction level of the symbol (empty string if not applicable)\n" |
|
":rtype: str") |
|
.def_prop_ro("content_type", &Barcode::contentType, |
|
":return: content type of symbol\n" |
|
":rtype: zxingcpp.ContentType") |
|
.def_prop_ro("position", &Barcode::position, |
|
":return: position of the decoded symbol\n" |
|
":rtype: zxingcpp.Position") |
|
.def_prop_ro("orientation", &Barcode::orientation, |
|
":return: orientation (in degree) of the decoded symbol\n" |
|
":rtype: int") |
|
.def_prop_ro( |
|
"error", [](const Barcode& res) { return res.error() ? std::optional(res.error()) : std::nullopt; }, |
|
":return: Error code or None\n" |
|
":rtype: zxingcpp.Error") |
|
.def_prop_ro( |
|
"extra", [](nb::object self) -> nb::object { |
|
if (nb::hasattr(self, "_cached_extra")) |
|
return self.attr("_cached_extra"); |
|
const auto extra = nb::cast<const Barcode&>(self).extra(); |
|
if (extra.empty()) { |
|
self.attr("_cached_extra") = nb::none(); |
|
} else { |
|
try { |
|
auto json = nb::module_::import_("json"); |
|
auto parsed = json.attr("loads")(extra); |
|
self.attr("_cached_extra") = parsed; |
|
} catch (nb::python_error& e) { |
|
throw nb::value_error((std::string("Invalid JSON in Barcode::extra(): ") + e.what()).c_str()); |
|
} |
|
} |
|
return self.attr("_cached_extra"); |
|
}, |
|
":return: Symbology specific extra information as a Python dictionary (might be empty)\n" |
|
":rtype: dict") |
|
.def("to_image", &write_barcode_to_image, |
|
nb::arg("scale") = 1, |
|
nb::arg("add_hrt") = false, |
|
nb::arg("add_quiet_zones") = true) |
|
.def("to_svg", &write_barcode_to_svg, |
|
nb::arg("scale") = 1, |
|
nb::arg("add_hrt") = false, |
|
nb::arg("add_quiet_zones") = true) |
|
; |
|
m.attr("Result") = m.attr("Barcode"); // alias to deprecated name for the Barcode class |
|
|
|
// MARK: - Functions |
|
|
|
m.def("barcode_format_from_str", &BarcodeFormatFromString, |
|
nb::arg("str"), |
|
"Convert string to BarcodeFormat\n\n" |
|
":type str: str\n" |
|
":param str: string representing barcode format\n" |
|
":return: corresponding barcode format\n" |
|
":rtype: zxingcpp.BarcodeFormat"); |
|
m.def("barcode_formats_from_str", &BarcodeFormatsFromString, |
|
nb::arg("str"), |
|
"Convert string to BarcodeFormats\n\n" |
|
":type str: str\n" |
|
":param str: string representing a list of barcodes formats\n" |
|
":return: corresponding barcode formats\n" |
|
":rtype: zxingcpp.BarcodeFormats"); |
|
m.def("barcode_formats_list", &BarcodeFormats::list, |
|
nb::arg("filter") = BarcodeFormats{}, |
|
"Returns a list of available/supported barcode formats, optionally filtered by the provided format(s).\n\n" |
|
":type filter: zxingcpp.BarcodeFormats\n" |
|
":param filter: the BarcodeFormat(s) to filter by\n" |
|
":return: list of available/supported barcode formats (optionally filtered)\n" |
|
":rtype: list[zxingcpp.BarcodeFormat]"); |
|
m.def("read_barcode", &read_barcode, |
|
nb::arg("image"), |
|
nb::arg("formats") = BarcodeFormats{}, |
|
nb::arg("try_rotate") = true, |
|
nb::arg("try_downscale") = true, |
|
nb::arg("try_invert") = true, |
|
nb::arg("text_mode") = TextMode::HRI, |
|
nb::arg("binarizer") = Binarizer::LocalAverage, |
|
nb::arg("is_pure") = false, |
|
nb::arg("ean_add_on_symbol") = EanAddOnSymbol::Ignore, |
|
nb::arg("return_errors") = false, |
|
"Read (decode) a barcode from a numpy BGR or grayscale image array or from a PIL image.\n\n" |
|
":type image: buffer|numpy.ndarray|PIL.Image.Image\n" |
|
":param image: The image object to decode. The image can be either:\n" |
|
" - a buffer with the correct shape, use .cast on memory view to convert\n" |
|
" - a numpy array containing image either in grayscale (1 byte per pixel) or BGR mode (3 bytes per pixel)\n" |
|
" - a PIL Image\n" |
|
" - a QtGui.QImage\n" |
|
" - a zxingcpp.ImageView object, which is effectively a memory view but with custom strides and ImageFormat\n" |
|
":type formats: zxing.BarcodeFormat|zxing.BarcodeFormats\n" |
|
":param formats: the format(s) to decode. If ``None``, decode all formats.\n" |
|
":type try_rotate: bool\n" |
|
":param try_rotate: if ``True`` (the default), decoder searches for barcodes in any direction; \n" |
|
" if ``False``, it will not search for 90° / 270° rotated barcodes.\n" |
|
":type try_downscale: bool\n" |
|
":param try_downscale: if ``True`` (the default), decoder also scans downscaled versions of the input; \n" |
|
" if ``False``, it will only search in the resolution provided.\n" |
|
":type try_invert: bool\n" |
|
":param try_invert: if ``True`` (the default), decoder also tries inverted (light on dark) barcodes.\n" |
|
":type text_mode: zxing.TextMode\n" |
|
":param text_mode: specifies the TextMode that governs how the raw bytes content is transcoded to text.\n" |
|
" Defaults to :py:attr:`zxing.TextMode.HRI`." |
|
":type binarizer: zxing.Binarizer\n" |
|
":param binarizer: the binarizer used to convert image before decoding barcodes.\n" |
|
" Defaults to :py:attr:`zxing.Binarizer.LocalAverage`." |
|
":type is_pure: bool\n" |
|
":param is_pure: Set to True if the input contains nothing but a perfectly aligned barcode (generated image).\n" |
|
" Speeds up detection in that case. Default is False." |
|
":type ean_add_on_symbol: zxing.EanAddOnSymbol\n" |
|
":param ean_add_on_symbol: Specify whether to Ignore, Read or Require EAN-2/5 add-on symbols while scanning \n" |
|
" EAN/UPC codes. Default is ``Ignore``.\n" |
|
":type return_errors: bool\n" |
|
":param return_errors: Set to True to return the barcodes with errors as well (e.g. checksum errors); see ``Barcode.error``.\n" |
|
" Default is False." |
|
":rtype: zxingcpp.Barcode\n" |
|
":return: a Barcode if found, None otherwise" |
|
); |
|
m.def("read_barcodes", &read_barcodes, |
|
nb::arg("image"), |
|
nb::arg("formats") = BarcodeFormats{}, |
|
nb::arg("try_rotate") = true, |
|
nb::arg("try_downscale") = true, |
|
nb::arg("try_invert") = true, |
|
nb::arg("text_mode") = TextMode::HRI, |
|
nb::arg("binarizer") = Binarizer::LocalAverage, |
|
nb::arg("is_pure") = false, |
|
nb::arg("ean_add_on_symbol") = EanAddOnSymbol::Ignore, |
|
nb::arg("return_errors") = false, |
|
"Read (decode) multiple barcodes from a numpy BGR or grayscale image array or from a PIL image.\n\n" |
|
":type image: buffer|numpy.ndarray|PIL.Image.Image\n" |
|
":param image: The image object to decode. The image can be either:\n" |
|
" - a buffer with the correct shape, use .cast on memoryview to convert\n" |
|
" - a numpy array containing image either in grayscale (1 byte per pixel) or BGR mode (3 bytes per pixel)\n" |
|
" - a PIL Image\n" |
|
" - a QtGui.QImage\n" |
|
" - a zxingcpp.ImageView object, which is effectively a memory view but with custom strides and ImageFormat\n" |
|
":type formats: zxing.BarcodeFormat|zxing.BarcodeFormats\n" |
|
":param formats: the format(s) to decode. If ``None``, decode all formats.\n" |
|
":type try_rotate: bool\n" |
|
":param try_rotate: if ``True`` (the default), decoder searches for barcodes in any direction; \n" |
|
" if ``False``, it will not search for 90° / 270° rotated barcodes.\n" |
|
":type try_downscale: bool\n" |
|
":param try_downscale: if ``True`` (the default), decoder also scans downscaled versions of the input; \n" |
|
" if ``False``, it will only search in the resolution provided.\n" |
|
":type try_invert: bool\n" |
|
":param try_invert: if ``True`` (the default), decoder also tries inverted (light on dark) barcodes.\n" |
|
":type text_mode: zxing.TextMode\n" |
|
":param text_mode: specifies the TextMode that governs how the raw bytes content is transcoded to text.\n" |
|
" Defaults to :py:attr:`zxing.TextMode.HRI`." |
|
":type binarizer: zxing.Binarizer\n" |
|
":param binarizer: the binarizer used to convert image before decoding barcodes.\n" |
|
" Defaults to :py:attr:`zxing.Binarizer.LocalAverage`." |
|
":type is_pure: bool\n" |
|
":param is_pure: Set to True if the input contains nothing but a perfectly aligned barcode (generated image).\n" |
|
" Speeds up detection in that case. Default is False." |
|
":type ean_add_on_symbol: zxing.EanAddOnSymbol\n" |
|
":param ean_add_on_symbol: Specify whether to Ignore, Read or Require EAN-2/5 add-on symbols while scanning \n" |
|
" EAN/UPC codes. Default is ``Ignore``.\n" |
|
":type return_errors: bool\n" |
|
":param return_errors: Set to True to return the barcodes with errors as well (e.g. checksum errors); see ``Barcode.error``.\n" |
|
" Default is False.\n" |
|
":rtype: list[zxingcpp.Barcode]\n" |
|
":return: a list of Barcodes, the list is empty if none is found" |
|
); |
|
|
|
nb::class_<Image>(m, "Image", nb::type_slots(ImageView_slots), nb::is_final{}) |
|
.def_prop_ro("__array_interface__", [](const Image& image) { |
|
auto d = nb::dict(); |
|
d["version"] = 3; |
|
d["data"] = ℑ |
|
d["shape"] = nb::make_tuple(image.height(), image.width()); |
|
d["typestr"] = "|u1"; |
|
return d; |
|
}) |
|
.def_prop_ro("shape", [](const Image& m) { return nb::make_tuple(m.height(), m.width()); }) |
|
; |
|
m.def("create_barcode", &create_barcode, |
|
nb::arg("content"), |
|
nb::arg("format"), |
|
nb::arg("**kwargs") |
|
); |
|
|
|
m.def("write_barcode_to_image", &write_barcode_to_image, |
|
nb::arg("barcode"), |
|
nb::arg("scale") = 1, |
|
nb::arg("add_hrt") = false, |
|
nb::arg("add_quiet_zones") = true |
|
); |
|
|
|
m.def("write_barcode_to_svg", &write_barcode_to_svg, |
|
nb::arg("barcode"), |
|
nb::arg("scale") = 1, |
|
nb::arg("add_hrt") = false, |
|
nb::arg("add_quiet_zones") = true |
|
); |
|
|
|
nb::class_<ImageView>(m, "ImageView", nb::type_slots(ImageView_slots), nb::is_final{}) |
|
.def("__init__", |
|
[](ImageView* self, nb::ndarray<nb::ro> buffer, int width, int height, ImageFormat format, int rowStride, int pixStride) { |
|
if (buffer.dtype() != nb::dtype<uint8_t>()) |
|
nb::raise_type_error("Incompatible buffer format '%s': expected a uint8_t array.", ToString(buffer.dtype()).c_str()); |
|
|
|
new (self) ImageView(static_cast<const uint8_t*>(buffer.data()), buffer.size(), width, height, format, rowStride, pixStride); |
|
}, |
|
nb::arg("buffer"), |
|
nb::arg("width"), |
|
nb::arg("height"), |
|
nb::arg("format"), |
|
nb::arg("row_stride") = 0, |
|
nb::arg("pix_stride") = 0) |
|
.def_prop_ro("format", [](const ImageView& iv) { return iv.format(); }) |
|
; |
|
|
|
m.attr("Bitmap") = m.attr("Image"); // alias to deprecated name for the Image class |
|
m.def("write_barcode", &write_barcode, |
|
nb::arg("format"), |
|
nb::arg("text"), |
|
nb::arg("width") = 0, |
|
nb::arg("height") = 0, |
|
nb::arg("quiet_zone") = -1, |
|
nb::arg("ec_level") = -1, |
|
"Write (encode) a text into a barcode and return 8-bit grayscale bitmap buffer\n\n" |
|
":type format: zxing.BarcodeFormat\n" |
|
":param format: format of the barcode to create\n" |
|
":type text: str|bytes\n" |
|
":param text: the text/content of the barcode. A str is encoded as utf8 text and bytes as binary data\n" |
|
":type width: int\n" |
|
":param width: width (in pixels) of the barcode to create. If undefined (or set to 0), barcode will be\n" |
|
" created with the minimum possible width\n" |
|
":type height: int\n" |
|
":param height: height (in pixels) of the barcode to create. If undefined (or set to 0), barcode will be\n" |
|
" created with the minimum possible height\n" |
|
":type quiet_zone: int\n" |
|
":param quiet_zone: minimum size (in pixels) of the quiet zone around barcode. If undefined (or set to -1), \n" |
|
" the minimum quiet zone of respective barcode is used." |
|
":type ec_level: int\n" |
|
":param ec_level: error correction level of the barcode (Used for Aztec, PDF417, and QRCode only).\n" |
|
":rtype: zxingcpp.Bitmap\n" |
|
); |
|
}
|
|
|