diff --git a/.travis.yml b/.travis.yml index 6300c6b21..f65cb2935 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,6 @@ install: - "travis_retry pip install cffi" - "travis_retry pip install nose" - "travis_retry pip install check-manifest" - - "travis_retry pip install Sphinx" # Pyroma tests sometimes hang on PyPy and Python 2.6; skip for those - if [ $TRAVIS_PYTHON_VERSION != "pypy" && $TRAVIS_PYTHON_VERSION != "2.6" ]; then travis_retry pip install pyroma; fi @@ -33,6 +32,9 @@ install: # Coverage 4.0 doesn't support Python 3.2 - if [ "$TRAVIS_PYTHON_VERSION" == "3.2" ]; then travis_retry pip install coverage==3.7.1; fi - if [ "$TRAVIS_PYTHON_VERSION" != "3.2" ]; then travis_retry pip install coverage; fi + + # docs only on python 2.7 + - if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then travis_retry pip install -r requirements.txt ; fi # clean checkout for manifest - mkdir /tmp/check-manifest && cp -a . /tmp/check-manifest @@ -51,9 +53,9 @@ script: - if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage run --append --include=PIL/* selftest.py; fi - if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then coverage run --append --include=PIL/* -m nose -vx Tests/test_*.py; fi - pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd - # Sphinx - - make install - - pushd docs; make html; make linkcheck; popd + + # Docs + - if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then make install && make doccheck; fi after_success: # gather the coverage data diff --git a/CHANGES.rst b/CHANGES.rst index 5741de86b..04ee11b4b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,51 @@ Changelog (Pillow) 3.3.0 (unreleased) ------------------ +- Jpeg qtables are unsigned chars #1814 + [thebostik] + +- Added additional EXIF tags #1841, TIFF Tags #1821 + [radarhere] + +- Changed documentation to refer to ImageSequence Iterator #1833 + [radarhere] + +- Fix Fedora prerequisites in installation docs, depends script #1842 + [living180] + +- Added _accept hook for PixarImagePlugin #1843 + [radarhere] + +- Removed outdated scanner classifier #1823 + [radarhere] + +- Combined identical error messages in _imaging #1825 + [radarhere] + +- Added debug option for setup.py to trace header and library finding #1790 + [wiredfool] + +- Fix doc building on travis #1820, #1844 + [wiredfool] + +- Fix for DIB/BMP images #1813 + [wiredfool] + +- Add PixarImagePlugin file extension #1809 + [radarhere] + +- Catch struct.errors when verifying png files #1805 + [wiredfool] + +- SpiderImagePlugin: raise an error when seeking in a non-stack file #1794 + [radarhere, jmichalon] + +- Added Support for 2/4 bpp Tiff Grayscale Images #1789 + [zwhfly] + +- Removed unused variable from selftest #1788 + [radarhere] + - Added warning for as_dict method (deprecated in 3.0.0) #1799 [radarhere] @@ -34,7 +79,7 @@ Changelog (Pillow) - Fixed documented name of JPEG property #1783 [radarhere] -- Fixed UnboundLocalErrorwhen loading a corrupt jpeg2k file #1780 +- Fixed UnboundLocalError when loading a corrupt jpeg2k file #1780 [wiredfool] - Fixed integer overflow in path.c #1773 diff --git a/Makefile b/Makefile index 148a70070..e4ead2923 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,12 @@ coverage: doc: $(MAKE) -C docs html +doccheck: + $(MAKE) -C docs html +# Don't make our test rely on the links in the docs being up every single build. +# We don't control them. But do check, and update them to the target of their redirectes. + $(MAKE) -C docs linkcheck || true + docserve: cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null& diff --git a/PIL/BmpImagePlugin.py b/PIL/BmpImagePlugin.py index e398445fa..94764855d 100644 --- a/PIL/BmpImagePlugin.py +++ b/PIL/BmpImagePlugin.py @@ -109,7 +109,12 @@ class BmpImageFile(ImageFile.ImageFile): for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']): file_info[mask] = i32(header_data[36+idx*4:40+idx*4]) else: - for mask in ['r_mask', 'g_mask', 'b_mask', 'a_mask']: + # 40 byte headers only have the three components in the bitfields masks, + # ref: https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376(v=vs.85).aspx + # See also https://github.com/python-pillow/Pillow/issues/1293 + # Note below, None is a key in the SUPPORTED and MASK_MODES structures. + file_info['a_mask'] = None + for mask in ['r_mask', 'g_mask', 'b_mask']: file_info[mask] = i32(read(4)) file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask']) file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask']) @@ -130,12 +135,22 @@ class BmpImageFile(ImageFile.ImageFile): # ----------------- Process BMP with Bitfields compression (not palette) if file_info['compression'] == self.BITFIELDS: SUPPORTED = { - 32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)], + 32: [(0xff0000, 0xff00, 0xff, 0x0), + (0xff0000, 0xff00, 0xff, None), + (0xff0000, 0xff00, 0xff, 0xff000000), + (0x0, 0x0, 0x0, 0x0)], 24: [(0xff0000, 0xff00, 0xff)], 16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)] } + # From inspecting DIB files from Vista screenshots, the + # file format is a 32 bit color image, where the 'Alpha' + # channel for screenshots is actually a transparency mask + # (255-alpha) where 00 is solid and ff is transparent. I + # can't find documentation of this, but it matches + # imagemagick's conversion of the .dib file into a .png. MASK_MODES = { (32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", + (32, (0xff0000, 0xff00, 0xff, None)): "BGRT", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA", (24, (0xff0000, 0xff00, 0xff)): "BGR", @@ -145,7 +160,7 @@ class BmpImageFile(ImageFile.ImageFile): if file_info['bits'] in SUPPORTED: if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]: raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])] - self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode + self.mode = "RGBA" if raw_mode in ("BGRA", "BGRT") else self.mode elif file_info['bits'] in (24, 16) and file_info['rgb_mask'] in SUPPORTED[file_info['bits']]: raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])] else: diff --git a/PIL/ExifTags.py b/PIL/ExifTags.py index 72e629dc5..a8ad26bcc 100644 --- a/PIL/ExifTags.py +++ b/PIL/ExifTags.py @@ -20,6 +20,7 @@ TAGS = { # possibly incomplete + 0x000b: "ProcessingSoftware", 0x00fe: "NewSubfileType", 0x00ff: "SubfileType", 0x0100: "ImageWidth", @@ -32,7 +33,6 @@ TAGS = { 0x0109: "CellLength", 0x010a: "FillOrder", 0x010d: "DocumentName", - 0x011d: "PageName", 0x010e: "ImageDescription", 0x010f: "Make", 0x0110: "Model", @@ -46,35 +46,74 @@ TAGS = { 0x011a: "XResolution", 0x011b: "YResolution", 0x011c: "PlanarConfiguration", + 0x011d: "PageName", 0x0120: "FreeOffsets", 0x0121: "FreeByteCounts", 0x0122: "GrayResponseUnit", 0x0123: "GrayResponseCurve", + 0x0124: "T4Options", + 0x0125: "T6Options", 0x0128: "ResolutionUnit", + 0x0129: "PageNumber", 0x012d: "TransferFunction", 0x0131: "Software", 0x0132: "DateTime", 0x013b: "Artist", 0x013c: "HostComputer", + 0x013d: "Predictor", 0x013e: "WhitePoint", 0x013f: "PrimaryChromaticities", 0x0140: "ColorMap", + 0x0141: "HalftoneHints", + 0x0142: "TileWidth", + 0x0143: "TileLength", + 0x0144: "TileOffsets", + 0x0145: "TileByteCounts", + 0x014a: "SubIFDs", + 0x014c: "InkSet", + 0x014d: "InkNames", + 0x014e: "NumberOfInks", + 0x0150: "DotRange", + 0x0151: "TargetPrinter", 0x0152: "ExtraSamples", + 0x0153: "SampleFormat", + 0x0154: "SMinSampleValue", + 0x0155: "SMaxSampleValue", + 0x0156: "TransferRange", + 0x0157: "ClipPath", + 0x0158: "XClipPathUnits", + 0x0159: "YClipPathUnits", + 0x015a: "Indexed", + 0x015b: "JPEGTables", + 0x015f: "OPIProxy", + 0x0200: "JPEGProc", 0x0201: "JpegIFOffset", 0x0202: "JpegIFByteCount", + 0x0203: "JpegRestartInterval", + 0x0205: "JpegLosslessPredictors", + 0x0206: "JpegPointTransforms", + 0x0207: "JpegQTables", + 0x0208: "JpegDCTables", + 0x0209: "JpegACTables", 0x0211: "YCbCrCoefficients", 0x0212: "YCbCrSubSampling", 0x0213: "YCbCrPositioning", 0x0214: "ReferenceBlackWhite", + 0x02bc: "XMLPacket", 0x1000: "RelatedImageFileFormat", 0x1001: "RelatedImageWidth", 0x1002: "RelatedImageLength", + 0x4746: "Rating", + 0x4749: "RatingPercent", + 0x800d: "ImageID", 0x828d: "CFARepeatPatternDim", 0x828e: "CFAPattern", 0x828f: "BatteryLevel", 0x8298: "Copyright", 0x829a: "ExposureTime", 0x829d: "FNumber", + 0x83bb: "IPTCNAA", + 0x8649: "ImageResources", 0x8769: "ExifOffset", 0x8773: "InterColorProfile", 0x8822: "ExposureProgram", @@ -114,6 +153,11 @@ TAGS = { 0x9290: "SubsecTime", 0x9291: "SubsecTimeOriginal", 0x9292: "SubsecTimeDigitized", + 0x9c9b: "XPTitle", + 0x9c9c: "XPComment", + 0x9c9d: "XPAuthor", + 0x9c9e: "XPKeywords", + 0x9c9f: "XPSubject", 0xa000: "FlashPixVersion", 0xa001: "ColorSpace", 0xa002: "ExifImageWidth", @@ -151,7 +195,85 @@ TAGS = { 0xa434: "LensModel", 0xa435: "LensSerialNumber", 0xa500: "Gamma", - + 0xc4a5: "PrintImageMatching", + 0xc612: "DNGVersion", + 0xc613: "DNGBackwardVersion", + 0xc614: "UniqueCameraModel", + 0xc615: "LocalizedCameraModel", + 0xc616: "CFAPlaneColor", + 0xc617: "CFALayout", + 0xc618: "LinearizationTable", + 0xc619: "BlackLevelRepeatDim", + 0xc61a: "BlackLevel", + 0xc61b: "BlackLevelDeltaH", + 0xc61c: "BlackLevelDeltaV", + 0xc61d: "WhiteLevel", + 0xc61e: "DefaultScale", + 0xc61f: "DefaultCropOrigin", + 0xc620: "DefaultCropSize", + 0xc621: "ColorMatrix1", + 0xc622: "ColorMatrix2", + 0xc623: "CameraCalibration1", + 0xc624: "CameraCalibration2", + 0xc625: "ReductionMatrix1", + 0xc626: "ReductionMatrix2", + 0xc627: "AnalogBalance", + 0xc628: "AsShotNeutral", + 0xc629: "AsShotWhiteXY", + 0xc62a: "BaselineExposure", + 0xc62b: "BaselineNoise", + 0xc62c: "BaselineSharpness", + 0xc62d: "BayerGreenSplit", + 0xc62e: "LinearResponseLimit", + 0xc62f: "CameraSerialNumber", + 0xc630: "LensInfo", + 0xc631: "ChromaBlurRadius", + 0xc632: "AntiAliasStrength", + 0xc633: "ShadowScale", + 0xc634: "DNGPrivateData", + 0xc635: "MakerNoteSafety", + 0xc65a: "CalibrationIlluminant1", + 0xc65b: "CalibrationIlluminant2", + 0xc65c: "BestQualityScale", + 0xc65d: "RawDataUniqueID", + 0xc68b: "OriginalRawFileName", + 0xc68c: "OriginalRawFileData", + 0xc68d: "ActiveArea", + 0xc68e: "MaskedAreas", + 0xc68f: "AsShotICCProfile", + 0xc690: "AsShotPreProfileMatrix", + 0xc691: "CurrentICCProfile", + 0xc692: "CurrentPreProfileMatrix", + 0xc6bf: "ColorimetricReference", + 0xc6f3: "CameraCalibrationSignature", + 0xc6f4: "ProfileCalibrationSignature", + 0xc6f6: "AsShotProfileName", + 0xc6f7: "NoiseReductionApplied", + 0xc6f8: "ProfileName", + 0xc6f9: "ProfileHueSatMapDims", + 0xc6fa: "ProfileHueSatMapData1", + 0xc6fb: "ProfileHueSatMapData2", + 0xc6fc: "ProfileToneCurve", + 0xc6fd: "ProfileEmbedPolicy", + 0xc6fe: "ProfileCopyright", + 0xc714: "ForwardMatrix1", + 0xc715: "ForwardMatrix2", + 0xc716: "PreviewApplicationName", + 0xc717: "PreviewApplicationVersion", + 0xc718: "PreviewSettingsName", + 0xc719: "PreviewSettingsDigest", + 0xc71a: "PreviewColorSpace", + 0xc71b: "PreviewDateTime", + 0xc71c: "RawImageDigest", + 0xc71d: "OriginalRawFileDigest", + 0xc71e: "SubTileBlockSize", + 0xc71f: "RowInterleaveFactor", + 0xc725: "ProfileLookTableDims", + 0xc726: "ProfileLookTableData", + 0xc740: "OpcodeList1", + 0xc741: "OpcodeList2", + 0xc74e: "OpcodeList3", + 0xc761: "NoiseProfile" } ## diff --git a/PIL/GbrImagePlugin.py b/PIL/GbrImagePlugin.py index 5fef39960..d62981c28 100644 --- a/PIL/GbrImagePlugin.py +++ b/PIL/GbrImagePlugin.py @@ -55,7 +55,7 @@ class GbrImageFile(ImageFile.ImageFile): if width <= 0 or height <= 0: raise SyntaxError("not a GIMP brush") if color_depth not in (1, 4): - raise SyntaxError("Unsupported GMP brush color depth: %s" % color_depth) + raise SyntaxError("Unsupported GIMP brush color depth: %s" % color_depth) if version == 1: comment_length = header_size-20 diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index ba5504acb..5eea2fd38 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -1,7 +1,7 @@ # The Python Imaging Library. # $Id$ -# Optional color managment support, based on Kevin Cazabon's PyCMS +# Optional color management support, based on Kevin Cazabon's PyCMS # library. # History: diff --git a/PIL/JpegImagePlugin.py b/PIL/JpegImagePlugin.py index 386939ba7..0ed1fc73f 100644 --- a/PIL/JpegImagePlugin.py +++ b/PIL/JpegImagePlugin.py @@ -640,7 +640,7 @@ def _save(im, fp, filename): try: if len(table) != 64: raise - table = array.array('b', table) + table = array.array('B', table) except TypeError: raise ValueError("Invalid quantization table") else: diff --git a/PIL/OleFileIO.py b/PIL/OleFileIO.py index 4cf106d97..1998e3c10 100755 --- a/PIL/OleFileIO.py +++ b/PIL/OleFileIO.py @@ -540,16 +540,16 @@ class OleMetadata(object): OLE file. References for SummaryInformation stream: - - http://msdn.microsoft.com/en-us/library/dd942545.aspx - - http://msdn.microsoft.com/en-us/library/dd925819%28v=office.12%29.aspx - - http://msdn.microsoft.com/en-us/library/windows/desktop/aa380376%28v=vs.85%29.aspx - - http://msdn.microsoft.com/en-us/library/aa372045.aspx - - http://sedna-soft.de/summary-information-stream/ + - https://msdn.microsoft.com/en-us/library/dd942545.aspx + - https://msdn.microsoft.com/en-us/library/dd925819%28v=office.12%29.aspx + - https://msdn.microsoft.com/en-us/library/windows/desktop/aa380376%28v=vs.85%29.aspx + - https://msdn.microsoft.com/en-us/library/aa372045.aspx + - http://sedna-soft.de/articles/summary-information-stream/ - http://poi.apache.org/apidocs/org/apache/poi/hpsf/SummaryInformation.html References for DocumentSummaryInformation stream: - - http://msdn.microsoft.com/en-us/library/dd945671%28v=office.12%29.aspx - - http://msdn.microsoft.com/en-us/library/windows/desktop/aa380374%28v=vs.85%29.aspx + - https://msdn.microsoft.com/en-us/library/dd945671%28v=office.12%29.aspx + - https://msdn.microsoft.com/en-us/library/windows/desktop/aa380374%28v=vs.85%29.aspx - http://poi.apache.org/apidocs/org/apache/poi/hpsf/DocumentSummaryInformation.html new in version 0.25 diff --git a/PIL/PixarImagePlugin.py b/PIL/PixarImagePlugin.py index db2ee55f8..fd002d9cf 100644 --- a/PIL/PixarImagePlugin.py +++ b/PIL/PixarImagePlugin.py @@ -29,6 +29,10 @@ __version__ = "0.1" i16 = _binary.i16le +def _accept(prefix): + return prefix[:4] == b"\200\350\000\000" + + ## # Image plugin for PIXAR raster images. @@ -39,7 +43,7 @@ class PixarImageFile(ImageFile.ImageFile): def _open(self): - # assuming a 4-byte magic label (FIXME: add "_accept" hook) + # assuming a 4-byte magic label s = self.fp.read(4) if s != b"\200\350\000\000": raise SyntaxError("not a PIXAR file") @@ -62,7 +66,6 @@ class PixarImageFile(ImageFile.ImageFile): # # -------------------------------------------------------------------- -Image.register_open(PixarImageFile.format, PixarImageFile) +Image.register_open(PixarImageFile.format, PixarImageFile, _accept) -# -# FIXME: what's the standard extension? +Image.register_extension(PixarImageFile.format, ".pxr") diff --git a/PIL/PngImagePlugin.py b/PIL/PngImagePlugin.py index d6778821b..cc58fbbc4 100644 --- a/PIL/PngImagePlugin.py +++ b/PIL/PngImagePlugin.py @@ -36,6 +36,7 @@ from __future__ import print_function import logging import re import zlib +import struct from PIL import Image, ImageFile, ImagePalette, _binary @@ -106,6 +107,7 @@ class ChunkStream(object): def read(self): "Fetch a new chunk. Returns header information." + cid = None if self.queue: cid, pos, length = self.queue[-1] @@ -116,7 +118,7 @@ class ChunkStream(object): cid = s[4:] pos = self.fp.tell() length = i32(s) - + if not is_cid(cid): raise SyntaxError("broken PNG file (chunk %s)" % repr(cid)) @@ -138,11 +140,15 @@ class ChunkStream(object): def crc(self, cid, data): "Read and verify checksum" - crc1 = Image.core.crc32(data, Image.core.crc32(cid)) - crc2 = i16(self.fp.read(2)), i16(self.fp.read(2)) - if crc1 != crc2: - raise SyntaxError("broken PNG file" - "(bad header checksum in %s)" % cid) + try: + crc1 = Image.core.crc32(data, Image.core.crc32(cid)) + crc2 = i16(self.fp.read(2)), i16(self.fp.read(2)) + if crc1 != crc2: + raise SyntaxError("broken PNG file (bad header checksum in %s)" + % cid) + except struct.error: + raise SyntaxError("broken PNG file (incomplete checksum in %s)" + % cid) def crc_skip(self, cid, data): "Read checksum. Used if the C module is not present" @@ -157,7 +163,11 @@ class ChunkStream(object): cids = [] while True: - cid, pos, length = self.read() + try: + cid, pos, length = self.read() + except struct.error: + raise IOError("truncated PNG file") + if cid == endchunk: break self.crc(cid, ImageFile._safe_read(self.fp, length)) diff --git a/PIL/SpiderImagePlugin.py b/PIL/SpiderImagePlugin.py index d5457893c..a59329497 100644 --- a/PIL/SpiderImagePlugin.py +++ b/PIL/SpiderImagePlugin.py @@ -173,7 +173,7 @@ class SpiderImageFile(ImageFile.ImageFile): def seek(self, frame): if self.istack == 0: - return + raise EOFError("attempt to seek in a non-stack file") if frame >= self._nimages: raise EOFError("attempt to seek past end of file") self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes) diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 035d6f8d4..99beb0f97 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -141,34 +141,56 @@ OPEN_INFO = { (MM, 0, (1,), 1, (1,), ()): ("1", "1;I"), (II, 0, (1,), 2, (1,), ()): ("1", "1;IR"), (MM, 0, (1,), 2, (1,), ()): ("1", "1;IR"), + (II, 1, (1,), 1, (1,), ()): ("1", "1"), + (MM, 1, (1,), 1, (1,), ()): ("1", "1"), + (II, 1, (1,), 2, (1,), ()): ("1", "1;R"), + (MM, 1, (1,), 2, (1,), ()): ("1", "1;R"), + + (II, 0, (1,), 1, (2,), ()): ("L", "L;2I"), + (MM, 0, (1,), 1, (2,), ()): ("L", "L;2I"), + (II, 0, (1,), 2, (2,), ()): ("L", "L;2IR"), + (MM, 0, (1,), 2, (2,), ()): ("L", "L;2IR"), + (II, 1, (1,), 1, (2,), ()): ("L", "L;2"), + (MM, 1, (1,), 1, (2,), ()): ("L", "L;2"), + (II, 1, (1,), 2, (2,), ()): ("L", "L;2R"), + (MM, 1, (1,), 2, (2,), ()): ("L", "L;2R"), + + (II, 0, (1,), 1, (4,), ()): ("L", "L;4I"), + (MM, 0, (1,), 1, (4,), ()): ("L", "L;4I"), + (II, 0, (1,), 2, (4,), ()): ("L", "L;4IR"), + (MM, 0, (1,), 2, (4,), ()): ("L", "L;4IR"), + (II, 1, (1,), 1, (4,), ()): ("L", "L;4"), + (MM, 1, (1,), 1, (4,), ()): ("L", "L;4"), + (II, 1, (1,), 2, (4,), ()): ("L", "L;4R"), + (MM, 1, (1,), 2, (4,), ()): ("L", "L;4R"), + (II, 0, (1,), 1, (8,), ()): ("L", "L;I"), (MM, 0, (1,), 1, (8,), ()): ("L", "L;I"), (II, 0, (1,), 2, (8,), ()): ("L", "L;IR"), (MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"), - (II, 0, (3,), 1, (32,), ()): ("F", "F;32F"), - (MM, 0, (3,), 1, (32,), ()): ("F", "F;32BF"), - (II, 1, (1,), 1, (1,), ()): ("1", "1"), - (MM, 1, (1,), 1, (1,), ()): ("1", "1"), - (II, 1, (1,), 1, (4,), ()): ("L", "L;4"), - # ? - (II, 1, (1,), 2, (1,), ()): ("1", "1;R"), - (MM, 1, (1,), 2, (1,), ()): ("1", "1;R"), (II, 1, (1,), 1, (8,), ()): ("L", "L"), (MM, 1, (1,), 1, (8,), ()): ("L", "L"), - (II, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), - (MM, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), (II, 1, (1,), 2, (8,), ()): ("L", "L;R"), (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"), + (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"), + (II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"), (MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"), (II, 1, (2,), 1, (16,), ()): ("I;16S", "I;16S"), (MM, 1, (2,), 1, (16,), ()): ("I;16BS", "I;16BS"), + + (II, 0, (3,), 1, (32,), ()): ("F", "F;32F"), + (MM, 0, (3,), 1, (32,), ()): ("F", "F;32BF"), (II, 1, (1,), 1, (32,), ()): ("I", "I;32N"), (II, 1, (2,), 1, (32,), ()): ("I", "I;32S"), (MM, 1, (2,), 1, (32,), ()): ("I;32BS", "I;32BS"), (II, 1, (3,), 1, (32,), ()): ("F", "F;32F"), (MM, 1, (3,), 1, (32,), ()): ("F", "F;32BF"), + + (II, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), + (MM, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"), + (II, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), (MM, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"), (II, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"), @@ -183,6 +205,7 @@ OPEN_INFO = { (MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"), (II, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 (MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"), # Corel Draw 10 + (II, 3, (1,), 1, (1,), ()): ("P", "P;1"), (MM, 3, (1,), 1, (1,), ()): ("P", "P;1"), (II, 3, (1,), 2, (1,), ()): ("P", "P;1R"), @@ -201,10 +224,13 @@ OPEN_INFO = { (MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"), (II, 3, (1,), 2, (8,), ()): ("P", "P;R"), (MM, 3, (1,), 2, (8,), ()): ("P", "P;R"), + (II, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"), (MM, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"), + (II, 6, (1,), 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"), (MM, 6, (1,), 1, (8, 8, 8), ()): ("YCbCr", "YCbCr"), + (II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"), } diff --git a/PIL/TiffTags.py b/PIL/TiffTags.py index bee921dcd..23433a120 100644 --- a/PIL/TiffTags.py +++ b/PIL/TiffTags.py @@ -188,10 +188,28 @@ TAGS = {347: 'JPEGTables', 700: 'XMP', # Additional Exif Info + 32932: 'Wang Annotation', 33434: 'ExposureTime', 33437: 'FNumber', + 33445: 'MD FileTag', + 33446: 'MD ScalePixel', + 33447: 'MD ColorTable', + 33448: 'MD LabName', + 33449: 'MD SampleInfo', + 33450: 'MD PrepDate', + 33451: 'MD PrepTime', + 33452: 'MD FileUnits', + 33550: 'ModelPixelScaleTag', 33723: 'IptcNaaInfo', + 33918: 'INGR Packet Data Tag', + 33919: 'INGR Flag Registers', + 33920: 'IrasB Transformation Matrix', + 33922: 'ModelTiepointTag', + 34264: 'ModelTransformationTag', 34377: 'PhotoshopInfo', + 34735: 'GeoKeyDirectoryTag', + 34736: 'GeoDoubleParamsTag', + 34737: 'GeoAsciiParamsTag', 34850: 'ExposureProgram', 34852: 'SpectralSensitivity', 34855: 'ISOSpeedRatings', @@ -202,11 +220,15 @@ TAGS = {347: 'JPEGTables', 34867: 'ISOSpeed', 34868: 'ISOSpeedLatitudeyyy', 34869: 'ISOSpeedLatitudezzz', + 34908: 'HylaFAX FaxRecvParams', + 34909: 'HylaFAX FaxSubAddress', + 34910: 'HylaFAX FaxRecvTime', 36864: 'ExifVersion', 36867: 'DateTimeOriginal', 36868: 'DateTImeDigitized', 37121: 'ComponentsConfiguration', 37122: 'CompressedBitsPerPixel', + 37724: 'ImageSourceData', 37377: 'ShutterSpeedValue', 37378: 'ApertureValue', 37379: 'BrightnessValue', @@ -259,7 +281,13 @@ TAGS = {347: 'JPEGTables', 42035: 'LensMake', 42036: 'LensModel', 42037: 'LensSerialNumber', + 42112: 'GDAL_METADATA', + 42113: 'GDAL_NODATA', 42240: 'Gamma', + 50215: 'Oce Scanjob Description', + 50216: 'Oce Application Selector', + 50217: 'Oce Identification Number', + 50218: 'Oce ImageLogic Characteristics', # Adobe DNG 50706: 'DNGVersion', @@ -298,6 +326,7 @@ TAGS = {347: 'JPEGTables', 50740: 'DNGPrivateData', 50778: 'CalibrationIlluminant1', 50779: 'CalibrationIlluminant2', + 50784: 'Alias Layer Metadata' } diff --git a/README.rst b/README.rst index e996ed4f7..13fd77a78 100644 --- a/README.rst +++ b/README.rst @@ -6,41 +6,56 @@ Python Imaging Library (Fork) Pillow is the friendly PIL fork by `Alex Clark and Contributors `_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. -.. image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg - :target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow +.. start-badges -.. image:: https://readthedocs.org/projects/pillow/badge/?version=latest +.. list-table:: + :stub-columns: 1 + + * - docs + - |docs| + * - tests + - | |linux| |osx| |windows| |coverage| |health| + * - package + - |zenodo| |version| |downloads| + +.. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest :target: http://pillow.readthedocs.org/?badge=latest :alt: Documentation Status -.. image:: https://img.shields.io/travis/python-pillow/Pillow/master.svg?label=Linux%20build +.. |linux| image:: https://img.shields.io/travis/python-pillow/Pillow/master.svg?label=Linux%20build :target: https://travis-ci.org/python-pillow/Pillow :alt: Travis CI build status (Linux) -.. image:: https://img.shields.io/travis/python-pillow/pillow-wheels/latest.svg?label=OS%20X%20build +.. |osx| image:: https://img.shields.io/travis/python-pillow/pillow-wheels/latest.svg?label=OS%20X%20build :target: https://travis-ci.org/python-pillow/pillow-wheels :alt: Travis CI build status (OS X) -.. image:: https://img.shields.io/appveyor/ci/python-pillow/Pillow/master.svg?label=Windows%20build +.. |windows| image:: https://img.shields.io/appveyor/ci/python-pillow/Pillow/master.svg?label=Windows%20build :target: https://ci.appveyor.com/project/python-pillow/Pillow :alt: AppVeyor CI build status (Windows) -.. image:: https://img.shields.io/pypi/v/pillow.svg - :target: https://pypi.python.org/pypi/Pillow/ - :alt: Latest PyPI version - -.. image:: https://img.shields.io/pypi/dm/pillow.svg - :target: https://pypi.python.org/pypi/Pillow/ - :alt: Number of PyPI downloads - -.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.svg?branch=master&service=github +.. |coverage| image:: https://coveralls.io/repos/python-pillow/Pillow/badge.svg?branch=master&service=github :target: https://coveralls.io/github/python-pillow/Pillow?branch=master :alt: Code coverage -.. image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.svg +.. |health| image:: https://landscape.io/github/python-pillow/Pillow/master/landscape.svg :target: https://landscape.io/github/python-pillow/Pillow/master :alt: Code health +.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg + :target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow + +.. |version| image:: https://img.shields.io/pypi/v/pillow.svg + :target: https://pypi.python.org/pypi/Pillow/ + :alt: Latest PyPI version + +.. |downloads| image:: https://img.shields.io/pypi/dm/pillow.svg + :target: https://pypi.python.org/pypi/Pillow/ + :alt: Number of PyPI downloads + +.. end-badges + + More Information ---------------- @@ -50,7 +65,7 @@ More Information - `Installation `_ - `Handbook `_ -- `Contribute `_ +- `Contribute `_ - `Issues `_ - `Pull requests `_ diff --git a/Tests/images/clipboard.dib b/Tests/images/clipboard.dib new file mode 100644 index 000000000..03dcab81d Binary files /dev/null and b/Tests/images/clipboard.dib differ diff --git a/Tests/images/clipboard_target.png b/Tests/images/clipboard_target.png new file mode 100644 index 000000000..46a696543 Binary files /dev/null and b/Tests/images/clipboard_target.png differ diff --git a/Tests/images/hopper_jpg.tif b/Tests/images/hopper_jpg.tif new file mode 100644 index 000000000..fee98f5ec Binary files /dev/null and b/Tests/images/hopper_jpg.tif differ diff --git a/Tests/images/tiff_gray_2_4_bpp/hopper2.tif b/Tests/images/tiff_gray_2_4_bpp/hopper2.tif new file mode 100644 index 000000000..70057219a Binary files /dev/null and b/Tests/images/tiff_gray_2_4_bpp/hopper2.tif differ diff --git a/Tests/images/tiff_gray_2_4_bpp/hopper2I.tif b/Tests/images/tiff_gray_2_4_bpp/hopper2I.tif new file mode 100644 index 000000000..9feaa08f0 Binary files /dev/null and b/Tests/images/tiff_gray_2_4_bpp/hopper2I.tif differ diff --git a/Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif b/Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif new file mode 100644 index 000000000..462416955 Binary files /dev/null and b/Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif differ diff --git a/Tests/images/tiff_gray_2_4_bpp/hopper2R.tif b/Tests/images/tiff_gray_2_4_bpp/hopper2R.tif new file mode 100644 index 000000000..65e0f623e Binary files /dev/null and b/Tests/images/tiff_gray_2_4_bpp/hopper2R.tif differ diff --git a/Tests/images/tiff_gray_2_4_bpp/hopper4.tif b/Tests/images/tiff_gray_2_4_bpp/hopper4.tif new file mode 100644 index 000000000..7a38271ea Binary files /dev/null and b/Tests/images/tiff_gray_2_4_bpp/hopper4.tif differ diff --git a/Tests/images/tiff_gray_2_4_bpp/hopper4I.tif b/Tests/images/tiff_gray_2_4_bpp/hopper4I.tif new file mode 100644 index 000000000..5c3a17f52 Binary files /dev/null and b/Tests/images/tiff_gray_2_4_bpp/hopper4I.tif differ diff --git a/Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif b/Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif new file mode 100644 index 000000000..7652f5e6d Binary files /dev/null and b/Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif differ diff --git a/Tests/images/tiff_gray_2_4_bpp/hopper4R.tif b/Tests/images/tiff_gray_2_4_bpp/hopper4R.tif new file mode 100644 index 000000000..0cdf4d26e Binary files /dev/null and b/Tests/images/tiff_gray_2_4_bpp/hopper4R.tif differ diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 79ad439ba..885bd4996 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -24,7 +24,7 @@ class TestBmpReference(PillowTestCase): # print ("Bad Image %s: %s" %(f,msg)) def test_questionable(self): - """ These shouldn't crash/dos, but its not well defined that these + """ These shouldn't crash/dos, but it's not well defined that these are in spec """ for f in self.get_files('q'): try: diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 25f70139d..29d59ee44 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -70,6 +70,13 @@ class TestFileBmp(PillowTestCase): self.assertEqual(im.size, reloaded.size) self.assertEqual(reloaded.format, "JPEG") + def test_load_dib(self): + # test for #1293, Imagegrab returning Unsupported Bitfields Format + im = BmpImagePlugin.DibImageFile('Tests/images/clipboard.dib') + target = Image.open('Tests/images/clipboard_target.png') + self.assert_image_equal(im.convert('RGB'), target.convert('RGB')) + self.assert_image_similar(im, target, 1) + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index 84fa873be..374b9989d 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -7,18 +7,6 @@ TEST_FILE = "Tests/images/iptc.jpg" class TestFileIptc(PillowTestCase): - # Helpers - - def dummy_IptcImagePlugin(self): - # Create an IptcImagePlugin object without initializing it - class FakeImage(object): - pass - im = FakeImage() - im.__class__ = IptcImagePlugin.IptcImageFile - return im - - # Tests - def test_getiptcinfo_jpg_none(self): # Arrange im = hopper() diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 7b60f60b1..3b2e6cb20 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -314,6 +314,10 @@ class TestFileJpeg(PillowTestCase): 30) self.assert_image_similar(im, self.roundtrip(im, qtables='keep'), 30) + # valid bounds for baseline qtable + bounds_qtable = [int(s) for s in ("255 1 " * 32).split(None)] + self.roundtrip(im, qtables=[bounds_qtable]) + # values from wizard.txt in jpeg9-a src package. standard_l_qtable = [int(s) for s in """ 16 11 10 16 24 40 51 61 diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index bca638e22..83d347239 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -419,6 +419,39 @@ class TestFileLibTiff(LibTiffTestCase): self.assertEqual(im.mode, "L") self.assert_image_similar(im, original, 7.3) + def test_gray_semibyte_per_pixel(self): + test_files = ( + ( + 24.8,#epsilon + (#group + "Tests/images/tiff_gray_2_4_bpp/hopper2.tif", + "Tests/images/tiff_gray_2_4_bpp/hopper2I.tif", + "Tests/images/tiff_gray_2_4_bpp/hopper2R.tif", + "Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif", + ) + ), + ( + 7.3,#epsilon + (#group + "Tests/images/tiff_gray_2_4_bpp/hopper4.tif", + "Tests/images/tiff_gray_2_4_bpp/hopper4I.tif", + "Tests/images/tiff_gray_2_4_bpp/hopper4R.tif", + "Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif", + ) + ), + ) + original = hopper("L") + for epsilon, group in test_files: + im = Image.open(group[0]) + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.mode, "L") + self.assert_image_similar(im, original, epsilon) + for file in group[1:]: + im2 = Image.open(file) + self.assertEqual(im2.size, (128, 128)) + self.assertEqual(im2.mode, "L") + self.assert_image_equal(im, im2) + def test_save_bytesio(self): # PR 1011 # Test TIFF saving to io.BytesIO() object. diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index cb72b2d73..f6a47c464 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -253,6 +253,22 @@ class TestFilePng(PillowTestCase): im.load() self.assertRaises(RuntimeError, im.verify) + def test_verify_struct_error(self): + # Check open/load/verify exception (#1755) + + # offsets to test, -10: breaks in i32() in read. (IOError) + # -13: breaks in crc, txt chunk. + # -14: malformed chunk + + for offset in (-10, -13, -14): + with open(TEST_PNG_FILE,'rb') as f: + test_file = f.read()[:offset] + + im = Image.open(BytesIO(test_file)) + self.assertTrue(im.fp is not None) + self.assertRaises((IOError, SyntaxError), im.verify) + + def test_roundtrip_dpi(self): # Check dpi roundtripping diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 53cf3915b..956063167 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -1,6 +1,7 @@ from helper import unittest, PillowTestCase, hopper from PIL import Image +from PIL import ImageSequence from PIL import SpiderImagePlugin TEST_FILE = "Tests/images/hopper.spider" @@ -85,6 +86,17 @@ class TestImageSpider(PillowTestCase): self.assertRaises(IOError, lambda: Image.open(invalid_file)) + def test_nonstack_file(self): + im = Image.open(TEST_FILE) + + self.assertRaises(EOFError, lambda: im.seek(0)) + + def test_nonstack_dos(self): + im = Image.open(TEST_FILE) + for i, frame in enumerate(ImageSequence.Iterator(im)): + if i > 1: + self.fail("Non-stack DOS file test failed") + if __name__ == '__main__': unittest.main() diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 5af7b55aa..84fdd0f49 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -337,6 +337,39 @@ class TestFileTiff(PillowTestCase): self.assertEqual(im.mode, "L") self.assert_image_similar(im, original, 7.3) + def test_gray_semibyte_per_pixel(self): + test_files = ( + ( + 24.8,#epsilon + (#group + "Tests/images/tiff_gray_2_4_bpp/hopper2.tif", + "Tests/images/tiff_gray_2_4_bpp/hopper2I.tif", + "Tests/images/tiff_gray_2_4_bpp/hopper2R.tif", + "Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif", + ) + ), + ( + 7.3,#epsilon + (#group + "Tests/images/tiff_gray_2_4_bpp/hopper4.tif", + "Tests/images/tiff_gray_2_4_bpp/hopper4I.tif", + "Tests/images/tiff_gray_2_4_bpp/hopper4R.tif", + "Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif", + ) + ), + ) + original = hopper("L") + for epsilon, group in test_files: + im = Image.open(group[0]) + self.assertEqual(im.size, (128, 128)) + self.assertEqual(im.mode, "L") + self.assert_image_similar(im, original, epsilon) + for file in group[1:]: + im2 = Image.open(file) + self.assertEqual(im2.size, (128, 128)) + self.assertEqual(im2.mode, "L") + self.assert_image_equal(im, im2) + def test_page_number_x_0(self): # Issue 973 # Test TIFF with tag 297 (Page Number) having value of 0 0. @@ -404,6 +437,19 @@ class TestFileTiff(PillowTestCase): self.assertEqual(im.size, (10, 10)) im.load() + def test_save_tiff_with_jpegtables(self): + # Arrange + outfile = self.tempfile("temp.tif") + + # Created with ImageMagick: convert hopper.jpg hopper_jpg.tif + # Contains JPEGTables (347) tag + infile = "Tests/images/hopper_jpg.tif" + im = Image.open(infile) + + # Act / Assert + # Should not raise UnicodeDecodeError or anything else + im.save(outfile) + if __name__ == '__main__': unittest.main() diff --git a/_imaging.c b/_imaging.c index aaa11d8d9..12ed32168 100644 --- a/_imaging.c +++ b/_imaging.c @@ -280,6 +280,8 @@ int PyImaging_GetBuffer(PyObject* buffer, Py_buffer *view) /* error messages */ static const char* must_be_sequence = "argument must be a sequence"; +static const char* must_be_two_coordinates = + "coordinate list must contain exactly 2 coordinates"; static const char* wrong_mode = "unrecognized image mode"; static const char* wrong_raw_mode = "unrecognized raw mode"; static const char* outside_image = "image index out of range"; @@ -2398,9 +2400,7 @@ _draw_arc(ImagingDrawObject* self, PyObject* args) if (n < 0) return NULL; if (n != 2) { - PyErr_SetString(PyExc_TypeError, - "coordinate list must contain exactly 2 coordinates" - ); + PyErr_SetString(PyExc_TypeError, must_be_two_coordinates); return NULL; } @@ -2472,9 +2472,7 @@ _draw_chord(ImagingDrawObject* self, PyObject* args) if (n < 0) return NULL; if (n != 2) { - PyErr_SetString(PyExc_TypeError, - "coordinate list must contain exactly 2 coordinates" - ); + PyErr_SetString(PyExc_TypeError, must_be_two_coordinates); return NULL; } @@ -2509,9 +2507,7 @@ _draw_ellipse(ImagingDrawObject* self, PyObject* args) if (n < 0) return NULL; if (n != 2) { - PyErr_SetString(PyExc_TypeError, - "coordinate list must contain exactly 2 coordinates" - ); + PyErr_SetString(PyExc_TypeError, must_be_two_coordinates); return NULL; } @@ -2692,9 +2688,7 @@ _draw_pieslice(ImagingDrawObject* self, PyObject* args) if (n < 0) return NULL; if (n != 2) { - PyErr_SetString(PyExc_TypeError, - "coordinate list must contain exactly 2 coordinates" - ); + PyErr_SetString(PyExc_TypeError, must_be_two_coordinates); return NULL; } @@ -2774,9 +2768,7 @@ _draw_rectangle(ImagingDrawObject* self, PyObject* args) if (n < 0) return NULL; if (n != 2) { - PyErr_SetString(PyExc_TypeError, - "coordinate list must contain exactly 2 coordinates" - ); + PyErr_SetString(PyExc_TypeError, must_be_two_coordinates); return NULL; } diff --git a/appveyor.yml b/appveyor.yml index 81f62dfdd..39ee1f5cf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,6 +21,7 @@ install: build_script: - '%PYTHON%\python.exe c:\pillow\winbuild\build.py' - cd c:\pillow +- dir dist\*.egg - '%PYTHON%\python.exe selftest.py --installed' test_script: - cd c:\pillow @@ -28,3 +29,17 @@ test_script: - '%PYTHON%\python.exe test-installed.py' matrix: fast_finish: true +artifacts: +- path: pillow\dist\*.egg + name: build +after_test: + - ps: Get-ChildItem .\dist\*.egg | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } +deploy: + provider: S3 + access_key_id: AKIAIRAXC62ZNTVQJMOQ + secret_access_key: + secure: Hwb6klTqtBeMgxAjRoDltiiqpuH8xbwD4UooDzBSiCWXjuFj1lyl4kHgHwTCCGqi + bucket: pillow-nightly + folder: win/$(APPVEYOR_BUILD_NUMBER)/ + artifact: /.*egg/ + diff --git a/depends/fedora_23.sh b/depends/fedora_23.sh index 2825eba56..72032cd2e 100755 --- a/depends/fedora_23.sh +++ b/depends/fedora_23.sh @@ -13,6 +13,6 @@ sudo dnf install redhat-rpm-config sudo dnf install python-devel python3-devel python-virtualenv make gcc -sudo dnf install libtiff-devel libjpeg-devel libzip-devel freetype-devel \ +sudo dnf install libtiff-devel libjpeg-devel zlib-devel freetype-devel \ lcms2-devel libwebp-devel openjpeg2-devel tkinter python3-tkinter \ tcl-devel tk-devel \ No newline at end of file diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst index b5ad9331a..9ad83f1d9 100644 --- a/docs/handbook/concepts.rst +++ b/docs/handbook/concepts.rst @@ -24,7 +24,7 @@ To get the number and names of bands in an image, use the Modes ----- -The :term:`mode` of an image defines the type and depth of a pixel in the +The ``mode`` of an image defines the type and depth of a pixel in the image. The current release supports the following standard modes: * ``1`` (1-bit pixels, black and white, stored with one pixel per byte) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 914f7cde8..2777feaee 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -400,26 +400,15 @@ 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 use the for-statement to loop over the -sequence: +The following class lets you use the for-statement to loop over the sequence: -A sequence iterator class -^^^^^^^^^^^^^^^^^^^^^^^^^ +Using the ImageSequence Iterator class +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: - class ImageSequence: - def __init__(self, im): - self.im = im - def __getitem__(self, ix): - try: - if ix: - self.im.seek(ix) - return self.im - except EOFError: - raise IndexError # end of sequence - - for frame in ImageSequence(im): + from PIL import ImageSequence + for frame in ImageSequence.Iterator(im): # ...do something to frame... diff --git a/docs/installation.rst b/docs/installation.rst index 53d6b5bb7..4b4d6d07e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -202,6 +202,11 @@ Build Options the libraries are not found. Webpmux (WebP metadata) relies on WebP support. Tcl and Tk also must be used together. +* Build flags: ``--debug``. Adds a debugging flag to the include and + library search process to dump all paths searched for and found to + stdout. + + Sample Usage:: $ MAX_CONCURRENCY=1 python setup.py build_ext --enable-[feature] install @@ -288,7 +293,7 @@ Prerequisites are installed on **Ubuntu 14.04 LTS** with:: Prerequisites are installed on **Fedora 23** with:: - $ sudo dnf install libtiff-devel libjpeg-devel libzip-devel freetype-devel \ + $ sudo dnf install libtiff-devel libjpeg-devel zlib-devel freetype-devel \ lcms2-devel libwebp-devel tcl-devel tk-devel diff --git a/docs/reference/ImageMath.rst b/docs/reference/ImageMath.rst index 5d7345d79..445a7e277 100644 --- a/docs/reference/ImageMath.rst +++ b/docs/reference/ImageMath.rst @@ -81,13 +81,13 @@ Bitwise operators don’t work on floating point images. Logical Operators ^^^^^^^^^^^^^^^^^ -Logical operators like :keyword:`and`, :keyword:`or`, and :keyword:`not` work +Logical operators like ``and``, ``or``, and ``not`` work on entire images, rather than individual pixels. An empty image (all pixels zero) is treated as false. All other images are treated as true. -Note that :keyword:`and` and :keyword:`or` return the last evaluated operand, +Note that ``and`` and ``or`` return the last evaluated operand, while not always returns a boolean value. Built-in Functions diff --git a/docs/releasenotes/3.1.1.rst b/docs/releasenotes/3.1.1.rst index 969bbb7ca..8c32a43e7 100644 --- a/docs/releasenotes/3.1.1.rst +++ b/docs/releasenotes/3.1.1.rst @@ -48,7 +48,7 @@ off the end of the memory buffer, causing a segfault. This issue was found by Alyssa Besseling at Atlassian CVE-2016-2533 -- Buffer overflow in PcdDecode.c ----------------------------------------------- +----------------------------------------------- In all versions of Pillow, dating back at least to the last PIL 1.1.7 release, ``PcdDecode.c`` has a buffer overflow error. @@ -64,7 +64,7 @@ Integer overflow in Resample.c If a large value was passed into the new size for an image, it is possible to overflow an int32 value passed into malloc. - + kk = malloc(xsize * kmax * sizeof(float)); ... xbounds = malloc(xsize * 2 * sizeof(int)); diff --git a/libImaging/Unpack.c b/libImaging/Unpack.c index 3fab73cec..ff1c51cc5 100644 --- a/libImaging/Unpack.c +++ b/libImaging/Unpack.c @@ -191,14 +191,64 @@ unpack1IR(UINT8* out, const UINT8* in, int pixels) static void unpackL2(UINT8* out, const UINT8* in, int pixels) { - /* nibbles */ + /* nibbles (msb first, white is non-zero) */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = ((byte >> 6) & 3) * 255 / 3; byte <<= 2; - case 3: *out++ = ((byte >> 6) & 3) * 255 / 3; byte <<= 2; - case 2: *out++ = ((byte >> 6) & 3) * 255 / 3; byte <<= 2; - case 1: *out++ = ((byte >> 6) & 3) * 255 / 3; + default: *out++ = ((byte >> 6) & 0x03U) * 0x55U; byte <<= 2; + case 3: *out++ = ((byte >> 6) & 0x03U) * 0x55U; byte <<= 2; + case 2: *out++ = ((byte >> 6) & 0x03U) * 0x55U; byte <<= 2; + case 1: *out++ = ((byte >> 6) & 0x03U) * 0x55U; + } + pixels -= 4; + } +} + +static void +unpackL2I(UINT8* out, const UINT8* in, int pixels) +{ + /* nibbles (msb first, white is zero) */ + while (pixels > 0) { + UINT8 byte = *in++; + switch (pixels) { + default: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); byte <<= 2; + case 3: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); byte <<= 2; + case 2: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); byte <<= 2; + case 1: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); + } + pixels -= 4; + } +} + +static void +unpackL2R(UINT8* out, const UINT8* in, int pixels) +{ + /* nibbles (bit order reversed, white is non-zero) */ + while (pixels > 0) { + UINT8 byte = *in++; + byte = BITFLIP[byte]; + switch (pixels) { + default: *out++ = ((byte >> 6) & 0x03U) * 0x55U; byte <<= 2; + case 3: *out++ = ((byte >> 6) & 0x03U) * 0x55U; byte <<= 2; + case 2: *out++ = ((byte >> 6) & 0x03U) * 0x55U; byte <<= 2; + case 1: *out++ = ((byte >> 6) & 0x03U) * 0x55U; + } + pixels -= 4; + } +} + +static void +unpackL2IR(UINT8* out, const UINT8* in, int pixels) +{ + /* nibbles (bit order reversed, white is zero) */ + while (pixels > 0) { + UINT8 byte = *in++; + byte = BITFLIP[byte]; + switch (pixels) { + default: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); byte <<= 2; + case 3: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); byte <<= 2; + case 2: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); byte <<= 2; + case 1: *out++ = 0xFFU - (UINT8)(((byte >> 6) & 0x03U) * 0x55U); } pixels -= 4; } @@ -207,12 +257,56 @@ unpackL2(UINT8* out, const UINT8* in, int pixels) static void unpackL4(UINT8* out, const UINT8* in, int pixels) { - /* nibbles */ + /* nibbles (msb first, white is non-zero) */ while (pixels > 0) { UINT8 byte = *in++; switch (pixels) { - default: *out++ = ((byte >> 4) & 15) * 255 / 15; byte <<= 4; - case 1: *out++ = ((byte >> 4) & 15) * 255 / 15; + default: *out++ = ((byte >> 4) & 0x0FU) * 0x11U; byte <<= 4; + case 1: *out++ = ((byte >> 4) & 0x0FU) * 0x11U; + } + pixels -= 2; + } +} + +static void +unpackL4I(UINT8* out, const UINT8* in, int pixels) +{ + /* nibbles (msb first, white is zero) */ + while (pixels > 0) { + UINT8 byte = *in++; + switch (pixels) { + default: *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U); byte <<= 4; + case 1: *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U); + } + pixels -= 2; + } +} + +static void +unpackL4R(UINT8* out, const UINT8* in, int pixels) +{ + /* nibbles (bit order reversed, white is non-zero) */ + while (pixels > 0) { + UINT8 byte = *in++; + byte = BITFLIP[byte]; + switch (pixels) { + default: *out++ = ((byte >> 4) & 0x0FU) * 0x11U; byte <<= 4; + case 1: *out++ = ((byte >> 4) & 0x0FU) * 0x11U; + } + pixels -= 2; + } +} + +static void +unpackL4IR(UINT8* out, const UINT8* in, int pixels) +{ + /* nibbles (bit order reversed, white is zero) */ + while (pixels > 0) { + UINT8 byte = *in++; + byte = BITFLIP[byte]; + switch (pixels) { + default: *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U); byte <<= 4; + case 1: *out++ = 0xFFU - (UINT8)(((byte >> 4) & 0x0FU) * 0x11U); } pixels -= 2; } @@ -738,6 +832,21 @@ unpackBGRA(UINT8* out, const UINT8* in, int pixels) } } +static void +unpackBGRT(UINT8* out, const UINT8* in, int pixels) +{ + int i; + /* RGBA, reversed bytes */ + /* Transparency map, rather than an alpha channel */ + for (i = 0; i < pixels; i++) { + out[R] = in[2]; + out[G] = in[1]; + out[B] = in[0]; + out[A] = 255 - in[3]; + out += 4; in += 4; + } +} + /* Unpack to "CMYK" image */ @@ -1053,7 +1162,15 @@ static struct { /* greyscale */ {"L", "L;2", 2, unpackL2}, + {"L", "L;2I", 2, unpackL2I}, + {"L", "L;2R", 2, unpackL2R}, + {"L", "L;2IR", 2, unpackL2IR}, + {"L", "L;4", 4, unpackL4}, + {"L", "L;4I", 4, unpackL4I}, + {"L", "L;4R", 4, unpackL4R}, + {"L", "L;4IR", 4, unpackL4IR}, + {"L", "L", 8, copy1}, {"L", "L;I", 8, unpackLI}, {"L", "L;R", 8, unpackLR}, @@ -1111,6 +1228,7 @@ static struct { {"RGBA", "RGBA;4B", 16, ImagingUnpackRGBA4B}, {"RGBA", "RGBA;16B", 64, unpackRGBA16B}, {"RGBA", "BGRA", 32, unpackBGRA}, + {"RGBA", "BGRT", 32, unpackBGRT}, {"RGBA", "ARGB", 32, unpackARGB}, {"RGBA", "ABGR", 32, unpackABGR}, {"RGBA", "YCCA;P", 32, ImagingUnpackYCCA}, diff --git a/setup.py b/setup.py index 44c226092..21119293b 100644 --- a/setup.py +++ b/setup.py @@ -37,21 +37,35 @@ _LIB_IMAGING = ( "XbmEncode", "ZipDecode", "ZipEncode", "TiffDecode", "Incremental", "Jpeg2KDecode", "Jpeg2KEncode", "BoxBlur") +DEBUG = False -def _add_directory(path, dir, where=None): - if dir is None: + +def _dbg(s, tp=None): + if DEBUG: + if tp: + print(s % tp) + return + print(s) + + +def _add_directory(path, subdir, where=None): + if subdir is None: return - dir = os.path.realpath(dir) - if os.path.isdir(dir) and dir not in path: + subdir = os.path.realpath(subdir) + if os.path.isdir(subdir) and subdir not in path: if where is None: - path.append(dir) + _dbg('Appending path %s', subdir) + path.append(subdir) else: - path.insert(where, dir) + _dbg('Inserting path %s', subdir) + path.insert(where, subdir) def _find_include_file(self, include): for directory in self.compiler.include_dirs: + _dbg('Checking for include file %s in %s', (include, directory)) if os.path.isfile(os.path.join(directory, include)): + _dbg('Found %s', include) return 1 return 0 @@ -61,15 +75,22 @@ def _find_library_file(self, library): # lib extension, not the system shared lib extension: e.g. .cpython-33.so # vs .so. See Python bug http://bugs.python.org/16754 if 'cpython' in self.compiler.shared_lib_extension: + _dbg('stripping cpython from shared library extension %s', + self.compiler.shared_lib_extension) existing = self.compiler.shared_lib_extension self.compiler.shared_lib_extension = "." + existing.split('.')[-1] ret = self.compiler.find_library_file(self.compiler.library_dirs, library) self.compiler.shared_lib_extension = existing - return ret else: - return self.compiler.find_library_file(self.compiler.library_dirs, - library) + ret = self.compiler.find_library_file(self.compiler.library_dirs, + library) + if ret: + _dbg('Found library %s at %s', (library, ret)) + else: + _dbg("Couldn't find library %s in %s", + (library, self.compiler.library_dirs)) + return ret def _lib_include(root): @@ -100,10 +121,15 @@ LCMS_ROOT = None class pil_build_ext(build_ext): class feature: - zlib = jpeg = tiff = freetype = tcl = tk = lcms = webp = webpmux = None - jpeg2000 = None + features = ['zlib', 'jpeg', 'tiff', 'freetype', 'tcl', 'tk', 'lcms', + 'webp', 'webpmux', 'jpeg2000'] + required = set(['jpeg', 'zlib']) + def __init__(self): + for f in self.features: + setattr(self, f, None) + def require(self, feat): return feat in self.required @@ -111,9 +137,8 @@ class pil_build_ext(build_ext): return getattr(self, feat) is None def __iter__(self): - for x in dir(self): - if x[1] != '_': - yield x + for x in self.features: + yield x feature = feature() @@ -121,7 +146,7 @@ class pil_build_ext(build_ext): ('disable-%s' % x, None, 'Disable support for %s' % x) for x in feature ] + [ ('enable-%s' % x, None, 'Enable support for %s' % x) for x in feature - ] + ] + [('debug', None, 'Debug logging')] def initialize_options(self): build_ext.initialize_options(self) @@ -131,15 +156,20 @@ class pil_build_ext(build_ext): def finalize_options(self): build_ext.finalize_options(self) + if self.debug: + global DEBUG + DEBUG = True for x in self.feature: if getattr(self, 'disable_%s' % x): setattr(self.feature, x, False) self.feature.required.discard(x) + _dbg('Disabling %s', x) if getattr(self, 'enable_%s' % x): raise ValueError( 'Conflicting options: --enable-%s and --disable-%s' % (x, x)) if getattr(self, 'enable_%s' % x): + _dbg('Requiring %s', x) self.feature.required.add(x) def build_extensions(self): @@ -314,6 +344,9 @@ class pil_build_ext(build_ext): if _tkinter: TCL_VERSION = _tkinter.TCL_VERSION[:3] + _dbg('Tkinter found, will check for Tcl/Tk') + else: + _dbg('Tkinter not found') if _tkinter and not TCL_ROOT: # we have Tkinter but the TCL_ROOT variable was not set; @@ -334,6 +367,7 @@ class pil_build_ext(build_ext): ] for TCL_ROOT in roots: TCL_ROOT = os.path.abspath(TCL_ROOT) + _dbg('Checking %s for tk.h', TCL_ROOT) if os.path.isfile(os.path.join(TCL_ROOT, "include", "tk.h")): # FIXME: use distutils logging (?) print("--- using Tcl/Tk libraries at", TCL_ROOT) @@ -342,6 +376,7 @@ class pil_build_ext(build_ext): break else: TCL_ROOT = None + _dbg('Tcl/tk not found') # add standard directories @@ -373,6 +408,7 @@ class pil_build_ext(build_ext): best_path = os.path.join(program_files, name) if best_path: + _dbg('Adding %s to search list', best_path) _add_directory(library_dirs, os.path.join(best_path, 'lib')) _add_directory(include_dirs, os.path.join(best_path, 'include')) @@ -390,6 +426,7 @@ class pil_build_ext(build_ext): feature = self.feature if feature.want('zlib'): + _dbg('Looking for zlib') if _find_include_file(self, "zlib.h"): if _find_library_file(self, "z"): feature.zlib = "z" @@ -398,6 +435,7 @@ class pil_build_ext(build_ext): feature.zlib = "zlib" # alternative name if feature.want('jpeg'): + _dbg('Looking for jpeg') if _find_include_file(self, "jpeglib.h"): if _find_library_file(self, "jpeg"): feature.jpeg = "jpeg" @@ -407,11 +445,13 @@ class pil_build_ext(build_ext): feature.openjpeg_version = None if feature.want('jpeg2000'): + _dbg('Looking for jpeg2000') best_version = None best_path = None # Find the best version for directory in self.compiler.include_dirs: + _dbg('Checking for openjpeg-#.# in %s', directory) try: listdir = os.listdir(directory) except Exception: @@ -421,10 +461,13 @@ class pil_build_ext(build_ext): if name.startswith('openjpeg-') and \ os.path.isfile(os.path.join(directory, name, 'openjpeg.h')): + _dbg('Found openjpeg.h in %s/%s', (directory, name)) version = tuple([int(x) for x in name[9:].split('.')]) if best_version is None or version > best_version: best_version = version best_path = os.path.join(directory, name) + _dbg('Best openjpeg version %s so far in %s', + (best_version, best_path)) if best_version and _find_library_file(self, 'openjp2'): # Add the directory to the include path so we can include @@ -436,34 +479,42 @@ class pil_build_ext(build_ext): best_version]) if feature.want('tiff'): - if _find_library_file(self, "tiff"): - feature.tiff = "tiff" - if sys.platform == "win32" and _find_library_file(self, "libtiff"): - feature.tiff = "libtiff" - if (sys.platform == "darwin" and - _find_library_file(self, "libtiff")): - feature.tiff = "libtiff" + _dbg('Looking for tiff') + if _find_include_file(self, 'tiff.h'): + if _find_library_file(self, "tiff"): + feature.tiff = "tiff" + if sys.platform == "win32" and _find_library_file(self, "libtiff"): + feature.tiff = "libtiff" + if (sys.platform == "darwin" and + _find_library_file(self, "libtiff")): + feature.tiff = "libtiff" if feature.want('freetype'): + _dbg('Looking for freetype') if _find_library_file(self, "freetype"): # look for freetype2 include files freetype_version = 0 - for dir in self.compiler.include_dirs: - if os.path.isfile(os.path.join(dir, "ft2build.h")): + for subdir in self.compiler.include_dirs: + _dbg('Checking for include file %s in %s', ("ft2build.h", subdir)) + if os.path.isfile(os.path.join(subdir, "ft2build.h")): + _dbg('Found %s in %s', ("ft2build.h", subdir)) freetype_version = 21 - dir = os.path.join(dir, "freetype2") + subdir = os.path.join(subdir, "freetype2") break - dir = os.path.join(dir, "freetype2") - if os.path.isfile(os.path.join(dir, "ft2build.h")): + subdir = os.path.join(subdir, "freetype2") + _dbg('Checking for include file %s in %s', ("ft2build.h", subdir)) + if os.path.isfile(os.path.join(subdir, "ft2build.h")): + _dbg('Found %s in %s', ("ft2build.h", subdir)) freetype_version = 21 break if freetype_version: feature.freetype = "freetype" feature.freetype_version = freetype_version - if dir: - _add_directory(self.compiler.include_dirs, dir, 0) + if subdir: + _add_directory(self.compiler.include_dirs, subdir, 0) if feature.want('lcms'): + _dbg('Looking for lcms') if _find_include_file(self, "lcms2.h"): if _find_library_file(self, "lcms2"): feature.lcms = "lcms2" @@ -475,17 +526,20 @@ class pil_build_ext(build_ext): # the library names may vary somewhat (e.g. tcl85 or tcl8.5) version = TCL_VERSION[0] + TCL_VERSION[2] if feature.want('tcl'): + _dbg('Looking for TCL') if _find_library_file(self, "tcl" + version): feature.tcl = "tcl" + version elif _find_library_file(self, "tcl" + TCL_VERSION): feature.tcl = "tcl" + TCL_VERSION if feature.want('tk'): + _dbg('Looking for TK') if _find_library_file(self, "tk" + version): feature.tk = "tk" + version elif _find_library_file(self, "tk" + TCL_VERSION): feature.tk = "tk" + TCL_VERSION if feature.want('webp'): + _dbg('Looking for webp') if (_find_include_file(self, "webp/encode.h") and _find_include_file(self, "webp/decode.h")): # In Google's precompiled zip it is call "libwebp": @@ -495,6 +549,7 @@ class pil_build_ext(build_ext): feature.webp = "libwebp" if feature.want('webpmux'): + _dbg('Looking for webpmux') if (_find_include_file(self, "webp/mux.h") and _find_include_file(self, "webp/demux.h")): if (_find_library_file(self, "webpmux") and @@ -518,10 +573,10 @@ class pil_build_ext(build_ext): # core library files = ["_imaging.c"] - for file in _IMAGING: - files.append(file + ".c") - for file in _LIB_IMAGING: - files.append(os.path.join("libImaging", file + ".c")) + for src_file in _IMAGING: + files.append(src_file + ".c") + for src_file in _LIB_IMAGING: + files.append(os.path.join("libImaging", src_file + ".c")) libs = [] defs = [] @@ -557,7 +612,7 @@ class pil_build_ext(build_ext): ["_imagingft.c"], libraries=["freetype"])) - if os.path.isfile("_imagingcms.c") and feature.lcms: + if feature.lcms: extra = [] if sys.platform == "win32": extra.extend(["user32", "gdi32"]) @@ -565,7 +620,7 @@ class pil_build_ext(build_ext): ["_imagingcms.c"], libraries=[feature.lcms] + extra)) - if os.path.isfile("_webp.c") and feature.webp: + if feature.webp: libs = [feature.webp] defs = [] @@ -586,16 +641,17 @@ class pil_build_ext(build_ext): framework_roots = [ "/Library/Frameworks", "/System/Library/Frameworks" ] + _dbg('Looking for TclTk Framework Build') for root in framework_roots: root_tcl = os.path.join(root, "Tcl.framework") root_tk = os.path.join(root, "Tk.framework") if (os.path.exists(root_tcl) and os.path.exists(root_tk)): print("--- using frameworks at %s" % root) frameworks = ["-framework", "Tcl", "-framework", "Tk"] - dir = os.path.join(root_tcl, "Headers") - _add_directory(self.compiler.include_dirs, dir, 0) - dir = os.path.join(root_tk, "Headers") - _add_directory(self.compiler.include_dirs, dir, 1) + subdir = os.path.join(root_tcl, "Headers") + _add_directory(self.compiler.include_dirs, subdir, 0) + subdir = os.path.join(root_tk, "Headers") + _add_directory(self.compiler.include_dirs, subdir, 1) break if frameworks: exts.append(Extension("PIL._imagingtk", @@ -607,11 +663,8 @@ class pil_build_ext(build_ext): ["_imagingtk.c", "Tk/tkImaging.c"], libraries=[feature.tcl, feature.tk])) - if os.path.isfile("_imagingmath.c"): - exts.append(Extension("PIL._imagingmath", ["_imagingmath.c"])) - - if os.path.isfile("_imagingmorph.c"): - exts.append(Extension("PIL._imagingmorph", ["_imagingmorph.c"])) + exts.append(Extension("PIL._imagingmath", ["_imagingmath.c"])) + exts.append(Extension("PIL._imagingmorph", ["_imagingmorph.c"])) self.extensions[:] = exts @@ -680,8 +733,8 @@ class pil_build_ext(build_ext): if not all: print("To add a missing option, make sure you have the required") - print("library, and set the corresponding ROOT variable in the") - print("setup.py script.") + print("library and headers.") + print("See https://pillow.readthedocs.org/en/latest/installation.html#building-from-source") print("") print("To check the build, run the selftest.py script.") @@ -689,8 +742,8 @@ class pil_build_ext(build_ext): def check_zlib_version(self, include_dirs): # look for unsafe versions of zlib - for dir in include_dirs: - zlibfile = os.path.join(dir, "zlib.h") + for subdir in include_dirs: + zlibfile = os.path.join(subdir, "zlib.h") if os.path.isfile(zlibfile): break else: @@ -702,7 +755,7 @@ class pil_build_ext(build_ext): if m.group(1) < "1.2.3": return m.group(1) - # http://hg.python.org/users/barry/rev/7e8deab93d5a + # https://hg.python.org/users/barry/rev/7e8deab93d5a def add_multiarch_paths(self): # Debian/Ubuntu multiarch support. # https://wiki.ubuntu.com/MultiarchSpec @@ -741,7 +794,6 @@ setup(name=NAME, "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",