Skip to content

Commit 81f55d3

Browse files
committed
Accept PIL.Image instead of filename
Ping #21
1 parent c2c6569 commit 81f55d3

5 files changed

Lines changed: 54 additions & 7 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ BarCode(raw='This should be QR_CODE', parsed='This should be QR_CODE', path='tes
3131
The attributes of the decoded `BarCode` object are `raw`, `parsed`, `path`, `format`, `type`, and `points`. The list of formats which ZXing can decode is
3232
[here](https://zxing.github.io/zxing/apidocs/com/google/zxing/BarcodeFormat.html).
3333

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

requirements-test.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
nose>=1.0
2+
3+
pillow>=3.0,<6.0; python_version < '3.5'
4+
pillow>=3.0,<8.0; python_version >= '3.5' and python_version < '3.6'
5+
pillow>=8.0; python_version >= '3.6'

setup.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,21 @@ def download_java_files(force=False):
4646
name='zxing',
4747
version=version_pep,
4848
description="Wrapper for decoding/reading barcodes with ZXing (Zebra Crossing) library",
49-
long_description="More information: https://github.com/dlenski/python-zxing",
49+
long_description=open('README.md').read(),
50+
long_description_content_type='text/markdown',
5051
url="https://github.com/dlenski/python-zxing",
5152
author='Daniel Lenski',
5253
author_email='dlenski@gmail.com',
5354
packages=['zxing'],
5455
package_data={'zxing': download_java_files()},
5556
entry_points={'console_scripts': ['zxing=zxing.__main__:main']},
57+
extras_require={
58+
"Image": [
59+
"pillow>=3.0,<6.0; python_version < '3.5'",
60+
"pillow>=3.0,<8.0; python_version >= '3.5' and python_version < '3.6'",
61+
"pillow>=8.0; python_version >= '3.6'",
62+
]
63+
},
5664
install_requires=open('requirements.txt').readlines(),
5765
tests_require=open('requirements-test.txt').readlines(),
5866
test_suite='nose.collector',

test/test_all.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import os
33
from tempfile import mkdtemp
44

5+
from PIL import Image
6+
57
from nose import with_setup
68
from nose.tools import raises
79

@@ -43,11 +45,12 @@ def test_version():
4345

4446

4547
@with_setup(setup_reader)
46-
def _check_decoding(filename, expected_format, expected_raw, extra={}):
48+
def _check_decoding(filename, expected_format, expected_raw, extra={}, as_Image=False):
4749
global test_reader
4850
path = os.path.join(test_barcode_dir, filename)
51+
what = Image.open(path) if as_Image else path
4952
logging.debug('Trying to parse {}, expecting {!r}.'.format(path, expected_raw))
50-
dec = test_reader.decode(path, pure_barcode=True, **extra)
53+
dec = test_reader.decode(what, pure_barcode=True, **extra)
5154
if expected_raw is None:
5255
assert dec.raw is None, (
5356
'Expected failure, but got result in {} format'.format(expected_format, dec.format))
@@ -56,13 +59,21 @@ def _check_decoding(filename, expected_format, expected_raw, extra={}):
5659
'Expected {!r} but got {!r}'.format(expected_raw, dec.raw))
5760
assert dec.format == expected_format, (
5861
'Expected {!r} but got {!r}'.format(expected_format, dec.format))
62+
if as_Image:
63+
assert not os.path.exists(dec.path), (
64+
'Expected temporary file {!r} to be deleted, but it still exists'.format(dec.path))
5965

6066

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

6571

72+
def test_decoding_from_Image():
73+
global test_reader
74+
yield from ((_check_decoding, filename, expected_format, expected_raw, {}, True) for filename, expected_format, expected_raw in test_valid_images)
75+
76+
6677
def test_possible_formats():
6778
yield from ((_check_decoding, filename, expected_format, expected_raw, dict(possible_formats=('CODE_93', expected_format, 'DATA_MATRIX')))
6879
for filename, expected_format, expected_raw in test_barcodes)

zxing/__init__.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
import zipfile
1515
from enum import Enum
1616

17+
try:
18+
from PIL.Image import Image
19+
from tempfile import NamedTemporaryFile
20+
have_pil = True
21+
except ImportError:
22+
have_pil = None
23+
1724
from .version import __version__ # noqa: F401
1825

1926

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

56-
if isinstance(filenames, str):
63+
if isinstance(filenames, (str, Image) if have_pil else str):
5764
one_file = True
5865
filenames = filenames,
5966
else:
6067
one_file = False
61-
file_uris = [pathlib.Path(f).absolute().as_uri() for f in filenames]
68+
69+
file_uris = []
70+
temp_files = []
71+
for fn_or_im in filenames:
72+
if have_pil and isinstance(fn_or_im, Image):
73+
tf = NamedTemporaryFile(prefix='PIL_image_', suffix='.png')
74+
temp_files.append(tf)
75+
fn_or_im.save(tf, compresslevel=0)
76+
tf.flush()
77+
fn = tf.name
78+
else:
79+
fn = fn_or_im
80+
file_uris.append(pathlib.Path(fn).absolute().as_uri())
6281

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

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

0 commit comments

Comments
 (0)