From 45a616ef807219805613e3ab6c852d164b8267f2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 18 Sep 2014 14:44:41 +0300 Subject: [PATCH 01/48] Created with Pillow: im = Image.open('hopper.jpg'); im = im.convert('1'); im.save('hopper.msp') --- Tests/images/hopper.msp | Bin 0 -> 2080 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/hopper.msp diff --git a/Tests/images/hopper.msp b/Tests/images/hopper.msp new file mode 100644 index 0000000000000000000000000000000000000000..91d9a147ff0a7d2a27f89c66405e9011a464d359 GIT binary patch literal 2080 zcmXX{Z)jWB7609*XXn{2^vP|^=oo(1WELy)+QBwPH8!W5a}-@UlSkr0KD5&0 zg>`MRlGxX#Ep{2q(WDdt1y4v|UHLUx@zV!8)M}f-=qD%ZbAp^WPN0x^WYI>vq?v2& zys|6ly?f62oxk_oqwq_kF9-;*|9`x_w?g0k>g7Frvp?O=M1tPel0-MpnA_gIkSWEA znC{Xi%~QznSdMoGvh9E>!pF|3nPPMxo8y4iH&xwu{)*Mp!=q9nVnFowJ@^;5a@{?_ zhz`aA=BvwJlzJl3Y^Dg9x;S(DMfHB6*mHo#0ud-vpWOOyeJ2;nmU=>aC{FS7JB@lH zcE+-@nP;GPz4?cYo1ZnL><$*KAB2TEb91CoAHNbQMKgR~GvQAw%a@P-J!n~c;M8M# z46%q`|Mv0Ca(7Q;@6K}@{ZsWl59)pQvvb8z;o`1pRlL;xyV&5r^SM|dx3BZkv3%s7 z*uVH}!m@Hv|55QPf%on=*1y|*Fjxxx@abxO3Qt6yJRmYJM2lSCb?BNf0G0<2NFLOi zZylc%RE6Ux$OzT%Eba(~Nds!A6e{tcs-F~4b->7Q-W`OBOL-6p!L5SKX%IrFME1if zMN8b6(FK{KDj=2%<)TmsNi;#Iy0M&!@IciBGXSf|qa~e8g^?RjXB8gH$Rhd;2!)3# z^9V_qi>aP{R@o|71P_ru>FL#un;-7f1ZBfH+I5)a#*>%p zqrgP(q10T~BDpL*+}85_GuZceO64BsdOaZCV(afF+5(Zr1QoJV><@q<2p_p7qdia6o0zyX1LzN<>M+>GhHM!9H+S*!*rxOlm1H$Ay zaW$Q`Oh7AFMChT&_xJewLQ@vyV6@G#}RJ=oU8%=iS9cFB}*BX3Gud^|f^R zeYrMF_B1GEZg&r^mN`$_WKaE8ZFOPl?NpK&+*VCIwe$;l|Bc>?laLPAncjQj?knf5 zT$-oiZqn4SDl5{hgeo?P`K*DZ>sx;tTYL8nm-Dz?6@eSC1&Zan{2lVndET5z%@xojl?D;0rL7=5qmiUJa3ZL7_ zRchCct(cvX(zX*0chB2pO^&_a}p4Wlx86DSo;yP02hROQoq|m(v_tByh#|ig_TRNH-x@jV`o) z?h6&3oJXZ1%L#nY6PWWQ&3ESUl$&oJQ%f8ncX1| zZQRd)$q-+y+5RI@1HN1=cM5tN)r4(Q27R&^y-1C~(+)cV_(MZ*pFSpP%(wn229c$(|yDySFsC>hMD?f#{7(M z*gK~a7hj{F%4jlue?`z1&%h6zMi+W75M>$!)lD*p-PfRz9Wh}L+Ut!ue5KHN3*KgO z_lg3NsGScrsOF5;`m#=ZMc-eC=La1ZOiI(lc-@#Hb+oNORq4IFA?T@~)$>QIahu@N zn$dchycqc7_$+im{I^EDXr`1#RRg7uHp+*VuK}|khJmIPt+!7F`!Y;i8r01rM(5ZH z{9|+|F_RAHg4kDnnc)je`&%cevQ>O%eZ|0C`rkWf0!<_M(TQ!S6$OvUg*nZa#}PXY zGYR8GdG5H`w1PmTrs5csZ}3tVCPx$~bcupG!ZJpkH|s?OZHD=ObN%B%v{$_{>0}v~ vIoDN%c7g~hkxCEUH*4x%Wz?`~_K~Phz$6729)j+U@)2!>qEZc8JWcjLt-ix9 literal 0 HcmV?d00001 From 3bbfcb23aa0d32d1979806c0b34db3e7810ffd84 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 18 Sep 2014 14:48:07 +0300 Subject: [PATCH 02/48] More tests for MspImagePlugin.py --- Tests/helper.py | 6 +++--- Tests/test_file_msp.py | 24 +++++++++++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 637e77f9c..34dafa645 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -191,11 +191,11 @@ def hopper(mode="RGB", cache={}): if mode == "RGB": im = Image.open("Tests/images/hopper.ppm") elif mode == "F": - im = lena("L").convert(mode) + im = hopper("L").convert(mode) elif mode[:4] == "I;16": - im = lena("I").convert(mode) + im = hopper("I").convert(mode) else: - im = lena("RGB").convert(mode) + im = hopper("RGB").convert(mode) # cache[mode] = im return im diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index a64faad10..2245f9ed6 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -1,15 +1,16 @@ -from helper import unittest, PillowTestCase, lena +from helper import unittest, PillowTestCase, hopper from PIL import Image +TEST_FILE = "Tests/images/hopper.msp" + class TestFileMsp(PillowTestCase): def test_sanity(self): - file = self.tempfile("temp.msp") - lena("1").save(file) + hopper("1").save(file) im = Image.open(file) im.load() @@ -17,6 +18,23 @@ class TestFileMsp(PillowTestCase): self.assertEqual(im.size, (128, 128)) self.assertEqual(im.format, "MSP") + def test_open(self): + # Arrange + # Act + im = Image.open(TEST_FILE) + + # Assert + self.assertEqual(im.size, (128, 128)) + self.assert_image_similar(im, hopper("1"), 4) + + def test_cannot_save_save_wrong_mode(self): + # Arrange + im = hopper() + file = self.tempfile("temp.msp") + + # Act/Assert + self.assertRaises(IOError, lambda: im.save(file)) + if __name__ == '__main__': unittest.main() From 6abc0d2f40e79bc96c1e25a5127642d9746b4d17 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 29 Sep 2014 13:00:19 -0700 Subject: [PATCH 03/48] Version Bump - 2.6.0-rc1 --- CHANGES.rst | 2 +- PIL/__init__.py | 2 +- _imaging.c | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 81ba0d0e0..f7a0a7f84 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,7 @@ Changelog (Pillow) ================== -2.6.0 (unreleased) +2.6.0-rc1 (2014-09-29) ------------------ - Use redistributable image for testing #884 diff --git a/PIL/__init__.py b/PIL/__init__.py index c6e27d791..30e845182 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.5.3' # Pillow +PILLOW_VERSION = '2.6.0-rc1' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/_imaging.c b/_imaging.c index ec8205dd4..2b5825dd8 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.5.3" +#define PILLOW_VERSION "2.6.0-rc1" #include "Python.h" diff --git a/setup.py b/setup.py index 46293fbc8..4bce62e36 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -PILLOW_VERSION = '2.5.3' +PILLOW_VERSION = '2.6.0-rc1' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None From 81ebc21abfdd9d152f05d8516b17efba26e4d5b7 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 29 Sep 2014 13:14:32 -0700 Subject: [PATCH 04/48] Relax pyroma for RC versions --- Tests/test_pyroma.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index 45d62b82f..59aa3810e 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -1,5 +1,7 @@ from helper import unittest, PillowTestCase +from PIL import PILLOW_VERSION + try: import pyroma except ImportError: @@ -23,8 +25,14 @@ class TestPyroma(PillowTestCase): rating = pyroma.ratings.rate(data) # Assert - # Should have a perfect score - self.assertEqual(rating, (10, [])) + if 'rc' in PILLOW_VERSION: + #Pyroma needs to chill about RC versions and not kill all our tests. + self.assertEqual(rating, (9, + ['The packages version number does not comply with PEP-386.'])) + + else: + # Should have a perfect score + self.assertEqual(rating, (10, [])) if __name__ == '__main__': From 383f7130758578d14c02fee9b1e2c0b6069e7771 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 29 Sep 2014 16:36:09 -0700 Subject: [PATCH 05/48] Relax exact equals to approximate --- Tests/test_file_gimpgradient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_gimpgradient.py b/Tests/test_file_gimpgradient.py index c64deb79d..c54dca7c1 100644 --- a/Tests/test_file_gimpgradient.py +++ b/Tests/test_file_gimpgradient.py @@ -80,7 +80,7 @@ class TestImage(PillowTestCase): ret = GimpGradientFile.sphere_increasing(middle, pos) # Assert - self.assertEqual(ret, 0.9682458365518543) + self.assert_almost_equal(ret, 0.9682458365518543) def test_sphere_decreasing(self): # Arrange From 1a91078154a16a3c2d32a8858ace8c1e6aa697af Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Mon, 29 Sep 2014 22:14:26 -0700 Subject: [PATCH 06/48] Test failure explanation on PPC --- Tests/test_imagefile.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 3556661ae..662a3bfb0 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -55,6 +55,12 @@ class TestImageFile(PillowTestCase): if EpsImagePlugin.has_ghostscript(): im1, im2 = roundtrip("EPS") + # This test fails on Ubuntu 12.04, PPC (Bigendian) It + # appears to be a ghostscript 9.05 bug, since the + # ghostscript rendering is wonky and the file is identical + # to that written on ubuntu 12.04 x64 + # md5sum: ba974835ff2d6f3f2fd0053a23521d4a + # EPS comes back in RGB: self.assert_image_similar(im1, im2.convert('L'), 20) From 1bb850427dca1a29156abb2580d6411b57966795 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 30 Sep 2014 08:33:29 -0700 Subject: [PATCH 07/48] Slightly relax imagedraw tests to pass on x86 --- Tests/test_imagedraw.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index ad0f33530..6adc6c1f2 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -63,8 +63,8 @@ class TestImageDraw(PillowTestCase): del draw # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_arc.png")) + self.assert_image_similar( + im, Image.open("Tests/images/imagedraw_arc.png"),1) def test_arc1(self): self.helper_arc(BBOX1) @@ -96,8 +96,8 @@ class TestImageDraw(PillowTestCase): del draw # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_chord.png")) + self.assert_image_similar( + im, Image.open("Tests/images/imagedraw_chord.png"),1) def test_chord1(self): self.helper_chord(BBOX1) @@ -115,8 +115,8 @@ class TestImageDraw(PillowTestCase): del draw # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_ellipse.png")) + self.assert_image_similar( + im, Image.open("Tests/images/imagedraw_ellipse.png"),1) def test_ellipse1(self): self.helper_ellipse(BBOX1) @@ -153,8 +153,8 @@ class TestImageDraw(PillowTestCase): del draw # Assert - self.assert_image_equal( - im, Image.open("Tests/images/imagedraw_pieslice.png")) + self.assert_image_similar( + im, Image.open("Tests/images/imagedraw_pieslice.png"),1) def test_pieslice1(self): self.helper_pieslice(BBOX1) From 9634e437efeeda906ad6bfcc275b17732d64f32a Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 1 Oct 2014 09:59:00 -0700 Subject: [PATCH 08/48] Version Bump -- 2.6.0 --- CHANGES.rst | 8 +++++++- PIL/__init__.py | 2 +- _imaging.c | 2 +- setup.py | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f7a0a7f84..52419088f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,9 +1,15 @@ Changelog (Pillow) ================== -2.6.0-rc1 (2014-09-29) +2.6.0 (2014-10-01) ------------------ +- Relax precision of ImageDraw tests for x86, GimpGradient for PPC + [wiredfool] + +2.6.0-rc1 (2014-09-29) +---------------------- + - Use redistributable image for testing #884 [hugovk] diff --git a/PIL/__init__.py b/PIL/__init__.py index 30e845182..1bb1250c8 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -12,7 +12,7 @@ # ;-) VERSION = '1.1.7' # PIL version -PILLOW_VERSION = '2.6.0-rc1' # Pillow +PILLOW_VERSION = '2.6.0' # Pillow _plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', diff --git a/_imaging.c b/_imaging.c index 2b5825dd8..1759d4c8d 100644 --- a/_imaging.c +++ b/_imaging.c @@ -71,7 +71,7 @@ * See the README file for information on usage and redistribution. */ -#define PILLOW_VERSION "2.6.0-rc1" +#define PILLOW_VERSION "2.6.0" #include "Python.h" diff --git a/setup.py b/setup.py index 4bce62e36..2d8cafa34 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ except (ImportError, OSError): NAME = 'Pillow' -PILLOW_VERSION = '2.6.0-rc1' +PILLOW_VERSION = '2.6.0' TCL_ROOT = None JPEG_ROOT = None JPEG2K_ROOT = None From 7b398c5804d6eb48745b246ca542ffbb61bacc55 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Wed, 1 Oct 2014 15:21:49 -0400 Subject: [PATCH 09/48] Troubleshooting RTD [ci skip] --- docs/requirements.txt | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 7611ea463..f08f7633e 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,17 +1,19 @@ -# requirements for working on docs +sphinx-better-theme -# install pillow from master if you're into that, but RtD needs this -pillow>=2.4.0 - -Jinja2==2.7.1 -MarkupSafe==0.18 -Pygments==1.6 -Sphinx==1.1.3 -docopt==0.6.1 -docutils==0.11 -wsgiref==0.1.2 -sphinx-better-theme==0.1.5 - -# livereload not strictly necessary but really useful (make livehtml) -tornado==3.1.1 -livereload==1.0.1 +## requirements for working on docs +# +## install pillow from master if you're into that, but RtD needs this +#pillow>=2.4.0 +# +#Jinja2==2.7.1 +#MarkupSafe==0.18 +#Pygments==1.6 +#Sphinx==1.1.3 +#docopt==0.6.1 +#docutils==0.11 +#wsgiref==0.1.2 +#sphinx-better-theme==0.1.5 +# +## livereload not strictly necessary but really useful (make livehtml) +#tornado==3.1.1 +#livereload==1.0.1 From db223a7ff51e4a40a8ad1377dce895e034e34c0d Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Wed, 1 Oct 2014 15:23:09 -0400 Subject: [PATCH 10/48] Troubleshooting RTD [ci skip] --- docs/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 987b6dbb3..b25ea6056 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,9 +35,9 @@ pygments_style = 'sphinx' ### HTML output ### -from better import better_theme_path -html_theme_path = [better_theme_path] -html_theme = 'better' +#from better import better_theme_path +#html_theme_path = [better_theme_path] +#html_theme = 'better' html_title = "Pillow v{release} (PIL fork)".format(release=release) html_short_title = "Home" From 006229595aa5e0fa30fcce1d73f80edf336881a7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 2 Oct 2014 07:48:12 +1000 Subject: [PATCH 11/48] Remove unused variable in libImaging --- libImaging/Draw.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libImaging/Draw.c b/libImaging/Draw.c index 2ff03e049..c21f7cd83 100644 --- a/libImaging/Draw.c +++ b/libImaging/Draw.c @@ -600,7 +600,6 @@ ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, double big_hypotenuse, small_hypotenuse, ratio_max, ratio_min; int dxmin, dxmax, dymin, dymax; Edge e[4]; - int vertices[4][2]; DRAWINIT(); From 29b1c81a9e059152bd9c4fb899e165d7ae598258 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 2 Oct 2014 10:31:30 +0300 Subject: [PATCH 12/48] Created with ImageMagick: convert tests\images\hopper.jpg -colorspace Gray -colors 16 -depth 4 tests\images\hopper_gray_4bpp.tif --- Tests/images/hopper_gray_4bpp.tif | Bin 0 -> 8414 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/hopper_gray_4bpp.tif diff --git a/Tests/images/hopper_gray_4bpp.tif b/Tests/images/hopper_gray_4bpp.tif new file mode 100644 index 0000000000000000000000000000000000000000..7d23958a7e56fbcdb50586748c78b8cac9e9f663 GIT binary patch literal 8414 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 N6076?^t1K({{fcVst5o8 literal 0 HcmV?d00001 From 552a9a28a04eabc241d2a3a4b5c920834a52de62 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 2 Oct 2014 10:43:22 +0300 Subject: [PATCH 13/48] Support and test for 4-bit greyscale TIFF --- PIL/TiffImagePlugin.py | 5 +++-- Tests/test_file_tiff.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 50648288e..90babb9b3 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -149,6 +149,7 @@ OPEN_INFO = { (II, 0, 1, 2, (8,), ()): ("L", "L;IR"), (II, 0, 3, 1, (32,), ()): ("F", "F;32F"), (II, 1, 1, 1, (1,), ()): ("1", "1"), + (II, 1, 1, 1, (4,), ()): ("L", "L;4"), (II, 1, 1, 2, (1,), ()): ("1", "1;R"), (II, 1, 1, 1, (8,), ()): ("L", "L"), (II, 1, 1, 1, (8, 8), (2,)): ("LA", "LA"), @@ -660,7 +661,7 @@ class TiffImageFile(ImageFile.ImageFile): raise EOFError("no more images in TIFF file") if Image.DEBUG: print("Seeking to frame %s, on frame %s, __next %s, location: %s"% - (frame, self.__frame, self.__next, self.fp.tell())) + (frame, self.__frame, self.__next, self.fp.tell())) # reset python3 buffered io handle in case fp # was passed to libtiff, invalidating the buffer self.fp.tell() @@ -671,7 +672,7 @@ class TiffImageFile(ImageFile.ImageFile): self.__next = self.tag.next self.__frame += 1 self._setup() - + def _tell(self): return self.__frame diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index cf809d5d0..583e91379 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -298,6 +298,17 @@ class TestFileTiff(PillowTestCase): # Assert self.assertEqual(ret, [0, 1]) + def test_4bit(self): + # Arrange + test_file = "Tests/images/hopper_gray_4bpp.tif" + + # Act + im = Image.open(test_file) + + # Assert + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.mode, "L") + if __name__ == '__main__': unittest.main() From 8cb5688047cc71d947a7549b0b119bed37246e7c Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 2 Oct 2014 10:45:41 +0300 Subject: [PATCH 14/48] flake8 --- PIL/TiffImagePlugin.py | 8 ++++---- Tests/test_file_tiff.py | 25 +++++++++++-------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 90babb9b3..f7a0116dd 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -450,10 +450,10 @@ class ImageFileDirectory(collections.MutableMapping): if size > 4: here = fp.tell() if Image.DEBUG: - print ("Tag Location: %s" %here) + print("Tag Location: %s" % here) fp.seek(i32(ifd, 8)) if Image.DEBUG: - print ("Data Location: %s" %fp.tell()) + print("Data Location: %s" % fp.tell()) data = ImageFile._safe_read(fp, size) fp.seek(here) else: @@ -660,14 +660,14 @@ class TiffImageFile(ImageFile.ImageFile): if not self.__next: raise EOFError("no more images in TIFF file") if Image.DEBUG: - print("Seeking to frame %s, on frame %s, __next %s, location: %s"% + print("Seeking to frame %s, on frame %s, __next %s, location: %s" % (frame, self.__frame, self.__next, self.fp.tell())) # reset python3 buffered io handle in case fp # was passed to libtiff, invalidating the buffer self.fp.tell() self.fp.seek(self.__next) if Image.DEBUG: - print("Loading tags, location: %s"%self.fp.tell()) + print("Loading tags, location: %s" % self.fp.tell()) self.tag.load(self.fp) self.__next = self.tag.next self.__frame += 1 diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 583e91379..52db1c85f 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -144,28 +144,27 @@ class TestFileTiff(PillowTestCase): def test_multipage(self): # issue #862 im = Image.open('Tests/images/multipage.tiff') - # file is a multipage tiff, 10x10 green, 10x10 red, 20x20 blue + # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue im.seek(0) - self.assertEqual(im.size, (10,10)) - self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,128,0)) + self.assertEqual(im.size, (10, 10)) + self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 128, 0)) im.seek(1) im.load() - self.assertEqual(im.size, (10,10)) - self.assertEqual(im.convert('RGB').getpixel((0,0)), (255,0,0)) + self.assertEqual(im.size, (10, 10)) + self.assertEqual(im.convert('RGB').getpixel((0, 0)), (255, 0, 0)) im.seek(2) im.load() - self.assertEqual(im.size, (20,20)) - self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,0,255)) + self.assertEqual(im.size, (20, 20)) + self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 0, 255)) def test_multipage_last_frame(self): im = Image.open('Tests/images/multipage-lastframe.tif') im.load() - self.assertEqual(im.size, (20,20)) - self.assertEqual(im.convert('RGB').getpixel((0,0)), (0,0,255)) - + self.assertEqual(im.size, (20, 20)) + self.assertEqual(im.convert('RGB').getpixel((0, 0)), (0, 0, 255)) def test___str__(self): # Arrange @@ -199,7 +198,6 @@ class TestFileTiff(PillowTestCase): def test_load_byte(self): # Arrange - from PIL import TiffImagePlugin ifd = TiffImagePlugin.ImageFileDirectory() data = b"abc" @@ -211,7 +209,6 @@ class TestFileTiff(PillowTestCase): def test_load_string(self): # Arrange - from PIL import TiffImagePlugin ifd = TiffImagePlugin.ImageFileDirectory() data = b"abc\0" @@ -223,7 +220,6 @@ class TestFileTiff(PillowTestCase): def test_load_float(self): # Arrange - from PIL import TiffImagePlugin ifd = TiffImagePlugin.ImageFileDirectory() data = b"abcdabcd" @@ -235,7 +231,6 @@ class TestFileTiff(PillowTestCase): def test_load_double(self): # Arrange - from PIL import TiffImagePlugin ifd = TiffImagePlugin.ImageFileDirectory() data = b"abcdefghabcdefgh" @@ -301,6 +296,7 @@ class TestFileTiff(PillowTestCase): def test_4bit(self): # Arrange test_file = "Tests/images/hopper_gray_4bpp.tif" + original = hopper("L") # Act im = Image.open(test_file) @@ -308,6 +304,7 @@ class TestFileTiff(PillowTestCase): # Assert self.assertEqual(im.size, (128, 128)) self.assertEqual(im.mode, "L") + self.assert_image_similar(im, original, 7.3) if __name__ == '__main__': From fd77bcd5a74ff8525fcc7d3fc2df6782d34e0ef9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Thu, 2 Oct 2014 12:43:49 +0300 Subject: [PATCH 15/48] Fix rename regression --- PIL/ImageCms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index fbc9fab12..ed219f7ba 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -90,8 +90,8 @@ try: except ImportError as ex: # Allow error import for doc purposes, but error out when accessing # anything in core. - from _util import import_err - _imagingcms = import_err(ex) + from _util import deferred_error + _imagingcms = deferred_error(ex) from PIL._util import isStringType core = _imagingcms From 78043591bda9f9b177405f5ed1f27c9dd243c29e Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 2 Oct 2014 06:29:29 -0400 Subject: [PATCH 16/48] Add `make pre` step Feel free to incorporate this better in other sections. --- RELEASING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASING.md b/RELEASING.md index 5a389b1ef..282f4cd51 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -53,3 +53,7 @@ python setup.py sdist upload * [ ] Retrieve the OS X Wheels from Rackspace files, upload to PyPi (Twine?) * [ ] Grab Windows binaries, `twine upload dist/*.[whl|egg]`. Manually upload .exe installers. * [ ] Announce release availability. [Twitter](https://twitter.com/pythonpillow), web. + +## Pre-release check + +* [ ] Run pre-release check via `make pre` From 48c965c3f692bca67e12a560a4588dd9413677ec Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Thu, 2 Oct 2014 06:35:48 -0400 Subject: [PATCH 17/48] Fix manifest Run `make pre` to check MANIFEST --- MANIFEST.in | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 643e8056c..29347a5c9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,10 +1,10 @@ - include *.c include *.h include *.md include *.py include *.rst include *.txt +include *.yaml include .coveragerc include .gitattributes include .travis.yml @@ -44,9 +44,11 @@ recursive-include Tests *.dcx recursive-include Tests *.doc recursive-include Tests *.eps recursive-include Tests *.fli +recursive-include Tests *.ggr recursive-include Tests *.gif recursive-include Tests *.gnuplot recursive-include Tests *.html +recursive-include Tests *.icc recursive-include Tests *.icm recursive-include Tests *.icns recursive-include Tests *.ico @@ -70,6 +72,7 @@ recursive-include Tests *.rst recursive-include Tests *.sgi recursive-include Tests *.spider recursive-include Tests *.tar +recursive-include Tests *.tga recursive-include Tests *.tif recursive-include Tests *.tiff recursive-include Tests *.ttf From c046323587ba635120ae1a2a6057c8daf0be561f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Thu, 2 Oct 2014 10:10:58 -0700 Subject: [PATCH 18/48] Update Releasing.md [ci skip] --- RELEASING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 282f4cd51..1bd8a28ae 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -6,6 +6,7 @@ Released quarterly. * [ ] Get master to the appropriate code release state. [Travis CI](https://travis-ci.org/python-pillow/Pillow) should be running cleanly for all merges to master. * [ ] Update version in `PIL/__init__.py`, `setup.py`, `_imaging.c`, Update date in `CHANGES.rst`. +* [ ] Run pre-release check via `make pre` * [ ] Tag and push to release branch in python-pillow repo. * [ ] Upload binaries. @@ -16,6 +17,7 @@ Released as required for security or installation fixes. * [ ] Make necessary changes in master. * [ ] Cherry pick individual commits. Touch up `CHANGES.rst` to reflect reality. * [ ] Update version in `PIL/__init__.py`, `setup.py`, `_imaging.c` +* [ ] Run pre-release check via `make pre` * [ ] Push to release branch in personal repo. Let Travis run cleanly. * [ ] Tag and push to release branch in python-pillow repo. * [ ] Upload binaries. @@ -28,6 +30,7 @@ Security fixes that need to be pushed to the distros prior to public release. * [ ] Commit against master, cherry pick to affected release branches. * [ ] Run local test matrix on each release & Python version. * [ ] Privately send to distros. +* [ ] Run pre-release check via `make pre` * [ ] Amend any commits with the CVE # * [ ] On release date, tag and push to GitHub. ``` @@ -54,6 +57,3 @@ python setup.py sdist upload * [ ] Grab Windows binaries, `twine upload dist/*.[whl|egg]`. Manually upload .exe installers. * [ ] Announce release availability. [Twitter](https://twitter.com/pythonpillow), web. -## Pre-release check - -* [ ] Run pre-release check via `make pre` From ae5d01995e511e115c90747a6fcd82057cf94790 Mon Sep 17 00:00:00 2001 From: Mike Driscoll Date: Thu, 2 Oct 2014 13:43:28 -0500 Subject: [PATCH 19/48] Update overview.rst --- docs/handbook/overview.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/handbook/overview.rst b/docs/handbook/overview.rst index f1c26e616..b52939b89 100644 --- a/docs/handbook/overview.rst +++ b/docs/handbook/overview.rst @@ -16,7 +16,7 @@ Let’s look at a few possible uses of this library. Image Archives -------------- -The Python Imaging Library is ideal for for image archival and batch processing +The Python Imaging Library is ideal for image archival and batch processing applications. You can use the library to create thumbnails, convert between file formats, print images, etc. From 0ca102f9fe62fec38dfd6ba8e11cf9acb9a5f488 Mon Sep 17 00:00:00 2001 From: Sandro Mani Date: Thu, 2 Oct 2014 18:08:10 +0200 Subject: [PATCH 20/48] Convert file to utf-8 --- PIL/WalImageFile.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/PIL/WalImageFile.py b/PIL/WalImageFile.py index 29af35fa1..fc2bb30a7 100644 --- a/PIL/WalImageFile.py +++ b/PIL/WalImageFile.py @@ -1,5 +1,3 @@ -# -*- coding: iso-8859-1 -*- -# # The Python Imaging Library. # $Id$ # @@ -76,7 +74,7 @@ def open(filename): quake2palette = ( - # default palette taken from piffo 0.93 by Hans Häggström + # default palette taken from piffo 0.93 by Hans Häggström b"\x01\x01\x01\x0b\x0b\x0b\x12\x12\x12\x17\x17\x17\x1b\x1b\x1b\x1e" b"\x1e\x1e\x22\x22\x22\x26\x26\x26\x29\x29\x29\x2c\x2c\x2c\x2f\x2f" b"\x2f\x32\x32\x32\x35\x35\x35\x37\x37\x37\x3a\x3a\x3a\x3c\x3c\x3c" From 7502e6dd454267feb90cab690fb48e7e54763862 Mon Sep 17 00:00:00 2001 From: Sandro Mani Date: Thu, 2 Oct 2014 17:24:08 +0200 Subject: [PATCH 21/48] Remove executable permissions from Jpeg2KEncode.c --- libImaging/Jpeg2KEncode.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 libImaging/Jpeg2KEncode.c diff --git a/libImaging/Jpeg2KEncode.c b/libImaging/Jpeg2KEncode.c old mode 100755 new mode 100644 From 58f2b10e0bf4885d5ca140faaee56bb4329471c4 Mon Sep 17 00:00:00 2001 From: Sandro Mani Date: Fri, 3 Oct 2014 09:59:25 +0200 Subject: [PATCH 22/48] Make OleFileIO.py executable, fix shebang --- PIL/OleFileIO.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) mode change 100644 => 100755 PIL/OleFileIO.py diff --git a/PIL/OleFileIO.py b/PIL/OleFileIO.py old mode 100644 new mode 100755 index e35bfa679..c1cc5d5b6 --- a/PIL/OleFileIO.py +++ b/PIL/OleFileIO.py @@ -1,5 +1,4 @@ -#!/usr/local/bin/python -# -*- coding: latin-1 -*- +#!/usr/bin/env python ## OleFileIO_PL: ## Module to read Microsoft OLE2 files (also called Structured Storage or ## Microsoft Compound Document File Format), such as Microsoft Office From 7bed246e85dfa39cdb80d44b21bb686118fe83ae Mon Sep 17 00:00:00 2001 From: Mike Driscoll Date: Fri, 3 Oct 2014 10:00:50 -0500 Subject: [PATCH 23/48] Update tutorial.rst Just a couple grammatical fixes --- docs/handbook/tutorial.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 05d619f40..c6d2bf9e4 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -296,7 +296,7 @@ Point Operations The :py:meth:`~PIL.Image.Image.point` method can be used to translate the pixel values of an image (e.g. image contrast manipulation). In most cases, a -function object expecting one argument can be passed to the this method. Each +function object expecting one argument can be passed to this method. Each pixel is processed according to that function: Applying point transforms @@ -397,7 +397,7 @@ Note that most drivers in the current version of the library only allow you to seek to the next frame (as in the above example). To rewind the file, you may have to reopen it. -The following iterator class lets you to use the for-statement to loop over the +The following iterator class lets you use the for-statement to loop over the sequence: A sequence iterator class From ba2792083844e701a8c1a5bd477594df16a47d19 Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 4 Oct 2014 23:19:30 +0000 Subject: [PATCH 24/48] Fix for regression in scipy --- PIL/Image.py | 1 + Tests/test_scipy.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 Tests/test_scipy.py diff --git a/PIL/Image.py b/PIL/Image.py index 99ab6327b..3a7c81548 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1530,6 +1530,7 @@ class Image: self.load() + size=tuple(size) if self.size == size: return self._new(self.im) diff --git a/Tests/test_scipy.py b/Tests/test_scipy.py new file mode 100644 index 000000000..e3d1d97cd --- /dev/null +++ b/Tests/test_scipy.py @@ -0,0 +1,42 @@ +from helper import PillowTestCase + +try: + import numpy as np + from numpy.testing import assert_equal + + from scipy import misc + HAS_SCIPY = True +except: + HAS_SCIPY = False + + +class Test_scipy_resize(PillowTestCase): + """ Tests for scipy regression in 2.6.0 """ + + def setUp(self): + if not HAS_SCIPY: + self.skipTest("Scipy Required") + + def test_imresize(self): + im = np.random.random((10,20)) + for T in np.sctypes['float'] + [float]: + # 1.1 rounds to below 1.1 for float16, 1.101 works + im1 = misc.imresize(im,T(1.101)) + self.assertEqual(im1.shape,(11,22)) + + def test_imresize4(self): + im = np.array([[1,2], + [3,4]]) + res = np.array([[ 1. , 1. , 1.5, 2. ], + [ 1. , 1. , 1.5, 2. ], + [ 2. , 2. , 2.5, 3. ], + [ 3. , 3. , 3.5, 4. ]], dtype=np.float32) + # Check that resizing by target size, float and int are the same + im2 = misc.imresize(im, (4,4), mode='F') # output size + im3 = misc.imresize(im, 2., mode='F') # fraction + im4 = misc.imresize(im, 200, mode='F') # percentage + assert_equal(im2, res) + assert_equal(im3, res) + assert_equal(im4, res) + + From aa0c512eb3008f856ee21865164f91c55cb174ad Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 5 Oct 2014 13:12:33 +0300 Subject: [PATCH 25/48] Update CHANGES.rst [CI skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 52419088f..3b77e4b62 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,12 @@ Changelog (Pillow) ================== +2.7.0 (unreleased) +------------------ + +- Fix for regression in SciPy #945 + [wiredfool] + 2.6.0 (2014-10-01) ------------------ From fe03d366eb3a73f411e8794c7acd3f1cadb15265 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 7 Oct 2014 21:40:32 +0300 Subject: [PATCH 26/48] Update CHANGES.rst [CI skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3b77e4b62..37360538d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.7.0 (unreleased) ------------------ +- Fixes for things rpmlint complains about #942 + [manisandro] + - Fix for regression in SciPy #945 [wiredfool] From fe6a8cede7607fdfd44b0b6f39c7464b70c21f3c Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 11 Oct 2014 09:14:39 -0700 Subject: [PATCH 27/48] Merge branch 'master', remote-tracking branch 'origin' From 991c847c7320611f2a68f243513b9107f3954ede Mon Sep 17 00:00:00 2001 From: Eric Soroos Date: Sat, 11 Oct 2014 09:42:10 -0700 Subject: [PATCH 28/48] Skip webp mux with the right skip message --- Tests/test_file_webp_metadata.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index 6aadf9c7e..f2f18d713 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -8,10 +8,13 @@ class TestFileWebpMetadata(PillowTestCase): def setUp(self): try: from PIL import _webp - if not _webp.HAVE_WEBPMUX: - self.skipTest('webpmux support not installed') except: self.skipTest('WebP support not installed') + return + + if not _webp.HAVE_WEBPMUX: + self.skipTest('WebPMux support not installed') + def test_read_exif_metadata(self): From 598f99816836cb09c8dabdacd50480b2e8a932f8 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 11 Oct 2014 09:57:41 -0700 Subject: [PATCH 29/48] Updated Changes.rst [ci skip] --- CHANGES.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 37360538d..3c314445e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,8 +7,14 @@ Changelog (Pillow) - Fixes for things rpmlint complains about #942 [manisandro] -- Fix for regression in SciPy #945 +2.6.1 (2041-10-11) +------------------ + +- Fix SciPy regression in Image.resize #945 [wiredfool] + +- Fix manifest to include all test files. + [aclark] 2.6.0 (2014-10-01) ------------------ From e165d5518d20479dc52d2a86b744f9b2e337a4b9 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sat, 11 Oct 2014 09:59:23 -0700 Subject: [PATCH 30/48] Updated Changes.rst [ci skip] Removed 2041 bug. --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3c314445e..492927871 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,7 +7,7 @@ Changelog (Pillow) - Fixes for things rpmlint complains about #942 [manisandro] -2.6.1 (2041-10-11) +2.6.1 (2014-10-11) ------------------ - Fix SciPy regression in Image.resize #945 From 838afa152861239768951d739e2c8dc8fec9f7b2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 13 Oct 2014 10:45:11 +0300 Subject: [PATCH 31/48] Update CHANGES.rst [CI skip] --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 492927871..d0b6651ff 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,8 @@ Changelog (Pillow) - Fixes for things rpmlint complains about #942 [manisandro] +- Webp Metadata Skip Test comments + [wiredfool] 2.6.1 (2014-10-11) ------------------ From c9619ae61af12818e7bfb8d7f30f98593b44de67 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 13 Oct 2014 10:52:44 +0300 Subject: [PATCH 32/48] Allow latest PyPy 2.4.0 to fail and temporarily add previous passing 2.3.1 for the time-being (#952) --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6f14035e8..6272b2678 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ env: MAX_CONCURRENCY=4 # Then run the remainder. python: - "pypy" + - "pypy-2.3.1" - "pypy3" - 3.4 - 2.7 @@ -69,5 +70,8 @@ after_success: # (Installation is very slow on Py3, so just do it for Py2.) - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi + matrix: - fast_finish: true + - fast_finish: true + - allow_failures: + - python: pypy From 1693e4bf6d8bfc942058898cf3de2faf50b1b0a0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 13 Oct 2014 10:58:57 +0300 Subject: [PATCH 33/48] Fix allow_failures --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6272b2678..084057702 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,6 +72,6 @@ after_success: - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi matrix: - - fast_finish: true - - allow_failures: - - python: pypy + fast_finish: true + allow_failures: + - python: pypy From d8114ce2aa4d53111a5ade58fb028a361c9598eb Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 13 Oct 2014 09:35:55 -0700 Subject: [PATCH 34/48] Updated manifest to follow README.rst reformatting, removed ref to Images directory --- MANIFEST.in | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 29347a5c9..292421671 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,31 +10,17 @@ include .gitattributes include .travis.yml include Makefile include tox.ini -recursive-include Images *.bdf -recursive-include Images *.fli -recursive-include Images *.gif -recursive-include Images *.icns -recursive-include Images *.ico -recursive-include Images *.jpg -recursive-include Images *.pbm -recursive-include Images *.pil -recursive-include Images *.png -recursive-include Images *.ppm -recursive-include Images *.psd -recursive-include Images *.tar -recursive-include Images *.webp -recursive-include Images *.xpm recursive-include PIL *.md recursive-include Sane *.c recursive-include Sane *.py recursive-include Sane *.rst recursive-include Sane *.txt recursive-include Sane CHANGES -recursive-include Sane README +recursive-include Sane README.rst recursive-include Scripts *.py recursive-include Scripts *.rst recursive-include Scripts *.sh -recursive-include Scripts README +recursive-include Scripts README.rst recursive-include Tests *.bdf recursive-include Tests *.bin recursive-include Tests *.bmp @@ -49,7 +35,6 @@ recursive-include Tests *.gif recursive-include Tests *.gnuplot recursive-include Tests *.html recursive-include Tests *.icc -recursive-include Tests *.icm recursive-include Tests *.icns recursive-include Tests *.ico recursive-include Tests *.j2k @@ -81,7 +66,6 @@ recursive-include Tests *.webp recursive-include Tests *.xpm recursive-include Tk *.c recursive-include Tk *.rst -recursive-include Tk *.txt recursive-include depends *.rst recursive-include depends *.sh recursive-include docs *.bat From 4f9b2dbe69a8bcd37f1e8759864883acb4449bce Mon Sep 17 00:00:00 2001 From: wiredfool Date: Mon, 13 Oct 2014 11:12:14 -0700 Subject: [PATCH 35/48] Fix pypy 2.4 regression, #952 --- PIL/TiffImagePlugin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 50648288e..4e76379c0 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -883,6 +883,10 @@ class TiffImageFile(ImageFile.ImageFile): try: fp = hasattr(self.fp, "fileno") and \ os.dup(self.fp.fileno()) + # flush the file descriptor, prevents error on pypy 2.4+ + # should also eliminate the need for fp.tell for py3 + # in _seek + self.fp.flush() except IOError: # io.BytesIO have a fileno, but returns an IOError if # it doesn't use a file descriptor. From bb76b455455bf02ab5391b23efdc4426b9f6ef04 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 13 Oct 2014 22:22:33 +0300 Subject: [PATCH 36/48] Revert "Workaround Pypy regression" --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 084057702..6f14035e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ env: MAX_CONCURRENCY=4 # Then run the remainder. python: - "pypy" - - "pypy-2.3.1" - "pypy3" - 3.4 - 2.7 @@ -70,8 +69,5 @@ after_success: # (Installation is very slow on Py3, so just do it for Py2.) - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-install.sh; fi - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then Scripts/diffcover-run.sh; fi - matrix: fast_finish: true - allow_failures: - - python: pypy From 392abc4f344580066043ffd2fdd2d09c870ce568 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 13 Oct 2014 22:35:15 +0300 Subject: [PATCH 37/48] Update CHANGES.rst [CI skip] --- CHANGES.rst | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d0b6651ff..679eaa73b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,10 +4,17 @@ Changelog (Pillow) 2.7.0 (unreleased) ------------------ +- Updated manifest #957 + [wiredfool] + +- Fix PyPy 2.4 regression #952 + [wiredfool] + +- Webp Metadata Skip Test comments #954 + [wiredfool] + - Fixes for things rpmlint complains about #942 [manisandro] -- Webp Metadata Skip Test comments - [wiredfool] 2.6.1 (2014-10-11) ------------------ From d77cc88482dfbdddb411567d9da2a768e0aa38ae Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 15 Oct 2014 13:19:09 +0300 Subject: [PATCH 38/48] Remove Sane directory now it's in its own repo: https://github.com/python-pillow/Sane --- Sane/CHANGES | 34 - Sane/README.rst | 22 - Sane/_sane.c | 1405 ----------------------------------------- Sane/demo_numarray.py | 41 -- Sane/demo_pil.py | 35 - Sane/sane.py | 288 --------- Sane/sanedoc.txt | 294 --------- Sane/setup.py | 24 - 8 files changed, 2143 deletions(-) delete mode 100644 Sane/CHANGES delete mode 100644 Sane/README.rst delete mode 100644 Sane/_sane.c delete mode 100644 Sane/demo_numarray.py delete mode 100644 Sane/demo_pil.py delete mode 100644 Sane/sane.py delete mode 100644 Sane/sanedoc.txt delete mode 100644 Sane/setup.py diff --git a/Sane/CHANGES b/Sane/CHANGES deleted file mode 100644 index 47fb96cf1..000000000 --- a/Sane/CHANGES +++ /dev/null @@ -1,34 +0,0 @@ - -from V1.0 to V2.0 - -_sane.c: - - Values for option constraints are correctly translated to floats - if value type is TYPE_FIXED for SANE_CONSTRAINT_RANGE and - SANE_CONSTRAINT_WORD_LIST - - added constants INFO_INEXACT, INFO_RELOAD_OPTIONS, - INFO_RELOAD_PARAMS (possible return values of set_option()) - to module dictionnary. - - removed additional return variable 'i' from SaneDev_get_option(), - because it is only set when SANE_ACTION_SET_VALUE is used. - - scanDev.get_parameters() now returns the scanner mode as 'format', - no more the typical PIL codes. So 'L' became 'gray', 'RGB' is now - 'color', 'R' is 'red', 'G' is 'green', 'B' is 'red'. This matches - the way scanDev.mode is set. - This should be the only incompatibility vs. version 1.0. - -sane.py - - ScanDev got new method __load_option_dict() called from __init__() - and from __setattr__() if backend reported that the frontend should - reload the options. - - Nice human-readable __repr__() method added for class Option - - if __setattr__ (i.e. set_option) reports that all other options - have to be reloaded due to a change in the backend then they are reloaded. - - due to the change in SaneDev_get_option() only the 'value' is - returned from get_option(). - - in __setattr__ integer values are automatically converted to floats - if SANE backend expects SANE_FIXED (i.e. fix-point float) - - The scanner options can now directly be accessed via scanDev[optionName] - instead scanDev.opt[optionName]. (The old way still works). - -V1.0: - A.M. Kuchling's original pysane package. \ No newline at end of file diff --git a/Sane/README.rst b/Sane/README.rst deleted file mode 100644 index 173934040..000000000 --- a/Sane/README.rst +++ /dev/null @@ -1,22 +0,0 @@ -Python SANE module V1.1 (30 Sep. 2004) -================================================================================ - -The SANE module provides an interface to the SANE scanner and frame -grabber interface for Linux. This module was contributed by Andrew -Kuchling and is extended and currently maintained by Ralph Heinkel -(rheinkel-at-email.de). If you write to me please make sure to have the -word 'SANE' or 'sane' in the subject of your mail, otherwise it might -be classified as spam in the future. - - -To build this module, type (in the Sane directory):: - - python setup.py build - -In order to install the module type:: - - python setup.py install - - -For some basic documentation please look at the file sanedoc.txt -The two demo_*.py scripts give basic examples on how to use the software. diff --git a/Sane/_sane.c b/Sane/_sane.c deleted file mode 100644 index 2ebcb1834..000000000 --- a/Sane/_sane.c +++ /dev/null @@ -1,1405 +0,0 @@ -/*********************************************************** -(C) Copyright 2003 A.M. Kuchling. All Rights Reserved -(C) Copyright 2004 A.M. Kuchling, Ralph Heinkel All Rights Reserved - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, -provided that the above copyright notice appear in all copies and that -both that copyright notice and this permission notice appear in -supporting documentation, and that the name of A.M. Kuchling and -Ralph Heinkel not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior permission. - -A.M. KUCHLING, R.H. HEINKEL DISCLAIM ALL WARRANTIES WITH REGARD TO THIS -SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR -CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF -USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. - -******************************************************************/ - -/* SaneDev objects */ - -#include "Python.h" -#include "Imaging.h" -#include - -#include - -#if PY_MAJOR_VERSION >= 3 - #define PyInt_AsLong PyLong_AsLong - #define PyInt_FromLong PyLong_FromLong - #define PyInt_Check PyLong_Check -#endif - -static PyObject *ErrorObject; - -typedef struct { - PyObject_HEAD - SANE_Handle h; -} SaneDevObject; - -#ifdef WITH_THREAD -PyThreadState *_save; -#endif - -/* Raise a SANE exception */ -PyObject * -PySane_Error(SANE_Status st) -{ - const char *string; - - if (st==SANE_STATUS_GOOD) {Py_INCREF(Py_None); return (Py_None);} - string=sane_strstatus(st); - PyErr_SetString(ErrorObject, string); - return NULL; -} - -static PyTypeObject SaneDev_Type; - -#define SaneDevObject_Check(v) (Py_TYPE(v) == &SaneDev_Type) - -static SaneDevObject * -newSaneDevObject(void) -{ - SaneDevObject *self; - - if (PyType_Ready(&SaneDev_Type) < 0) - return NULL; - - self = PyObject_NEW(SaneDevObject, &SaneDev_Type); - if (self == NULL) - return NULL; - self->h=NULL; - return self; -} - -/* SaneDev methods */ - -static void -SaneDev_dealloc(SaneDevObject *self) -{ - if (self->h) sane_close(self->h); - self->h=NULL; - PyObject_DEL(self); -} - -static PyObject * -SaneDev_close(SaneDevObject *self, PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) - return NULL; - if (self->h) sane_close(self->h); - self->h=NULL; - Py_INCREF(Py_None); - return (Py_None); -} - -static PyObject * -SaneDev_get_parameters(SaneDevObject *self, PyObject *args) -{ - SANE_Status st; - SANE_Parameters p; - char *format="unknown format"; - - if (!PyArg_ParseTuple(args, "")) - return NULL; - if (self->h==NULL) - { - PyErr_SetString(ErrorObject, "SaneDev object is closed"); - return NULL; - } - Py_BEGIN_ALLOW_THREADS - st=sane_get_parameters(self->h, &p); - Py_END_ALLOW_THREADS - - if (st) return PySane_Error(st); - switch (p.format) - { - case(SANE_FRAME_GRAY): format="gray"; break; - case(SANE_FRAME_RGB): format="color"; break; - case(SANE_FRAME_RED): format="red"; break; - case(SANE_FRAME_GREEN): format="green"; break; - case(SANE_FRAME_BLUE): format="blue"; break; - } - - return Py_BuildValue("si(ii)ii", format, p.last_frame, p.pixels_per_line, - p.lines, p.depth, p.bytes_per_line); -} - - -static PyObject * -SaneDev_fileno(SaneDevObject *self, PyObject *args) -{ - SANE_Status st; - SANE_Int fd; - - if (!PyArg_ParseTuple(args, "")) - return NULL; - if (self->h==NULL) - { - PyErr_SetString(ErrorObject, "SaneDev object is closed"); - return NULL; - } - st=sane_get_select_fd(self->h, &fd); - if (st) return PySane_Error(st); - return PyInt_FromLong(fd); -} - -static PyObject * -SaneDev_start(SaneDevObject *self, PyObject *args) -{ - SANE_Status st; - - if (!PyArg_ParseTuple(args, "")) - return NULL; - if (self->h==NULL) - { - PyErr_SetString(ErrorObject, "SaneDev object is closed"); - return NULL; - } - /* sane_start can take several seconds, if the user initiates - a new scan, while the scan head of a flatbed scanner moves - back to the start position after finishing a previous scan. - Hence it is worth to allow threads here. - */ - Py_BEGIN_ALLOW_THREADS - st=sane_start(self->h); - Py_END_ALLOW_THREADS - if (st) return PySane_Error(st); - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject * -SaneDev_cancel(SaneDevObject *self, PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) - return NULL; - if (self->h==NULL) - { - PyErr_SetString(ErrorObject, "SaneDev object is closed"); - return NULL; - } - sane_cancel(self->h); - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject * -SaneDev_get_options(SaneDevObject *self, PyObject *args) -{ - const SANE_Option_Descriptor *d; - PyObject *list, *value; - int i=1; - - if (!PyArg_ParseTuple(args, "")) - return NULL; - if (self->h==NULL) - { - PyErr_SetString(ErrorObject, "SaneDev object is closed"); - return NULL; - } - if (!(list = PyList_New(0))) - return NULL; - - do - { - d=sane_get_option_descriptor(self->h, i); - if (d!=NULL) - { - PyObject *constraint=NULL; - int j; - - switch (d->constraint_type) - { - case(SANE_CONSTRAINT_NONE): - Py_INCREF(Py_None); constraint=Py_None; break; - case(SANE_CONSTRAINT_RANGE): - if (d->type == SANE_TYPE_INT) - constraint=Py_BuildValue("iii", d->constraint.range->min, - d->constraint.range->max, - d->constraint.range->quant); - else - constraint=Py_BuildValue("ddd", - SANE_UNFIX(d->constraint.range->min), - SANE_UNFIX(d->constraint.range->max), - SANE_UNFIX(d->constraint.range->quant)); - break; - case(SANE_CONSTRAINT_WORD_LIST): - constraint=PyList_New(d->constraint.word_list[0]); - if (d->type == SANE_TYPE_INT) - for (j=1; j<=d->constraint.word_list[0]; j++) - PyList_SetItem(constraint, j-1, - PyInt_FromLong(d->constraint.word_list[j])); - else - for (j=1; j<=d->constraint.word_list[0]; j++) - PyList_SetItem(constraint, j-1, - PyFloat_FromDouble(SANE_UNFIX(d->constraint.word_list[j]))); - break; - case(SANE_CONSTRAINT_STRING_LIST): - constraint=PyList_New(0); - for(j=0; d->constraint.string_list[j]!=NULL; j++) - PyList_Append(constraint, -#if PY_MAJOR_VERSION >= 3 - PyUnicode_DecodeLatin1(d->constraint.string_list[j], strlen(d->constraint.string_list[j]), NULL)); -#else - PyString_FromString(d->constraint.string_list[j])); -#endif - break; - } - value=Py_BuildValue("isssiiiiO", i, d->name, d->title, d->desc, - d->type, d->unit, d->size, d->cap, constraint); - PyList_Append(list, value); - } - i++; - } while (d!=NULL); - return list; -} - -static PyObject * -SaneDev_get_option(SaneDevObject *self, PyObject *args) -{ - SANE_Status st; - const SANE_Option_Descriptor *d; - PyObject *value=NULL; - int n; - void *v; - - if (!PyArg_ParseTuple(args, "i", &n)) - { - return NULL; - } - if (self->h==NULL) - { - PyErr_SetString(ErrorObject, "SaneDev object is closed"); - return NULL; - } - d=sane_get_option_descriptor(self->h, n); - v=malloc(d->size+1); - st=sane_control_option(self->h, n, SANE_ACTION_GET_VALUE, - v, NULL); - - if (st) - { - free(v); - return PySane_Error(st); - } - - switch(d->type) - { - case(SANE_TYPE_BOOL): - case(SANE_TYPE_INT): - value=Py_BuildValue("i", *( (SANE_Int*)v) ); - break; - case(SANE_TYPE_FIXED): - value=Py_BuildValue("d", SANE_UNFIX((*((SANE_Fixed*)v))) ); - break; - case(SANE_TYPE_STRING): -#if PY_MAJOR_VERSION >= 3 - value=PyUnicode_DecodeLatin1((const char *) v, strlen((const char *) v), NULL); -#else - value=Py_BuildValue("s", v); -#endif - break; - case(SANE_TYPE_BUTTON): - case(SANE_TYPE_GROUP): - value=Py_BuildValue("O", Py_None); - break; - } - - free(v); - return value; -} - -static PyObject * -SaneDev_set_option(SaneDevObject *self, PyObject *args) -{ - SANE_Status st; - const SANE_Option_Descriptor *d; - SANE_Int i; - PyObject *value; - int n; - void *v; - - if (!PyArg_ParseTuple(args, "iO", &n, &value)) - return NULL; - if (self->h==NULL) - { - PyErr_SetString(ErrorObject, "SaneDev object is closed"); - return NULL; - } - d=sane_get_option_descriptor(self->h, n); - v=malloc(d->size+1); - - switch(d->type) - { - case(SANE_TYPE_BOOL): - if (!PyInt_Check(value)) - { - PyErr_SetString(PyExc_TypeError, "SANE_BOOL requires an integer"); - free(v); - return NULL; - } - /* fall through */ - case(SANE_TYPE_INT): - if (!PyInt_Check(value)) - { - PyErr_SetString(PyExc_TypeError, "SANE_INT requires an integer"); - free(v); - return NULL; - } - *( (SANE_Int*)v) = PyInt_AsLong(value); - break; - case(SANE_TYPE_FIXED): - if (!PyFloat_Check(value)) - { - PyErr_SetString(PyExc_TypeError, "SANE_FIXED requires a floating point number"); - free(v); - return NULL; - } - *( (SANE_Fixed*)v) = SANE_FIX(PyFloat_AsDouble(value)); - break; - case(SANE_TYPE_STRING): -#if PY_MAJOR_VERSION >= 3 - if (!PyUnicode_Check(value)) - { - PyErr_SetString(PyExc_TypeError, "SANE_STRING requires a string"); - free(v); - return NULL; - } - { - PyObject *encoded = PyUnicode_AsLatin1String(value); - - if (!encoded) - return NULL; - - strncpy(v, PyBytes_AsString(encoded), d->size-1); - ((char*)v)[d->size-1] = 0; - Py_DECREF(encoded); - } -#else - if (!PyString_Check(value)) - { - PyErr_SetString(PyExc_TypeError, "SANE_STRING requires a string"); - free(v); - return NULL; - } - strncpy(v, PyString_AsString(value), d->size-1); - ((char*)v)[d->size-1] = 0; -#endif - break; - case(SANE_TYPE_BUTTON): - case(SANE_TYPE_GROUP): - break; - } - - st=sane_control_option(self->h, n, SANE_ACTION_SET_VALUE, - v, &i); - if (st) {free(v); return PySane_Error(st);} - - free(v); - return Py_BuildValue("i", i); -} - -static PyObject * -SaneDev_set_auto_option(SaneDevObject *self, PyObject *args) -{ - SANE_Status st; - SANE_Int i; - int n; - - if (!PyArg_ParseTuple(args, "i", &n)) - return NULL; - if (self->h==NULL) - { - PyErr_SetString(ErrorObject, "SaneDev object is closed"); - return NULL; - } - st=sane_control_option(self->h, n, SANE_ACTION_SET_AUTO, - NULL, &i); - if (st) {return PySane_Error(st);} - - return Py_BuildValue("i", i); - } - -#define READSIZE 32768 - -static PyObject * -SaneDev_snap(SaneDevObject *self, PyObject *args) -{ - SANE_Status st; - /* The buffer should be a multiple of 3 in size, so each sane_read - operation will return an integral number of RGB triples. */ - SANE_Byte buffer[READSIZE]; /* XXX how big should the buffer be? */ - SANE_Int len, lastlen; - Imaging im; - SANE_Parameters p; - int px, py, remain, cplen, bufpos, padbytes; - long L; - char errmsg[80]; - union - { char c[2]; - INT16 i16; - } - endian; - PyObject *pyNoCancel = NULL; - int noCancel = 0; - - endian.i16 = 1; - - if (!PyArg_ParseTuple(args, "l|O", &L, &pyNoCancel)) - return NULL; - if (self->h==NULL) - { - PyErr_SetString(ErrorObject, "SaneDev object is closed"); - return NULL; - } - im=(Imaging)L; - - if (pyNoCancel) - noCancel = PyObject_IsTrue(pyNoCancel); - - st=SANE_STATUS_GOOD; px=py=0; - /* xxx not yet implemented - - handscanner support (i.e., unknown image length during start) - - generally: move the functionality from method snap in sane.py - down here -- I don't like this cross-dependency. - we need to call sane_get_parameters here, and we can create - the result Image object here. - */ - - Py_UNBLOCK_THREADS - sane_get_parameters(self->h, &p); - if (p.format == SANE_FRAME_GRAY) - { - switch (p.depth) - { - case 1: - remain = p.bytes_per_line * im->ysize; - padbytes = p.bytes_per_line - (im->xsize+7)/8; - bufpos = 0; - lastlen = len = 0; - while (st!=SANE_STATUS_EOF && py < im->ysize) - { - while (len > 0 && py < im->ysize) - { - int i, j, k; - j = buffer[bufpos++]; - k = 0x80; - for (i = 0; i < 8 && px < im->xsize; i++) - { - im->image8[py][px++] = (k&j) ? 0 : 0xFF; - k = k >> 1; - } - len--; - if (px >= im->xsize) - { - bufpos += padbytes; - len -= padbytes; - py++; - px = 0; - } - } - st=sane_read(self->h, buffer, - remainh); - Py_BLOCK_THREADS - return PySane_Error(st); - } - bufpos -= lastlen; - lastlen = len; - remain -= len; - /* skip possible pad bytes at the start of the buffer */ - len -= bufpos; - } - break; - case 8: - remain = p.bytes_per_line * im->ysize; - padbytes = p.bytes_per_line - im->xsize; - bufpos = 0; - len = 0; - while (st!=SANE_STATUS_EOF && py < im->ysize) - { - while (len > 0 && py < im->ysize) - { - cplen = len; - if (px + cplen >= im->xsize) - cplen = im->xsize - px; - memcpy(&im->image8[py][px], &buffer[bufpos], cplen); - len -= cplen; - bufpos += cplen; - px += cplen; - if (px >= im->xsize) - { - px = 0; - py++; - bufpos += padbytes; - len -= padbytes; - } - } - bufpos = -len; - - st=sane_read(self->h, buffer, - remainh); - Py_BLOCK_THREADS - return PySane_Error(st); - } - remain -= len; - len -= bufpos; - } - break; - case 16: - remain = p.bytes_per_line * im->ysize; - padbytes = p.bytes_per_line - 2 * im->xsize; - bufpos = endian.c[0]; - lastlen = len = 0; - while (st!=SANE_STATUS_EOF && py < im->ysize) - { - while (len > 0 && py < im->ysize) - { - im->image8[py][px++] = buffer[bufpos]; - bufpos += 2; - len -= 2; - if (px >= im->xsize) - { - bufpos += padbytes; - len -= padbytes; - py++; - px = 0; - } - } - st=sane_read(self->h, buffer, - remainh); - Py_BLOCK_THREADS - return PySane_Error(st); - } - remain -= len; - bufpos -= lastlen; - lastlen = len; - len -= bufpos; - } - break; - default: - /* other depths are not formally "illegal" according to the - Sane API, but it's agreed by Sane developers that other - depths than 1, 8, 16 should not be used - */ - sane_cancel(self->h); - Py_BLOCK_THREADS - snprintf(errmsg, 80, "unsupported pixel depth: %i", p.depth); - PyErr_SetString(ErrorObject, errmsg); - return NULL; - } - } - else if (p.format == SANE_FRAME_RGB) - { - int incr, color, pxs, pxmax, bit, val, mask; - switch (p.depth) - { - case 1: - remain = p.bytes_per_line * im->ysize; - padbytes = p.bytes_per_line - ((im->xsize+7)/8) * 3; - bufpos = 0; - len = 0; - lastlen = 0; - pxmax = 4 * im->xsize; - while (st!=SANE_STATUS_EOF && py < im->ysize) - { - pxs = px; - for (color = 0; color < 3; color++) - { - while (len <= 0 && st == SANE_STATUS_GOOD) - { - st=sane_read(self->h, buffer, - remainh); - Py_BLOCK_THREADS - return PySane_Error(st); - } - bufpos -= lastlen; - remain -= len; - lastlen = len; - /* skip possible pad bytes at the start of the buffer */ - len -= bufpos; - } - if (st == SANE_STATUS_EOF) break; - pxs = px; - val = buffer[bufpos++]; - len--; - mask = 0x80; - for (bit = 0; (bit < 8) && (px < pxmax); bit++) - { - ((UINT8**)(im->image32))[py][px] = (val&mask) ? 0xFF : 0; - mask = mask >> 1; - px += 4; - } - pxs++; - px = pxs; - } - if (st == SANE_STATUS_EOF) - break; - for (bit = 0; bit < 8 && px < pxmax; bit++) - { - ((UINT8**)(im->image32))[py][px] = 0; - px += 4; - } - px -= 3; - if (px >= pxmax) - { - bufpos += padbytes; - len -= padbytes; - py++; - px = 0; - } - } - break; - case 8: - case 16: - if (p.depth == 8) - { - padbytes = p.bytes_per_line - 3 * im->xsize; - bufpos = 0; - incr = 1; - } - else - { - padbytes = p.bytes_per_line - 6 * im->xsize; - bufpos = endian.c[0]; - incr = 2; - } - remain = p.bytes_per_line * im->ysize; - len = 0; - lastlen = 0; - pxmax = 4 * im->xsize; - /* probably not very efficient. But we have to deal with these - possible conditions: - - we may have padding bytes at the end of a scan line - - the number of bytes read with sane_read may be smaller - than the number of pad bytes - - the buffer may become empty after setting any of the - red/green/blue pixel values - - */ - while (st != SANE_STATUS_EOF && py < im->ysize) - { - for (color = 0; color < 3; color++) - { - while (len <= 0 && st == SANE_STATUS_GOOD) - { - bufpos -= lastlen; - if (remain == 0) - { - sane_cancel(self->h); - Py_BLOCK_THREADS - PyErr_SetString(ErrorObject, "internal _sane error: premature end of scan"); - return NULL; - } - st = sane_read(self->h, buffer, - remain<(READSIZE) ? remain : (READSIZE), &len); - if (st && (st!=SANE_STATUS_EOF)) - { - sane_cancel(self->h); - Py_BLOCK_THREADS - return PySane_Error(st); - } - lastlen = len; - remain -= len; - len -= bufpos; - } - if (st == SANE_STATUS_EOF) break; - ((UINT8**)(im->image32))[py][px++] = buffer[bufpos]; - bufpos += incr; - len -= incr; - } - if (st == SANE_STATUS_EOF) break; - - ((UINT8**)(im->image32))[py][px++] = 0; - - if (px >= pxmax) - { - px = 0; - py++; - bufpos += padbytes; - len -= padbytes; - } - } - break; - default: - Py_BLOCK_THREADS - sane_cancel(self->h); - snprintf(errmsg, 80, "unsupported pixel depth: %i", p.depth); - PyErr_SetString(ErrorObject, errmsg); - return NULL; - } - - } - else /* should be SANE_FRAME_RED, GREEN or BLUE */ - { - int lastlen, pxa, pxmax, offset, incr, frame_count = 0; - /* at least the Sane test backend behaves a bit weird, if - it returns "premature EOF" for sane_read, i.e., if the - option "return value of sane_read" is set to SANE_STATUS_EOF. - In this case, the test backend does not advance to the next frame, - so p.last_frame will never be set... - So let's count the number of frames we try to acquire - */ - while (!p.last_frame && frame_count < 4) - { - frame_count++; - st = sane_get_parameters(self->h, &p); - if (st) - { - sane_cancel(self->h); - Py_BLOCK_THREADS - return PySane_Error(st); - } - remain = p.bytes_per_line * im->ysize; - bufpos = 0; - len = 0; - lastlen = 0; - py = 0; - switch (p.format) - { - case SANE_FRAME_RED: - offset = 0; - break; - case SANE_FRAME_GREEN: - offset = 1; - break; - case SANE_FRAME_BLUE: - offset = 2; - break; - default: - sane_cancel(self->h); - Py_BLOCK_THREADS - snprintf(errmsg, 80, "unknown/invalid frame format: %i", p.format); - PyErr_SetString(ErrorObject, errmsg); - return NULL; - } - px = offset; - pxa = 3; - pxmax = im->xsize * 4; - switch (p.depth) - { - case 1: - padbytes = p.bytes_per_line - (im->xsize+7)/8; - st = SANE_STATUS_GOOD; - while (st != SANE_STATUS_EOF && py < im->ysize) - { - while (len > 0) - { - int bit, mask, val; - val = buffer[bufpos++]; len--; - mask = 0x80; - for (bit = 0; bit < 8 && px < pxmax; bit++) - { - ((UINT8**)(im->image32))[py][px] - = val&mask ? 0xFF : 0; - ((UINT8**)(im->image32))[py][pxa] = 0; - px += 4; - pxa += 4; - mask = mask >> 1; - } - - if (px >= pxmax) - { - px = offset; - pxa = 3; - py++; - bufpos += padbytes; - len -= padbytes; - } - } - while (len <= 0 && st == SANE_STATUS_GOOD && remain > 0) - { - bufpos -= lastlen; - st = sane_read(self->h, buffer, - remain<(READSIZE) ? remain : (READSIZE), &len); - if (st && (st!=SANE_STATUS_EOF)) - { - sane_cancel(self->h); - Py_BLOCK_THREADS - return PySane_Error(st); - } - remain -= len; - lastlen = len; - len -= bufpos; - } - } - break; - case 8: - case 16: - if (p.depth == 8) - { - padbytes = p.bytes_per_line - im->xsize; - incr = 1; - } - else - { - padbytes = p.bytes_per_line - 2 * im->xsize; - incr = 2; - bufpos = endian.c[0]; - } - st = SANE_STATUS_GOOD; - while (st != SANE_STATUS_EOF && py < im->ysize) - { - while (len <= 0) - { - bufpos -= lastlen; - if (remain == 0) - { - sane_cancel(self->h); - Py_BLOCK_THREADS - PyErr_SetString(ErrorObject, "internal _sane error: premature end of scan"); - return NULL; - } - st = sane_read(self->h, buffer, - remain<(READSIZE) ? remain : (READSIZE), &len); - if (st && (st!=SANE_STATUS_EOF)) - { - sane_cancel(self->h); - Py_BLOCK_THREADS - return PySane_Error(st); - } - if (st == SANE_STATUS_EOF) - break; - lastlen = len; - remain -= len; - if (bufpos >= len) - len = 0; - else - len -= bufpos; - } - if (st == SANE_STATUS_EOF) - break; - ((UINT8**)(im->image32))[py][px] = buffer[bufpos]; - ((UINT8**)(im->image32))[py][pxa] = 0; - bufpos += incr; - len -= incr; - px += 4; - pxa += 4; - - if (px >= pxmax) - { - px = offset; - pxa = 3; - py++; - bufpos += padbytes; - len -= padbytes; - } - } - break; - default: - sane_cancel(self->h); - Py_BLOCK_THREADS - snprintf(errmsg, 80, "unsupported pixel depth: %i", p.depth); - PyErr_SetString(ErrorObject, errmsg); - return NULL; - } - if (!p.last_frame) - { - /* all sane_read calls in the above loop may return - SANE_STATUS_GOOD, but the backend may need another sane_read - call which returns SANE_STATUS_EOF in order to start - a new frame. - */ - if (st != SANE_STATUS_EOF) - { - do { - st = sane_read(self->h, buffer, READSIZE, &len); - } - while (st == SANE_STATUS_GOOD); - } - if (st != SANE_STATUS_EOF) - { - Py_BLOCK_THREADS - sane_cancel(self->h); - return PySane_Error(st); - } - - st = sane_start(self->h); - if (st) - { - Py_BLOCK_THREADS - return PySane_Error(st); - } - } - } - } - /* enforce SANE_STATUS_EOF. Can be necessary for ADF scans for some backends */ - if (st != SANE_STATUS_EOF) - { - do { - st = sane_read(self->h, buffer, READSIZE, &len); - } - while (st == SANE_STATUS_GOOD); - } - if (st != SANE_STATUS_EOF) - { - sane_cancel(self->h); - Py_BLOCK_THREADS - return PySane_Error(st); - } - - if (!noCancel) - sane_cancel(self->h); - Py_BLOCK_THREADS - Py_INCREF(Py_None); - return Py_None; -} - - -#ifdef WITH_NUMARRAY - -#include "numarray/libnumarray.h" - -/* this global variable is set to 1 in 'init_sane()' after successfully - importing the numarray module. */ -int NUMARRAY_IMPORTED = 0; - -static PyObject * -SaneDev_arr_snap(SaneDevObject *self, PyObject *args) -{ - SANE_Status st; - SANE_Byte buffer[READSIZE]; - SANE_Int len; - SANE_Parameters p; - - PyArrayObject *pyArr = NULL; - NumarrayType arrType; - int line, line_index, buffer_index, remain_bytes_line, num_pad_bytes; - int cp_num_bytes, total_remain, bpp, arr_bytes_per_line; - int pixels_per_line = -1; - char errmsg[80]; - - if (!NUMARRAY_IMPORTED) - { - PyErr_SetString(ErrorObject, "numarray package not available"); - return NULL; - } - - if (!PyArg_ParseTuple(args, "|i", &pixels_per_line)) - return NULL; - if (self->h==NULL) - { - PyErr_SetString(ErrorObject, "SaneDev object is closed"); - return NULL; - } - - sane_get_parameters(self->h, &p); - if (p.format != SANE_FRAME_GRAY) - { - sane_cancel(self->h); - snprintf(errmsg, 80, "numarray only supports gray-scale images"); - PyErr_SetString(ErrorObject, errmsg); - return NULL; - } - - if (p.depth == 8) - { - bpp=1; /* bytes-per-pixel */ - arrType = tUInt8; - } - else if (p.depth == 16) - { - bpp=2; /* bytes-per-pixel */ - arrType = tUInt16; - } - else - { - sane_cancel(self->h); - snprintf(errmsg, 80, "arrsnap: unsupported pixel depth: %i", p.depth); - PyErr_SetString(ErrorObject, errmsg); - return NULL; - } - - if (pixels_per_line < 1) - /* The user can choose a smaller result array than the actual scan */ - pixels_per_line = p.pixels_per_line; - else - if (pixels_per_line > p.pixels_per_line) - { - PyErr_SetString(ErrorObject,"given pixels_per_line too big"); - return NULL; - } - /* important: NumArray have indices like (y, x) !! */ - if (!(pyArr = NA_NewArray(NULL, arrType, 2, p.lines, pixels_per_line))) - { - PyErr_SetString(ErrorObject, "failed to create NumArray object"); - return NULL; - } - - arr_bytes_per_line = pixels_per_line * bpp; - st=SANE_STATUS_GOOD; -#ifdef WRITE_PGM - FILE *fp; - fp = fopen("sane_p5.pgm", "w"); - fprintf(fp, "P5\n%d %d\n%d\n", p.pixels_per_line, - p.lines, (int) pow(2.0, (double) p.depth)-1); -#endif - line_index = line = 0; - remain_bytes_line = arr_bytes_per_line; - total_remain = p.bytes_per_line * p.lines; - num_pad_bytes = p.bytes_per_line - arr_bytes_per_line; - - while (st!=SANE_STATUS_EOF) - { - Py_BEGIN_ALLOW_THREADS - st = sane_read(self->h, buffer, - READSIZE < total_remain ? READSIZE : total_remain, &len); - Py_END_ALLOW_THREADS -#ifdef WRITE_PGM - printf("p5_write: read %d of %d\n", len, READSIZE); - fwrite(buffer, 1, len, fp); -#endif - - buffer_index = 0; - total_remain -= len; - - while (len > 0) - { - /* copy at most the number of bytes that fit into (the rest of) - one line: */ - cp_num_bytes = (len > remain_bytes_line ? remain_bytes_line : len); - remain_bytes_line -= cp_num_bytes; - len -= cp_num_bytes; -#ifdef DEBUG - printf("copying %d bytes from b_idx %d to d_idx %d\n", - cp_num_bytes, buffer_index, - line * arr_bytes_per_line + line_index); - printf("len is now %d\n", len); -#endif - memcpy(pyArr->data + line * arr_bytes_per_line + line_index, - buffer + buffer_index, cp_num_bytes); - - buffer_index += cp_num_bytes; - if (remain_bytes_line ==0) - { - /* The line has been completed, so reinitialize remain_bytes_line - increase the line counter, and reset line_index */ -#ifdef DEBUG - printf("line %d full, skipping %d bytes\n",line,num_pad_bytes); -#endif - remain_bytes_line = arr_bytes_per_line; - line++; - line_index = 0; - /* Skip the number of bytes in the input stream which - are not used: */ - len -= num_pad_bytes; - buffer_index += num_pad_bytes; - } - else - line_index += cp_num_bytes; - } - } -#ifdef WRITE_PGM - fclose(fp); - printf("p5_write finished\n"); -#endif - sane_cancel(self->h); - return (PyObject*) pyArr; -} - - - -#endif /* WITH_NUMARRAY */ - -static PyMethodDef SaneDev_methods[] = { - {"get_parameters", (PyCFunction)SaneDev_get_parameters, 1}, - - {"get_options", (PyCFunction)SaneDev_get_options, 1}, - {"get_option", (PyCFunction)SaneDev_get_option, 1}, - {"set_option", (PyCFunction)SaneDev_set_option, 1}, - {"set_auto_option", (PyCFunction)SaneDev_set_auto_option, 1}, - - {"start", (PyCFunction)SaneDev_start, 1}, - {"cancel", (PyCFunction)SaneDev_cancel, 1}, - {"snap", (PyCFunction)SaneDev_snap, 1}, -#ifdef WITH_NUMARRAY - {"arr_snap", (PyCFunction)SaneDev_arr_snap, 1}, -#endif /* WITH_NUMARRAY */ - {"fileno", (PyCFunction)SaneDev_fileno, 1}, - {"close", (PyCFunction)SaneDev_close, 1}, - {NULL, NULL} /* sentinel */ -}; - -static PyTypeObject SaneDev_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "SaneDev", /*tp_name*/ - sizeof(SaneDevObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - /* methods */ - (destructor)SaneDev_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number */ - 0, /*tp_as_sequence */ - 0, /*tp_as_mapping */ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ - 0, /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - SaneDev_methods, /*tp_methods*/ - 0, /*tp_members*/ - 0, /*tp_getset*/ -}; - -/* --------------------------------------------------------------------- */ - -static PyObject * -PySane_init(PyObject *self, PyObject *args) -{ - SANE_Status st; - SANE_Int version; - - if (!PyArg_ParseTuple(args, "")) - return NULL; - - /* XXX Authorization is not yet supported */ - st=sane_init(&version, NULL); - if (st) return PySane_Error(st); - return Py_BuildValue("iiii", version, SANE_VERSION_MAJOR(version), - SANE_VERSION_MINOR(version), SANE_VERSION_BUILD(version)); -} - -static PyObject * -PySane_exit(PyObject *self, PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) - return NULL; - - sane_exit(); - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject * -PySane_get_devices(PyObject *self, PyObject *args) -{ - const SANE_Device **devlist; - const SANE_Device *dev; - SANE_Status st; - PyObject *list; - int local_only = 0, i; - - if (!PyArg_ParseTuple(args, "|i", &local_only)) - { - return NULL; - } - - Py_BEGIN_ALLOW_THREADS - st=sane_get_devices(&devlist, local_only); - Py_END_ALLOW_THREADS - if (st) return PySane_Error(st); - if (!(list = PyList_New(0))) - return NULL; - for(i=0; devlist[i]!=NULL; i++) - { - dev=devlist[i]; - PyList_Append(list, Py_BuildValue("ssss", dev->name, dev->vendor, - dev->model, dev->type)); - } - - return list; -} - -/* Function returning new SaneDev object */ - -static PyObject * -PySane_open(PyObject *self, PyObject *args) -{ - SaneDevObject *rv; - SANE_Status st; - char *name; - - if (!PyArg_ParseTuple(args, "s", &name)) - return NULL; - rv = newSaneDevObject(); - if ( rv == NULL ) - return NULL; - Py_BEGIN_ALLOW_THREADS - st = sane_open(name, &(rv->h)); - Py_END_ALLOW_THREADS - if (st) - { - Py_DECREF(rv); - return PySane_Error(st); - } - return (PyObject *)rv; -} - -static PyObject * -PySane_OPTION_IS_ACTIVE(PyObject *self, PyObject *args) -{ - SANE_Int cap; - long lg; - - if (!PyArg_ParseTuple(args, "l", &lg)) - return NULL; - cap=lg; - return PyInt_FromLong( SANE_OPTION_IS_ACTIVE(cap)); -} - -static PyObject * -PySane_OPTION_IS_SETTABLE(PyObject *self, PyObject *args) -{ - SANE_Int cap; - long lg; - - if (!PyArg_ParseTuple(args, "l", &lg)) - return NULL; - cap=lg; - return PyInt_FromLong( SANE_OPTION_IS_SETTABLE(cap)); -} - - -/* List of functions defined in the module */ - -static PyMethodDef PySane_methods[] = { - {"init", PySane_init, 1}, - {"exit", PySane_exit, 1}, - {"get_devices", PySane_get_devices, 1}, - {"_open", PySane_open, 1}, - {"OPTION_IS_ACTIVE", PySane_OPTION_IS_ACTIVE, 1}, - {"OPTION_IS_SETTABLE", PySane_OPTION_IS_SETTABLE, 1}, - {NULL, NULL} /* sentinel */ -}; - - -static void -insint(PyObject *d, char *name, int value) -{ - PyObject *v = PyInt_FromLong((long) value); - if (!v || PyDict_SetItemString(d, name, v)) - Py_FatalError("can't initialize sane module"); - - Py_DECREF(v); -} - -#if PY_MAJOR_VERSION >= 3 -static struct PyModuleDef PySane_moduledef = { - PyModuleDef_HEAD_INIT, - "_sane", - NULL, - 0, - PySane_methods, - NULL, - NULL, - NULL, - NULL -}; - -PyMODINIT_FUNC -PyInit__sane(void) -{ - /* Create the module and add the functions */ - PyObject *m = PyModule_Create(&PySane_moduledef); - if(!m) - return NULL; -#else /* if PY_MAJOR_VERSION < 3 */ - -PyMODINIT_FUNC -init_sane(void) -{ - /* Create the module and add the functions */ - PyObject *m = Py_InitModule("_sane", PySane_methods); - if(!m) - return; -#endif - - /* Add some symbolic constants to the module */ - PyObject *d = PyModule_GetDict(m); - ErrorObject = PyErr_NewException("_sane.error", NULL, NULL); - PyDict_SetItemString(d, "error", ErrorObject); - - insint(d, "INFO_INEXACT", SANE_INFO_INEXACT); - insint(d, "INFO_RELOAD_OPTIONS", SANE_INFO_RELOAD_OPTIONS); - insint(d, "RELOAD_PARAMS", SANE_INFO_RELOAD_PARAMS); - - insint(d, "FRAME_GRAY", SANE_FRAME_GRAY); - insint(d, "FRAME_RGB", SANE_FRAME_RGB); - insint(d, "FRAME_RED", SANE_FRAME_RED); - insint(d, "FRAME_GREEN", SANE_FRAME_GREEN); - insint(d, "FRAME_BLUE", SANE_FRAME_BLUE); - - insint(d, "CONSTRAINT_NONE", SANE_CONSTRAINT_NONE); - insint(d, "CONSTRAINT_RANGE", SANE_CONSTRAINT_RANGE); - insint(d, "CONSTRAINT_WORD_LIST", SANE_CONSTRAINT_WORD_LIST); - insint(d, "CONSTRAINT_STRING_LIST", SANE_CONSTRAINT_STRING_LIST); - - insint(d, "TYPE_BOOL", SANE_TYPE_BOOL); - insint(d, "TYPE_INT", SANE_TYPE_INT); - insint(d, "TYPE_FIXED", SANE_TYPE_FIXED); - insint(d, "TYPE_STRING", SANE_TYPE_STRING); - insint(d, "TYPE_BUTTON", SANE_TYPE_BUTTON); - insint(d, "TYPE_GROUP", SANE_TYPE_GROUP); - - insint(d, "UNIT_NONE", SANE_UNIT_NONE); - insint(d, "UNIT_PIXEL", SANE_UNIT_PIXEL); - insint(d, "UNIT_BIT", SANE_UNIT_BIT); - insint(d, "UNIT_MM", SANE_UNIT_MM); - insint(d, "UNIT_DPI", SANE_UNIT_DPI); - insint(d, "UNIT_PERCENT", SANE_UNIT_PERCENT); - insint(d, "UNIT_MICROSECOND", SANE_UNIT_MICROSECOND); - - insint(d, "CAP_SOFT_SELECT", SANE_CAP_SOFT_SELECT); - insint(d, "CAP_HARD_SELECT", SANE_CAP_HARD_SELECT); - insint(d, "CAP_SOFT_DETECT", SANE_CAP_SOFT_DETECT); - insint(d, "CAP_EMULATED", SANE_CAP_EMULATED); - insint(d, "CAP_AUTOMATIC", SANE_CAP_AUTOMATIC); - insint(d, "CAP_INACTIVE", SANE_CAP_INACTIVE); - insint(d, "CAP_ADVANCED", SANE_CAP_ADVANCED); - - /* handy for checking array lengths: */ - insint(d, "SANE_WORD_SIZE", sizeof(SANE_Word)); - - /* possible return values of set_option() */ - insint(d, "INFO_INEXACT", SANE_INFO_INEXACT); - insint(d, "INFO_RELOAD_OPTIONS", SANE_INFO_RELOAD_OPTIONS); - insint(d, "INFO_RELOAD_PARAMS", SANE_INFO_RELOAD_PARAMS); - - /* Check for errors */ - if (PyErr_Occurred()) - Py_FatalError("can't initialize module _sane"); - -#ifdef WITH_NUMARRAY - import_libnumarray(); - if (PyErr_Occurred()) - PyErr_Clear(); - else - /* this global variable is declared just in front of the - arr_snap() function and should be set to 1 after - successfully importing the numarray module. */ - NUMARRAY_IMPORTED = 1; - -#endif /* WITH_NUMARRAY */ -#if PY_MAJOR_VERSION >= 3 - return m; -#endif -} diff --git a/Sane/demo_numarray.py b/Sane/demo_numarray.py deleted file mode 100644 index 57fcc4407..000000000 --- a/Sane/demo_numarray.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python - -# -# Shows how to scan a 16 bit grayscale image into a numarray object -# - -from __future__ import print_function - -# Get the path set up to find PIL modules if not installed yet: -import sys ; sys.path.append('../PIL') - -from numarray import * -import sane -import Image - -def toImage(arr): - if arr.type().bytes == 1: - # need to swap coordinates btw array and image (with [::-1]) - im = Image.frombytes('L', arr.shape[::-1], arr.tostring()) - else: - arr_c = arr - arr.min() - arr_c *= (255./arr_c.max()) - arr = arr_c.astype(UInt8) - # need to swap coordinates btw array and image (with [::-1]) - im = Image.frombytes('L', arr.shape[::-1], arr.tostring()) - return im - -print('SANE version:', sane.init()) -print('Available devices=', sane.get_devices()) - -s = sane.open(sane.get_devices()[0][0]) - -# Set scan parameters -s.mode = 'gray' -s.br_x=320. ; s.br_y=240. - -print('Device parameters:', s.get_parameters()) - -s.depth=16 -arr16 = s.arr_scan() -toImage(arr16).show() diff --git a/Sane/demo_pil.py b/Sane/demo_pil.py deleted file mode 100644 index 490f33158..000000000 --- a/Sane/demo_pil.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python - -# -# Shows how to scan a color image into a PIL rgb-image -# - -from __future__ import print_function - -# Get the path set up to find PIL modules if not installed yet: -import sys ; sys.path.append('../PIL') - -import sane -print('SANE version:', sane.init()) -print('Available devices=', sane.get_devices()) - -s = sane.open(sane.get_devices()[0][0]) - -s.mode = 'color' -s.br_x=320. ; s.br_y=240. - -print('Device parameters:', s.get_parameters()) - -# Initiate the scan -s.start() - -# Get an Image object -# (For my B&W QuickCam, this is a grey-scale image. Other scanning devices -# may return a -im=s.snap() - -# Write the image out as a GIF file -#im.save('foo.gif') - -# The show method() simply saves the image to a temporary file and calls "xv". -im.show() diff --git a/Sane/sane.py b/Sane/sane.py deleted file mode 100644 index 331776f95..000000000 --- a/Sane/sane.py +++ /dev/null @@ -1,288 +0,0 @@ -# sane.py -# -# Python wrapper on top of the _sane module, which is in turn a very -# thin wrapper on top of the SANE library. For a complete understanding -# of SANE, consult the documentation at the SANE home page: -# http://www.mostang.com/sane/ . - -__version__ = '2.0' -__author__ = ['Andrew Kuchling', 'Ralph Heinkel'] - -from PIL import Image - -import _sane -from _sane import * - -TYPE_STR = { TYPE_BOOL: "TYPE_BOOL", TYPE_INT: "TYPE_INT", - TYPE_FIXED: "TYPE_FIXED", TYPE_STRING: "TYPE_STRING", - TYPE_BUTTON: "TYPE_BUTTON", TYPE_GROUP: "TYPE_GROUP" } - -UNIT_STR = { UNIT_NONE: "UNIT_NONE", - UNIT_PIXEL: "UNIT_PIXEL", - UNIT_BIT: "UNIT_BIT", - UNIT_MM: "UNIT_MM", - UNIT_DPI: "UNIT_DPI", - UNIT_PERCENT: "UNIT_PERCENT", - UNIT_MICROSECOND: "UNIT_MICROSECOND" } - - -class Option: - """Class representing a SANE option. - Attributes: - index -- number from 0 to n, giving the option number - name -- a string uniquely identifying the option - title -- single-line string containing a title for the option - desc -- a long string describing the option; useful as a help message - type -- type of this option. Possible values: TYPE_BOOL, - TYPE_INT, TYPE_STRING, and so forth. - unit -- units of this option. Possible values: UNIT_NONE, - UNIT_PIXEL, etc. - size -- size of the value in bytes - cap -- capabilities available; CAP_EMULATED, CAP_SOFT_SELECT, etc. - constraint -- constraint on values. Possible values: - None : No constraint - (min,max,step) Integer values, from min to max, stepping by - list of integers or strings: only the listed values are allowed - """ - - def __init__(self, args, scanDev): - self.scanDev = scanDev # needed to get current value of this option - self.index, self.name = args[0], args[1] - self.title, self.desc = args[2], args[3] - self.type, self.unit = args[4], args[5] - self.size, self.cap = args[6], args[7] - self.constraint = args[8] - def f(x): - if x=='-': return '_' - else: return x - if not isinstance(self.name, str): self.py_name=str(self.name) - else: self.py_name=''.join(map(f, self.name)) - - def is_active(self): - return _sane.OPTION_IS_ACTIVE(self.cap) - def is_settable(self): - return _sane.OPTION_IS_SETTABLE(self.cap) - def __repr__(self): - if self.is_settable(): - settable = 'yes' - else: - settable = 'no' - if self.is_active(): - active = 'yes' - curValue = repr(getattr(self.scanDev, self.py_name)) - else: - active = 'no' - curValue = '' - s = """\nName: %s -Cur value: %s -Index: %d -Title: %s -Desc: %s -Type: %s -Unit: %s -Constr: %s -active: %s -settable: %s\n""" % (self.py_name, curValue, - self.index, self.title, self.desc, - TYPE_STR[self.type], UNIT_STR[self.unit], - repr(self.constraint), active, settable) - return s - - -class _SaneIterator: - """ intended for ADF scans. - """ - - def __init__(self, device): - self.device = device - - def __iter__(self): - return self - - def __del__(self): - self.device.cancel() - - def next(self): - try: - self.device.start() - except error as v: - if v == 'Document feeder out of documents': - raise StopIteration - else: - raise - return self.device.snap(1) - - - -class SaneDev: - """Class representing a SANE device. - Methods: - start() -- initiate a scan, using the current settings - snap() -- snap a picture, returning an Image object - arr_snap() -- snap a picture, returning a numarray object - cancel() -- cancel an in-progress scanning operation - fileno() -- return the file descriptor for the scanner (handy for select) - - Also available, but rather low-level: - get_parameters() -- get the current parameter settings of the device - get_options() -- return a list of tuples describing all the options. - - Attributes: - optlist -- list of option names - - You can also access an option name to retrieve its value, and to - set it. For example, if one option has a .name attribute of - imagemode, and scanner is a SaneDev object, you can do: - print scanner.imagemode - scanner.imagemode = 'Full frame' - scanner.['imagemode'] returns the corresponding Option object. - """ - def __init__(self, devname): - d=self.__dict__ - d['sane_signature'] = self._getSaneSignature(devname) - d['scanner_model'] = d['sane_signature'][1:3] - d['dev'] = _sane._open(devname) - self.__load_option_dict() - - def _getSaneSignature(self, devname): - devices = get_devices() - if not devices: - raise RuntimeError('no scanner available') - for dev in devices: - if devname == dev[0]: - return dev - raise RuntimeError('no such scan device "%s"' % devname) - - def __load_option_dict(self): - d=self.__dict__ - d['opt']={} - optlist=d['dev'].get_options() - for t in optlist: - o=Option(t, self) - if o.type!=TYPE_GROUP: - d['opt'][o.py_name]=o - - def __setattr__(self, key, value): - dev=self.__dict__['dev'] - optdict=self.__dict__['opt'] - if key not in optdict: - self.__dict__[key]=value ; return - opt=optdict[key] - if opt.type==TYPE_GROUP: - raise AttributeError("Groups can't be set: "+key) - if not _sane.OPTION_IS_ACTIVE(opt.cap): - raise AttributeError('Inactive option: '+key) - if not _sane.OPTION_IS_SETTABLE(opt.cap): - raise AttributeError("Option can't be set by software: "+key) - if isinstance(value, int) and opt.type == TYPE_FIXED: - # avoid annoying errors of backend if int is given instead float: - value = float(value) - self.last_opt = dev.set_option(opt.index, value) - # do binary AND to find if we have to reload options: - if self.last_opt & INFO_RELOAD_OPTIONS: - self.__load_option_dict() - - def __getattr__(self, key): - dev=self.__dict__['dev'] - optdict=self.__dict__['opt'] - if key=='optlist': - return list(self.opt.keys()) - if key=='area': - return (self.tl_x, self.tl_y),(self.br_x, self.br_y) - if key not in optdict: - raise AttributeError('No such attribute: '+key) - opt=optdict[key] - if opt.type==TYPE_BUTTON: - raise AttributeError("Buttons don't have values: "+key) - if opt.type==TYPE_GROUP: - raise AttributeError("Groups don't have values: "+key) - if not _sane.OPTION_IS_ACTIVE(opt.cap): - raise AttributeError('Inactive option: '+key) - value = dev.get_option(opt.index) - return value - - def __getitem__(self, key): - return self.opt[key] - - def get_parameters(self): - """Return a 5-tuple holding all the current device settings: - (format, last_frame, (pixels_per_line, lines), depth, bytes_per_line) - -- format is one of 'L' (grey), 'RGB', 'R' (red), 'G' (green), 'B' (blue). -- last_frame [bool] indicates if this is the last frame of a multi frame image -- (pixels_per_line, lines) specifies the size of the scanned image (x,y) -- lines denotes the number of scanlines per frame -- depth gives number of pixels per sample -""" - return self.dev.get_parameters() - - def get_options(self): - "Return a list of tuples describing all the available options" - return self.dev.get_options() - - def start(self): - "Initiate a scanning operation" - return self.dev.start() - - def cancel(self): - "Cancel an in-progress scanning operation" - return self.dev.cancel() - - def snap(self, no_cancel=0): - "Snap a picture, returning a PIL image object with the results" - (mode, last_frame, - (xsize, ysize), depth, bytes_per_line) = self.get_parameters() - if mode in ['gray', 'red', 'green', 'blue']: - format = 'L' - elif mode == 'color': - format = 'RGB' - else: - raise ValueError('got unknown "mode" from self.get_parameters()') - im=Image.new(format, (xsize,ysize)) - self.dev.snap( im.im.id, no_cancel ) - return im - - def scan(self): - self.start() - return self.snap() - - def multi_scan(self): - return _SaneIterator(self) - - def arr_snap(self, multipleOf=1): - """Snap a picture, returning a numarray object with the results. - By default the resulting array has the same number of pixels per - line as specified in self.get_parameters()[2][0] - However sometimes it is necessary to obtain arrays where - the number of pixels per line is e.g. a multiple of 4. This can then - be achieved with the option 'multipleOf=4'. So if the scanner - scanned 34 pixels per line, you will obtain an array with 32 pixels - per line. - """ - (mode, last_frame, (xsize, ysize), depth, bpl) = self.get_parameters() - if not mode in ['gray', 'red', 'green', 'blue']: - raise RuntimeError('arr_snap() only works with monochrome images') - if multipleOf < 1: - raise ValueError('option "multipleOf" must be a positive number') - elif multipleOf > 1: - pixels_per_line = xsize - divmod(xsize, 4)[1] - else: - pixels_per_line = xsize - return self.dev.arr_snap(pixels_per_line) - - def arr_scan(self, multipleOf=1): - self.start() - return self.arr_snap(multipleOf=multipleOf) - - def fileno(self): - "Return the file descriptor for the scanning device" - return self.dev.fileno() - - def close(self): - self.dev.close() - - -def open(devname): - "Open a device for scanning" - new=SaneDev(devname) - return new diff --git a/Sane/sanedoc.txt b/Sane/sanedoc.txt deleted file mode 100644 index f23000122..000000000 --- a/Sane/sanedoc.txt +++ /dev/null @@ -1,294 +0,0 @@ -The _sane_ module is an Python interface to the SANE (Scanning is Now -Easy) library, which provides access to various raster scanning -devices such as flatbed scanners and digital cameras. For more -information about SANE, consult the SANE Web site at -http://www.mostang.com/sane/ . Note that this -documentation doesn't duplicate all the information in the SANE -documentation, which you must also consult to get a complete -understanding. - -This module has been originally developed by A.M. Kuchling (amk1@erols.com), -now development has been taken over by Ralph Heinkel (rheinkel-at-email.de). -If you write to me please make sure to have the word 'SANE' or 'sane' in -the subject of your mail, otherwise it might be classified as spam in the -future. - - -The module exports two object types, a bunch of constants, and two -functions. - -get_devices() - Return a list of 4-tuples containing the available scanning - devices. Each tuple contains 4 strings: the device name, suitable for - passing to _open()_; the device's vendor; the model; and the type of - device, such as 'virtual device' or 'video camera'. - - >>> import sane ; sane.get_devices() - [('epson:libusb:001:004', 'Epson', 'GT-8300', 'flatbed scanner')] - -open(devicename) - Open a device, given a string containing its name. SANE - devices have names like 'epson:libusb:001:004'. If the attempt - to open the device fails, a _sane.error_ exception will be raised. If - there are no problems, a SaneDev object will be returned. - As an easy way to open the scanner (if only one is available) just type - >>> sane.open(sane.get_devices()[0][0]) - - -SaneDev objects -=============== - -The basic process of scanning an image consists of getting a SaneDev -object for the device, setting various parameters, starting the scan, -and then reading the image data. Images are composed of one or more -frames; greyscale and one-pass colour scanners return a single frame -containing all the image data, but 3-pass scanners will usually return -3 frames, one for each of the red, green, blue channels. - -Methods: --------- -fileno() - Returns a file descriptor for the scanning device. This - method's existence means that SaneDev objects can be used by the - select module. - -get_parameters() - Return a tuple containing information about the current settings of - the device and the current frame: (format, last_frame, - pixels_per_line, lines, depth, bytes_per_line). - - mode -- 'gray' for greyscale image, 'color' for RGB image, or - one of 'red', 'green', 'blue' if the image is a single - channel of an RGB image (from PIL's point of view, - this is equivalent to 'L'). - last_frame -- A Boolean value, which is true if this is the - last frame of the image, and false otherwise. - pixels_per_line -- Width of the frame. - lines -- Height of the frame. - depth -- Depth of the image, measured in bits. SANE will only - allow using 8, 16, or 24-bit depths. - bytes_per_line -- Bytes required to store a single line of - data, as computed from pixels_per_line and depth. - -start() - Start a scan. This function must be called before the - _snap()_ method can be used. - -cancel() - Cancel a scan already in progress. - -snap(no_cancel=0) - Snap a single frame of data, returning a PIL Image object - containing the data. If no_cancel is false, the Sane library function - sane_cancel is called after the scan. This is reasonable in most cases, - but may cause backends for duplex ADF scanners to drop the backside image, - when snap() is called for the front side image. If no_cancel is true, - cancel() should be called manually, after all scans are finished. - -scan() - This is just a shortcut for s.start(); s.snap() - Returns a PIL image - -multi_scan() - This method returns an iterator. It is intended to be used for - scanning with an automatic document feeder. The next() method of the - iterator tries to start a scan. If this is successful, it returns a - PIL Image object, like scan(); if the document feeder runs out of - paper, it raises StopIteration, thereby signaling that the sequence - is ran out of items. - -arr_snap(multipleOf=1) - same as snap, but the result is a NumArray object. (Not that - num_array must be installed already at compilation time, otherwise - this feature will not be activated). - By default the resulting array has the same number of pixels per - line as specified in self.get_parameters()[2][0] - However sometimes it is necessary to obtain arrays where - the number of pixels per line is e.g. a multiple of 4. This can then - be achieved with the option 'multipleOf=4'. So if the scanner - scanned 34 pixels per line, you will obtain an array with 32 pixels - per line. - Note that this only works with monochrome images (e.g. gray-scales) - -arr_scan(multipleOf=1) - This is just a shortcut for s.start(); s.arr_snap(multipleOf=1) - Returns a NumArray object - -close() - Closes the object. - - -Attributes: ------------ -SaneDev objects have a few fixed attributes which are always -available, and a larger collection of attributes which vary depending -on the device. An Epson 1660 photo scanner has attributes like -'mode', 'depth', etc. -Another (pseudo scanner), the _pnm:0_ device, takes a PNM file and -simulates a scanner using the image data; a SaneDev object -representing the _pnm:0_ device therefore has a _filename_ attribute -which can be changed to specify the filename, _contrast_ and -_brightness_ attributes to modify the returned image, and so forth. - -The values of the scanner options may be an integer, floating-point -value, or string, depending on the nature of the option. - -sane_signature - The tuple for this scandev that is returned by sane.get_devices() - e.g. ('epson:libusb:001:006', 'Epson', 'GT-8300', 'flatbed scanner') - -scanner_model - same as sane_signature[1:3], i.e. ('Epson', 'GT-8300') for the case above. - -optlist - A list containing the all the options supported by this device. - - >>> import sane ; s=sane.open('epson:libusb:001:004') ; s.optlist - ['focus_position', 'color_correction', 'sharpness', ...., 'br_x'] - -A closer look at all options listed in s.optlist can be obtained -through the SaneOption objects. - -SaneOption objects -================== - -SANE's option handling is its most elaborate subsystem, intended to -allow automatically generating dialog boxes and prompts for user -configuration of the scanning device. The SaneOption object can be -used to get a human-readable name and description for an option, the -units to use, and what the legal values are. No information about the -current value of the option is available; for that, read the -corresponding attribute of a SaneDev object. - -This documentation does not explain all the details of SANE's option -handling; consult the SANE documentation for all the details. - -A scandevice option is accessed via __getitem__. For example -s['mode'] returns the option descriptor for the mode-option which -controls whether the scanner works in color, grayscale, or b/w mode. - ->>> s['mode'] -Name: mode -Cur value: Color -Index: 2 -Title: Scan mode -Desc: Selects the scan mode (e.g., lineart, monochrome, or color). -Type: TYPE_STRING -Unit: UNIT_NONE -Constr: ['Binary', 'Gray', 'Color'] -active: yes -settable: yes - -In order to change 'mode' to 'gray', just type: ->>> s.mode = 'gray' - - -With the attributes and methods of sane-option objects it is possible -to access individual option values: - -is_active() - Returns true if the option is active. - -is_settable() - Returns true if the option can be set under software control. - - -Attributes: - -cap - An integer containing various flags about the object's - capabilities; whether it's active, whether it's settable, etc. Also - available as the _capability_ attribute. - -constraint - The constraint placed on the value of this option. If it's - _None_, there are essentially no constraint of the value. It may also - be a list of integers or strings, in which case the value *must* be - one of the possibilities in the list. Numeric values may have a - 3-tuple as the constraint; this 3-tuple contains _(minimum, maximum, - increment)_, and the value must be in the defined range. - -desc - A lengthy description of what the option does; it may be shown - to the user for clarification. - -index - An integer giving the option's index in the option list. - -name - A short name for the option, as it comes from the sane-backend. - -py_name - The option's name, as a legal Python identifier. The name - attribute may contain the '-' character, so it will be converted to - '_' for the py_name attribute. - -size - For a string-valued option, this is the maximum length allowed. - -title - A single-line string that can be used as a title string. - -type - A constant giving the type of this option: will be one of the following - constants found in the SANE module: - TYPE_BOOL - TYPE_INT - TYPE_FIXED - TYPE_STRING - TYPE_BUTTON - TYPE_GROUP - -unit - For numeric-valued options, this is a constant representing - the unit used for this option. It will be one of the following - constants found in the SANE module: - UNIT_NONE - UNIT_PIXEL - UNIT_BIT - UNIT_MM - UNIT_DPI - UNIT_PERCENT - - - -Example us usage: -================= ->>> import sane ->>> print 'SANE version:', sane.init() ->>> print 'Available devices=', sane.get_devices() -SANE version: (16777230, 1, 0, 14) ->>> s = sane.open(sane.get_devices()[0][0]) ->>> print 'Device parameters:', s.get_parameters() -Device parameters: ('L', 1, (424, 585), 1, 53) ->>> print s.resolution -50 - -## In order to scan a color image into a PIL object: ->>> s.mode = 'color' ->>> s.start() ->>> img = s.snap() ->>> img.show() - - -## In order to obtain a 16-bit grayscale image at 100DPI in a numarray object -## with bottom-right coordinates set to (160, 120) [in millimeter] : ->>> s.mode = 'gray' ->>> s.br_x=160. ; s.br_y=120. ->>> s.resolution = 100 ->>> s.depth=16 ->>> s.start() ->>> s.get_parameters()[2] # just check the size -(624, 472) ->>> arr16 = s.arr_snap() ->>> arr16 -array([[63957, 64721, 65067, ..., 65535, 65535, 65535], - [63892, 64342, 64236, ..., 65535, 65535, 65535], - [64286, 64248, 64705, ..., 65535, 65535, 65535], - ..., - [65518, 65249, 65058, ..., 65535, 65535, 65535], - [64435, 65047, 65081, ..., 65535, 65535, 65535], - [65309, 65438, 65535, ..., 65535, 65535, 65535]], type=UInt16) ->>> arr16.shape # inverse order of coordinates, first y, then x! -(472, 624) - diff --git a/Sane/setup.py b/Sane/setup.py deleted file mode 100644 index 3837198ec..000000000 --- a/Sane/setup.py +++ /dev/null @@ -1,24 +0,0 @@ -from distutils.core import setup, Extension - -PIL_BUILD_DIR = '..' -PIL_IMAGING_DIR = PIL_BUILD_DIR+'/libImaging' - -defs = [] -try: - import numarray - defs.append(('WITH_NUMARRAY',None)) -except ImportError: - pass - -sane = Extension('_sane', - include_dirs = [PIL_IMAGING_DIR], - libraries = ['sane'], - library_dirs = [PIL_IMAGING_DIR], - define_macros = defs, - sources = ['_sane.c']) - -setup (name = 'pysane', - version = '2.0', - description = 'This is the pysane package', - py_modules = ['sane'], - ext_modules = [sane]) From 042b809f8136407576d4f7d09b465b1d188fa55d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 21 Oct 2014 09:57:37 -0700 Subject: [PATCH 39/48] Revert incorrect doc change [ci skip] --- PIL/Image.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 3a7c81548..105976fcb 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1010,8 +1010,6 @@ class Image: def draft(self, mode, size): """ - NYI - Configures the image file loader so it returns a version of the image that as closely as possible matches the given mode and size. For example, you can use this method to convert a color From 8b7a98901821cf48750f9835bf581ba365ff0d88 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 25 Oct 2014 11:07:34 +0300 Subject: [PATCH 40/48] Fix "can can" typo Closes #971. [CI skip] --- PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/Image.py b/PIL/Image.py index 105976fcb..ed948e048 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -2334,7 +2334,7 @@ def composite(image1, image2, mask): :param image1: The first image. :param image2: The second image. Must have the same mode and size as the first image. - :param mask: A mask image. This image can can have mode + :param mask: A mask image. This image can have mode "1", "L", or "RGBA", and must have the same size as the other two images. """ From 0df43e4f93fcfbbbef8befc1b71b800d6ff1a7d7 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 25 Oct 2014 23:42:02 +0300 Subject: [PATCH 41/48] Avoid divide by zero --- PIL/TiffImagePlugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 4e76379c0..b95fde51d 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -1153,8 +1153,11 @@ def _save(im, fp, filename): # following tiffcp.c->cpTag->TIFF_RATIONAL atts[k] = float(v[0][0])/float(v[0][1]) continue - if type(v) == tuple and len(v) > 2: + if (type(v) == tuple and + (len(v) > 2 or + (len(v) == 2 and v[1] == 0))): # List of ints? + # Avoid divide by zero in next if-clause if type(v[0]) in (int, float): atts[k] = list(v) continue From 86c5fdc7aa4f20b8787ee7069953bc74f79c819f Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 26 Oct 2014 18:14:34 +0200 Subject: [PATCH 42/48] Created by printing a page in Chrome to PDF, then: /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif -dNOPAUSE /tmp/test.pdf -c quit --- Tests/images/total-pages-zero.tif | Bin 0 -> 97123 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/images/total-pages-zero.tif diff --git a/Tests/images/total-pages-zero.tif b/Tests/images/total-pages-zero.tif new file mode 100644 index 0000000000000000000000000000000000000000..50df07af3dd3c42d49cda056a023b9b673c3b990 GIT binary patch literal 97123 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 YY)#+Wzu608505%C53JJu&wF9~52r8vIRF3v literal 0 HcmV?d00001 From 6ec6b16eb6549c2a43daf5556d63733b7d3940c2 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sun, 26 Oct 2014 18:16:08 +0200 Subject: [PATCH 43/48] Test TIFF with PageNumber 0 0 --- Tests/test_file_tiff.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index cf809d5d0..ad6642d0f 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -299,6 +299,26 @@ class TestFileTiff(PillowTestCase): self.assertEqual(ret, [0, 1]) + def test_page_number_x_0(self): + # Issue 973 + # Test TIFF with tag 297 (Page Number) having value of 0 0. + # The first number is the current page number. + # The second is the total number of pages, zero means not available. + + # Arrange + outfile = self.tempfile("temp.tif") + + # Created by printing a page in Chrome to PDF, then: + # /usr/bin/gs -q -sDEVICE=tiffg3 -sOutputFile=total-pages-zero.tif + # -dNOPAUSE /tmp/test.pdf -c quit + infile = "Tests/images/total-pages-zero.tif" + im = Image.open(infile) + + # Act / Assert + # Should not divide by zero + im.save("test.tif") + + if __name__ == '__main__': unittest.main() From 4a92c24aa6d316116728639f591e64938cea6da6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 27 Oct 2014 09:31:22 +0200 Subject: [PATCH 44/48] Save to a self.tempfile() so it's properly cleaned up afterwards --- Tests/test_file_tiff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index ad6642d0f..2241123ac 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -316,7 +316,7 @@ class TestFileTiff(PillowTestCase): # Act / Assert # Should not divide by zero - im.save("test.tif") + im.save(outfile) if __name__ == '__main__': From f67eb56203d1ca43a518ec286aa7bb9683acf595 Mon Sep 17 00:00:00 2001 From: Nathan Cahill Date: Tue, 28 Oct 2014 21:03:45 -0600 Subject: [PATCH 45/48] Add Ubuntu 14.04 prerequisites Replaces `tcl8.5-dev` and `tk8.5-dev` with `tcl8.6-dev` and `tk8.6-dev` for 14.04. --- docs/installation.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index a61213e15..fa8f93510 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -127,6 +127,11 @@ Prerequisites are installed with on **Ubuntu 12.04 LTS** or **Raspian Wheezy $ sudo apt-get install libtiff4-dev libjpeg8-dev zlib1g-dev \ libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk +Prerequisites are installed with on **Ubuntu 14.04 LTS** with:: + + $ sudo apt-get install libtiff4-dev libjpeg8-dev zlib1g-dev \ + libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk + Prerequisites are installed on **Fedora 20** with:: $ sudo yum install libtiff-devel libjpeg-devel libzip-devel freetype-devel \ From 2d2474685a48575f4aa4b1e3c0d9b07dafd21b4e Mon Sep 17 00:00:00 2001 From: wiredfool Date: Wed, 29 Oct 2014 11:07:20 -0700 Subject: [PATCH 46/48] Added 4bit test to libtiff as well --- Tests/test_file_libtiff.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 85b796242..efd2d5817 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -342,6 +342,24 @@ class TestFileLibTiff(LibTiffTestCase): im.load() self.assertFalse(im.tag.next) + def test_4bit(self): + # Arrange + test_file = "Tests/images/hopper_gray_4bpp.tif" + original = hopper("L") + + # Act + TiffImagePlugin.READ_LIBTIFF = True + im = Image.open(test_file) + TiffImagePlugin.READ_LIBTIFF = False + + # Assert + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.mode, "L") + self.assert_image_similar(im, original, 7.3) + + + + if __name__ == '__main__': unittest.main() From cf6dc5a3bca0840c53b0eac8c15752db39d2247f Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 29 Oct 2014 20:09:00 +0200 Subject: [PATCH 47/48] Typos [CI skip] --- docs/installation.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index fa8f93510..ac02a645c 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -67,10 +67,10 @@ Many of Pillow's features require external libraries: * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and above uses liblcms2. Tested with **1.19** and **2.2**. -* **libwebp** provides the Webp format. +* **libwebp** provides the WebP format. * Pillow has been tested with version **0.1.3**, which does not read - transparent webp files. Versions **0.3.0** and **0.4.0** support + transparent WebP files. Versions **0.3.0** and **0.4.0** support transparency. * **tcl/tk** provides support for tkinter bitmap and photo images. @@ -121,13 +121,13 @@ Prerequisites are installed on **Ubuntu 10.04 LTS** with:: $ sudo apt-get install libtiff4-dev libjpeg62-dev zlib1g-dev \ libfreetype6-dev tcl8.5-dev tk8.5-dev python-tk -Prerequisites are installed with on **Ubuntu 12.04 LTS** or **Raspian Wheezy +Prerequisites are installed on **Ubuntu 12.04 LTS** or **Raspian Wheezy 7.0** with:: $ sudo apt-get install libtiff4-dev libjpeg8-dev zlib1g-dev \ libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev python-tk -Prerequisites are installed with on **Ubuntu 14.04 LTS** with:: +Prerequisites are installed on **Ubuntu 14.04 LTS** with:: $ sudo apt-get install libtiff4-dev libjpeg8-dev zlib1g-dev \ libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk From 59fa39c1dc2359d44a313e6c735d1b521c780527 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 29 Oct 2014 21:46:25 +0200 Subject: [PATCH 48/48] Update CHANGES.rst [CI skip] --- CHANGES.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 679eaa73b..69f0bd1e2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,11 +4,14 @@ Changelog (Pillow) 2.7.0 (unreleased) ------------------ +- Support for 4-bit greyscale TIFF images #980 + [hugovk, wiredfool] + - Updated manifest #957 [wiredfool] - Fix PyPy 2.4 regression #952 - [wiredfool] + [wiredfool] - Webp Metadata Skip Test comments #954 [wiredfool]