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

/*
* 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"] = &image;
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"
);
}