Skip to content
2 changes: 1 addition & 1 deletion pylibdmtx/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Read and write Data Matrix barcodes from Python 2 and 3."""

__version__ = '0.1.10'
__version__ = '0.2.0'
15 changes: 13 additions & 2 deletions pylibdmtx/dmtx_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
"""
import platform
import sys
import os
import re

from ctypes import cdll
from ctypes.util import find_library
from pathlib import Path

__all__ = ['load']
_lib_name = 'dmtx'


def _windows_fname():
Expand All @@ -16,7 +19,7 @@ def _windows_fname():

This logic has its own function to make testing easier
"""
return 'libdmtx-64.dll' if sys.maxsize > 2**32 else 'libdmtx-32.dll'
return f'lib{_lib_name}-64.dll' if sys.maxsize > 2**32 else f'lib{_lib_name}-32.dll'


def load():
Expand All @@ -42,7 +45,15 @@ def load():
)
else:
# Assume a shared library on the path
path = find_library('dmtx')
path = find_library(_lib_name)
if not path:
# Search on local folder
_base_path = Path(__file__).parent.absolute()
# linux do put 'lib' as name prefix and can put the version after the extension (e.g. libdmtx.so.0.1.0 )
_lib_search = [f for f in os.listdir(_base_path) if re.search(rf'[a-z]*{_lib_name}[a_z]*.so[.0-9]*', f)]
if _lib_search:
path = _base_path.joinpath(_lib_search[0])

if not path:
raise ImportError('Unable to find dmtx shared library')
libdmtx = cdll.LoadLibrary(path)
Expand Down
18 changes: 13 additions & 5 deletions pylibdmtx/pylibdmtx.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
dmtxDecodeDestroy, dmtxRegionDestroy, dmtxMessageDestroy, dmtxTimeAdd,
dmtxTimeNow, dmtxDecodeMatrixRegion, dmtxRegionFindNext,
dmtxMatrix3VMultiplyBy, dmtxDecodeSetProp, DmtxPackOrder, DmtxProperty,
DmtxUndefined, DmtxVector2, EXTERNAL_DEPENDENCIES,
DmtxUndefined, DmtxVector2, EXTERNAL_DEPENDENCIES, DmtxFalse, DmtxTrue,
DmtxSymbolSize, DmtxScheme, dmtxEncodeSetProp, dmtxEncodeDataMatrix,
dmtxImageGetProp, dmtxEncodeCreate, dmtxEncodeDestroy
dmtxImageGetProp, dmtxEncodeCreate, dmtxEncodeDestroy, dmtxHasReaderProgramming
)

__all__ = [
'decode', 'encode', 'Encoded', 'ENCODING_SCHEME_NAMES',
'decode', 'encode', 'Encoded', 'ENCODING_SCHEME_NAMES', 'DmtxFalse', 'DmtxTrue',
'ENCODING_SIZE_NAMES', 'EXTERNAL_DEPENDENCIES',
]

Expand Down Expand Up @@ -313,7 +313,7 @@ def _encoder():
dmtxEncodeDestroy(byref(encoder))


def encode(data, scheme=None, size=None):
def encode(data, scheme=None, size=None, reader_programming=DmtxFalse):
"""
Encodes `data` in a DataMatrix image.

Expand Down Expand Up @@ -360,7 +360,15 @@ def encode(data, scheme=None, size=None):
dmtxEncodeSetProp(encoder, DmtxProperty.DmtxPropScheme, scheme)
dmtxEncodeSetProp(encoder, DmtxProperty.DmtxPropSizeRequest, size)

if dmtxEncodeDataMatrix(encoder, len(data), cast(data, c_ubyte_p)) == 0:
if reader_programming == DmtxTrue and dmtxHasReaderProgramming():
dmtxEncodeSetProp(encoder, DmtxProperty.DmtxPropFnc1, 0xF1)

if dmtxHasReaderProgramming():
_ret = dmtxEncodeDataMatrix(encoder, len(data), cast(data, c_ubyte_p), reader_programming)
else:
_ret = dmtxEncodeDataMatrix(encoder, len(data), cast(data, c_ubyte_p))

if _ret == 0:
raise PyLibDMTXError(
'Could not encode data, possibly because the image is not '
'large enough to contain the data'
Expand Down
11 changes: 9 additions & 2 deletions pylibdmtx/scripts/write_datamatrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import pylibdmtx
from pylibdmtx.pylibdmtx import (
encode, ENCODING_SIZE_NAMES, ENCODING_SCHEME_NAMES
encode, ENCODING_SIZE_NAMES, ENCODING_SCHEME_NAMES, DmtxFalse, DmtxTrue
)


Expand All @@ -31,6 +31,9 @@ def main(args=None):
help="Encoding method; default is 'Ascii'",
choices=ENCODING_SCHEME_NAMES
)
parser.add_argument(
'-r', '--reader-programming', action='store_true'
)
parser.add_argument(
'-v', '--version', action='version',
version='%(prog)s ' + pylibdmtx.__version__
Expand All @@ -39,8 +42,12 @@ def main(args=None):

from PIL import Image

_rp = DmtxFalse
if args.reader_programming:
_rp = DmtxTrue

encoded = encode(
args.data.encode('utf-8'), size=args.size, scheme=args.scheme
args.data.encode('utf-8'), size=args.size, scheme=args.scheme, reader_programming=_rp
)
im = Image.frombytes('RGB', (encoded.width, encoded.height), encoded.pixels)
im.save(args.file)
Expand Down
Binary file added pylibdmtx/tests/reader_programming.bmp
Binary file not shown.
84 changes: 84 additions & 0 deletions pylibdmtx/tests/test_reader_programming.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
import sys
import tempfile
import unittest

from PIL import Image
from PIL import ImageChops
from pathlib import Path
from contextlib import contextmanager

# TODO Would io.StringIO not work in all cases?
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO

from pylibdmtx.scripts.read_datamatrix import main as main_read
from pylibdmtx.scripts.write_datamatrix import main as main_write
from pylibdmtx.wrapper import dmtxVersion, dmtxHasReaderProgramming


@contextmanager
def capture_stdout():
sys.stdout, old_stdout = StringIO(), sys.stdout
try:
yield sys.stdout
finally:
sys.stdout = old_stdout


class TestReaderProgramming(unittest.TestCase):

def test_libdmtx_version(self):
"""Check feature availability on minimum version and if it was compiled."""
self.assertGreaterEqual(dmtxVersion(), '1.0.0', 'Feature not present on older library version.')
self.assertEqual(dmtxHasReaderProgramming(), True, 'Feature not built on loaded library.')

def test_read_datamatrix_reader_programming(self):
"""Read datamatrix reader programming barcodes."""
with capture_stdout() as stdout:
main_read([str(Path(__file__).parent.joinpath('reader_programming.bmp'))])

if 2 == sys.version_info[0]:
expected = "$P\\r"
else:
expected = "b'$P\\r'"

self.assertEqual(expected, stdout.getvalue().strip())

def test_write_reader_programming_datamatrix(self):
"""Create reader programming datamatrix and read it."""
tmpfile = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
tmpfile.close()
try:
main_write(['--reader-programming', '--size', '16x16', tmpfile.name, '$P\r'])
with capture_stdout() as stdout:
main_read([tmpfile.name])

expected = (
"$P\\r" if 2 == sys.version_info[0] else "b'$P\\r'"
)
self.assertEqual(expected, stdout.getvalue().strip())
finally:
os.unlink(tmpfile.name)

def test_compare_reader_programming_images(self):
"""Compare output image with existing correct one."""
tmpfile = tempfile.NamedTemporaryFile(suffix='.bmp', delete=False)
tmpfile.close()
try:
main_write(['--reader-programming', '--size', '16x16', tmpfile.name, '$P\r'])

_expected_image = Image.open(Path(__file__).parent.joinpath('reader_programming.bmp'))
_created_image = Image.open(tmpfile.name)

diff = ImageChops.difference(_expected_image, _created_image)

self.assertEqual(diff.getbbox(), None)
finally:
os.unlink(tmpfile.name)


if __name__ == '__main__':
unittest.main()
56 changes: 48 additions & 8 deletions pylibdmtx/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
'dmtxDecodeCreate', 'dmtxDecodeDestroy', 'dmtxRegionDestroy',
'dmtxMessageDestroy', 'dmtxTimeAdd', 'dmtxMatrix3VMultiplyBy',
'dmtxDecodeSetProp', 'DmtxPackOrder', 'DmtxProperty', 'dmtxTimeNow',
'dmtxDecodeMatrixRegion', 'dmtxRegionFindNext'
'dmtxDecodeMatrixRegion', 'dmtxRegionFindNext', 'dmtxVersion',
'dmtxHasReaderProgramming'
]

# Globals populated in load_libdmtx
Expand All @@ -29,6 +30,8 @@
"""


_reader_programming_min_version = '1.0.0'

def load_libdmtx():
"""Loads the libdmtx shared library.

Expand Down Expand Up @@ -66,6 +69,9 @@ def libdmtx_function(fname, restype, *args):

# Defines and enums
DmtxUndefined = -1
DmtxTrue = 1
DmtxFalse = 0


# Define this function early so that it can be used in the definitions below.
_dmtxVersion = libdmtx_function('dmtxVersion', c_char_p)
Expand All @@ -80,6 +86,27 @@ def dmtxVersion():
return _dmtxVersion().decode()


if LooseVersion(dmtxVersion()) < LooseVersion(_reader_programming_min_version):
def dmtxHasReaderProgramming() -> bool:
"""Returns False, feature not present on older verion.

Returns:
bool: Feature not enabled
"""
return False
else:
_dmtxHasReaderProgramming = libdmtx_function('dmtxHasReaderProgramming', c_uint)

def dmtxHasReaderProgramming() -> bool:
"""Returns true if Reader Programming feature was configured at build time.

Returns:
bool: Feature enabled or not
"""
_ret_val = True if _dmtxHasReaderProgramming() == 1 else False
return _ret_val


@unique
class DmtxProperty(IntEnum):
DmtxPropScheme = 100
Expand Down Expand Up @@ -193,6 +220,7 @@ class DmtxSymbolSize(IntEnum):
# Types
DmtxPassFail = c_uint
DmtxMatrix3 = c_double * 3 * 3
DmtxBoolean = c_uint


# Structs
Expand Down Expand Up @@ -543,10 +571,22 @@ class DmtxEncode(Structure):
c_int # value
)

dmtxEncodeDataMatrix = libdmtx_function(
'dmtxEncodeDataMatrix',
DmtxPassFail,
POINTER(DmtxEncode),
c_int,
POINTER(c_ubyte)
)
if dmtxHasReaderProgramming():
dmtxEncodeDataMatrix = libdmtx_function(
'dmtxEncodeDataMatrix',
DmtxPassFail,
POINTER(DmtxEncode),
c_int,
POINTER(c_ubyte),
DmtxBoolean
)
else:
dmtxEncodeDataMatrix = libdmtx_function(
'dmtxEncodeDataMatrix',
DmtxPassFail,
POINTER(DmtxEncode),
c_int,
POINTER(c_ubyte)
)