Merge branch 'master' into reduce

This commit is contained in:
Alexander 2019-12-25 15:42:47 +03:00
commit dda5558129
29 changed files with 206 additions and 116 deletions

View File

@ -6,8 +6,6 @@ codecov:
# https://docs.codecov.io/docs/comparing-commits # https://docs.codecov.io/docs/comparing-commits
allow_coverage_offsets: true allow_coverage_offsets: true
token: 6dafc396-e7f5-4221-a38a-8b07a49fbdae
comment: off comment: off
# Matches 'omit:' in .coveragerc # 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-vcvars: "x86_amd64"
platform-msbuild: "x64" platform-msbuild: "x64"
- python-version: "pypy3.6" - python-version: "pypy3.6"
pypy-version: "pypy3.6-v7.2.0-win32" pypy-version: "pypy3.6-v7.3.0-win32"
pypy-url: "https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.2.0-win32.zip" pypy-url: "https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.0-win32.zip"
exclude: exclude:
- python-version: "pypy3.6" - python-version: "pypy3.6"
architecture: "x64" architecture: "x64"
@ -364,6 +364,10 @@ jobs:
name: errors name: errors
path: Tests/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 - name: Upload coverage
if: success() if: success()
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v1

View File

@ -83,21 +83,18 @@ jobs:
name: errors name: errors
path: Tests/errors path: Tests/errors
- name: Docs
if: matrix.python-version == 3.8
run: |
pip install sphinx-rtd-theme
make doccheck
- name: After success - name: After success
if: success() if: success()
run: | run: |
.travis/after_success.sh .travis/after_success.sh
env: env:
MATRIX_OS: ${{ matrix.os }} MATRIX_OS: ${{ matrix.os }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_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 - name: Upload coverage
if: success() if: success()
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v1

View File

@ -3,7 +3,7 @@
set -e set -e
sudo apt-get update 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\ ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
cmake imagemagick libharfbuzz-dev libfribidi-dev cmake imagemagick libharfbuzz-dev libfribidi-dev
@ -20,8 +20,8 @@ if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then
pip install pyqt5 pip install pyqt5
fi fi
# docs only on Python 3.7 # docs only on Python 3.8
if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ]; then pip install -r requirements.txt ; fi if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ]; then pip install -r requirements.txt ; fi
# webp # webp
pushd depends && ./install_webp.sh && popd 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 python -m pytest -v -x --cov PIL --cov Tests --cov-report term Tests
# Docs # 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) 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 - Change default resize resampling filter from NEAREST to BICUBIC #4255
[homm] [homm]
- Fixed black lines on upscaled images with the BOX filter #4278
[homm]
- Better thumbnail aspect ratio preservation #4256 - Better thumbnail aspect ratio preservation #4256
[homm] [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(im.size, reloaded.size)
self.assertEqual(reloaded.format, "BMP") 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): def test_dpi(self):
dpi = (72, 72) dpi = (72, 72)

View File

@ -638,6 +638,14 @@ class TestFileJpeg(PillowTestCase):
# OSError for unidentified image. # OSError for unidentified image.
self.assertEqual(im.info.get("dpi"), (72, 72)) 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): def test_ifd_offset_exif(self):
# Arrange # Arrange
# This image has been manually hexedited to have an IFD offset of 10, # 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: with Image.open(target_path) as target:
self.assert_image_equal(im, 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): def test_open_windows_v2(self):
files = ( files = (

View File

@ -30,7 +30,7 @@ class TestFileSun(PillowTestCase):
with Image.open("Tests/images/sunraster.im1.png") as target: with Image.open("Tests/images/sunraster.im1.png") as target:
self.assert_image_equal(im, 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): def test_others(self):
files = ( files = (
os.path.join(EXTRA_DIR, f) os.path.join(EXTRA_DIR, f)

View File

@ -26,7 +26,7 @@ class TestUnsupportedWebp(PillowTestCase):
WebPImagePlugin.SUPPORTED = True WebPImagePlugin.SUPPORTED = True
@unittest.skipIf(not HAVE_WEBP, "WebP support not installed") @unittest.skipUnless(HAVE_WEBP, "WebP support not installed")
class TestFileWebp(PillowTestCase): class TestFileWebp(PillowTestCase):
def setUp(self): def setUp(self):
self.rgb_mode = "RGB" self.rgb_mode = "RGB"

View File

@ -2,10 +2,9 @@ import unittest
from PIL import Image, ImageDraw, ImageFont, features 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): class TestTTypeFontLeak(PillowLeakTestCase):
# fails at iteration 3 in master # fails at iteration 3 in master
iterations = 10 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): def test_leak(self):
ttype = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20) ttype = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
self._test_font(ttype) self._test_font(ttype)

View File

@ -13,7 +13,11 @@ class TestImageDraft(PillowTestCase):
im = Image.new(in_mode, in_size) im = Image.new(in_mode, in_size)
data = tostring(im, "JPEG") data = tostring(im, "JPEG")
im = fromstring(data) im = fromstring(data)
im.draft(req_mode, req_size) mode, box = im.draft(req_mode, req_size)
scale, _ = im.decoderconfig
self.assertEqual(box[:2], (0, 0))
self.assertTrue((im.width - scale) < box[2] <= im.width)
self.assertTrue((im.height - scale) < box[3] <= im.height)
return im return im
def test_size(self): def test_size(self):

View File

@ -1,6 +1,6 @@
from PIL import Image from PIL import Image
from .helper import PillowTestCase, hopper from .helper import PillowTestCase, fromstring, hopper, tostring
class TestImageThumbnail(PillowTestCase): class TestImageThumbnail(PillowTestCase):
@ -58,3 +58,15 @@ class TestImageThumbnail(PillowTestCase):
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:
im.thumbnail((64, 64)) im.thumbnail((64, 64))
self.assertEqual(im.size, (64, 64)) self.assertEqual(im.size, (64, 64))
def test_DCT_scaling_edges(self):
# Make an image with red borders and size (N * 8) + 1 to cross DCT grid
im = Image.new("RGB", (257, 257), "red")
im.paste(Image.new("RGB", (235, 235)), (11, 11))
thumb = fromstring(tostring(im, "JPEG", quality=99, subsampling=0))
thumb.thumbnail((32, 32), Image.BICUBIC)
ref = im.resize((32, 32), Image.BICUBIC)
# This is still JPEG, some error is present. Without the fix it is 11.5
self.assert_image_similar(thumb, ref, 1.5)

View File

@ -11,7 +11,7 @@ except ImportError:
image_font_installed = False 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): class TestImageFontBitmap(PillowTestCase):
def test_similar(self): def test_similar(self):
text = "EmbeddedBitmap" text = "EmbeddedBitmap"

View File

@ -18,7 +18,7 @@ except (OSError, ImportError):
TK_MODES = ("1", "L", "P", "RGB", "RGBA") 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): class TestImageTk(PillowTestCase):
def setUp(self): def setUp(self):
try: try:

View File

@ -16,7 +16,7 @@ class TestUtil(PillowTestCase):
# Assert # Assert
self.assertTrue(it_is) 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): def test_path_obj_is_path(self):
# Arrange # Arrange
from pathlib import Path from pathlib import Path

View File

@ -42,7 +42,7 @@ clean:
-rm -rf $(BUILDDIR)/* -rm -rf $(BUILDDIR)/*
html: html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html $(SPHINXBUILD) -b html -W --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo @echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

View File

@ -47,7 +47,8 @@ Basic Installation
Install Pillow with :command:`pip`:: Install Pillow with :command:`pip`::
$ pip install Pillow python -m pip install pip
python -m pip install Pillow
Windows Installation 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 executable installers. These binaries have all of the optional
libraries included except for raqm and libimagequant:: libraries included except for raqm and libimagequant::
> pip install Pillow python -m pip install pip
python -m pip install Pillow
macOS Installation macOS Installation
@ -69,7 +71,8 @@ versions in the wheel format. These include support for all optional
libraries except libimagequant. Raqm support requires libraqm, libraries except libimagequant. Raqm support requires libraqm,
fribidi, and harfbuzz to be installed separately:: fribidi, and harfbuzz to be installed separately::
$ pip install Pillow python -m pip install pip
python -m pip install Pillow
Linux Installation Linux Installation
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
@ -79,7 +82,8 @@ versions in the manylinux wheel format. These include support for all
optional libraries except libimagequant. Raqm support requires optional libraries except libimagequant. Raqm support requires
libraqm, fribidi, and harfbuzz to be installed separately:: 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 Most major Linux distributions, including Fedora, Debian/Ubuntu and
ArchLinux also include Pillow in packages that previously contained 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**:: **Ports**::
$ cd /usr/ports/graphics/py-pillow && make install clean cd /usr/ports/graphics/py-pillow && make install clean
**Packages**:: **Packages**::
$ pkg install py36-pillow pkg install py36-pillow
.. note:: .. note::
@ -191,7 +195,8 @@ Many of Pillow's features require external libraries:
Once you have installed the prerequisites, run:: 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 If the prerequisites are installed in the standard library locations
for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no 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 :file:`setup.cfg`, or by adding environment variables on the command
line:: 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 If Pillow has been previously built without the required
prerequisites, it may be necessary to manually clear the pip cache or prerequisites, it may be necessary to manually clear the pip cache or
@ -245,11 +250,11 @@ Build Options
Sample usage:: 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:: 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 Building on macOS
@ -265,21 +270,22 @@ tools.
The easiest way to install external libraries is via `Homebrew The easiest way to install external libraries is via `Homebrew
<https://brew.sh/>`_. After you install Homebrew, run:: <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:: 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. Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
Now install Pillow with:: Now install Pillow with::
$ pip install Pillow python -m pip install pip
python -m pip install Pillow
or from within the uncompressed source directory:: or from within the uncompressed source directory::
$ python setup.py install python setup.py install
Building on Windows Building on Windows
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
@ -293,17 +299,17 @@ Building on FreeBSD
.. Note:: Only FreeBSD 10 and 11 tested .. 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:: Or for Python 3::
$ sudo pkg install python3 sudo pkg install python3
Prerequisites are installed on **FreeBSD 10 or 11** with:: 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. Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
@ -316,33 +322,33 @@ development libraries installed.
In Debian or Ubuntu:: In Debian or Ubuntu::
$ sudo apt-get install python-dev python-setuptools sudo apt-get install python-dev python-setuptools
Or for Python 3:: 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:: 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:: 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. .. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
Prerequisites are installed on **Ubuntu 16.04 LTS** with:: Prerequisites are installed on **Ubuntu 16.04 LTS** with::
$ sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \ 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 \ libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
libharfbuzz-dev libfribidi-dev libharfbuzz-dev libfribidi-dev
Then see ``depends/install_raqm.sh`` to install libraqm. Then see ``depends/install_raqm.sh`` to install libraqm.
Prerequisites are installed on recent **RedHat** **Centos** or **Fedora** with:: 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 \ freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel \
harfbuzz-devel fribidi-devel libraqm-devel libimagequant-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 Basic Android support has been added for compilation within the Termux
environment. The dependencies can be installed by:: 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 libjpeg-turbo
This has been tested within the Termux app on ChromeOS, on x86. This has been tested within the Termux app on ChromeOS, on x86.

View File

@ -52,12 +52,26 @@ Default resampling filter
The default resampling filter has been changed to the high-quality convolution 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` ``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`` method and the :py:meth:`~PIL.ImageOps.pad`, :py:meth:`~PIL.ImageOps.scale`
and :py:meth:`~PIL.ImageOps.fit`` functions. and :py:meth:`~PIL.ImageOps.fit` functions.
``Image.NEAREST`` is still always used for images in "P" and "1" modes. ``Image.NEAREST`` is still always used for images in "P" and "1" modes.
See :ref:`concept-filters` to learn the difference. In short, See :ref:`concept-filters` to learn the difference. In short,
``Image.NEAREST`` is a very fast filter, but simple and low-quality. ``Image.NEAREST`` is a very fast filter, but simple and low-quality.
Image.draft() return value
^^^^^^^^^^^^^^^^^^^^^^^^^^
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
API Changes API Changes
=========== ===========
@ -87,8 +101,8 @@ Image.__del__
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
Implicitly closing the image's underlying file in ``Image.__del__`` has been removed. 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 Use a context manager or call :py:meth:`~PIL.Image.Image.close` instead to close
deterministic way. the file in a deterministic way.
Previous method: Previous method:
@ -104,8 +118,12 @@ Use instead:
with Image.open("hopper.png") as im: with Image.open("hopper.png") as im:
im.save("out.jpg") im.save("out.jpg")
Better thumbnail aspect ratio preservation Better thumbnail geometry
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
When calculating the new dimensions in ``Image.thumbnail``, round to the When calculating the new dimensions in :py:meth:`~PIL.Image.Image.thumbnail`,
nearest integer, instead of always rounding down. 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 # bitmap header
if bitmap_header: if bitmap_header:
offset = 14 + header + colors * 4 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( fp.write(
b"BM" b"BM" # file type (magic)
+ o32(offset + image) # file type (magic) + o32(file_size) # file size
+ o32(0) # file size + o32(0) # reserved
+ o32(offset) # reserved + o32(offset) # image data offset
) # image data offset )
# bitmap info header # bitmap info header
fp.write( fp.write(

View File

@ -616,6 +616,7 @@ def _save_netpbm(im, fp, filename):
# below for information on how to enable this. # below for information on how to enable this.
tempfile = im._dump() tempfile = im._dump()
try:
with open(filename, "wb") as f: with open(filename, "wb") as f:
if im.mode != "RGB": if im.mode != "RGB":
subprocess.check_call( subprocess.check_call(
@ -630,7 +631,10 @@ def _save_netpbm(im, fp, filename):
quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL quant_cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
) )
togif_proc = subprocess.Popen( togif_proc = subprocess.Popen(
togif_cmd, stdin=quant_proc.stdout, stdout=f, stderr=subprocess.DEVNULL togif_cmd,
stdin=quant_proc.stdout,
stdout=f,
stderr=subprocess.DEVNULL,
) )
# Allow ppmquant to receive SIGPIPE if ppmtogif exits # Allow ppmquant to receive SIGPIPE if ppmtogif exits
@ -643,7 +647,7 @@ def _save_netpbm(im, fp, filename):
retcode = togif_proc.wait() retcode = togif_proc.wait()
if retcode: if retcode:
raise subprocess.CalledProcessError(retcode, togif_cmd) raise subprocess.CalledProcessError(retcode, togif_cmd)
finally:
try: try:
os.unlink(tempfile) os.unlink(tempfile)
except OSError: except OSError:

View File

@ -1127,15 +1127,17 @@ class Image:
Configures the image file loader so it returns a version of the Configures the image file loader so it returns a version of the
image that as closely as possible matches the given mode and image that as closely as possible matches the given mode and
size. For example, you can use this method to convert a color size. For example, you can use this method to convert a color
JPEG to greyscale while loading it, or to extract a 128x192 JPEG to greyscale while loading it.
version from a PCD file.
If any changes are made, returns a tuple with the chosen ``mode`` and
``box`` with coordinates of the original image within the altered one.
Note that this method modifies the :py:class:`~PIL.Image.Image` object Note that this method modifies the :py:class:`~PIL.Image.Image` object
in place. If the image has already been loaded, this method has no in place. If the image has already been loaded, this method has no
effect. effect.
Note: This method is not implemented for most images. It is 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 mode: The requested mode.
:param size: The requested size. :param size: The requested size.
@ -2177,14 +2179,17 @@ class Image:
x = max(round(x * size[1] / y), 1) x = max(round(x * size[1] / y), 1)
y = round(size[1]) y = round(size[1])
size = x, y size = x, y
box = None
if size == self.size: if size == self.size:
return return
self.draft(None, size) res = self.draft(None, size)
if res is not None:
box = res[1]
if self.size != size: if self.size != size:
im = self.resize(size, resample) im = self.resize(size, resample, box=box)
self.im = im.im self.im = im.im
self._size = size self._size = size
@ -2214,12 +2219,14 @@ class Image:
It may also be an :py:class:`~PIL.Image.ImageTransformHandler` It may also be an :py:class:`~PIL.Image.ImageTransformHandler`
object:: object::
class Example(Image.ImageTransformHandler): class Example(Image.ImageTransformHandler):
def transform(size, method, data, resample, fill=1): def transform(size, method, data, resample, fill=1):
# Return result # Return result
It may also be an object with a :py:meth:`~method.getdata` method It may also be an object with a :py:meth:`~method.getdata` method
that returns a tuple supplying new **method** and **data** values:: that returns a tuple supplying new **method** and **data** values::
class Example(object): class Example(object):
def getdata(self): def getdata(self):
method = Image.EXTENT method = Image.EXTENT

View File

@ -122,11 +122,6 @@ class ImageFile(Image.Image):
self.fp.close() self.fp.close()
raise raise
def draft(self, mode, size):
"""Set draft mode"""
pass
def get_format_mimetype(self): def get_format_mimetype(self):
if self.custom_mimetype: if self.custom_mimetype:
return self.custom_mimetype return self.custom_mimetype

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 color: The background color of the padded image.
:param centering: Control the position of the original image within the :param centering: Control the position of the original image within the
padded version. padded version.
(0.5, 0.5) will keep the image centered (0.5, 0.5) will keep the image centered
(0, 0) will keep the image aligned to the top left (0, 0) will keep the image aligned to the top left
(1, 1) will keep the image aligned to the bottom (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 # 1 dpcm = 2.54 dpi
dpi *= 2.54 dpi *= 2.54
self.info["dpi"] = int(dpi + 0.5), int(dpi + 0.5) 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 # SyntaxError for invalid/unreadable EXIF
# KeyError for dpi not included # KeyError for dpi not included
# ZeroDivisionError for invalid dpi rational value # ZeroDivisionError for invalid dpi rational value
# ValueError for x_resolution[0] being an invalid float
self.info["dpi"] = 72, 72 self.info["dpi"] = 72, 72
@ -412,7 +413,8 @@ class JpegImageFile(ImageFile.ImageFile):
return return
d, e, o, a = self.tile[0] d, e, o, a = self.tile[0]
scale = 0 scale = 1
original_size = self.size
if a[0] == "RGB" and mode in ["L", "YCbCr"]: if a[0] == "RGB" and mode in ["L", "YCbCr"]:
self.mode = mode self.mode = mode
@ -435,7 +437,8 @@ class JpegImageFile(ImageFile.ImageFile):
self.tile = [(d, e, o, a)] self.tile = [(d, e, o, a)]
self.decoderconfig = (scale, 0) self.decoderconfig = (scale, 0)
return self box = (0, 0, original_size[0] / float(scale), original_size[1] / float(scale))
return (self.mode, box)
def load_djpeg(self): def load_djpeg(self):

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