Skip to content

Commit

Permalink
Accept PIL.Image instead of filename
Browse files Browse the repository at this point in the history
Ping #21
  • Loading branch information
dlenski committed Nov 21, 2021
1 parent c2c6569 commit 81f55d3
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 7 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ BarCode(raw='This should be QR_CODE', parsed='This should be QR_CODE', path='tes
The attributes of the decoded `BarCode` object are `raw`, `parsed`, `path`, `format`, `type`, and `points`. The list of formats which ZXing can decode is
[here](https://zxing.github.io/zxing/apidocs/com/google/zxing/BarcodeFormat.html).

The `decode()` method accepts an image path (or list of paths) and takes optional parameters `try_harder` (boolean), `possible_formats` (list of formats to consider), and `pure_barcode` (boolean).
The `decode()` method accepts an image path or [PIL Image object](https://pillow.readthedocs.io/en/stable/reference/Image.html) (or list thereof)
and takes optional parameters `try_harder` (boolean), `possible_formats` (list of formats to consider), and `pure_barcode` (boolean).
If no barcode is found, it returns a `False`-y `BarCode` object with all fields except `path` set to `None`.
If it encounters any other recognizable error from the Java ZXing library, it raises `BarCodeReaderException`.

Expand Down
4 changes: 4 additions & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
nose>=1.0

pillow>=3.0,<6.0; python_version < '3.5'
pillow>=3.0,<8.0; python_version >= '3.5' and python_version < '3.6'
pillow>=8.0; python_version >= '3.6'
10 changes: 9 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,21 @@ def download_java_files(force=False):
name='zxing',
version=version_pep,
description="Wrapper for decoding/reading barcodes with ZXing (Zebra Crossing) library",
long_description="More information: https://github.com/dlenski/python-zxing",
long_description=open('README.md').read(),
long_description_content_type='text/markdown',
url="https://github.com/dlenski/python-zxing",
author='Daniel Lenski',
author_email='[email protected]',
packages=['zxing'],
package_data={'zxing': download_java_files()},
entry_points={'console_scripts': ['zxing=zxing.__main__:main']},
extras_require={
"Image": [
"pillow>=3.0,<6.0; python_version < '3.5'",
"pillow>=3.0,<8.0; python_version >= '3.5' and python_version < '3.6'",
"pillow>=8.0; python_version >= '3.6'",
]
},
install_requires=open('requirements.txt').readlines(),
tests_require=open('requirements-test.txt').readlines(),
test_suite='nose.collector',
Expand Down
15 changes: 13 additions & 2 deletions test/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import os
from tempfile import mkdtemp

from PIL import Image

from nose import with_setup
from nose.tools import raises

Expand Down Expand Up @@ -43,11 +45,12 @@ def test_version():


@with_setup(setup_reader)
def _check_decoding(filename, expected_format, expected_raw, extra={}):
def _check_decoding(filename, expected_format, expected_raw, extra={}, as_Image=False):
global test_reader
path = os.path.join(test_barcode_dir, filename)
what = Image.open(path) if as_Image else path
logging.debug('Trying to parse {}, expecting {!r}.'.format(path, expected_raw))
dec = test_reader.decode(path, pure_barcode=True, **extra)
dec = test_reader.decode(what, pure_barcode=True, **extra)
if expected_raw is None:
assert dec.raw is None, (
'Expected failure, but got result in {} format'.format(expected_format, dec.format))
Expand All @@ -56,13 +59,21 @@ def _check_decoding(filename, expected_format, expected_raw, extra={}):
'Expected {!r} but got {!r}'.format(expected_raw, dec.raw))
assert dec.format == expected_format, (
'Expected {!r} but got {!r}'.format(expected_format, dec.format))
if as_Image:
assert not os.path.exists(dec.path), (
'Expected temporary file {!r} to be deleted, but it still exists'.format(dec.path))


def test_decoding():
global test_reader
yield from ((_check_decoding, filename, expected_format, expected_raw) for filename, expected_format, expected_raw in test_valid_images)


def test_decoding_from_Image():
global test_reader
yield from ((_check_decoding, filename, expected_format, expected_raw, {}, True) for filename, expected_format, expected_raw in test_valid_images)


def test_possible_formats():
yield from ((_check_decoding, filename, expected_format, expected_raw, dict(possible_formats=('CODE_93', expected_format, 'DATA_MATRIX')))
for filename, expected_format, expected_raw in test_barcodes)
Expand Down
29 changes: 26 additions & 3 deletions zxing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
import zipfile
from enum import Enum

try:
from PIL.Image import Image
from tempfile import NamedTemporaryFile
have_pil = True
except ImportError:
have_pil = None

from .version import __version__ # noqa: F401


Expand Down Expand Up @@ -53,12 +60,24 @@ def __init__(self, classpath=None, java=None):
def decode(self, filenames, try_harder=False, possible_formats=None, pure_barcode=False, products_only=False):
possible_formats = (possible_formats,) if isinstance(possible_formats, str) else possible_formats

if isinstance(filenames, str):
if isinstance(filenames, (str, Image) if have_pil else str):
one_file = True
filenames = filenames,
else:
one_file = False
file_uris = [pathlib.Path(f).absolute().as_uri() for f in filenames]

file_uris = []
temp_files = []
for fn_or_im in filenames:
if have_pil and isinstance(fn_or_im, Image):
tf = NamedTemporaryFile(prefix='PIL_image_', suffix='.png')
temp_files.append(tf)
fn_or_im.save(tf, compresslevel=0)
tf.flush()
fn = tf.name
else:
fn = fn_or_im
file_uris.append(pathlib.Path(fn).absolute().as_uri())

cmd = [self.java, '-cp', self.classpath, self.cls] + file_uris
if try_harder:
Expand All @@ -75,7 +94,11 @@ def decode(self, filenames, try_harder=False, possible_formats=None, pure_barcod
p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.STDOUT, universal_newlines=False)
except OSError as e:
raise BarCodeReaderException("Could not execute specified Java binary", self.java) from e
stdout, stderr = p.communicate()
else:
stdout, stderr = p.communicate()
finally:
for tf in temp_files:
tf.close()

if stdout.startswith((b'Error: Could not find or load main class com.google.zxing.client.j2se.CommandLineRunner',
b'Exception in thread "main" java.lang.NoClassDefFoundError:')):
Expand Down

0 comments on commit 81f55d3

Please sign in to comment.