From 6597448e506567cca5015eec4a8af87dad21b7c6 Mon Sep 17 00:00:00 2001 From: Fabien Bochu Date: Tue, 22 Aug 2017 18:19:54 +0200 Subject: [PATCH] Add helper to add background color on image For some images with alpha channel, the background color and the foreground color are the same after grey scale conversion, this will no allow us to detect 1D or 2D code on the image. The copy_image_on_background() allow to add a background color on a image to avoid this issue. diff --git a/README.rst b/README.rst index 917726b..3866048 100644 --- a/README.rst +++ b/README.rst @@ -35,3 +35,24 @@ How To use ZbarLight codes = zbarlight.scan_codes('qrcode', image) print('QR codes: %s' % codes) + +Troubleshooting +=============== + +In some case ``zbarlight`` will not be able to detect the 1D or 2D code in an image, one of the known cause is that the +image background color is the same as the foreground color after conversion to grey scale (it's happen on images with +alpha channel). You can use the ``copy_image_on_background`` function to add a background color on your image. + +.. code-block:: python + + from PIL import Image + import zbarlight + + file_path = './tests/fixtures/two_qr_codes.png' + with open(file_path, 'rb') as image_file: + image = Image.open(image_file) + image.load() + + new_image = zbarlight.copy_image_on_background(image, color=zbarlight.WHITE) # <<<<<<<<<<<<<<<< Add this line <<<< + codes = zbarlight.scan_codes('qrcode', new_image) + print('QR codes: %s' % codes) diff --git a/docs/reference.rst b/docs/reference.rst index 829f6f6..0e361df 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -2,4 +2,4 @@ Reference ========= .. automodule:: zbarlight - :members: scan_codes + :members: scan_codes, copy_image_on_background diff --git a/src/zbarlight/__init__.py b/src/zbarlight/__init__.py index a0171f5..0f05c83 100644 --- a/src/zbarlight/__init__.py +++ b/src/zbarlight/__init__.py @@ -7,7 +7,17 @@ from ._zbarlight import Symbologies, zbar_code_scanner __version__ = pkg_resources.get_distribution('zbarlight').version -__ALL__ = ['Symbologies', 'UnknownSymbologieError', 'scan_codes'] +__ALL__ = [ + 'Symbologies', + 'UnknownSymbologieError', + 'scan_codes', + 'copy_image_on_background', + 'BLACK', + 'WHITE', +] + +WHITE = (255, 255, 255) +BLACK = (0, 0, 0) class UnknownSymbologieError(Exception): @@ -42,3 +52,18 @@ def scan_codes(code_type, image): if not symbologie: raise UnknownSymbologieError('Unknown Symbologie: %s' % code_type) return zbar_code_scanner(symbologie, raw, width, height) + + +def copy_image_on_background(image, color=WHITE): + """Create a new image by copying the image on a *color* background + + Args: + image (PIL.Image.Image): Image to copy + color (tuple): Background color usually WHITE or BLACK + + Returns: + PIL.Image.Image + """ + background = Image.new("RGB", image.size, color) + background.paste(image, mask=image.split()[3]) + return background diff --git a/tests/fixtures/sample_need_white_background.png b/tests/fixtures/sample_need_white_background.png new file mode 100644 index 0000000..1c2b5ef Binary files /dev/null and b/tests/fixtures/sample_need_white_background.png differ diff --git a/tests/test_scan_codes.py b/tests/test_scan_codes.py index 2b4ad81..f7ae31b 100644 --- a/tests/test_scan_codes.py +++ b/tests/test_scan_codes.py @@ -17,9 +17,9 @@ class ScanCodeTestCase(unittest.TestCase): def assertIsNone(self, obj, msg=None): # Python 2.6 hack return self.assertTrue(obj is None, '%s is not None' % repr(obj)) - def get_image(self, name): + def get_image(self, name, ext='png'): return pil_image( - os.path.join(os.path.dirname(__file__), 'fixtures', '{0}.png'.format(name)) + os.path.join(os.path.dirname(__file__), 'fixtures', '{0}.{1}'.format(name, ext)) ) def test_no_qr_code(self): @@ -59,3 +59,22 @@ class ScanCodeTestCase(unittest.TestCase): zbarlight.UnknownSymbologieError, zbarlight.scan_codes, 'not-a-zbar-symbologie', image, ) + + def test_need_white_background(self): + """ + User submitted sample that can only be decoded after add a white background + """ + # Not working + image = self.get_image('sample_need_white_background') + + self.assertEqual( + sorted(zbarlight.scan_codes('qrcode', image) or []), + [], + ) + + # Working when adding white background + image_with_background = zbarlight.copy_image_on_background(image) + self.assertEqual( + sorted(zbarlight.scan_codes('qrcode', image_with_background) or []), + sorted([b'http://en.m.wikipedia.org']), + ) --- Changelog.rst | 3 +- README.rst | 21 ++++++++++++++ docs/reference.rst | 2 +- src/zbarlight/__init__.py | 27 +++++++++++++++++- .../fixtures/sample_need_white_background.png | Bin 0 -> 5126 bytes tests/test_scan_codes.py | 23 +++++++++++++-- 6 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 tests/fixtures/sample_need_white_background.png diff --git a/Changelog.rst b/Changelog.rst index 5bfcc9f..b554727 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -4,7 +4,8 @@ ChangeLog 2.0 (unreleased) ---------------- -- Drop deprecated qr_code_scanner() method. +- Drop deprecated qr_code_scanner() method +- Add helper to add background color on image 1.2 (2017-03-09) diff --git a/README.rst b/README.rst index 917726b..3866048 100644 --- a/README.rst +++ b/README.rst @@ -35,3 +35,24 @@ How To use ZbarLight codes = zbarlight.scan_codes('qrcode', image) print('QR codes: %s' % codes) + +Troubleshooting +=============== + +In some case ``zbarlight`` will not be able to detect the 1D or 2D code in an image, one of the known cause is that the +image background color is the same as the foreground color after conversion to grey scale (it's happen on images with +alpha channel). You can use the ``copy_image_on_background`` function to add a background color on your image. + +.. code-block:: python + + from PIL import Image + import zbarlight + + file_path = './tests/fixtures/two_qr_codes.png' + with open(file_path, 'rb') as image_file: + image = Image.open(image_file) + image.load() + + new_image = zbarlight.copy_image_on_background(image, color=zbarlight.WHITE) # <<<<<<<<<<<<<<<< Add this line <<<< + codes = zbarlight.scan_codes('qrcode', new_image) + print('QR codes: %s' % codes) diff --git a/docs/reference.rst b/docs/reference.rst index 829f6f6..0e361df 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -2,4 +2,4 @@ Reference ========= .. automodule:: zbarlight - :members: scan_codes + :members: scan_codes, copy_image_on_background diff --git a/src/zbarlight/__init__.py b/src/zbarlight/__init__.py index a0171f5..0f05c83 100644 --- a/src/zbarlight/__init__.py +++ b/src/zbarlight/__init__.py @@ -7,7 +7,17 @@ __version__ = pkg_resources.get_distribution('zbarlight').version -__ALL__ = ['Symbologies', 'UnknownSymbologieError', 'scan_codes'] +__ALL__ = [ + 'Symbologies', + 'UnknownSymbologieError', + 'scan_codes', + 'copy_image_on_background', + 'BLACK', + 'WHITE', +] + +WHITE = (255, 255, 255) +BLACK = (0, 0, 0) class UnknownSymbologieError(Exception): @@ -42,3 +52,18 @@ def scan_codes(code_type, image): if not symbologie: raise UnknownSymbologieError('Unknown Symbologie: %s' % code_type) return zbar_code_scanner(symbologie, raw, width, height) + + +def copy_image_on_background(image, color=WHITE): + """Create a new image by copying the image on a *color* background + + Args: + image (PIL.Image.Image): Image to copy + color (tuple): Background color usually WHITE or BLACK + + Returns: + PIL.Image.Image + """ + background = Image.new("RGB", image.size, color) + background.paste(image, mask=image.split()[3]) + return background diff --git a/tests/fixtures/sample_need_white_background.png b/tests/fixtures/sample_need_white_background.png new file mode 100644 index 0000000000000000000000000000000000000000..1c2b5ef18a719c1d1d217da6fa46404a4ac3f44b GIT binary patch literal 5126 zcmbVQc|4SB8y*cq7*m!SvXnzAvI`R?N49K9h9qltA^Sd-Y9hp3&L87h)D}!O%N9o!}F|}b#3dlqFK}a0HZ5GAP|qh-e3de7rg(ZF@)94Kz~2}{l9Z3TsnHrCn1iDLlSxC zoZ*GX%8$Hw_#fYwP;TlhX-X(dBvr+8vvUt|C9^dec)l^GD7Y)8{V=hM4Ha$NJ$}G6 zUh3k3V^W*ssgKA$YfV#hqC)$ZUpcDKMuUph<}~}am;9&?=i{V|Gyb0&T4?}H zn-O#x1f{RS0}kSjxgL?=NHc9@g0@D_(`Eo+;Ic7o2pASh@34vgc7~R=&t{*6+Y(cX z6sEF+gxF_#P7B#`r@+43zv9`N!ePii$mhjBk0ub6><;>(y#~<61I&7P`}s*FVvN<* z{1)bLJM}PcyOzV$B5te?mY1<}J6`%7kRb&&$%vOWhU7?vy!Dh2odW}p)O23mBsBm= zu=E)t((ZYhZ!)6dDWb|Ffr1y?l2WvMKLyHhE9_8@8|7aMzBa(zjH3%sG6A6^YGRzg zsWyX5z)dhDA|ymkzqgRT6jAFI6TtWI+YK?)x?NriGvf5pZ~Pn6vTJa>fG_iPF?0O# zRZLS0jJ&E`+mT_HLBq>eh`nz2a57fLNwEUF?qs|dpqMfOG>_v9Q2pYg{Y8WgwXr2C z!5(VbxB?TsxVE^ynZ?Xd=N9Gk^$z+t1l%hh^%g0yjjAI$tHH}U1vl)hV>DVY&kN1s zClR$)Ph!Q2jfO*P@AFil#ZcF;k<}2V#~7XKq1v^m1zdF@p%OxC=BweU7&vNAc$Z87 z5Or+NZFbe=_O?mbHiK7gFSaLo24V?zW9crvPi}aDeBx4*82#W)nTew1*P5ar-)Z;h znYHlFK(Asa`=lSD8Ym3yZ^HdtN>UEd-3M!oyc&Zu|LACB2 ztD1gBjlB@^rC01d8s8&bj{GKwS6Juj#a&dL*oMmD* zqHBm2Egt5|hgHVi(Hrh>?5P8$3dYf>PD15WzY?DR!e4k~{U>Xq7qGwrhUqrF=ol_y z?l%8cangs56Cd%2U-Q;E=waQK9u(()d;+QWqH)HZRI7uZNw2K3`Iu7&G@l%ydyifL zLb47QGq|=cc2PYn|G5Q=~<%_yA^4)>mkjKz+W)Xqqfu$)@8BiH@r z0!eF0)T?nENe{_QMEVM!R{l0*GyDKb27E-oIZ!&_oMdedo8txc?vF_<_`myWK8L6dOwKTGugFUOg{`voTtgRpE<)3|DjvybL{U? z#3zU?$NhUX{8x$m#D-XAEC0FW3mhR;$C4LnE0mj&3#yA~dcZ*oHpVUL;#G0ZzPc^A zzuB!mQ6apg$I!)Z5z z0(g0poAHuCyn!9=4rIpFh=wZ=7O23+39yi#>dLT9@KAQp-ygf#lm|O&YxIQm`<@~=?^d9C0-Q1Tu5WD(WHNd&_ z<^2{qL?3E-g()oPqy5r;PAQh}c~$xSNo^s<*z5o4hZ@(~J=*!g)lHm3-n>x)Ch9Cl9r^ve4_Dm^pc6RSVQ0p$ zyI@_98HNr<+7d3eF()^0k}B^UVX}daIC>St=gI)O#c}`R5#+nbUA?}4gW@lSN0jr0 z*kZsmR%Y46yT=$NK-fvg#cxe~1s%Ov$lL&SjkF(zie?w(Rx$z=zF#h-zXWLBI-{3N zwoET&L9>Q^dXM6FKa?q3R9q_w0aE4KF23QPGbe zGc|Oh^!dEa2ALCkAozs_7STe}V{KtRu;j=3I~(X$3lP&dLTpel;DDm~pQ+pBMP|># zBvLv)B|Q`FReE5<5~tV;K8u0bSB{8woc%_3eY0#DefI%-S_K`U7`F*pF>1uJjR@cK z>Ji@Ec@UGJ^c*_c5vikrah0873adSkxqmp3!IQ|ChV7v1mowjBZ+%iChj@O=^z07q zq^)55kEA9O!fSp>=HDSE4YFxq%m9OKw61Vj)t^t^E1X~o`5f1$n2f8?j-MF$99dr; zBQhrZz-ny~7v{n%0yGG%Aj4AJFMT*_<~C{4Dl<`e(!0G|M`~%{w$J({mqN7@DB^?M zuS{7I9;2-qw1{KKk1=%DU&WHgi<$nMbi4t55f?}}5h*`YTFgA1=Nha>-d+**MF(+q zg=fI(iUsq2WaU@1V{zc8cZ3kE7B2^%FTnM$4kx%Nw9B#Yk6S^?xeu z-qzQ+ifAuS?;p}$#_srNuvg!!>hkEM&!!~rk%v&jI_OYgy^Q=id)manO5%ERu zp|1!B(Yp-(u%d<-QX6CBh^Mz#5E%{L0b(*cbOROK9;vdJ>X)jsK7n#a^ksj1MWFS^ zl$y%qXL9@X3jd%OcEGj9Q1ihdcJcD>e7mb)w4@E z_gm9%C&sGK`lf|jtcj>@m=5;-e;m!fY2=gY``a;}N5*WYGyH4x2C`eGtU|)0Kjg*_ zsq9R17i4;(xyCRHv8X9_E#p;SxY_xQvL#N5n&J|)agtW~|nEgo`qc2=W}O zXuG%yU&@HU_HO)Bi+(cwuz{GjMd=r}E|q2pdN(|q=e&hU(#VwwW%kY2M($C_l);o( zyir+9KuXPt1;cK?Zt%%MVVlq)ZSH8vKj|%iH>o#+s+5eV&T{3SLF!t98%6+iq zxzKV!>c=P2e|uRmT;IMb!8>_h1A`i&AyTM^$3OD!s^G59`uA~&%X>X6uYw_txfar` zg6gxj%}9mhSI^u+dHh8TEg5f{?;lFP>E}{dC|uCN0a#1l7!uq_xd*M)T0D_V?OuY2 zK3e_eQ5(_l<*xYC2#_L@t2ayLAy)Zbe#v;fSiGwWfmA|d49a9-5-(2u(ZW2;de@L2 zKU9B`)J}BOk_=xPKlYXBdB79DV)OeRmVSYXWf|T9)xAoe#a$a~P1Q#%lVhJ22A&#; zm7kYS9ZGWiT-Ktc`SoxMpBr>~-?CXFGNXk)?YUegr)qaaYXvglSm}AoMZ3l<35Q5( zQ)?f&s1=1UMZ^-Wg8R|lUCxH@ZqR_|=!>6sjC>vI!_^t}xa@{%@1 zSvq`f%`V-Q`r<-*{6?!yf=Lk_b$j@q6oZEGq|4_ zzscd66?mdkMY$$2bXMwnXG(-BpFKO#cpYD_Io%`5C7R`lE^A3whDqjF-JraS5!<1XTejIG@zNMK>QhgEG*mL29qpnEA(<7Vn$65x3xCI{rGL_q z$t5OSEH20vw)^0RNHHOiF$a7KcHd49HD$xFot0sbLGMild^vX&2N`Q5xI;;mYyMk} zbl3R28`Mvx_O8LBM}-l)9*~dtI#`!tGoNf^OfUA=kxr}(ljRSdwdq!CWXgWv`fitwU4!HNUF0g_ zWMp;jL;wmV6VLYHxyj@50G6eHqe@$gf)txoxPxtCP(k++-Y0#2X1sm1j&Enhkdg0f z?O2B7r82XB0rX&pXROIdAJTHU5wsmwZSZV{m%~jXGuUa z$N6+^)?a82;ep|&kmvii1i$b|i}OUQ4>G+-%`Aczu)VsfISCQX z;OcqAD>vMJ1b|?U^*jVjlBhO`j7&j`BKfF&A?n^$S`=oD`0&lRt)1L9xJkCR7#HmgdjPGr1tTU5F`foCLSCR z&^iBH(X$`g8N=djCV$F|F5s-E1Kad{)jc`A2ws;h`CmKzMKkzymQ_>mcri}a-HMqVzhwoEE-L^>vuX|oRC8}K`M=+M|CqCx{o_`EB1|1ZS6~k{X5VoH z5iV;$U|>hnNfj(7oe)11mz08gfw&1Zh!XQq<q7260eE!Fe+l;^-D}$5W zjw4bGvOB3ViCT2Ma5_5K1DP}YcgX&KPPL3P$|rUt?v4tYi8djLd&hak`sU~Be|Jjw7m^&^(*OVf literal 0 HcmV?d00001 diff --git a/tests/test_scan_codes.py b/tests/test_scan_codes.py index 2b4ad81..f7ae31b 100644 --- a/tests/test_scan_codes.py +++ b/tests/test_scan_codes.py @@ -17,9 +17,9 @@ class ScanCodeTestCase(unittest.TestCase): def assertIsNone(self, obj, msg=None): # Python 2.6 hack return self.assertTrue(obj is None, '%s is not None' % repr(obj)) - def get_image(self, name): + def get_image(self, name, ext='png'): return pil_image( - os.path.join(os.path.dirname(__file__), 'fixtures', '{0}.png'.format(name)) + os.path.join(os.path.dirname(__file__), 'fixtures', '{0}.{1}'.format(name, ext)) ) def test_no_qr_code(self): @@ -59,3 +59,22 @@ def test_unknown_symbology(self): zbarlight.UnknownSymbologieError, zbarlight.scan_codes, 'not-a-zbar-symbologie', image, ) + + def test_need_white_background(self): + """ + User submitted sample that can only be decoded after add a white background + """ + # Not working + image = self.get_image('sample_need_white_background') + + self.assertEqual( + sorted(zbarlight.scan_codes('qrcode', image) or []), + [], + ) + + # Working when adding white background + image_with_background = zbarlight.copy_image_on_background(image) + self.assertEqual( + sorted(zbarlight.scan_codes('qrcode', image_with_background) or []), + sorted([b'http://en.m.wikipedia.org']), + )