1# The MIT License (MIT)
2#
3# Copyright (c) 2014-2015 WUSTL ZPLAB
4#
5# Permission is hereby granted, free of charge, to any person obtaining a copy
6# of this software and associated documentation files (the "Software"), to deal
7# in the Software without restriction, including without limitation the rights
8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9# copies of the Software, and to permit persons to whom the Software is
10# furnished to do so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included in all
13# copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21# SOFTWARE.
22#
23# Authors: Zach Pincus
24
25import ctypes
26import numpy
27import sys
28import os.path
29import glob
30import collections
31
32__all__ = ['ZBAR_SYMBOLS', 'ZBAR_CONFIGS', 'Scanner', 'Symbol']
33
34
35def load_zbar():
36    if sys.platform == 'win32':
37        loader = ctypes.windll
38        functype = ctypes.WINFUNCTYPE
39    else:
40        loader = ctypes.cdll
41        functype = ctypes.CFUNCTYPE
42
43    zbar = None
44    errors = []
45    possible_zbar_libs = glob.glob(os.path.join(os.path.dirname(__file__), '_zbar.*'))
46    for lib in possible_zbar_libs:
47        try:
48            zbar = loader.LoadLibrary(lib)
49            break
50        except Exception:
51            # Get exception instance in Python 2.x/3.x compatible manner
52            e_type, e_value, e_tb = sys.exc_info()
53            del e_tb
54            errors.append((lib, e_value))
55
56    if zbar is None:
57        if errors:
58            # No zbar library loaded, and load-errors reported for some
59            # candidate libs
60            err_txt = ['%s:\n%s' % (l, str(e.args[0])) for l, e in errors]
61            raise RuntimeError('One or more zbar libraries were found, but '
62                               'could not be loaded due to the following errors:\n'
63                               '\n\n'.join(err_txt))
64        else:
65            # No errors, because no potential libraries found at all!
66            raise RuntimeError('Could not find a zbar library in ' + __file__)
67
68    return zbar
69
70_ZB = load_zbar()
71
72API = {
73    'zbar_image_scanner_create': (ctypes.c_void_p, ()),
74    'zbar_image_scanner_destroy': (None, (ctypes.c_void_p,)),
75    'zbar_image_scanner_set_config': (None, (ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_int)),
76    'zbar_scan_image': (ctypes.c_int, (ctypes.c_void_p, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p)),
77    'zbar_image_scanner_first_symbol': (ctypes.c_void_p, (ctypes.c_void_p,)),
78    'zbar_symbol_next': (ctypes.c_void_p, (ctypes.c_void_p,)),
79    'zbar_symbol_get_type': (ctypes.c_uint, (ctypes.c_void_p,)),
80    'zbar_get_symbol_name': (ctypes.c_char_p, (ctypes.c_uint,)),
81    'zbar_symbol_get_data': (ctypes.c_void_p, (ctypes.c_void_p,)),
82    'zbar_symbol_get_data_length': (ctypes.c_uint, (ctypes.c_void_p,)),
83    'zbar_symbol_get_quality': (ctypes.c_int, (ctypes.c_void_p,)),
84    'zbar_symbol_get_loc_size': (ctypes.c_uint, (ctypes.c_void_p,)),
85    'zbar_symbol_get_loc_x': (ctypes.c_int, (ctypes.c_void_p, ctypes.c_uint)),
86    'zbar_symbol_get_loc_y': (ctypes.c_int, (ctypes.c_void_p,ctypes.c_uint)),
87    }
88
89def register_api(lib, api):
90    for f, (restype, argtypes) in api.items():
91        func = getattr(lib, f)
92        func.restype = restype
93        func.argtypes = argtypes
94
95register_api(_ZB, API)
96
97ZBAR_SYMBOLS = {
98    'ZBAR_NONE'        :      0, # /**< no symbol decoded */
99    'ZBAR_PARTIAL'     :      1, # /**< intermediate status */
100    'ZBAR_EAN8'        :      8, # /**< EAN-8 */
101    'ZBAR_UPCE'        :      9, # /**< UPC-E */
102    'ZBAR_ISBN10'      :     10, # /**< ISBN-10 (from EAN-13). @since 0.4 */
103    'ZBAR_UPCA'        :     12, # /**< UPC-A */
104    'ZBAR_EAN13'       :     13, # /**< EAN-13 */
105    'ZBAR_ISBN13'      :     14, # /**< ISBN-13 (from EAN-13). @since 0.4 */
106    'ZBAR_I25'         :     25, # /**< Interleaved 2 of 5. @since 0.4 */
107    'ZBAR_CODE39'      :     39, # /**< Code 39. @since 0.4 */
108    'ZBAR_PDF417'      :     57, # /**< PDF417. @since 0.6 */
109    'ZBAR_QRCODE'      :     64, # /**< QR Code. @since 0.10 */
110    'ZBAR_CODE128'     :    128, # /**< Code 128 */
111    'ZBAR_SYMBOL'      : 0x00ff, # /**< mask for base symbol type */
112    'ZBAR_ADDON2'      : 0x0200, # /**< 2-digit add-on flag */
113    'ZBAR_ADDON5'      : 0x0500, # /**< 5-digit add-on flag */
114    'ZBAR_ADDON'       : 0x0700, # /**< add-on flag mask */
115}
116
117ZBAR_CONFIGS = {
118    'ZBAR_CFG_ENABLE':     0,       #/**< enable symbology/feature */
119    'ZBAR_CFG_ADD_CHECK':  1,       #/**< enable check digit when optional */
120    'ZBAR_CFG_EMIT_CHECK': 2,       #/**< return check digit when present */
121    'ZBAR_CFG_ASCII':      3,       #/**< enable full ASCII character set */
122    'ZBAR_CFG_NUM':        4,       #/**< number of boolean decoder configs */
123    'ZBAR_CFG_MIN_LEN':  0x20,      #/**< minimum data length for valid decode */
124    'ZBAR_CFG_MAX_LEN':  0x21,      #/**< maximum data length for valid decode */
125    'ZBAR_CFG_POSITION': 0x80,      #/**< enable scanner to collect position data */
126    'ZBAR_CFG_X_DENSITY':0x100,     #/**< image scanner vertical scan density */
127    'ZBAR_CFG_Y_DENSITY':0x101,     #/**< image scanner horizontal scan density */
128}
129
130
131Symbol = collections.namedtuple('Symbol', ['type', 'data', 'quality', 'position'])
132
133class Scanner(object):
134    def __init__(self, config=None):
135        """Create a barcode-scanner object.
136
137        By default, scanning for all barcode types is enabled, and reporting of
138        their locations is enabled. This can be controlled by the config parameter.
139
140        Parameters:
141            config: None or a list of (symbol_type, config_type, value) triples.
142                * symbol_type must be one of ZBAR_SYMBOLS, which refers to a
143                  class of barcodes. ZBAR_NONE will cause the configuration
144                  option to apply to all barcode types.
145                * config_type must be one of ZBAR_CONFIGS, defined in zbar.h.
146                  Of particular interest are ZBAR_CFG_ENABLE (enable specific
147                  symbol type), ZBAR_CFG_ADD_CHECK (enable check-digit
148                  verification) and ZBAR_CFG_MIN_LEN and ZBAR_CFG_MAX_LEN (only
149                  return decoded barcodes with the specified data length).
150                  NB: Enabling/disabling specific barcode types is complex and
151                  not particularly well supported by zbar (some barcode types
152                  will be scanned-for by default unless disabled; some require
153                  specific enablement; some types like ISBN and UPC that are
154                  subclasses of EAN barcodes require EAN to also be enabled).
155                  Thus is is STRONGLY recommended to use the default config
156                  and filter for barcode types after the fact.
157                * value should be 1 for boolean options, or an integer for the
158                  other options.
159        """
160        self._scanner = _ZB.zbar_image_scanner_create()
161        if config is None:
162            config = [('ZBAR_NONE', 'ZBAR_CFG_ENABLE', 1), ('ZBAR_NONE', 'ZBAR_CFG_POSITION', 1)]
163
164        for symbol_type, config_type, value in config:
165            _ZB.zbar_image_scanner_set_config(self._scanner, ZBAR_SYMBOLS[symbol_type], ZBAR_CONFIGS[config_type], value)
166
167    def __del__(self):
168        _ZB.zbar_image_scanner_destroy(self._scanner)
169        del self._scanner
170
171    def scan(self, image):
172        """Scan an image and return a list of barcodes identified.
173
174        Parameters:
175            image: must be a 2-dimensional numpy array of dtype uint8.
176
177        Returns: list of Symbol namedtuples.
178
179        Each Symbol has 'type', 'data', 'quality', and 'position' attributes.
180            * 'type' refers to the barcode's type (e.g. 'QR-Code')
181            * 'data' is a bytes instance containing the barcode payload
182            * 'quality' is a numerical score
183            * 'position' is either an empty list (if position recording was
184               disabled), or a list of (x, y) indices into the image that define
185               the barcode's location.
186        """
187        image = numpy.asarray(image)
188        if not image.dtype == numpy.uint8 and image.ndim == 2:
189            raise ValueError('Image must be 2D uint8 type')
190        if image.flags.c_contiguous:
191            height, width = image.shape
192        else:
193            image = numpy.asfortranarray(image)
194            width, height = image.shape
195        num_symbols = _ZB.zbar_scan_image(self._scanner, width, height, image.ctypes.data)
196        symbols = []
197        symbol = _ZB.zbar_image_scanner_first_symbol(self._scanner)
198        while(symbol):
199            sym_type = _ZB.zbar_symbol_get_type(symbol)
200            sym_name = _ZB.zbar_get_symbol_name(sym_type).decode('ascii')
201            sym_data_ptr = _ZB.zbar_symbol_get_data(symbol)
202            sym_data_len = _ZB.zbar_symbol_get_data_length(symbol)
203            sym_data = ctypes.string_at(sym_data_ptr, sym_data_len)
204            sym_quality = _ZB.zbar_symbol_get_quality(symbol)
205            sym_loc = []
206            for i in range(_ZB.zbar_symbol_get_loc_size(symbol)):
207                x = _ZB.zbar_symbol_get_loc_x(symbol, i)
208                y = _ZB.zbar_symbol_get_loc_y(symbol, i)
209                sym_loc.append((x, y))
210            symbols.append(Symbol(sym_name, sym_data, sym_quality, sym_loc))
211            symbol = _ZB.zbar_symbol_next(symbol)
212        assert len(symbols) == num_symbols
213        return symbols
214