Merge branch 'reduce' into reduce-in-resize

# Conflicts:
#	docs/releasenotes/7.0.0.rst
This commit is contained in:
Alexander 2019-12-25 15:45:17 +03:00
commit d719aafecb
26 changed files with 168 additions and 111 deletions

View File

@ -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

18
.github/codecov-upstream.yml vendored Normal file
View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -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)

View File

@ -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,

View File

@ -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 = (

View File

@ -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)

View File

@ -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"

View File

@ -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)

View File

@ -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"

View File

@ -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:

View File

@ -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

View File

@ -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."

View File

@ -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
<https://brew.sh/>`_. 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.

View File

@ -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.

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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