From 44e91e668ddb22a454fe41a50f6518ebcbff05bf Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 27 Jun 2014 11:29:30 +0300 Subject: [PATCH 01/31] Remove old, unused test --- Tests/cms_test.py | 222 ---------------------------------------------- 1 file changed, 222 deletions(-) delete mode 100644 Tests/cms_test.py diff --git a/Tests/cms_test.py b/Tests/cms_test.py deleted file mode 100644 index 464c9c7ef..000000000 --- a/Tests/cms_test.py +++ /dev/null @@ -1,222 +0,0 @@ -# PyCMSTests.py -# Examples of how to use pyCMS, as well as tests to verify it works properly -# By Kevin Cazabon (kevin@cazabon.com) - -# Imports -import os -from PIL import Image -from PIL import ImageCms - -# import PyCMSError separately so we can catch it -PyCMSError = ImageCms.PyCMSError - -####################################################################### -# Configuration: -####################################################################### -# set this to the image you want to test with -IMAGE = "c:\\temp\\test.tif" - -# set this to where you want to save the output images -OUTPUTDIR = "c:\\temp\\" - -# set these to two different ICC profiles, one for input, one for output -# set the corresponding mode to the proper PIL mode for that profile -INPUT_PROFILE = "c:\\temp\\profiles\\sRGB.icm" -INMODE = "RGB" - -OUTPUT_PROFILE = "c:\\temp\\profiles\\genericRGB.icm" -OUTMODE = "RGB" - -PROOF_PROFILE = "c:\\temp\\profiles\\monitor.icm" - -# set to True to show() images, False to save them into OUTPUT_DIRECTORY -SHOW = False - -# Tests you can enable/disable -TEST_error_catching = True -TEST_profileToProfile = True -TEST_profileToProfile_inPlace = True -TEST_buildTransform = True -TEST_buildTransformFromOpenProfiles = True -TEST_buildProofTransform = True -TEST_getProfileInfo = True -TEST_misc = False - -####################################################################### -# helper functions -####################################################################### -def outputImage(im, funcName = None): - # save or display the image, depending on value of SHOW_IMAGES - if SHOW: - im.show() - else: - im.save(os.path.join(OUTPUTDIR, "%s.tif" %funcName)) - - -####################################################################### -# The tests themselves -####################################################################### - -if TEST_error_catching: - im = Image.open(IMAGE) - try: - #neither of these proifles exists (unless you make them), so we should - # get an error - imOut = ImageCms.profileToProfile(im, "missingProfile.icm", "cmyk.icm") - - except PyCMSError as reason: - print("We caught a PyCMSError: %s\n\n" %reason) - - print("error catching test completed successfully (if you see the message \ - above that we caught the error).") - -if TEST_profileToProfile: - # open the image file using the standard PIL function Image.open() - im = Image.open(IMAGE) - - # send the image, input/output profiles, and rendering intent to - # ImageCms.profileToProfile() - imOut = ImageCms.profileToProfile(im, INPUT_PROFILE, OUTPUT_PROFILE, \ - outputMode = OUTMODE) - - # now that the image is converted, save or display it - outputImage(imOut, "profileToProfile") - - print("profileToProfile test completed successfully.") - -if TEST_profileToProfile_inPlace: - # we'll do the same test as profileToProfile, but modify im in place - # instead of getting a new image returned to us - im = Image.open(IMAGE) - - # send the image to ImageCms.profileToProfile(), specifying inPlace = True - result = ImageCms.profileToProfile(im, INPUT_PROFILE, OUTPUT_PROFILE, \ - outputMode = OUTMODE, inPlace = True) - - # now that the image is converted, save or display it - if result is None: - # this is the normal result when modifying in-place - outputImage(im, "profileToProfile_inPlace") - else: - # something failed... - print("profileToProfile in-place failed: %s" %result) - - print("profileToProfile in-place test completed successfully.") - -if TEST_buildTransform: - # make a transform using the input and output profile path strings - transform = ImageCms.buildTransform(INPUT_PROFILE, OUTPUT_PROFILE, INMODE, \ - OUTMODE) - - # now, use the trnsform to convert a couple images - im = Image.open(IMAGE) - - # transform im normally - im2 = ImageCms.applyTransform(im, transform) - outputImage(im2, "buildTransform") - - # then transform it again using the same transform, this time in-place. - result = ImageCms.applyTransform(im, transform, inPlace = True) - outputImage(im, "buildTransform_inPlace") - - print("buildTransform test completed successfully.") - - # and, to clean up a bit, delete the transform - # this should call the C destructor for the transform structure. - # Python should also do this automatically when it goes out of scope. - del(transform) - -if TEST_buildTransformFromOpenProfiles: - # we'll actually test a couple profile open/creation functions here too - - # first, get a handle to an input profile, in this case we'll create - # an sRGB profile on the fly: - inputProfile = ImageCms.createProfile("sRGB") - - # then, get a handle to the output profile - outputProfile = ImageCms.getOpenProfile(OUTPUT_PROFILE) - - # make a transform from these - transform = ImageCms.buildTransformFromOpenProfiles(inputProfile, \ - outputProfile, INMODE, OUTMODE) - - # now, use the trnsform to convert a couple images - im = Image.open(IMAGE) - - # transform im normally - im2 = ImageCms.applyTransform(im, transform) - outputImage(im2, "buildTransformFromOpenProfiles") - - # then do it again using the same transform, this time in-place. - result = ImageCms.applyTransform(im, transform, inPlace = True) - outputImage(im, "buildTransformFromOpenProfiles_inPlace") - - print("buildTransformFromOpenProfiles test completed successfully.") - - # and, to clean up a bit, delete the transform - # this should call the C destructor for the each item. - # Python should also do this automatically when it goes out of scope. - del(inputProfile) - del(outputProfile) - del(transform) - -if TEST_buildProofTransform: - # make a transform using the input and output and proof profile path - # strings - # images converted with this transform will simulate the appearance - # of the output device while actually being displayed/proofed on the - # proof device. This usually means a monitor, but can also mean - # other proof-printers like dye-sub, etc. - transform = ImageCms.buildProofTransform(INPUT_PROFILE, OUTPUT_PROFILE, \ - PROOF_PROFILE, INMODE, OUTMODE) - - # now, use the trnsform to convert a couple images - im = Image.open(IMAGE) - - # transform im normally - im2 = ImageCms.applyTransform(im, transform) - outputImage(im2, "buildProofTransform") - - # then transform it again using the same transform, this time in-place. - result = ImageCms.applyTransform(im, transform, inPlace = True) - outputImage(im, "buildProofTransform_inPlace") - - print("buildProofTransform test completed successfully.") - - # and, to clean up a bit, delete the transform - # this should call the C destructor for the transform structure. - # Python should also do this automatically when it goes out of scope. - del(transform) - -if TEST_getProfileInfo: - # get a profile handle - profile = ImageCms.getOpenProfile(INPUT_PROFILE) - - # lets print some info about our input profile: - print("Profile name (retrieved from profile string path name): %s" %ImageCms.getProfileName(INPUT_PROFILE)) - - # or, you could do the same thing using a profile handle as the arg - print("Profile name (retrieved from profile handle): %s" %ImageCms.getProfileName(profile)) - - # now lets get the embedded "info" tag contents - # once again, you can use a path to a profile, or a profile handle - print("Profile info (retrieved from profile handle): %s" %ImageCms.getProfileInfo(profile)) - - # and what's the default intent of this profile? - print("The default intent is (this will be an integer): %d" %(ImageCms.getDefaultIntent(profile))) - - # Hmmmm... but does this profile support INTENT_ABSOLUTE_COLORIMETRIC? - print("Does it support INTENT_ABSOLUTE_COLORIMETRIC?: (1 is yes, -1 is no): %s" \ - %ImageCms.isIntentSupported(profile, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, \ - ImageCms.DIRECTION_INPUT)) - - print("getProfileInfo test completed successfully.") - -if TEST_misc: - # test the versions, about, and copyright functions - print("Versions: %s" %str(ImageCms.versions())) - print("About:\n\n%s" %ImageCms.about()) - print("Copyright:\n\n%s" %ImageCms.copyright()) - - print("misc test completed successfully.") - From 2004fd0845fadf060fc2e12f2860830aabe3dfc7 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 08:43:44 -0400 Subject: [PATCH 02/31] Prefer rst --- Tests/{README.md => README.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Tests/{README.md => README.rst} (100%) diff --git a/Tests/README.md b/Tests/README.rst similarity index 100% rename from Tests/README.md rename to Tests/README.rst From 2670204dbd5b9238d50a91fed0d64cff5af37794 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 08:44:28 -0400 Subject: [PATCH 03/31] Clean up --- Tests/README.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/README.rst b/Tests/README.rst index 84e2c4655..5f828a034 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -1,7 +1,11 @@ -Pillow test files. +Pillow Tests +============ Test scripts are named `test_xxx.py` and use the `unittest` module. A base class and helper functions can be found in `helper.py`. +Execution +--------- + Run the tests from the root of the Pillow source distribution: python selftest.py From 2effb0d429b05dfaf142ec2a2b4bb3f1c74320a7 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 08:44:53 -0400 Subject: [PATCH 04/31] Fix error(s) --- Tests/README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/README.rst b/Tests/README.rst index 5f828a034..be6da8722 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -6,12 +6,12 @@ Test scripts are named `test_xxx.py` and use the `unittest` module. A base class Execution --------- -Run the tests from the root of the Pillow source distribution: +Run the tests from the root of the Pillow source distribution:: python selftest.py nosetests Tests/test_*.py -Or with coverage: +Or with coverage:: coverage run --append --include=PIL/* selftest.py coverage run --append --include=PIL/* -m nose Tests/test_*.py @@ -19,6 +19,6 @@ Or with coverage: coverage html open htmlcov/index.html -To run an individual test: +To run an individual test:: python Tests/test_image.py From a45c8aaf61029bad7fb1e49edc1e8ed811979c94 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 08:45:39 -0400 Subject: [PATCH 05/31] Fix error(s) --- Tests/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/README.rst b/Tests/README.rst index be6da8722..1eaa4f3a2 100644 --- a/Tests/README.rst +++ b/Tests/README.rst @@ -1,7 +1,7 @@ Pillow Tests ============ -Test scripts are named `test_xxx.py` and use the `unittest` module. A base class and helper functions can be found in `helper.py`. +Test scripts are named ``test_xxx.py`` and use the ``unittest`` module. A base class and helper functions can be found in ``helper.py``. Execution --------- From c261674980956361d3b98b6239687e29d45b8405 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 27 Jun 2014 15:58:12 +0300 Subject: [PATCH 06/31] Remove obsolete Animated Raster Graphics support --- PIL/ArgImagePlugin.py | 506 ------------------------------------------ PIL/__init__.py | 3 +- Scripts/player.py | 23 -- docs/plugins.rst | 8 - 4 files changed, 1 insertion(+), 539 deletions(-) delete mode 100644 PIL/ArgImagePlugin.py diff --git a/PIL/ArgImagePlugin.py b/PIL/ArgImagePlugin.py deleted file mode 100644 index 7fc167c60..000000000 --- a/PIL/ArgImagePlugin.py +++ /dev/null @@ -1,506 +0,0 @@ -# -# THIS IS WORK IN PROGRESS -# -# The Python Imaging Library. -# $Id$ -# -# ARG animation support code -# -# history: -# 1996-12-30 fl Created -# 1996-01-06 fl Added safe scripting environment -# 1996-01-10 fl Added JHDR, UHDR and sYNC support -# 2005-03-02 fl Removed AAPP and ARUN support -# -# Copyright (c) Secret Labs AB 1997. -# Copyright (c) Fredrik Lundh 1996-97. -# -# See the README file for information on usage and redistribution. -# - -from __future__ import print_function - -__version__ = "0.4" - -from PIL import Image, ImageFile, ImagePalette - -from PIL.PngImagePlugin import i8, i16, i32, ChunkStream, _MODES - -MAGIC = b"\212ARG\r\n\032\n" - -# -------------------------------------------------------------------- -# ARG parser - -class ArgStream(ChunkStream): - "Parser callbacks for ARG data" - - def __init__(self, fp): - - ChunkStream.__init__(self, fp) - - self.eof = 0 - - self.im = None - self.palette = None - - self.__reset() - - def __reset(self): - - # reset decoder state (called on init and sync) - - self.count = 0 - self.id = None - self.action = ("NONE",) - - self.images = {} - self.names = {} - - - def chunk_AHDR(self, offset, bytes): - "AHDR -- animation header" - - # assertions - if self.count != 0: - raise SyntaxError("misplaced AHDR chunk") - - s = self.fp.read(bytes) - self.size = i32(s), i32(s[4:]) - try: - self.mode, self.rawmode = _MODES[(i8(s[8]), i8(s[9]))] - except: - raise SyntaxError("unknown ARG mode") - - if Image.DEBUG: - print("AHDR size", self.size) - print("AHDR mode", self.mode, self.rawmode) - - return s - - def chunk_AFRM(self, offset, bytes): - "AFRM -- next frame follows" - - # assertions - if self.count != 0: - raise SyntaxError("misplaced AFRM chunk") - - self.show = 1 - self.id = 0 - self.count = 1 - self.repair = None - - s = self.fp.read(bytes) - if len(s) >= 2: - self.id = i16(s) - if len(s) >= 4: - self.count = i16(s[2:4]) - if len(s) >= 6: - self.repair = i16(s[4:6]) - else: - self.repair = None - - if Image.DEBUG: - print("AFRM", self.id, self.count) - - return s - - def chunk_ADEF(self, offset, bytes): - "ADEF -- store image" - - # assertions - if self.count != 0: - raise SyntaxError("misplaced ADEF chunk") - - self.show = 0 - self.id = 0 - self.count = 1 - self.repair = None - - s = self.fp.read(bytes) - if len(s) >= 2: - self.id = i16(s) - if len(s) >= 4: - self.count = i16(s[2:4]) - - if Image.DEBUG: - print("ADEF", self.id, self.count) - - return s - - def chunk_NAME(self, offset, bytes): - "NAME -- name the current image" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced NAME chunk") - - name = self.fp.read(bytes) - self.names[self.id] = name - - return name - - def chunk_AEND(self, offset, bytes): - "AEND -- end of animation" - - if Image.DEBUG: - print("AEND") - - self.eof = 1 - - raise EOFError("end of ARG file") - - def __getmodesize(self, s, full=1): - - size = i32(s), i32(s[4:]) - - try: - mode, rawmode = _MODES[(i8(s[8]), i8(s[9]))] - except: - raise SyntaxError("unknown image mode") - - if full: - if i8(s[12]): - pass # interlace not yet supported - if i8(s[11]): - raise SyntaxError("unknown filter category") - - return size, mode, rawmode - - def chunk_PAST(self, offset, bytes): - "PAST -- paste one image into another" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced PAST chunk") - - if self.repair is not None: - # we must repair the target image before we - # start pasting - - # brute force; a better solution would be to - # update only the dirty rectangles in images[id]. - # note that if images[id] doesn't exist, it must - # be created - - self.images[self.id] = self.images[self.repair].copy() - self.repair = None - - s = self.fp.read(bytes) - im = self.images[i16(s)] - x, y = i32(s[2:6]), i32(s[6:10]) - bbox = x, y, im.size[0]+x, im.size[1]+y - - if im.mode in ["RGBA"]: - # paste with transparency - # FIXME: should handle P+transparency as well - self.images[self.id].paste(im, bbox, im) - else: - # paste without transparency - self.images[self.id].paste(im, bbox) - - self.action = ("PAST",) - self.__store() - - return s - - def chunk_BLNK(self, offset, bytes): - "BLNK -- create blank image" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced BLNK chunk") - - s = self.fp.read(bytes) - size, mode, rawmode = self.__getmodesize(s, 0) - - # store image (FIXME: handle colour) - self.action = ("BLNK",) - self.im = Image.core.fill(mode, size, 0) - self.__store() - - return s - - def chunk_IHDR(self, offset, bytes): - "IHDR -- full image follows" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced IHDR chunk") - - # image header - s = self.fp.read(bytes) - size, mode, rawmode = self.__getmodesize(s) - - # decode and store image - self.action = ("IHDR",) - self.im = Image.core.new(mode, size) - self.decoder = Image.core.zip_decoder(rawmode) - self.decoder.setimage(self.im, (0,0) + size) - self.data = b"" - - return s - - def chunk_DHDR(self, offset, bytes): - "DHDR -- delta image follows" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced DHDR chunk") - - s = self.fp.read(bytes) - - size, mode, rawmode = self.__getmodesize(s) - - # delta header - diff = i8(s[13]) - offs = i32(s[14:18]), i32(s[18:22]) - - bbox = offs + (offs[0]+size[0], offs[1]+size[1]) - - if Image.DEBUG: - print("DHDR", diff, bbox) - - # FIXME: decode and apply image - self.action = ("DHDR", diff, bbox) - - # setup decoder - self.im = Image.core.new(mode, size) - - self.decoder = Image.core.zip_decoder(rawmode) - self.decoder.setimage(self.im, (0,0) + size) - - self.data = b"" - - return s - - def chunk_JHDR(self, offset, bytes): - "JHDR -- JPEG image follows" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced JHDR chunk") - - # image header - s = self.fp.read(bytes) - size, mode, rawmode = self.__getmodesize(s, 0) - - # decode and store image - self.action = ("JHDR",) - self.im = Image.core.new(mode, size) - self.decoder = Image.core.jpeg_decoder(rawmode) - self.decoder.setimage(self.im, (0,0) + size) - self.data = b"" - - return s - - def chunk_UHDR(self, offset, bytes): - "UHDR -- uncompressed image data follows (EXPERIMENTAL)" - - # assertions - if self.count == 0: - raise SyntaxError("misplaced UHDR chunk") - - # image header - s = self.fp.read(bytes) - size, mode, rawmode = self.__getmodesize(s, 0) - - # decode and store image - self.action = ("UHDR",) - self.im = Image.core.new(mode, size) - self.decoder = Image.core.raw_decoder(rawmode) - self.decoder.setimage(self.im, (0,0) + size) - self.data = b"" - - return s - - def chunk_IDAT(self, offset, bytes): - "IDAT -- image data block" - - # pass compressed chunks through the decoder - s = self.fp.read(bytes) - self.data = self.data + s - n, e = self.decoder.decode(self.data) - if n < 0: - # end of image - if e < 0: - raise IOError("decoder error %d" % e) - else: - self.data = self.data[n:] - - return s - - def chunk_DEND(self, offset, bytes): - return self.chunk_IEND(offset, bytes) - - def chunk_JEND(self, offset, bytes): - return self.chunk_IEND(offset, bytes) - - def chunk_UEND(self, offset, bytes): - return self.chunk_IEND(offset, bytes) - - def chunk_IEND(self, offset, bytes): - "IEND -- end of image" - - # we now have a new image. carry out the operation - # defined by the image header. - - # won't need these anymore - del self.decoder - del self.data - - self.__store() - - return self.fp.read(bytes) - - def __store(self): - - # apply operation - cid = self.action[0] - - if cid in ["BLNK", "IHDR", "JHDR", "UHDR"]: - # store - self.images[self.id] = self.im - - elif cid == "DHDR": - # paste - cid, mode, bbox = self.action - im0 = self.images[self.id] - im1 = self.im - if mode == 0: - im1 = im1.chop_add_modulo(im0.crop(bbox)) - im0.paste(im1, bbox) - - self.count -= 1 - - if self.count == 0 and self.show: - self.im = self.images[self.id] - raise EOFError # end of this frame - - def chunk_PLTE(self, offset, bytes): - "PLTE -- palette data" - - s = self.fp.read(bytes) - if self.mode == "P": - self.palette = ImagePalette.raw("RGB", s) - return s - - def chunk_sYNC(self, offset, bytes): - "SYNC -- reset decoder" - - if self.count != 0: - raise SyntaxError("misplaced sYNC chunk") - - s = self.fp.read(bytes) - self.__reset() - return s - - -# -------------------------------------------------------------------- -# ARG reader - -def _accept(prefix): - return prefix[:8] == MAGIC - -## -# Image plugin for the experimental Animated Raster Graphics format. - -class ArgImageFile(ImageFile.ImageFile): - - format = "ARG" - format_description = "Animated raster graphics" - - def _open(self): - - if Image.warnings: - Image.warnings.warn( - "The ArgImagePlugin driver is obsolete, and will be removed " - "from a future release of PIL. If you rely on this module, " - "please contact the PIL authors.", - RuntimeWarning - ) - - if self.fp.read(8) != MAGIC: - raise SyntaxError("not an ARG file") - - self.arg = ArgStream(self.fp) - - # read and process the first chunk (AHDR) - - cid, offset, bytes = self.arg.read() - - if cid != "AHDR": - raise SyntaxError("expected an AHDR chunk") - - s = self.arg.call(cid, offset, bytes) - - self.arg.crc(cid, s) - - # image characteristics - self.mode = self.arg.mode - self.size = self.arg.size - - def load(self): - - if self.arg.im is None: - self.seek(0) - - # image data - self.im = self.arg.im - self.palette = self.arg.palette - - # set things up for further processing - Image.Image.load(self) - - def seek(self, frame): - - if self.arg.eof: - raise EOFError("end of animation") - - self.fp = self.arg.fp - - while True: - - # - # process chunks - - cid, offset, bytes = self.arg.read() - - if self.arg.eof: - raise EOFError("end of animation") - - try: - s = self.arg.call(cid, offset, bytes) - except EOFError: - break - - except "glurk": # AttributeError - if Image.DEBUG: - print(cid, bytes, "(unknown)") - s = self.fp.read(bytes) - - self.arg.crc(cid, s) - - self.fp.read(4) # ship extra CRC - - def tell(self): - return 0 - - def verify(self): - "Verify ARG file" - - # back up to first chunk - self.fp.seek(8) - - self.arg.verify(self) - self.arg.close() - - self.fp = None - -# -# -------------------------------------------------------------------- - -Image.register_open("ARG", ArgImageFile, _accept) - -Image.register_extension("ARG", ".arg") - -Image.register_mime("ARG", "video/x-arg") diff --git a/PIL/__init__.py b/PIL/__init__.py index c35f6207e..14daee5ca 100644 --- a/PIL/__init__.py +++ b/PIL/__init__.py @@ -14,8 +14,7 @@ VERSION = '1.1.7' # PIL version PILLOW_VERSION = '2.4.0' # Pillow -_plugins = ['ArgImagePlugin', - 'BmpImagePlugin', +_plugins = ['BmpImagePlugin', 'BufrStubImagePlugin', 'CurImagePlugin', 'DcxImagePlugin', diff --git a/Scripts/player.py b/Scripts/player.py index 84b636668..0c90286c5 100644 --- a/Scripts/player.py +++ b/Scripts/player.py @@ -18,25 +18,6 @@ import sys Image.DEBUG = 0 -# -------------------------------------------------------------------- -# experimental: support ARG animation scripts - -import ArgImagePlugin - -def applet_hook(animation, images): - app = animation(animation_display, images) - app.run() - -ArgImagePlugin.APPLET_HOOK = applet_hook - -class AppletDisplay: - def __init__(self, ui): - self.__ui = ui - def paste(self, im, bbox): - self.__ui.image.paste(im, bbox) - def update(self): - self.__ui.update_idletasks() - # -------------------------------------------------------------------- # an image animation player @@ -56,10 +37,6 @@ class UI(Label): else: self.image = ImageTk.PhotoImage(im) - # APPLET SUPPORT (very crude, and not 100% safe) - global animation_display - animation_display = AppletDisplay(self) - Label.__init__(self, master, image=self.image, bg="black", bd=0) self.update() diff --git a/docs/plugins.rst b/docs/plugins.rst index 001cee949..a069f80df 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -1,14 +1,6 @@ Plugin reference ================ -:mod:`ArgImagePlugin` Module ----------------------------- - -.. automodule:: PIL.ArgImagePlugin - :members: - :undoc-members: - :show-inheritance: - :mod:`BmpImagePlugin` Module ---------------------------- From c23f9002507e1a9c9afef1a760df0d866023e324 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 27 Jun 2014 08:57:49 -0700 Subject: [PATCH 07/31] reverted __main__ guard --- setup.py | 70 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/setup.py b/setup.py index e0a22f75f..10ef0db08 100644 --- a/setup.py +++ b/setup.py @@ -705,39 +705,39 @@ class pil_build_ext(build_ext): finally: os.unlink(tmpfile) -if __name__ == '__main__': - setup( - name=NAME, - version=VERSION, - description='Python Imaging Library (Fork)', - long_description=( - _read('README.rst') + b'\n' + - _read('CHANGES.rst')).decode('utf-8'), - author='Alex Clark (fork author)', - author_email='aclark@aclark.net', - url='http://python-pillow.github.io/', - classifiers=[ - "Development Status :: 6 - Mature", - "Topic :: Multimedia :: Graphics", - "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", - "Topic :: Multimedia :: Graphics :: Capture :: Scanners", - "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", - "Topic :: Multimedia :: Graphics :: Graphics Conversion", - "Topic :: Multimedia :: Graphics :: Viewers", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", ], - cmdclass={"build_ext": pil_build_ext}, - ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], - include_package_data=True, - packages=find_packages(), - scripts=glob.glob("Scripts/pil*.py"), - test_suite='PIL.tests', - keywords=["Imaging", ], - license='Standard PIL License', - zip_safe=True, - ) + +setup( + name=NAME, + version=VERSION, + description='Python Imaging Library (Fork)', + long_description=( + _read('README.rst') + b'\n' + + _read('CHANGES.rst')).decode('utf-8'), + author='Alex Clark (fork author)', + author_email='aclark@aclark.net', + url='http://python-pillow.github.io/', + classifiers=[ + "Development Status :: 6 - Mature", + "Topic :: Multimedia :: Graphics", + "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", + "Topic :: Multimedia :: Graphics :: Capture :: Scanners", + "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture", + "Topic :: Multimedia :: Graphics :: Graphics Conversion", + "Topic :: Multimedia :: Graphics :: Viewers", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.2", + "Programming Language :: Python :: 3.3", ], + cmdclass={"build_ext": pil_build_ext}, + ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], + include_package_data=True, + packages=find_packages(), + scripts=glob.glob("Scripts/pil*.py"), + test_suite='PIL.tests', + keywords=["Imaging", ], + license='Standard PIL License', + zip_safe=True, +) # End of file From 9305e8499bff96814ee07a1d43b77a35f70befe9 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 27 Jun 2014 08:58:27 -0700 Subject: [PATCH 08/31] Added python 3.4 --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 10ef0db08..b89a08f41 100644 --- a/setup.py +++ b/setup.py @@ -729,7 +729,9 @@ setup( "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", ], + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + ], cmdclass={"build_ext": pil_build_ext}, ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], include_package_data=True, From c927ab266e95710d6c15b53f4b844c74950dd712 Mon Sep 17 00:00:00 2001 From: hugovk Date: Fri, 27 Jun 2014 21:30:08 +0300 Subject: [PATCH 09/31] Warn about decompression bombs --- docs/reference/Image.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 7125fcad4..6dcb73638 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -49,6 +49,8 @@ Functions .. autofunction:: open + .. warning:: > To protect against potential DOS attacks caused by "[decompression bombs](https://en.wikipedia.org/wiki/Zip_bomb)" (i.e. malicious files which decompress into a huge amount of data and are designed to crash or cause disruption by using up a lot of memory), Pillow will issue a `DecompressionBombWarning` if the image is over a certain limit. If desired, the warning can be turned into an error with `warnings.simplefilter('error', Image.DecompressionBombWarning)` or suppressed entirely with `warnings.simplefilter('ignore', Image.DecompressionBombWarning)`. See also [the logging documentation](https://docs.python.org/2/library/logging.html?highlight=logging#integration-with-the-warnings-module) to have warnings output to the logging facility instead of stderr. + Image processing ^^^^^^^^^^^^^^^^ From e7aee1444c0c2fb48e419e990315298eae86c403 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 16:26:42 -0400 Subject: [PATCH 10/31] Update --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 3f4299f5e..c2ac9f136 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Remove obsolete Animated Raster Graphics (ARG) support + [hugovk] + - Fix test_imagedraw failures #727 [cgohlke] From 5e3bf95c84507a053e771fec86796fdae400a7f1 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 17:43:24 -0400 Subject: [PATCH 11/31] Fix manifest --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 79a70dddd..08bd0b4e3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -54,6 +54,7 @@ recursive-include Tests *.png recursive-include Tests *.ppm recursive-include Tests *.psd recursive-include Tests *.py +recursive-include Tests *.rst recursive-include Tests *.spider recursive-include Tests *.tar recursive-include Tests *.tif From b6d3983c59bdd5f0286823e7afd7a3009ab2bb8a Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 27 Jun 2014 16:02:42 -0700 Subject: [PATCH 12/31] Created README --- depends/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 depends/README.md diff --git a/depends/README.md b/depends/README.md new file mode 100644 index 000000000..a94773bb1 --- /dev/null +++ b/depends/README.md @@ -0,0 +1 @@ +Scripts in this directory download, build and install the non-packaged dependencies for testing with Travis CI. From 2f09622516c92ba564ad3453d69b44b1277e2481 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Fri, 27 Jun 2014 19:13:00 -0400 Subject: [PATCH 13/31] Top level flake8 fixes --- mp_compile.py | 22 ++++++++++++---------- setup.py | 5 +---- test-installed.py | 6 +++--- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/mp_compile.py b/mp_compile.py index 0ff5b4b62..71b4089e1 100644 --- a/mp_compile.py +++ b/mp_compile.py @@ -5,6 +5,7 @@ from multiprocessing import Pool, cpu_count from distutils.ccompiler import CCompiler import os + # hideous monkeypatching. but. but. but. def _mp_compile_one(tp): (self, obj, build, cc_args, extra_postargs, pp_opts) = tp @@ -15,21 +16,20 @@ def _mp_compile_one(tp): self._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) return + def _mp_compile(self, sources, output_dir=None, macros=None, include_dirs=None, debug=0, extra_preargs=None, extra_postargs=None, depends=None): """Compile one or more source files. - + see distutils.ccompiler.CCompiler.compile for comments. """ # A concrete compiler class can either override this method # entirely or implement _compile(). - - macros, objects, extra_postargs, pp_opts, build = \ - self._setup_compile(output_dir, macros, include_dirs, sources, - depends, extra_postargs) - cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) + macros, objects, extra_postargs, pp_opts, build = self._setup_compile( + output_dir, macros, include_dirs, sources, depends, extra_postargs) + cc_args = self._get_cc_args(pp_opts, debug, extra_preargs) try: max_procs = int(os.environ.get('MAX_CONCURRENCY', cpu_count())) @@ -38,10 +38,12 @@ def _mp_compile(self, sources, output_dir=None, macros=None, pool = Pool(max_procs) try: print ("Building using %d processes" % pool._processes) - except: pass - arr = [(self, obj, build, cc_args, extra_postargs, pp_opts) for obj in objects] - results = pool.map_async(_mp_compile_one,arr) - + except: + pass + arr = [ + (self, obj, build, cc_args, extra_postargs, pp_opts) for obj in objects + ] + pool.map_async(_mp_compile_one, arr) pool.close() pool.join() # Return *all* object filenames, not just the ones we just built. diff --git a/setup.py b/setup.py index 1a31a8981..e0a22f75f 100644 --- a/setup.py +++ b/setup.py @@ -14,8 +14,6 @@ import re import struct import sys -import mp_compile - from distutils.command.build_ext import build_ext from distutils import sysconfig from setuptools import Extension, setup, find_packages @@ -707,7 +705,7 @@ class pil_build_ext(build_ext): finally: os.unlink(tmpfile) -if __name__=='__main__': +if __name__ == '__main__': setup( name=NAME, version=VERSION, @@ -742,5 +740,4 @@ if __name__=='__main__': license='Standard PIL License', zip_safe=True, ) - # End of file diff --git a/test-installed.py b/test-installed.py index 7f58f4966..0248590a5 100755 --- a/test-installed.py +++ b/test-installed.py @@ -4,8 +4,8 @@ import os import sys import glob -# monkey with the path, removing the local directory but adding the Tests/ directory -# for helper.py and the other local imports there. +# monkey with the path, removing the local directory but adding the Tests/ +# directory for helper.py and the other local imports there. del(sys.path[0]) sys.path.insert(0, os.path.abspath('./Tests')) @@ -16,7 +16,7 @@ sys.path.insert(0, os.path.abspath('./Tests')) if len(sys.argv) == 1: sys.argv.extend(glob.glob('Tests/test*.py')) -# Make sure that nose doesn't muck with our paths. +# Make sure that nose doesn't muck with our paths. if ('--no-path-adjustment' not in sys.argv) and ('-P' not in sys.argv): sys.argv.insert(1, '--no-path-adjustment') From 5fd20b9f242f7305b4c9376d85efa8225687afc2 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 16:07:38 -0400 Subject: [PATCH 14/31] Prefer .rst --- depends/{README.md => README.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename depends/{README.md => README.rst} (100%) diff --git a/depends/README.md b/depends/README.rst similarity index 100% rename from depends/README.md rename to depends/README.rst From 8c8a9ef83f79736deefe3ba90f800b4ec13d4c12 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 16:10:42 -0400 Subject: [PATCH 15/31] Update --- depends/README.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/depends/README.rst b/depends/README.rst index a94773bb1..62c101ecf 100644 --- a/depends/README.rst +++ b/depends/README.rst @@ -1 +1,4 @@ -Scripts in this directory download, build and install the non-packaged dependencies for testing with Travis CI. +Depends +======= + +Scripts in this directory can be used to download, build & install non-packaged dependencies; useful for testing with Travis CI. From 31e2e95533ec521b991872dc0664dd0cd7d050b4 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 16:14:35 -0400 Subject: [PATCH 16/31] Fix manifest --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 08bd0b4e3..7c783f1b8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -67,6 +67,7 @@ recursive-include Tk *.c recursive-include Tk *.txt recursive-include Tk *.rst recursive-include depends *.sh +recursive-include depends *.rst recursive-include docs *.bat recursive-include docs *.gitignore recursive-include docs *.html From 2d7f0c06d0254ced28849494a10e93cdf5f36b5b Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 16:15:53 -0400 Subject: [PATCH 17/31] Add testing deps --- MANIFEST.in | 1 + requirements.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 requirements.txt diff --git a/MANIFEST.in b/MANIFEST.in index 7c783f1b8..00c132d47 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include *.c include *.h include *.py include *.rst +include *.txt include .coveragerc include .gitattributes include .travis.yml diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..f3c7e8e6f --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +nose From 103354facc79f48f6bdc140fb9a1f85c10ae9e18 Mon Sep 17 00:00:00 2001 From: gcq Date: Sat, 28 Jun 2014 22:18:47 +0200 Subject: [PATCH 18/31] BMP now uses a reasonable resolution, and customizable using the "dpi" option. --- PIL/BmpImagePlugin.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py index 436ca5dce..c20ba184a 100644 --- a/PIL/BmpImagePlugin.py +++ b/PIL/BmpImagePlugin.py @@ -203,30 +203,37 @@ def _save(im, fp, filename, check=0): if check: return check + info = im.encoderinfo + + dpi = info.get("dpi", (96, 96)) + + # 1 meter == 39.3701 inches + ppm = map(lambda x: int(x * 39.3701), dpi) + stride = ((im.size[0]*bits+7)//8+3)&(~3) header = 40 # or 64 for OS/2 version 2 offset = 14 + header + colors * 4 image = stride * im.size[1] # bitmap header - fp.write(b"BM" + # file type (magic) - o32(offset+image) + # file size - o32(0) + # reserved - o32(offset)) # image data offset + fp.write(b"BM" + # file type (magic) + o32(offset+image) + # file size + o32(0) + # reserved + o32(offset)) # image data offset # bitmap info header - fp.write(o32(header) + # info header size - o32(im.size[0]) + # width - o32(im.size[1]) + # height - o16(1) + # planes - o16(bits) + # depth - o32(0) + # compression (0=uncompressed) - o32(image) + # size of bitmap - o32(1) + o32(1) + # resolution - o32(colors) + # colors used - o32(colors)) # colors important + fp.write(o32(header) + # info header size + o32(im.size[0]) + # width + o32(im.size[1]) + # height + o16(1) + # planes + o16(bits) + # depth + o32(0) + # compression (0=uncompressed) + o32(image) + # size of bitmap + o32(ppm[0]) + o32(ppm[1]) + # resolution + o32(colors) + # colors used + o32(colors)) # colors important - fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) + fp.write(b"\0" * (header - 40)) # padding (for OS/2 format) if im.mode == "1": for i in (0, 255): From ed3cffab6fca734d80f2e5806e579cd4eacd0a9a Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 16:20:12 -0400 Subject: [PATCH 19/31] Add self; make note about reqs I am adding requirements.txt to make it even more obvious we are using nose for testing, and for my own convenience. --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index f3c7e8e6f..86ddbd771 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ +# Testing reqs +-e . nose From b1afb882685ccf2cf015081491621ff9c82a938b Mon Sep 17 00:00:00 2001 From: Hugo Date: Sat, 28 Jun 2014 23:22:21 +0300 Subject: [PATCH 20/31] Update CHANGES.rst [CI skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c2ac9f136..dc95e34bf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Pyroma fix and add Python 3.4 to setup metadata #742 + [wirefool] + +- Top level flake8 fixes #741 + [aclark] + - Remove obsolete Animated Raster Graphics (ARG) support [hugovk] From df7d89616e9c2a4f327ced686d36fa9fa9d6025a Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 16:22:52 -0400 Subject: [PATCH 21/31] Run same tests Travis runs --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index a226ea602..53c502664 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ pre: bin/python setup.py develop bin/python selftest.py + bin/nosetests Tests/test_*.py check-manifest pyroma . viewdoc From d5bb962f83673f1b498a7ded1a30379d277c2c9a Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 28 Jun 2014 23:48:14 +0300 Subject: [PATCH 22/31] Add test to ensure a Pyroma is a 10/10 Mascarpone --- .travis.yml | 2 +- Tests/test_pyroma.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 Tests/test_pyroma.py diff --git a/.travis.yml b/.travis.yml index 4de1fde99..2e6ce3610 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ python: install: - "sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-qt4 ghostscript libffi-dev cmake" - "pip install cffi" - - "pip install coveralls nose" + - "pip install coveralls nose pyroma" - if [ "$TRAVIS_PYTHON_VERSION" == "2.6" ]; then pip install unittest2; fi # webp diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py new file mode 100644 index 000000000..2c2aeae96 --- /dev/null +++ b/Tests/test_pyroma.py @@ -0,0 +1,33 @@ +import unittest + +try: + import pyroma +except ImportError: + # Skip via setUp() + pass + + +class TestPyroma(unittest.TestCase): + + def setUp(self): + try: + import pyroma + except ImportError: + self.skipTest("ImportError") + + def test_pyroma(self): + # Arrange + data = pyroma.projectdata.get_data(".") + + # Act + rating = pyroma.ratings.rate(data) + + # Assert + # Should have a perfect score + self.assertEqual(rating, (10, [])) + + +if __name__ == '__main__': + unittest.main() + +# End of file From 29a65c1373b0f85d9caa627e182afc43e39f8393 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Sat, 28 Jun 2014 22:03:40 +0100 Subject: [PATCH 23/31] FIX: fix error for setup.py for Python 3 The subprocess command in Python 3 returns a bytes object. If the homebrew subprocess check returns a not-empty result, then setup crashes trying to combine the bytes with the string constants with and error like "TypeError: Can't mix strings and bytes in path components." --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e0a22f75f..1e634bc12 100644 --- a/setup.py +++ b/setup.py @@ -210,7 +210,9 @@ class pil_build_ext(build_ext): # if Homebrew is installed, use its lib and include directories import subprocess try: - prefix = subprocess.check_output(['brew', '--prefix']).strip() + prefix = subprocess.check_output( + ['brew', '--prefix'] + ).strip().decode('latin1') except: # Homebrew not installed prefix = None From 9318755a180a8e4012ca743865588faad416d2e7 Mon Sep 17 00:00:00 2001 From: gcq Date: Sat, 28 Jun 2014 23:21:22 +0200 Subject: [PATCH 24/31] Adds dpi to the Image info dictinoary. --- PIL/BmpImagePlugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py index c20ba184a..e320feb7a 100644 --- a/PIL/BmpImagePlugin.py +++ b/PIL/BmpImagePlugin.py @@ -28,6 +28,7 @@ __version__ = "0.7" from PIL import Image, ImageFile, ImagePalette, _binary +import math i8 = _binary.i8 i16 = _binary.i16le @@ -88,6 +89,7 @@ class BmpImageFile(ImageFile.ImageFile): bits = i16(s[14:]) self.size = i32(s[4:]), i32(s[8:]) compression = i32(s[16:]) + pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter lutsize = 4 colors = i32(s[32:]) direction = -1 @@ -162,6 +164,7 @@ class BmpImageFile(ImageFile.ImageFile): (rawmode, ((self.size[0]*bits+31)>>3)&(~3), direction))] self.info["compression"] = compression + self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm)) def _open(self): @@ -208,7 +211,7 @@ def _save(im, fp, filename, check=0): dpi = info.get("dpi", (96, 96)) # 1 meter == 39.3701 inches - ppm = map(lambda x: int(x * 39.3701), dpi) + ppm = tuple(map(lambda x: int(x * 39.3701), dpi)) stride = ((im.size[0]*bits+7)//8+3)&(~3) header = 40 # or 64 for OS/2 version 2 From 92b070ceaf453ef66e8cab91ddd5d59d13e1aa41 Mon Sep 17 00:00:00 2001 From: gcq Date: Sat, 28 Jun 2014 23:22:52 +0200 Subject: [PATCH 25/31] Addes tests --- Tests/test_file_bmp.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 29ddb9824..2870aba04 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -37,6 +37,18 @@ class TestFileBmp(PillowTestCase): self.assertEqual(im.size, reloaded.size) self.assertEqual(reloaded.format, "BMP") + def test_dpi(self): + dpi = (72, 72) + + output = io.BytesIO() + im = lena() + im.save(output, "BMP", dpi=dpi) + + output.seek(0) + reloaded = Image.open(output) + + self.assertEqual(reloaded.info["dpi"], dpi) + if __name__ == '__main__': unittest.main() From 2633408dc35eecd53ceec627166ff4aaf1e9d8f1 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sat, 28 Jun 2014 17:48:32 -0400 Subject: [PATCH 26/31] Update --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index dc95e34bf..773dea6e9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Fix error in setup.py for Python 3 + [matthew-brett] + - Pyroma fix and add Python 3.4 to setup metadata #742 [wirefool] From 61be1d8b19452083bad9c897b30be6411d95bf4a Mon Sep 17 00:00:00 2001 From: gcq Date: Sat, 28 Jun 2014 23:56:22 +0200 Subject: [PATCH 27/31] dpi key should only be present when there is resolution info in the BMP header. --- PIL/BmpImagePlugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py index e320feb7a..fae6bd391 100644 --- a/PIL/BmpImagePlugin.py +++ b/PIL/BmpImagePlugin.py @@ -97,6 +97,8 @@ class BmpImageFile(ImageFile.ImageFile): # upside-down storage self.size = self.size[0], 2**32 - self.size[1] direction = 0 + + self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm)) else: raise IOError("Unsupported BMP header type (%d)" % len(s)) @@ -164,7 +166,6 @@ class BmpImageFile(ImageFile.ImageFile): (rawmode, ((self.size[0]*bits+31)>>3)&(~3), direction))] self.info["compression"] = compression - self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), pxperm)) def _open(self): From 526ac7e278ca332684582d28ec46cd14ff70d0bb Mon Sep 17 00:00:00 2001 From: cgohlke Date: Sat, 28 Jun 2014 16:15:06 -0700 Subject: [PATCH 28/31] Fix build failure when compiler.include_dirs refers to nonexistent directory --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f4ce7525e..97ff17d97 100644 --- a/setup.py +++ b/setup.py @@ -405,7 +405,12 @@ class pil_build_ext(build_ext): # Find the best version for directory in self.compiler.include_dirs: - for name in os.listdir(directory): + try: + listdir = os.listdir(directory) + except Exception: + # WindowsError, FileNotFoundError + continue + for name in listdir: if name.startswith('openjpeg-') and \ os.path.isfile(os.path.join(directory, name, 'openjpeg.h')): From 441756919539f1d51040f11e59a04e0d39361eb3 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 29 Jun 2014 08:01:25 -0400 Subject: [PATCH 29/31] Run test-installed.py --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 53c502664..af0d041ad 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ pre: bin/python setup.py develop bin/python selftest.py bin/nosetests Tests/test_*.py + bin/python setup.py install + bin/python test-installed.py check-manifest pyroma . viewdoc From 8534e675469e0c1772cd5b30a0ee373c5b182447 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Sun, 29 Jun 2014 08:04:58 -0400 Subject: [PATCH 30/31] Completely automate my pre-release testing routine This Makefile completely automates my pre-release testing routine which typically occurs only in Python 2.7, but gives me a "good enough" view of the status quo. --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index af0d041ad..4f6a00711 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ pre: + virtualenv . + bin/pip install -r requirements.txt bin/python setup.py develop bin/python selftest.py bin/nosetests Tests/test_*.py From dba6fd7cd4e1e30bcda4e76e5896c9efdc0f790d Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 29 Jun 2014 13:35:11 -0700 Subject: [PATCH 31/31] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 773dea6e9..69279fe47 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Support for Resolution in BMP files #734 + [gcq] + - Fix error in setup.py for Python 3 [matthew-brett]