15
15
#include " BitMatrix.h"
16
16
#include " MultiFormatWriter.h"
17
17
18
- #include < pybind11/numpy.h>
19
18
#include < pybind11/pybind11.h>
20
19
#include < pybind11/stl.h>
21
20
#include < optional>
22
21
#include < memory>
23
22
#include < vector>
23
+ #include < sstream>
24
+ #include < functional>
25
+ #include < list>
24
26
25
27
using namespace ZXing ;
26
28
namespace py = pybind11;
27
29
28
- // Numpy array wrapper class for images (either BGR or GRAYSCALE)
29
- using Image = py::array_t <uint8_t , py::array::c_style>;
30
-
31
30
std::ostream& operator <<(std::ostream& os, const Position& points) {
32
31
for (const auto & p : points)
33
32
os << p.x << " x" << p.y << " " ;
@@ -49,47 +48,91 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t
49
48
.setMaxNumberOfSymbols (max_number_of_symbols)
50
49
.setEanAddOnSymbol (ean_add_on_symbol);
51
50
const auto _type = std::string (py::str (py::type::of (_image)));
52
- Image image ;
51
+ py::buffer buffer ;
53
52
ImageFormat imgfmt = ImageFormat::None;
54
53
try {
55
- if (_type.find (" PIL." ) != std::string::npos) {
56
- _image.attr (" load" )();
57
- const auto mode = _image.attr (" mode" ).cast <std::string>();
58
- if (mode == " L" )
59
- imgfmt = ImageFormat::Lum;
60
- else if (mode == " RGB" )
61
- imgfmt = ImageFormat::RGB;
62
- else if (mode == " RGBA" )
63
- imgfmt = ImageFormat::RGBX;
64
- else {
65
- // Unsupported mode in ImageFormat. Let's do conversion to L mode with PIL.
66
- _image = _image.attr (" convert" )(" L" );
67
- imgfmt = ImageFormat::Lum;
54
+ if (py::hasattr (_image, " __array_interface__" )) {
55
+ if (_type.find (" PIL." ) != std::string::npos) {
56
+ _image.attr (" load" )();
57
+ const auto mode = _image.attr (" mode" ).cast <std::string>();
58
+ if (mode == " L" )
59
+ imgfmt = ImageFormat::Lum;
60
+ else if (mode == " RGB" )
61
+ imgfmt = ImageFormat::RGB;
62
+ else if (mode == " RGBA" )
63
+ imgfmt = ImageFormat::RGBX;
64
+ else {
65
+ // Unsupported mode in ImageFormat. Let's do conversion to L mode with PIL.
66
+ _image = _image.attr (" convert" )(" L" );
67
+ imgfmt = ImageFormat::Lum;
68
+ }
69
+ }
70
+
71
+ auto ai = _image.attr (" __array_interface__" ).cast <py::dict>();
72
+ auto ashape = ai[" shape" ].cast <py::tuple>();
73
+
74
+ if (ai.contains (" data" )) {
75
+ auto adata = ai[" data" ];
76
+
77
+ if (py::isinstance<py::tuple>(adata)) {
78
+ auto data_tuple = adata.cast <py::tuple>();
79
+ auto ashape_std = ashape.cast <std::list<py::size_t >>();
80
+ auto data_len = Reduce (ashape_std, py::size_t (1u ), std::multiplies<py::size_t >());
81
+ buffer = py::memoryview::from_memory (reinterpret_cast <void *>(data_tuple[0 ].cast <py::size_t >()), data_len, true );
82
+ } else if (py::isinstance<py::buffer>(adata)) {
83
+ // Numpy and our own __array_interface__ passes data as a buffer/bytes object
84
+ buffer = adata.cast <py::buffer>();
85
+ } else {
86
+ throw py::type_error (" No way to get data from __array_interface__" );
87
+ }
88
+ } else {
89
+ buffer = _image.cast <py::buffer>();
90
+ }
91
+
92
+ py::tuple bshape;
93
+ if (py::hasattr (buffer, " shape" )) {
94
+ bshape = buffer.attr (" shape" ).cast <py::tuple>();
68
95
}
96
+
97
+ if (!ashape.equal (bshape)) {
98
+ auto bufferview = py::memoryview (buffer);
99
+ buffer = bufferview.attr (" cast" )(" B" , ashape).cast <py::buffer>();
100
+ }
101
+ } else {
102
+ buffer = _image.cast <py::buffer>();
69
103
}
70
- image = _image.cast <Image>();
71
104
#if PYBIND11_VERSION_HEX > 0x02080000 // py::raise_from is available starting from 2.8.0
72
105
} catch (py::error_already_set &e) {
73
- py::raise_from (e, PyExc_TypeError, (" Could not convert " + _type + " to numpy array of dtype 'uint8' ." ).c_str ());
106
+ py::raise_from (e, PyExc_TypeError, (" Could not convert " + _type + " to buffer ." ).c_str ());
74
107
throw py::error_already_set ();
75
108
#endif
76
109
} catch (...) {
77
- throw py::type_error (" Could not convert " + _type + " to numpy array . Expecting a PIL Image or numpy array ." );
110
+ throw py::type_error (" Could not convert " + _type + " to buffer . Expecting a PIL Image or buffer ." );
78
111
}
79
- const auto height = narrow_cast<int >(image.shape (0 ));
80
- const auto width = narrow_cast<int >(image.shape (1 ));
81
- const auto channels = image.ndim () == 2 ? 1 : narrow_cast<int >(image.shape (2 ));
112
+
113
+ /* Request a buffer descriptor from Python */
114
+ py::buffer_info info = buffer.request ();
115
+
116
+ if (info.format != py::format_descriptor<uint8_t >::format ())
117
+ throw py::type_error (" Incompatible format: expected a uint8_t array." );
118
+
119
+ if (info.ndim != 2 && info.ndim != 3 )
120
+ throw py::type_error (" Incompatible buffer dimension." );
121
+
122
+ const auto height = narrow_cast<int >(info.shape [0 ]);
123
+ const auto width = narrow_cast<int >(info.shape [1 ]);
124
+ const auto channels = info.ndim == 2 ? 1 : narrow_cast<int >(info.shape [2 ]);
82
125
if (imgfmt == ImageFormat::None) {
83
126
// Assume grayscale or BGR image depending on channels number
84
127
if (channels == 1 )
85
128
imgfmt = ImageFormat::Lum;
86
129
else if (channels == 3 )
87
130
imgfmt = ImageFormat::BGR;
88
131
else
89
- throw py::value_error (" Unsupported number of channels for numpy array : " + std::to_string (channels));
132
+ throw py::value_error (" Unsupported number of channels for buffer : " + std::to_string (channels));
90
133
}
91
134
92
- const auto bytes = image. data ( );
135
+ const auto bytes = static_cast < uint8_t *>(info. ptr );
93
136
// Disables the GIL during zxing processing (restored automatically upon completion)
94
137
py::gil_scoped_release release;
95
138
return ReadBarcodes ({bytes, width, height, imgfmt, width * channels, channels}, hints);
@@ -108,17 +151,60 @@ Results read_barcodes(py::object _image, const BarcodeFormats& formats, bool try
108
151
return read_barcodes_impl (_image, formats, try_rotate, try_downscale, text_mode, binarizer, is_pure, ean_add_on_symbol);
109
152
}
110
153
111
- Image write_barcode (BarcodeFormat format, std::string text, int width, int height, int quiet_zone, int ec_level)
154
+ class WriteResult
155
+ {
156
+ public:
157
+ WriteResult (std::vector<char >&& result_data_, std::vector<py::ssize_t >&& shape_)
158
+ {
159
+ result_data = result_data_;
160
+ shape = shape_;
161
+ ndim = shape.size ();
162
+ }
163
+
164
+ WriteResult (WriteResult const & o) : ndim(o.ndim), shape(o.shape), result_data(o.result_data) {}
165
+
166
+ const std::vector<char >& get_result_data () const { return result_data; }
167
+
168
+ static py::buffer_info get_buffer (const WriteResult& wr)
169
+ {
170
+ return py::buffer_info (
171
+ reinterpret_cast <void *>(const_cast <char *>(wr.result_data .data ())),
172
+ sizeof (char ), " B" , wr.shape .size (), wr.shape , { wr.shape [0 ], py::ssize_t (1 ) }
173
+ );
174
+ }
175
+
176
+ const std::vector<py::ssize_t > get_shape () const { return shape; }
177
+
178
+ private:
179
+ py::ssize_t ndim;
180
+ std::vector<py::ssize_t > shape;
181
+ std::vector<char > result_data;
182
+ };
183
+
184
+ py::object write_barcode (BarcodeFormat format, std::string text, int width, int height, int quiet_zone, int ec_level)
112
185
{
113
186
auto writer = MultiFormatWriter (format).setEncoding (CharacterSet::UTF8).setMargin (quiet_zone).setEccLevel (ec_level);
114
187
auto bitmap = writer.encode (text, width, height);
115
188
116
- auto result = Image ({bitmap.height (), bitmap.width ()});
117
- auto r = result.mutable_unchecked <2 >();
118
- for (py::ssize_t y = 0 ; y < r.shape (0 ); y++)
119
- for (py::ssize_t x = 0 ; x < r.shape (1 ); x++)
120
- r (y, x) = bitmap.get (narrow_cast<int >(x), narrow_cast<int >(y)) ? 0 : 255 ;
121
- return result;
189
+ std::vector<char > result_data (bitmap.width () * bitmap.height ());
190
+ for (py::ssize_t y = 0 ; y < bitmap.height (); y++)
191
+ for (py::ssize_t x = 0 ; x < bitmap.width (); x++)
192
+ result_data[x + (y * bitmap.width ())] = bitmap.get (narrow_cast<int >(x), narrow_cast<int >(y)) ? 0 : 255 ;
193
+
194
+ WriteResult res (std::move (result_data), {bitmap.height (), bitmap.width ()});
195
+
196
+ auto resobj = py::cast (res);
197
+
198
+ // We add an __array_interface__ here to make the returned type easily convertible to PIL images,
199
+ // Numpy arrays and other libraries that spport the interface.
200
+ py::dict array_interface;
201
+ array_interface[" version" ] = py::cast (3 );
202
+ array_interface[" data" ] = resobj;
203
+ array_interface[" shape" ] = res.get_shape ();
204
+ array_interface[" typestr" ] = py::cast (" |u1" );
205
+ resobj.attr (" __array_interface__" ) = array_interface;
206
+
207
+ return resobj;
122
208
}
123
209
124
210
@@ -213,6 +299,10 @@ PYBIND11_MODULE(zxingcpp, m)
213
299
oss << pos;
214
300
return oss.str ();
215
301
});
302
+ py::class_<WriteResult>(m, " WriteResult" , " Result of barcode writing" , py::buffer_protocol (), py::dynamic_attr ())
303
+ .def_property_readonly (" shape" , &WriteResult::get_shape,
304
+ " :rtype: tuple" )
305
+ .def_buffer (&WriteResult::get_buffer);
216
306
py::class_<Result>(m, " Result" , " Result of barcode reading" )
217
307
.def_property_readonly (" valid" , &Result::isValid,
218
308
" :return: whether or not result is valid (i.e. a symbol was found)\n "
@@ -265,8 +355,9 @@ PYBIND11_MODULE(zxingcpp, m)
265
355
py::arg (" is_pure" ) = false ,
266
356
py::arg (" ean_add_on_symbol" ) = EanAddOnSymbol::Ignore,
267
357
" Read (decode) a barcode from a numpy BGR or grayscale image array or from a PIL image.\n\n "
268
- " :type image: numpy.ndarray|PIL.Image.Image\n "
358
+ " :type image: buffer| numpy.ndarray|PIL.Image.Image\n "
269
359
" :param image: The image object to decode. The image can be either:\n "
360
+ " - a buffer with the correct shape, use .cast on memory view to convert\n "
270
361
" - a numpy array containing image either in grayscale (1 byte per pixel) or BGR mode (3 bytes per pixel)\n "
271
362
" - a PIL Image\n "
272
363
" :type formats: zxing.BarcodeFormat|zxing.BarcodeFormats\n "
@@ -302,8 +393,9 @@ PYBIND11_MODULE(zxingcpp, m)
302
393
py::arg (" is_pure" ) = false ,
303
394
py::arg (" ean_add_on_symbol" ) = EanAddOnSymbol::Ignore,
304
395
" Read (decode) multiple barcodes from a numpy BGR or grayscale image array or from a PIL image.\n\n "
305
- " :type image: numpy.ndarray|PIL.Image.Image\n "
396
+ " :type image: buffer| numpy.ndarray|PIL.Image.Image\n "
306
397
" :param image: The image object to decode. The image can be either:\n "
398
+ " - a buffer with the correct shape, use .cast on memory view to convert\n "
307
399
" - a numpy array containing image either in grayscale (1 byte per pixel) or BGR mode (3 bytes per pixel)\n "
308
400
" - a PIL Image\n "
309
401
" :type formats: zxing.BarcodeFormat|zxing.BarcodeFormats\n "
@@ -336,7 +428,7 @@ PYBIND11_MODULE(zxingcpp, m)
336
428
py::arg (" height" ) = 0 ,
337
429
py::arg (" quiet_zone" ) = -1 ,
338
430
py::arg (" ec_level" ) = -1 ,
339
- " Write (encode) a text into a barcode and return numpy ( grayscale) image array \n\n "
431
+ " Write (encode) a text into a barcode and return 8-bit grayscale bitmap buffer \n\n "
340
432
" :type format: zxing.BarcodeFormat\n "
341
433
" :param format: format of the barcode to create\n "
342
434
" :type text: str\n "
@@ -353,5 +445,6 @@ PYBIND11_MODULE(zxingcpp, m)
353
445
" :type ec_level: int\n "
354
446
" :param ec_level: error correction level of the barcode\n "
355
447
" (Used for Aztec, PDF417, and QRCode only)."
448
+ " :rtype: zxing.WriteResult\n "
356
449
);
357
450
}
0 commit comments