diff --git a/.codecov.yml b/.codecov.yml index 060b2685e..a9ab1c2d7 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,8 +6,6 @@ codecov: # https://docs.codecov.io/docs/comparing-commits allow_coverage_offsets: true - token: 6dafc396-e7f5-4221-a38a-8b07a49fbdae - comment: off # Matches 'omit:' in .coveragerc diff --git a/.github/codecov-upstream.yml b/.github/codecov-upstream.yml new file mode 100644 index 000000000..060b2685e --- /dev/null +++ b/.github/codecov-upstream.yml @@ -0,0 +1,18 @@ +# Documentation: https://docs.codecov.io/docs/codecov-yaml + +codecov: + # Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]" + # https://github.com/codecov/support/issues/363 + # https://docs.codecov.io/docs/comparing-commits + allow_coverage_offsets: true + + token: 6dafc396-e7f5-4221-a38a-8b07a49fbdae + +comment: off + +# Matches 'omit:' in .coveragerc +ignore: + - "Tests/32bit_segfault_check.py" + - "Tests/bench_cffi_access.py" + - "Tests/check_*.py" + - "Tests/createfontdatachunk.py" diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index e0c55bc1a..4acf55052 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -19,8 +19,8 @@ jobs: platform-vcvars: "x86_amd64" platform-msbuild: "x64" - python-version: "pypy3.6" - pypy-version: "pypy3.6-v7.2.0-win32" - pypy-url: "https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.2.0-win32.zip" + pypy-version: "pypy3.6-v7.3.0-win32" + pypy-url: "https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.0-win32.zip" exclude: - python-version: "pypy3.6" architecture: "x64" @@ -364,12 +364,16 @@ jobs: name: errors path: Tests/errors + - name: Prepare coverage token + if: success() && github.repository == 'python-pillow/Pillow' + run: cp .github/codecov-upstream.yml .codecov.yml + - name: Upload coverage if: success() uses: codecov/codecov-action@v1 with: - token: ${{ secrets.CODECOV_TOKEN }} - name: ${{ runner.os }} Python ${{ matrix.python-version }} + token: ${{ secrets.CODECOV_TOKEN }} + name: ${{ runner.os }} Python ${{ matrix.python-version }} - name: Build wheel id: wheel diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 78307b7a1..8c80bcd71 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -83,21 +83,18 @@ jobs: name: errors path: Tests/errors - - name: Docs - if: matrix.python-version == 3.8 - run: | - pip install sphinx-rtd-theme - make doccheck - - name: After success if: success() run: | .travis/after_success.sh env: MATRIX_OS: ${{ matrix.os }} - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + - name: Prepare coverage token + if: success() && github.repository == 'python-pillow/Pillow' + run: cp .github/codecov-upstream.yml .codecov.yml + - name: Upload coverage if: success() uses: codecov/codecov-action@v1 diff --git a/.travis/install.sh b/.travis/install.sh index 747acb448..6fba26ccb 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -3,7 +3,7 @@ set -e sudo apt-get update -sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk\ +sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\ ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\ cmake imagemagick libharfbuzz-dev libfribidi-dev @@ -20,8 +20,8 @@ if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then pip install pyqt5 fi -# docs only on Python 3.7 -if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ]; then pip install -r requirements.txt ; fi +# docs only on Python 3.8 +if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ]; then pip install -r requirements.txt ; fi # webp pushd depends && ./install_webp.sh && popd diff --git a/.travis/test.sh b/.travis/test.sh index 91dae9f8d..832d90433 100755 --- a/.travis/test.sh +++ b/.travis/test.sh @@ -5,4 +5,4 @@ set -e python -m pytest -v -x --cov PIL --cov Tests --cov-report term Tests # Docs -if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ]; then make doccheck; fi +if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ]; then make doccheck; fi diff --git a/CHANGES.rst b/CHANGES.rst index 1ea09fff8..2e0f10845 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,9 +5,15 @@ Changelog (Pillow) 7.0.0 (unreleased) ------------------ +- Use default DPI when exif provides invalid x_resolution #4147 + [beipang2, radarhere] + - Change default resize resampling filter from NEAREST to BICUBIC #4255 [homm] +- Fixed black lines on upscaled images with the BOX filter #4278 + [homm] + - Better thumbnail aspect ratio preservation #4256 [homm] diff --git a/Tests/images/invalid-exif-without-x-resolution.jpg b/Tests/images/invalid-exif-without-x-resolution.jpg new file mode 100644 index 000000000..00f6bd2f3 Binary files /dev/null and b/Tests/images/invalid-exif-without-x-resolution.jpg differ diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 76cd98aba..338f52cd7 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -41,6 +41,13 @@ class TestFileBmp(PillowTestCase): self.assertEqual(im.size, reloaded.size) self.assertEqual(reloaded.format, "BMP") + def test_save_too_large(self): + outfile = self.tempfile("temp.bmp") + with Image.new("RGB", (1, 1)) as im: + im._size = (37838, 37838) + with self.assertRaises(ValueError): + im.save(outfile) + def test_dpi(self): dpi = (72, 72) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 72adbe537..af95139ac 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -638,6 +638,14 @@ class TestFileJpeg(PillowTestCase): # OSError for unidentified image. self.assertEqual(im.info.get("dpi"), (72, 72)) + def test_invalid_exif_x_resolution(self): + # When no x or y resolution is defined in EXIF + im = Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") + + # This should return the default, and not a ValueError or + # OSError for an unidentified image. + self.assertEqual(im.info.get("dpi"), (72, 72)) + def test_ifd_offset_exif(self): # Arrange # This image has been manually hexedited to have an IFD offset of 10, diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 51189b83b..18c312046 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -49,7 +49,7 @@ class TestFileMsp(PillowTestCase): with Image.open(target_path) as target: self.assert_image_equal(im, target) - @unittest.skipIf(not os.path.exists(EXTRA_DIR), "Extra image files not installed") + @unittest.skipUnless(os.path.exists(EXTRA_DIR), "Extra image files not installed") def test_open_windows_v2(self): files = ( diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index 835aead79..6fe9ff29c 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -30,7 +30,7 @@ class TestFileSun(PillowTestCase): with Image.open("Tests/images/sunraster.im1.png") as target: self.assert_image_equal(im, target) - @unittest.skipIf(not os.path.exists(EXTRA_DIR), "Extra image files not installed") + @unittest.skipUnless(os.path.exists(EXTRA_DIR), "Extra image files not installed") def test_others(self): files = ( os.path.join(EXTRA_DIR, f) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index a2fdbc333..00028edb4 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -26,7 +26,7 @@ class TestUnsupportedWebp(PillowTestCase): WebPImagePlugin.SUPPORTED = True -@unittest.skipIf(not HAVE_WEBP, "WebP support not installed") +@unittest.skipUnless(HAVE_WEBP, "WebP support not installed") class TestFileWebp(PillowTestCase): def setUp(self): self.rgb_mode = "RGB" diff --git a/Tests/test_font_leaks.py b/Tests/test_font_leaks.py index 393ddb0fb..be0612fa2 100644 --- a/Tests/test_font_leaks.py +++ b/Tests/test_font_leaks.py @@ -2,10 +2,9 @@ import unittest from PIL import Image, ImageDraw, ImageFont, features -from .helper import PillowLeakTestCase, is_win32 +from .helper import PillowLeakTestCase -@unittest.skipIf(is_win32(), "requires Unix or macOS") class TestTTypeFontLeak(PillowLeakTestCase): # fails at iteration 3 in master iterations = 10 @@ -20,7 +19,7 @@ class TestTTypeFontLeak(PillowLeakTestCase): ) ) - @unittest.skipIf(not features.check("freetype2"), "Test requires freetype2") + @unittest.skipUnless(features.check("freetype2"), "Test requires freetype2") def test_leak(self): ttype = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20) self._test_font(ttype) diff --git a/Tests/test_imagefont_bitmap.py b/Tests/test_imagefont_bitmap.py index eed0c70b6..6853504f6 100644 --- a/Tests/test_imagefont_bitmap.py +++ b/Tests/test_imagefont_bitmap.py @@ -11,7 +11,7 @@ except ImportError: image_font_installed = False -@unittest.skipIf(not image_font_installed, "image font not installed") +@unittest.skipUnless(image_font_installed, "image font not installed") class TestImageFontBitmap(PillowTestCase): def test_similar(self): text = "EmbeddedBitmap" diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index 5ee994dc7..e6032cc2e 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -18,7 +18,7 @@ except (OSError, ImportError): TK_MODES = ("1", "L", "P", "RGB", "RGBA") -@unittest.skipIf(not HAS_TK, "Tk not installed") +@unittest.skipUnless(HAS_TK, "Tk not installed") class TestImageTk(PillowTestCase): def setUp(self): try: diff --git a/Tests/test_util.py b/Tests/test_util.py index 6a111b5b9..c3ef29409 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -16,7 +16,7 @@ class TestUtil(PillowTestCase): # Assert self.assertTrue(it_is) - @unittest.skipIf(not _util.py36, "os.path support for Paths added in 3.6") + @unittest.skipUnless(_util.py36, "os.path support for Paths added in 3.6") def test_path_obj_is_path(self): # Arrange from pathlib import Path diff --git a/docs/Makefile b/docs/Makefile index 1a912039e..ba79d9070 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -42,7 +42,7 @@ clean: -rm -rf $(BUILDDIR)/* html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + $(SPHINXBUILD) -b html -W --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." diff --git a/docs/installation.rst b/docs/installation.rst index 385b570b6..929bc951a 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -47,7 +47,8 @@ Basic Installation Install Pillow with :command:`pip`:: - $ pip install Pillow + python -m pip install pip + python -m pip install Pillow Windows Installation @@ -58,7 +59,8 @@ supported Pythons in both 32 and 64-bit versions in wheel, egg, and executable installers. These binaries have all of the optional libraries included except for raqm and libimagequant:: - > pip install Pillow + python -m pip install pip + python -m pip install Pillow macOS Installation @@ -69,7 +71,8 @@ versions in the wheel format. These include support for all optional libraries except libimagequant. Raqm support requires libraqm, fribidi, and harfbuzz to be installed separately:: - $ pip install Pillow + python -m pip install pip + python -m pip install Pillow Linux Installation ^^^^^^^^^^^^^^^^^^ @@ -79,7 +82,8 @@ versions in the manylinux wheel format. These include support for all optional libraries except libimagequant. Raqm support requires libraqm, fribidi, and harfbuzz to be installed separately:: - $ pip install Pillow + python -m pip install pip + python -m pip install Pillow Most major Linux distributions, including Fedora, Debian/Ubuntu and ArchLinux also include Pillow in packages that previously contained @@ -92,11 +96,11 @@ Pillow can be installed on FreeBSD via the official Ports or Packages systems: **Ports**:: - $ cd /usr/ports/graphics/py-pillow && make install clean + cd /usr/ports/graphics/py-pillow && make install clean **Packages**:: - $ pkg install py36-pillow + pkg install py36-pillow .. note:: @@ -191,7 +195,8 @@ Many of Pillow's features require external libraries: Once you have installed the prerequisites, run:: - $ pip install Pillow + python -m pip install pip + python -m pip install Pillow If the prerequisites are installed in the standard library locations for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no @@ -201,7 +206,7 @@ those locations by editing :file:`setup.py` or :file:`setup.cfg`, or by adding environment variables on the command line:: - $ CFLAGS="-I/usr/pkg/include" pip install pillow + CFLAGS="-I/usr/pkg/include" python -m pip install pillow If Pillow has been previously built without the required prerequisites, it may be necessary to manually clear the pip cache or @@ -245,11 +250,11 @@ Build Options Sample usage:: - $ MAX_CONCURRENCY=1 python setup.py build_ext --enable-[feature] install + MAX_CONCURRENCY=1 python setup.py build_ext --enable-[feature] install or using pip:: - $ pip install pillow --global-option="build_ext" --global-option="--enable-[feature]" + python -m pip install pillow --global-option="build_ext" --global-option="--enable-[feature]" Building on macOS @@ -265,21 +270,22 @@ tools. The easiest way to install external libraries is via `Homebrew `_. After you install Homebrew, run:: - $ brew install libtiff libjpeg webp little-cms2 + brew install libtiff libjpeg webp little-cms2 To install libraqm on macOS use Homebrew to install its dependencies:: - $ brew install freetype harfbuzz fribidi + brew install freetype harfbuzz fribidi Then see ``depends/install_raqm_cmake.sh`` to install libraqm. Now install Pillow with:: - $ pip install Pillow + python -m pip install pip + python -m pip install Pillow or from within the uncompressed source directory:: - $ python setup.py install + python setup.py install Building on Windows ^^^^^^^^^^^^^^^^^^^ @@ -293,17 +299,17 @@ Building on FreeBSD .. Note:: Only FreeBSD 10 and 11 tested -Make sure you have Python's development libraries installed.:: +Make sure you have Python's development libraries installed:: - $ sudo pkg install python2 + sudo pkg install python2 Or for Python 3:: - $ sudo pkg install python3 + sudo pkg install python3 Prerequisites are installed on **FreeBSD 10 or 11** with:: - $ sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi + sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi Then see ``depends/install_raqm_cmake.sh`` to install libraqm. @@ -316,33 +322,33 @@ development libraries installed. In Debian or Ubuntu:: - $ sudo apt-get install python-dev python-setuptools + sudo apt-get install python-dev python-setuptools Or for Python 3:: - $ sudo apt-get install python3-dev python3-setuptools + sudo apt-get install python3-dev python3-setuptools In Fedora, the command is:: - $ sudo dnf install python-devel redhat-rpm-config + sudo dnf install python-devel redhat-rpm-config Or for Python 3:: - $ sudo dnf install python3-devel redhat-rpm-config + sudo dnf install python3-devel redhat-rpm-config .. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions. Prerequisites are installed on **Ubuntu 16.04 LTS** with:: - $ sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \ - libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk \ + sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \ + libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \ libharfbuzz-dev libfribidi-dev Then see ``depends/install_raqm.sh`` to install libraqm. Prerequisites are installed on recent **RedHat** **Centos** or **Fedora** with:: - $ sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \ + sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \ freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel \ harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel @@ -359,7 +365,7 @@ Building on Android Basic Android support has been added for compilation within the Termux environment. The dependencies can be installed by:: - $ pkg install -y python ndk-sysroot clang make \ + pkg install -y python ndk-sysroot clang make \ libjpeg-turbo This has been tested within the Termux app on ChromeOS, on x86. diff --git a/docs/releasenotes/7.0.0.rst b/docs/releasenotes/7.0.0.rst index 1b2067907..26f9a8cbc 100644 --- a/docs/releasenotes/7.0.0.rst +++ b/docs/releasenotes/7.0.0.rst @@ -52,8 +52,8 @@ Default resampling filter The default resampling filter has been changed to the high-quality convolution ``Image.BICUBIC`` instead of ``Image.NEAREST``, for the :py:meth:`~PIL.Image.Image.resize` -method and the :py:meth:`~PIL.ImageOps.pad``, :py:meth:`~PIL.ImageOps.scale`` -and :py:meth:`~PIL.ImageOps.fit`` functions. +method and the :py:meth:`~PIL.ImageOps.pad`, :py:meth:`~PIL.ImageOps.scale` +and :py:meth:`~PIL.ImageOps.fit` functions. ``Image.NEAREST`` is still always used for images in "P" and "1" modes. See :ref:`concept-filters` to learn the difference. In short, ``Image.NEAREST`` is a very fast filter, but simple and low-quality. @@ -61,14 +61,14 @@ See :ref:`concept-filters` to learn the difference. In short, Image.draft() return value ^^^^^^^^^^^^^^^^^^^^^^^^^^ -The ``Image.draft()`` method used to return ``None`` or the image itself. -Unlike other `chain methods`_, ``draft()`` modifies the image in-place -rather than returning a modified version. - -In the new version, ``draft()`` returns ``None`` if it has no effect or -a tuple of new image mode and the box of original coordinates in the -bounds of resulting image otherwise -(the box could be useful in subsequent ``resize()`` call). +If the :py:meth:`~PIL.Image.Image.draft` method has no effect, it returns ``None``. +If it does have an effect, then it previously returned the image itself. +However, unlike other `chain methods`_, :py:meth:`~PIL.Image.Image.draft` does not +return a modified version of the image, but modifies it in-place. So instead, if +:py:meth:`~PIL.Image.Image.draft` has an effect, Pillow will now return a tuple +of the image mode and a co-ordinate box. The box is the original coordinates in the +bounds of resulting image. This may be useful in a subsequent +:py:meth:`~PIL.Image.Image.resize` call. .. _chain methods: https://en.wikipedia.org/wiki/Method_chaining @@ -128,8 +128,8 @@ Image.__del__ ^^^^^^^^^^^^^ Implicitly closing the image's underlying file in ``Image.__del__`` has been removed. -Use a context manager or call ``Image.close()`` instead to close the file in a -deterministic way. +Use a context manager or call :py:meth:`~PIL.Image.Image.close` instead to close +the file in a deterministic way. Previous method: @@ -145,8 +145,12 @@ Use instead: with Image.open("hopper.png") as im: im.save("out.jpg") -Better thumbnail aspect ratio preservation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Better thumbnail geometry +^^^^^^^^^^^^^^^^^^^^^^^^^ -When calculating the new dimensions in ``Image.thumbnail``, round to the -nearest integer, instead of always rounding down. +When calculating the new dimensions in :py:meth:`~PIL.Image.Image.thumbnail`, +round to the nearest integer, instead of always rounding down. +This better preserves the original aspect ratio. + +When the image width or height is not divisible by 8 the last row and column +in the image get the correct weight after JPEG DCT scaling. diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index cdef77ced..85e2350c5 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -321,12 +321,15 @@ def _save(im, fp, filename, bitmap_header=True): # bitmap header if bitmap_header: offset = 14 + header + colors * 4 + file_size = offset + image + if file_size > 2 ** 32 - 1: + raise ValueError("File size is too large for the BMP format") fp.write( - b"BM" - + o32(offset + image) # file type (magic) - + o32(0) # file size - + o32(offset) # reserved - ) # image data offset + b"BM" # file type (magic) + + o32(file_size) # file size + + o32(0) # reserved + + o32(offset) # image data offset + ) # bitmap info header fp.write( diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 7717fc707..5f9ba59c1 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -616,38 +616,42 @@ def _save_netpbm(im, fp, filename): # below for information on how to enable this. tempfile = im._dump() - with open(filename, "wb") as f: - if im.mode != "RGB": - subprocess.check_call( - ["ppmtogif", tempfile], stdout=f, stderr=subprocess.DEVNULL - ) - else: - # Pipe ppmquant output into ppmtogif - # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename) - quant_cmd = ["ppmquant", "256", tempfile] - togif_cmd = ["ppmtogif"] - quant_proc = subprocess.Popen( - quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL - ) - togif_proc = subprocess.Popen( - togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=subprocess.DEVNULL - ) - - # Allow ppmquant to receive SIGPIPE if ppmtogif exits - quant_proc.stdout.close() - - retcode = quant_proc.wait() - if retcode: - raise subprocess.CalledProcessError(retcode, quant_cmd) - - retcode = togif_proc.wait() - if retcode: - raise subprocess.CalledProcessError(retcode, togif_cmd) - try: - os.unlink(tempfile) - except OSError: - pass + with open(filename, "wb") as f: + if im.mode != "RGB": + subprocess.check_call( + ["ppmtogif", tempfile], stdout=f, stderr=subprocess.DEVNULL + ) + else: + # Pipe ppmquant output into ppmtogif + # "ppmquant 256 %s | ppmtogif > %s" % (tempfile, filename) + quant_cmd = ["ppmquant", "256", tempfile] + togif_cmd = ["ppmtogif"] + quant_proc = subprocess.Popen( + quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL + ) + togif_proc = subprocess.Popen( + togif_cmd, + stdin=quant_proc.stdout, + stdout=f, + stderr=subprocess.DEVNULL, + ) + + # Allow ppmquant to receive SIGPIPE if ppmtogif exits + quant_proc.stdout.close() + + retcode = quant_proc.wait() + if retcode: + raise subprocess.CalledProcessError(retcode, quant_cmd) + + retcode = togif_proc.wait() + if retcode: + raise subprocess.CalledProcessError(retcode, togif_cmd) + finally: + try: + os.unlink(tempfile) + except OSError: + pass # Force optimization so that we can test performance against diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 92563e832..3417aeb4a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1130,8 +1130,7 @@ class Image: Configures the image file loader so it returns a version of the image that as closely as possible matches the given mode and size. For example, you can use this method to convert a color - JPEG to greyscale while loading it, or to extract a 128x192 - version from a PCD file. + JPEG to greyscale while loading it. If any changes are made, returns a tuple with the chosen ``mode`` and ``box`` with coordinates of the original image within the altered one. @@ -1141,7 +1140,7 @@ class Image: effect. Note: This method is not implemented for most images. It is - currently implemented only for JPEG and PCD images. + currently implemented only for JPEG and MPO images. :param mode: The requested mode. :param size: The requested size. @@ -2283,12 +2282,14 @@ class Image: It may also be an :py:class:`~PIL.Image.ImageTransformHandler` object:: + class Example(Image.ImageTransformHandler): def transform(size, method, data, resample, fill=1): # Return result It may also be an object with a :py:meth:`~method.getdata` method that returns a tuple supplying new **method** and **data** values:: + class Example(object): def getdata(self): method = Image.EXTENT diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 3a6dabf5e..4391af569 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -234,6 +234,7 @@ def pad(image, size, method=Image.BICUBIC, color=None, centering=(0.5, 0.5)): :param color: The background color of the padded image. :param centering: Control the position of the original image within the padded version. + (0.5, 0.5) will keep the image centered (0, 0) will keep the image aligned to the top left (1, 1) will keep the image aligned to the bottom diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index fe3d41e16..82fc12e10 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -166,10 +166,11 @@ def APP(self, marker): # 1 dpcm = 2.54 dpi dpi *= 2.54 self.info["dpi"] = int(dpi + 0.5), int(dpi + 0.5) - except (KeyError, SyntaxError, ZeroDivisionError): + except (KeyError, SyntaxError, ValueError, ZeroDivisionError): # SyntaxError for invalid/unreadable EXIF # KeyError for dpi not included # ZeroDivisionError for invalid dpi rational value + # ValueError for x_resolution[0] being an invalid float self.info["dpi"] = 72, 72 diff --git a/winbuild/appveyor_install_pypy3.cmd b/winbuild/appveyor_install_pypy3.cmd index 3622ed6ec..75a22ca59 100644 --- a/winbuild/appveyor_install_pypy3.cmd +++ b/winbuild/appveyor_install_pypy3.cmd @@ -1,3 +1,3 @@ -curl -fsSL -o pypy3.zip https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.2.0-win32.zip +curl -fsSL -o pypy3.zip https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.0-win32.zip 7z x pypy3.zip -oc:\ -c:\Python37\Scripts\virtualenv.exe -p c:\pypy3.6-v7.2.0-win32\pypy3.exe c:\vp\pypy3 +c:\Python37\Scripts\virtualenv.exe -p c:\pypy3.6-v7.3.0-win32\pypy3.exe c:\vp\pypy3