diff --git a/.ci/install.sh b/.ci/install.sh
index 3c75526fe03..2143c06137a 100755
--- a/.ci/install.sh
+++ b/.ci/install.sh
@@ -32,7 +32,6 @@ pip install test-image-results
pip install numpy
# TODO Remove when 3.9-dev includes setuptools 49.3.2+:
-if [ "$TRAVIS_PYTHON_VERSION" == "3.9-dev" ]; then pip install -U "setuptools>=49.3.2" ; fi
if [ "$GHA_PYTHON_VERSION" == "3.9-dev" ]; then pip install -U "setuptools>=49.3.2" ; fi
if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then
diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml
index 0372b558629..a90a0a95487 100644
--- a/.github/workflows/test-windows.yml
+++ b/.github/workflows/test-windows.yml
@@ -198,15 +198,27 @@ jobs:
msys:
runs-on: windows-2019
+ strategy:
+ fail-fast: false
+ matrix:
+ mingw: ["MINGW32", "MINGW64"]
+ include:
+ - mingw: "MINGW32"
+ name: "MSYS2 MinGW 32-bit"
+ package: "mingw-w64-i686"
+ - mingw: "MINGW64"
+ name: "MSYS2 MinGW 64-bit"
+ package: "mingw-w64-x86_64"
+
defaults:
run:
shell: bash.exe --login -eo pipefail "{0}"
env:
- MSYSTEM: MINGW64
+ MSYSTEM: ${{ matrix.mingw }}
CHERE_INVOKING: 1
timeout-minutes: 30
- name: MSYS2 MinGW 64-bit
+ name: ${{ matrix.name }}
steps:
- uses: actions/checkout@v2
@@ -218,23 +230,22 @@ jobs:
- name: Install Dependencies
run: |
pacman -S --noconfirm \
- mingw-w64-x86_64-python3-pip \
- mingw-w64-x86_64-python3-setuptools \
- mingw-w64-x86_64-python3-pytest \
- mingw-w64-x86_64-python3-pytest-cov \
- mingw-w64-x86_64-python3-cffi \
- mingw-w64-x86_64-python3-olefile \
- mingw-w64-x86_64-python3-numpy \
- mingw-w64-x86_64-python3-pyqt5 \
- mingw-w64-x86_64-python3-numpy \
- mingw-w64-x86_64-freetype \
- mingw-w64-x86_64-lcms2 \
- mingw-w64-x86_64-libwebp \
- mingw-w64-x86_64-libjpeg-turbo \
- mingw-w64-x86_64-openjpeg2 \
- mingw-w64-x86_64-libimagequant \
- mingw-w64-x86_64-libraqm \
- mingw-w64-x86_64-ghostscript \
+ ${{ matrix.package }}-python3-cffi \
+ ${{ matrix.package }}-python3-numpy \
+ ${{ matrix.package }}-python3-olefile \
+ ${{ matrix.package }}-python3-pip \
+ ${{ matrix.package }}-python3-pyqt5 \
+ ${{ matrix.package }}-python3-pytest \
+ ${{ matrix.package }}-python3-pytest-cov \
+ ${{ matrix.package }}-python3-setuptools \
+ ${{ matrix.package }}-freetype \
+ ${{ matrix.package }}-ghostscript \
+ ${{ matrix.package }}-lcms2 \
+ ${{ matrix.package }}-libimagequant \
+ ${{ matrix.package }}-libjpeg-turbo \
+ ${{ matrix.package }}-libraqm \
+ ${{ matrix.package }}-libwebp \
+ ${{ matrix.package }}-openjpeg2 \
subversion
python3 -m pip install pyroma
@@ -256,4 +267,4 @@ jobs:
python3 -m pip install codecov
bash <(curl -s https://codecov.io/bash) -F GHA_Windows
env:
- CODECOV_NAME: MSYS2 MinGW 64-bit
+ CODECOV_NAME: ${{ matrix.name }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 77c5f90f77b..459251d7733 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/psf/black
- rev: 6bedb5c58a7d8c25aa9509f8217bc24e9797e90d # frozen: 19.10b0
+ rev: e66be67b9b6811913470f70c28b4d50f94d05b22 # frozen: 20.8b1
hooks:
- id: black
args: ["--target-version", "py36"]
@@ -9,7 +9,7 @@ repos:
types: []
- repo: https://github.com/timothycrosley/isort
- rev: 9ae09866e278fbc6ec0383ccb16b5c84e78e6e4d # frozen: 5.3.2
+ rev: 377d260ffa6f746693f97b46d95025afc4bd8275 # frozen: 5.4.2
hooks:
- id: isort
@@ -31,7 +31,7 @@ repos:
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
- repo: https://github.com/pre-commit/pygrep-hooks
- rev: 20b9ac745c5adaab12b845b3564c773dcc051d0e # frozen: v1.5.2
+ rev: eae6397e4c259ed3d057511f6dd5330b92867e62 # frozen: v1.6.0
hooks:
- id: python-check-blanket-noqa
- id: rst-backticks
diff --git a/CHANGES.rst b/CHANGES.rst
index c9389ddb1c9..263c2a82367 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,12 +5,33 @@ Changelog (Pillow)
8.0.0 (unreleased)
------------------
+- Fix IFDRational __eq__ bug #4888
+ [luphord, radarhere]
+
+- Fixed duplicate variable name #4885
+ [liZe, radarhere]
+
+- Added homebrew zlib include directory #4842
+ [radarhere]
+
+- Corrected inverted PDF CMYK colors #4866
+ [radarhere]
+
+- Do not try to close file pointer if file pointer is empty #4823
+ [radarhere]
+
+- ImageOps.autocontrast: add mask parameter #4843
+ [navneeth, hugovk]
+
- Read EXIF data tEXt chunk into info as bytes instead of string #4828
[radarhere]
- Remove long-deprecated Image.py functions #4798
[hugovk, nulano, radarhere]
+- Replaced most uses of distutils with setuptools #4797, #4809, #4814, #4817, #4829
+ [hugovk, radarhere]
+
- Add MIME type to PsdImagePlugin #4788
[samamorgan]
diff --git a/README.md b/README.md
new file mode 100644
index 00000000000..6ca6cbf83d1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,96 @@
+
+
+
+
+# Pillow
+
+## Python Imaging Library (Fork)
+
+Pillow is the friendly PIL fork by [Alex Clark and
+Contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
+PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
+As of 2019, Pillow development is
+[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
+
+
+
+ docs |
+
+
+ |
+
+
+ tests |
+
+
+
+
+
+
+
+
+
+ |
+
+
+ package |
+
+
+
+
+
+ |
+
+
+ social |
+
+
+
+ |
+
+
+
+## More Information
+
+- [Documentation](https://pillow.readthedocs.io/)
+ - [Installation](https://pillow.readthedocs.io/en/latest/installation.html)
+ - [Handbook](https://pillow.readthedocs.io/en/latest/handbook/index.html)
+- [Contribute](https://github.com/python-pillow/Pillow/blob/master/.github/CONTRIBUTING.md)
+ - [Issues](https://github.com/python-pillow/Pillow/issues)
+ - [Pull requests](https://github.com/python-pillow/Pillow/pulls)
+- [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst)
+ - [Pre-fork](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork)
+
+## Report a Vulnerability
+
+To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security).
diff --git a/README.rst b/README.rst
deleted file mode 100644
index c1d5be57912..00000000000
--- a/README.rst
+++ /dev/null
@@ -1,103 +0,0 @@
-Pillow
-======
-
-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. As of 2019, Pillow development is `supported by Tidelift `_.
-
-.. start-badges
-
-.. list-table::
- :stub-columns: 1
-
- * - docs
- - |docs|
- * - tests
- - |linux| |macos| |windows| |gha_lint| |gha| |gha_windows| |gha_docker| |coverage|
- * - package
- - |zenodo| |tidelift| |version| |downloads|
- * - social
- - |gitter| |twitter|
-
-.. end-badges
-
-More Information
-----------------
-
-- `Documentation `_
-
- - `Installation `_
- - `Handbook `_
-
-- `Contribute `_
-
- - `Issues `_
- - `Pull requests `_
-
-- `Changelog `_
-
- - `Pre-fork `_
-
-Report a Vulnerability
-----------------------
-
-To report a security vulnerability, please follow the procedure described in the `Tidelift security policy `_.
-
-.. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest
- :target: https://pillow.readthedocs.io/?badge=latest
- :alt: Documentation Status
-
-.. |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)
-
-.. |macos| image:: https://img.shields.io/travis/python-pillow/pillow-wheels/master.svg?label=macOS%20build
- :target: https://travis-ci.org/python-pillow/pillow-wheels
- :alt: Travis CI build status (macOS)
-
-.. |windows| image:: https://img.shields.io/appveyor/build/python-pillow/Pillow/master.svg?label=Windows%20build
- :target: https://ci.appveyor.com/project/python-pillow/Pillow
- :alt: AppVeyor CI build status (Windows)
-
-.. |gha_lint| image:: https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg
- :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint
- :alt: GitHub Actions build status (Lint)
-
-.. |gha_docker| image:: https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg
- :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Docker%22
- :alt: GitHub Actions build status (Test Docker)
-
-.. |gha| image:: https://github.com/python-pillow/Pillow/workflows/Test/badge.svg
- :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ATest
- :alt: GitHub Actions build status (Test Linux and macOS)
-
-.. |gha_windows| image:: https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg
- :target: https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Windows%22
- :alt: GitHub Actions build status (Test Windows)
-
-.. |coverage| image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg
- :target: https://codecov.io/gh/python-pillow/Pillow
- :alt: Code coverage
-
-.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
- :target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
-
-.. |tidelift| image:: https://tidelift.com/badges/package/pypi/Pillow?style=flat
- :target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge
-
-.. |version| image:: https://img.shields.io/pypi/v/pillow.svg
- :target: https://pypi.org/project/Pillow/
- :alt: Latest PyPI version
-
-.. |downloads| image:: https://img.shields.io/pypi/dm/pillow.svg
- :target: https://pypi.org/project/Pillow/
- :alt: Number of PyPI downloads
-
-.. |gitter| image:: https://badges.gitter.im/python-pillow/Pillow.svg
- :target: https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
- :alt: Join the chat at https://gitter.im/python-pillow/Pillow
-
-.. |twitter| image:: https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg
- :target: https://twitter.com/PythonPillow
- :alt: Follow on https://twitter.com/PythonPillow
diff --git a/RELEASING.md b/RELEASING.md
index 3f62a70c484..c9a0439d809 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -101,11 +101,7 @@ Released as needed privately to individual vendors for critical security-related
cd pillow-wheels
./update-pillow-tag.sh [[release tag]]
```
-* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/).
- ```bash
- wget -m -A 'Pillow--*' \
- http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com
- ```
+* [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases).
## Publicize Release
diff --git a/Tests/check_jpeg_leaks.py b/Tests/check_jpeg_leaks.py
index b63fa2a1e59..ab8d7771992 100644
--- a/Tests/check_jpeg_leaks.py
+++ b/Tests/check_jpeg_leaks.py
@@ -119,60 +119,59 @@ def test_qtables_leak():
def test_exif_leak():
"""
-pre patch:
-
- MB
-177.1^ #
- | @@@#
- | :@@@@@@#
- | ::::@@@@@@#
- | ::::::::@@@@@@#
- | @@::::: ::::@@@@@@#
- | @@@@ ::::: ::::@@@@@@#
- | @@@@@@@ ::::: ::::@@@@@@#
- | @@::@@@@@@@ ::::: ::::@@@@@@#
- | @@@@ : @@@@@@@ ::::: ::::@@@@@@#
- | @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@#
- | @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
- | @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
- | ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
- | :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
- | ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
- | @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
- | @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
- | :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
- | @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
- 0 +----------------------------------------------------------------------->Gi
- 0 11.37
-
-
-post patch:
-
- MB
-21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
- 0 +----------------------------------------------------------------------->Gi
- 0 11.33
-
-"""
+ pre patch:
+
+ MB
+ 177.1^ #
+ | @@@#
+ | :@@@@@@#
+ | ::::@@@@@@#
+ | ::::::::@@@@@@#
+ | @@::::: ::::@@@@@@#
+ | @@@@ ::::: ::::@@@@@@#
+ | @@@@@@@ ::::: ::::@@@@@@#
+ | @@::@@@@@@@ ::::: ::::@@@@@@#
+ | @@@@ : @@@@@@@ ::::: ::::@@@@@@#
+ | @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@#
+ | @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
+ | @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
+ | ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
+ | :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
+ | ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
+ | @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
+ | @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
+ | :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
+ | @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
+ 0 +----------------------------------------------------------------------->Gi
+ 0 11.37
+
+
+ post patch:
+
+ MB
+ 21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
+ 0 +----------------------------------------------------------------------->Gi
+ 0 11.33
+ """
im = hopper("RGB")
exif = b"12345678" * 4096
@@ -183,31 +182,30 @@ def test_exif_leak():
def test_base_save():
"""
-base case:
- MB
-20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@:::
- | ##: : ::::::@::::::: :::: :::: : : : : : : :::::::::::: :::@:::
- | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | @@# : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | @@@ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | @ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | :@@@@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
- 0 +----------------------------------------------------------------------->Gi
- 0 7.882
-"""
+ base case:
+ MB
+ 20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@:::
+ | ##: : ::::::@::::::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | @@# : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | @@@ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | @ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | :@@@@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
+ 0 +----------------------------------------------------------------------->Gi
+ 0 7.882"""
im = hopper("RGB")
for _ in range(iterations):
diff --git a/Tests/check_libtiff_segfault.py b/Tests/check_libtiff_segfault.py
index 5a6116f4f6a..bd7f407e4ab 100644
--- a/Tests/check_libtiff_segfault.py
+++ b/Tests/check_libtiff_segfault.py
@@ -6,9 +6,9 @@
def test_libtiff_segfault():
- """ This test should not segfault. It will on Pillow <= 3.1.0 and
- libtiff >= 4.0.0
- """
+ """This test should not segfault. It will on Pillow <= 3.1.0 and
+ libtiff >= 4.0.0
+ """
with pytest.raises(OSError):
with Image.open(TEST_FILE) as im:
diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py
index ca7e877b3c7..19602c1e78c 100644
--- a/Tests/test_bmp_reference.py
+++ b/Tests/test_bmp_reference.py
@@ -16,8 +16,8 @@ def get_files(d, ext=".bmp"):
def test_bad():
- """ These shouldn't crash/dos, but they shouldn't return anything
- either """
+ """These shouldn't crash/dos, but they shouldn't return anything
+ either"""
for f in get_files("b"):
def open(f):
@@ -32,8 +32,8 @@ def open(f):
def test_questionable():
- """ These shouldn't crash/dos, but it's not well defined that these
- are in spec """
+ """These shouldn't crash/dos, but it's not well defined that these
+ are in spec"""
supported = [
"pal8os2v2.bmp",
"rgb24prof.bmp",
@@ -57,8 +57,8 @@ def test_questionable():
def test_good():
- """ These should all work. There's a set of target files in the
- html directory that we can compare against. """
+ """These should all work. There's a set of target files in the
+ html directory that we can compare against."""
# Target files, if they're not just replacing the extension
file_map = {
diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py
index 9c4d9c01f8f..2d50748bd6f 100644
--- a/Tests/test_file_apng.py
+++ b/Tests/test_file_apng.py
@@ -359,7 +359,10 @@ def test_apng_save_split_fdat(tmp_path):
with Image.open("Tests/images/old-style-jpeg-compression.png") as im:
frames = [im.copy(), Image.new("RGBA", im.size, (255, 0, 0, 255))]
im.save(
- test_file, save_all=True, default_image=True, append_images=frames,
+ test_file,
+ save_all=True,
+ default_image=True,
+ append_images=frames,
)
with Image.open(test_file) as im:
exception = None
diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py
index 03a2dba51a1..71779461465 100644
--- a/Tests/test_file_jpeg.py
+++ b/Tests/test_file_jpeg.py
@@ -39,7 +39,7 @@ def roundtrip(self, im, **options):
return im
def gen_random_image(self, size, mode="RGB"):
- """ Generates a very hard to compress file
+ """Generates a very hard to compress file
:param size: tuple
:param mode: optional image mode
@@ -99,7 +99,8 @@ def test_cmyk(self):
assert k > 0.9
@pytest.mark.parametrize(
- "test_image_path", [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"],
+ "test_image_path",
+ [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"],
)
def test_dpi(self, test_image_path):
def test(xdpi, ydpi=None):
@@ -242,6 +243,16 @@ def test_exif_gps(self):
# Assert
assert exif[gps_index] == expected_exif_gps
+ def test_exif_equality(self):
+ # In 7.2.0, Exif rationals were changed to be read as
+ # TiffImagePlugin.IFDRational. This class had a bug in __eq__,
+ # breaking the self-equality of Exif data
+ exifs = []
+ for i in range(2):
+ with Image.open("Tests/images/exif-200dpcm.jpg") as im:
+ exifs.append(im._getexif())
+ assert exifs[0] == exifs[1]
+
def test_exif_rollback(self):
# rolling back exif support in 3.1 to pre-3.0 formatting.
# expected from 2.9, with b/u qualifiers switched for 3.2 compatibility
diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
index c4af94e2fdd..da955b3de7d 100644
--- a/Tests/test_file_libtiff.py
+++ b/Tests/test_file_libtiff.py
@@ -402,8 +402,8 @@ def test_g4_string_info(self, tmp_path):
assert "temp.tif" == reread.tag[269][0]
def test_12bit_rawmode(self):
- """ Are we generating the same interpretation
- of the image as Imagemagick is? """
+ """Are we generating the same interpretation
+ of the image as Imagemagick is?"""
TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/12bit.cropped.tif") as im:
im.load()
@@ -503,9 +503,9 @@ def test_palette_save(self, tmp_path):
assert len(reloaded.tag_v2[320]) == 768
def xtest_bw_compression_w_rgb(self, tmp_path):
- """ This test passes, but when running all tests causes a failure due
- to output on stderr from the error thrown by libtiff. We need to
- capture that but not now"""
+ """This test passes, but when running all tests causes a failure due
+ to output on stderr from the error thrown by libtiff. We need to
+ capture that but not now"""
im = hopper("RGB")
out = str(tmp_path / "temp.tif")
@@ -768,7 +768,7 @@ def test_16bit_RGBa_tiff(self):
assert im.mode == "RGBA"
assert im.size == (100, 40)
assert im.tile, [
- ("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236),)
+ ("libtiff", (0, 0, 100, 40), 0, ("RGBa;16N", "tiff_lzw", False, 38236))
]
im.load()
diff --git a/Tests/test_file_libtiff_small.py b/Tests/test_file_libtiff_small.py
index 593a8eda83f..03137c8b603 100644
--- a/Tests/test_file_libtiff_small.py
+++ b/Tests/test_file_libtiff_small.py
@@ -7,13 +7,13 @@
class TestFileLibTiffSmall(LibTiffTestCase):
- """ The small lena image was failing on open in the libtiff
- decoder because the file pointer was set to the wrong place
- by a spurious seek. It wasn't failing with the byteio method.
+ """The small lena image was failing on open in the libtiff
+ decoder because the file pointer was set to the wrong place
+ by a spurious seek. It wasn't failing with the byteio method.
- It was fixed by forcing an lseek to the beginning of the
- file just before reading in libtiff. These tests remain
- to ensure that it stays fixed. """
+ It was fixed by forcing an lseek to the beginning of the
+ file just before reading in libtiff. These tests remain
+ to ensure that it stays fixed."""
def test_g4_hopper_file(self, tmp_path):
"""Testing the open file load path"""
diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py
index aefe6f9eaaa..59411504219 100644
--- a/Tests/test_file_tiff.py
+++ b/Tests/test_file_tiff.py
@@ -225,8 +225,8 @@ def test_16bit_s(self):
assert im.getpixel((0, 1)) == 0
def test_12bit_rawmode(self):
- """ Are we generating the same interpretation
- of the image as Imagemagick is? """
+ """Are we generating the same interpretation
+ of the image as Imagemagick is?"""
with Image.open("Tests/images/12bit.cropped.tif") as im:
# to make the target --
diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py
index 57e14e9543e..0f7f8adf198 100644
--- a/Tests/test_file_tiff_metadata.py
+++ b/Tests/test_file_tiff_metadata.py
@@ -12,10 +12,10 @@
def test_rt_metadata(tmp_path):
- """ Test writing arbitrary metadata into the tiff image directory
- Use case is ImageJ private tags, one numeric, one arbitrary
- data. https://github.com/python-pillow/Pillow/issues/291
- """
+ """Test writing arbitrary metadata into the tiff image directory
+ Use case is ImageJ private tags, one numeric, one arbitrary
+ data. https://github.com/python-pillow/Pillow/issues/291
+ """
img = hopper()
diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py
index d10b1acfd66..3b9c8b07146 100644
--- a/Tests/test_format_hsv.py
+++ b/Tests/test_format_hsv.py
@@ -85,7 +85,10 @@ def test_wedge():
im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong"
)
assert_image_similar(
- im.getchannel(1), comparable.getchannel(1), 1, "Saturation conversion is wrong",
+ im.getchannel(1),
+ comparable.getchannel(1),
+ 1,
+ "Saturation conversion is wrong",
)
assert_image_similar(
im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong"
@@ -113,7 +116,10 @@ def test_convert():
im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong"
)
assert_image_similar(
- im.getchannel(1), comparable.getchannel(1), 1, "Saturation conversion is wrong",
+ im.getchannel(1),
+ comparable.getchannel(1),
+ 1,
+ "Saturation conversion is wrong",
)
assert_image_similar(
im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong"
@@ -126,11 +132,20 @@ def test_hsv_to_rgb():
comparable = to_rgb_colorsys(comparable)
assert_image_similar(
- converted.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong",
+ converted.getchannel(0),
+ comparable.getchannel(0),
+ 3,
+ "R conversion is wrong",
)
assert_image_similar(
- converted.getchannel(1), comparable.getchannel(1), 3, "G conversion is wrong",
+ converted.getchannel(1),
+ comparable.getchannel(1),
+ 3,
+ "G conversion is wrong",
)
assert_image_similar(
- converted.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong",
+ converted.getchannel(2),
+ comparable.getchannel(2),
+ 3,
+ "B conversion is wrong",
)
diff --git a/Tests/test_image.py b/Tests/test_image.py
index cc0edbdd7a7..6d188e7405a 100644
--- a/Tests/test_image.py
+++ b/Tests/test_image.py
@@ -707,7 +707,8 @@ def test_exif_interop(self):
}
@pytest.mark.parametrize(
- "test_module", [PIL, Image],
+ "test_module",
+ [PIL, Image],
)
def test_pillow_version(self, test_module):
with pytest.warns(DeprecationWarning):
@@ -735,7 +736,7 @@ def test_pillow_version(self, test_module):
assert test_module.PILLOW_VERSION > "7.0.0"
def test_overrun(self):
- """ For overrun completeness, test as:
+ """For overrun completeness, test as:
valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c
"""
for file in [
diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py
index 7dd3b294f27..f7fe99bb4c2 100644
--- a/Tests/test_image_load.py
+++ b/Tests/test_image_load.py
@@ -1,3 +1,4 @@
+import logging
import os
import pytest
@@ -23,6 +24,14 @@ def test_close():
im.getpixel((0, 0))
+def test_close_after_load(caplog):
+ im = Image.open("Tests/images/hopper.gif")
+ im.load()
+ with caplog.at_level(logging.DEBUG):
+ im.close()
+ assert len(caplog.records) == 0
+
+
def test_contextmanager():
fn = None
with Image.open("Tests/images/hopper.gif") as im:
diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py
index fe868b7c2fe..51108ead2fd 100644
--- a/Tests/test_image_point.py
+++ b/Tests/test_image_point.py
@@ -24,9 +24,9 @@ def test_sanity():
def test_16bit_lut():
- """ Tests for 16 bit -> 8 bit lut for converting I->L images
- see https://github.com/python-pillow/Pillow/issues/440
- """
+ """Tests for 16 bit -> 8 bit lut for converting I->L images
+ see https://github.com/python-pillow/Pillow/issues/440
+ """
im = hopper("I")
im.point(list(range(256)) * 256, "L")
diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py
index 0c15b20844e..e9149b84332 100644
--- a/Tests/test_imagecms.py
+++ b/Tests/test_imagecms.py
@@ -437,7 +437,7 @@ def truncate_tuple(tuple_or_float):
def test_profile_typesafety():
- """ Profile init type safety
+ """Profile init type safety
prepatch, these would segfault, postpatch they should emit a typeerror
"""
diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py
index 5f5ea0748dd..271a1629d87 100644
--- a/Tests/test_imagedraw.py
+++ b/Tests/test_imagedraw.py
@@ -667,7 +667,10 @@ def test_floodfill_border():
# Act
ImageDraw.floodfill(
- im, centre_point, ImageColor.getrgb("red"), border=ImageColor.getrgb("black"),
+ im,
+ centre_point,
+ ImageColor.getrgb("red"),
+ border=ImageColor.getrgb("black"),
)
# Assert
diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py
index e0dbd909a3d..b4107e8e3a6 100644
--- a/Tests/test_imagefile.py
+++ b/Tests/test_imagefile.py
@@ -244,3 +244,8 @@ def test_no_format(self):
im = MockImageFile(buf)
assert im.format is None
assert im.get_format_mimetype() is None
+
+ def test_oserror(self):
+ im = Image.new("RGB", (1, 1))
+ with pytest.raises(OSError):
+ im.save(BytesIO(), "JPEG2000")
diff --git a/Tests/test_imagefont_bitmap.py b/Tests/test_imagefont_bitmap.py
index 337d2096e01..0ba6828858d 100644
--- a/Tests/test_imagefont_bitmap.py
+++ b/Tests/test_imagefont_bitmap.py
@@ -34,6 +34,9 @@ def test_similar():
(0, size_final[1] - size_bitmap[1]), text, fill=(0, 0, 0), font=font_bitmap
)
draw_outline.text(
- (0, size_final[1] - size_outline[1]), text, fill=(0, 0, 0), font=font_outline,
+ (0, size_final[1] - size_outline[1]),
+ text,
+ fill=(0, 0, 0),
+ font=font_outline,
)
assert_image_similar(im_bitmap, im_outline, 20)
diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py
index 5fbc0d3db3a..f17bfdd2f60 100644
--- a/Tests/test_imageops.py
+++ b/Tests/test_imageops.py
@@ -1,6 +1,6 @@
import pytest
-from PIL import Image, ImageOps, features
+from PIL import Image, ImageDraw, ImageOps, ImageStat, features
from .helper import (
assert_image_equal,
@@ -25,7 +25,9 @@ def test_sanity():
ImageOps.autocontrast(hopper("RGB"))
ImageOps.autocontrast(hopper("L"), cutoff=10)
+ ImageOps.autocontrast(hopper("L"), cutoff=(2, 10))
ImageOps.autocontrast(hopper("L"), ignore=[0, 255])
+ ImageOps.autocontrast(hopper("L"), mask=hopper("L"))
ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255))
ImageOps.colorize(hopper("L"), "black", "white")
@@ -312,3 +314,51 @@ def autocontrast(cutoff):
assert autocontrast(10) == autocontrast((10, 10))
assert autocontrast(10) != autocontrast((1, 10))
+
+
+def test_autocontrast_mask_toy_input():
+ # Test the mask argument of autocontrast
+ with Image.open("Tests/images/bw_gradient.png") as img:
+
+ rect_mask = Image.new("L", img.size, 0)
+ draw = ImageDraw.Draw(rect_mask)
+ x0 = img.size[0] // 4
+ y0 = img.size[1] // 4
+ x1 = 3 * img.size[0] // 4
+ y1 = 3 * img.size[1] // 4
+ draw.rectangle((x0, y0, x1, y1), fill=255)
+
+ result = ImageOps.autocontrast(img, mask=rect_mask)
+ result_nomask = ImageOps.autocontrast(img)
+
+ assert result != result_nomask
+ assert ImageStat.Stat(result, mask=rect_mask).median == [127]
+ assert ImageStat.Stat(result_nomask).median == [128]
+
+
+def test_auto_contrast_mask_real_input():
+ # Test the autocontrast with a rectangular mask
+ with Image.open("Tests/images/iptc.jpg") as img:
+
+ rect_mask = Image.new("L", img.size, 0)
+ draw = ImageDraw.Draw(rect_mask)
+ x0, y0 = img.size[0] // 2, img.size[1] // 2
+ x1, y1 = img.size[0] - 40, img.size[1]
+ draw.rectangle((x0, y0, x1, y1), fill=255)
+
+ result = ImageOps.autocontrast(img, mask=rect_mask)
+ result_nomask = ImageOps.autocontrast(img)
+
+ assert result_nomask != result
+ assert_tuple_approx_equal(
+ ImageStat.Stat(result, mask=rect_mask).median,
+ [195, 202, 184],
+ threshold=2,
+ msg="autocontrast with mask pixel incorrect",
+ )
+ assert_tuple_approx_equal(
+ ImageStat.Stat(result_nomask).median,
+ [119, 106, 79],
+ threshold=2,
+ msg="autocontrast without mask pixel incorrect",
+ )
diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py
index 043a7aaaec5..78e80f521d6 100644
--- a/Tests/test_imageshow.py
+++ b/Tests/test_imageshow.py
@@ -19,7 +19,8 @@ def test_register():
@pytest.mark.parametrize(
- "order", [-1, 0],
+ "order",
+ [-1, 0],
)
def test_viewer_show(order):
class TestViewer(ImageShow.Viewer):
@@ -41,7 +42,8 @@ def show_image(self, image, **options):
@pytest.mark.skipif(
- not on_ci() or is_win32(), reason="Only run on CIs; hangs on Windows CIs",
+ not on_ci() or is_win32(),
+ reason="Only run on CIs; hangs on Windows CIs",
)
def test_show():
for mode in ("1", "I;16", "LA", "RGB", "RGBA"):
diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py
index 707284d7b4f..1697a8d4946 100644
--- a/Tests/test_tiff_ifdrational.py
+++ b/Tests/test_tiff_ifdrational.py
@@ -29,6 +29,12 @@ def test_sanity():
_test_equal(1, 2, IFDRational(1, 2))
+def test_ranges():
+ for num in range(1, 10):
+ for denom in range(1, 10):
+ assert IFDRational(num, denom) == IFDRational(num, denom)
+
+
def test_nonetype():
# Fails if the _delegate function doesn't return a valid function
diff --git a/docs/conf.py b/docs/conf.py
index 78841cc15a4..a022e61cb07 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -77,7 +77,7 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
-exclude_patterns = ["_build"]
+exclude_patterns = ["_build", "releasenotes/template.rst"]
# The reST default role (used for this markup: `text`) to use for all
# documents.
diff --git a/docs/deprecations.rst b/docs/deprecations.rst
index 10ccec632eb..e3ad2a9e334 100644
--- a/docs/deprecations.rst
+++ b/docs/deprecations.rst
@@ -18,16 +18,16 @@ Image.show command parameter
.. deprecated:: 7.2.0
The ``command`` parameter was deprecated and will be removed in a future release.
-Use a subclass of ``ImageShow.Viewer`` instead.
+Use a subclass of :py:class:`.ImageShow.Viewer` instead.
Image._showxv
~~~~~~~~~~~~~
.. deprecated:: 7.2.0
-``Image._showxv`` has been deprecated. Use :py:meth:`~PIL.Image.Image.show`
-instead. If custom behaviour is required, use :py:meth:`~PIL.ImageShow.register` to add
-a custom :py:class:`~PIL.ImageShow.Viewer` class.
+``Image._showxv`` has been deprecated. Use :py:meth:`.Image.Image.show`
+instead. If custom behaviour is required, use :py:func:`.ImageShow.register` to add
+a custom :py:class:`.ImageShow.Viewer` class.
ImageFile.raise_ioerror
~~~~~~~~~~~~~~~~~~~~~~~
@@ -61,7 +61,7 @@ im.offset
.. deprecated:: 1.1.2
.. versionremoved:: 8.0.0
-``im.offset()`` has been removed, call ``ImageChops.offset()`` instead.
+``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead.
It was documented as deprecated in PIL 1.1.2,
raised a ``DeprecationWarning`` since 1.1.5,
@@ -88,20 +88,21 @@ ImageCms.CmsProfile attributes
.. deprecated:: 3.2.0
.. versionremoved:: 8.0.0
-Some attributes in ``ImageCms.CmsProfile`` have been removed. From 6.0.0, they issued a
-``DeprecationWarning``:
+Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed. From 6.0.0,
+they issued a ``DeprecationWarning``:
+
+======================== ===================================================
-======================== ===============================
Removed Use instead
-======================== ===============================
-``color_space`` Padded ``xcolor_space``
-``pcs`` Padded ``connection_space``
-``product_copyright`` Unicode ``copyright``
-``product_desc`` Unicode ``profile_description``
-``product_description`` Unicode ``profile_description``
-``product_manufacturer`` Unicode ``manufacturer``
-``product_model`` Unicode ``model``
-======================== ===============================
+======================== ===================================================
+``color_space`` Padded :py:attr:`~.CmsProfile.xcolor_space`
+``pcs`` Padded :py:attr:`~.CmsProfile.connection_space`
+``product_copyright`` Unicode :py:attr:`~.CmsProfile.copyright`
+``product_desc`` Unicode :py:attr:`~.CmsProfile.profile_description`
+``product_description`` Unicode :py:attr:`~.CmsProfile.profile_description`
+``product_manufacturer`` Unicode :py:attr:`~.CmsProfile.manufacturer`
+``product_model`` Unicode :py:attr:`~.CmsProfile.model`
+======================== ===================================================
Python 2.7
~~~~~~~~~~
diff --git a/docs/installation.rst b/docs/installation.rst
index 706cfb1d707..4c41cc9ee6f 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -412,12 +412,12 @@ These platforms are built and tested for every change.
| Windows Server 2016 | 3.8 |x86 |
| +--------------------------+-----------------------+
| | 3.6 |x86-64 |
-| +--------------------------+-----------------------+
-| | 3.7/MinGW |x86 |
+----------------------------------+--------------------------+-----------------------+
| Windows Server 2019 | 3.6, 3.7, 3.8 |x86, x86-64 |
| +--------------------------+-----------------------+
| | PyPy3 |x86 |
+| +--------------------------+-----------------------+
+| | 3.8/MinGW |x86, x86-64 |
+----------------------------------+--------------------------+-----------------------+
diff --git a/docs/releasenotes/8.0.0.rst b/docs/releasenotes/8.0.0.rst
new file mode 100644
index 00000000000..bbb954e25b0
--- /dev/null
+++ b/docs/releasenotes/8.0.0.rst
@@ -0,0 +1,81 @@
+8.0.0
+-----
+
+Backwards Incompatible Changes
+==============================
+
+Python 3.5
+^^^^^^^^^^
+
+Pillow has dropped support for Python 3.5, which reached end-of-life on 2020-09-13.
+
+im.offset
+^^^^^^^^^
+
+``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead.
+
+Image.fromstring, im.fromstring and im.tostring
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* ``Image.fromstring()`` has been removed, call :py:func:`.Image.frombytes()` instead.
+* ``im.fromstring()`` has been removed, call :py:meth:`~PIL.Image.Image.frombytes()` instead.
+* ``im.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead.
+
+ImageCms.CmsProfile attributes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed:
+
+======================== ===================================================
+Removed Use instead
+======================== ===================================================
+``color_space`` Padded :py:attr:`~.CmsProfile.xcolor_space`
+``pcs`` Padded :py:attr:`~.CmsProfile.connection_space`
+``product_copyright`` Unicode :py:attr:`~.CmsProfile.copyright`
+``product_desc`` Unicode :py:attr:`~.CmsProfile.profile_description`
+``product_description`` Unicode :py:attr:`~.CmsProfile.profile_description`
+``product_manufacturer`` Unicode :py:attr:`~.CmsProfile.manufacturer`
+``product_model`` Unicode :py:attr:`~.CmsProfile.model`
+======================== ===================================================
+
+API Changes
+===========
+
+Add MIME type to PsdImagePlugin
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+"image/vnd.adobe.photoshop" is now registered as the
+:py:class:`.PsdImagePlugin.PsdImageFile` MIME type.
+
+API Additions
+=============
+
+ImageOps.autocontrast: add mask parameter
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+:py:func:`.ImageOps.autocontrast` can now take a ``mask`` parameter:
+
+* Histogram used in contrast operation is computed using pixels within the mask.
+ If no mask is given the entire image is used for histogram computation.
+
+ImageOps.autocontrast cutoffs
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Previously, the ``cutoff`` parameter of :py:func:`.ImageOps.autocontrast` could only
+be a single number, used as the percent to cut off from the histogram on the low and
+high ends.
+
+Now, it can also be a tuple ``(low, high)``.
+
+Security
+========
+
+TODO
+
+Other Changes
+=============
+
+TODO
+^^^^
+
+TODO
diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst
index 2d7747c3e9b..ba81fbaf814 100644
--- a/docs/releasenotes/index.rst
+++ b/docs/releasenotes/index.rst
@@ -13,6 +13,7 @@ expected to be backported to earlier versions.
.. toctree::
:maxdepth: 2
+ 8.0.0
7.2.0
7.1.2
7.1.1
diff --git a/docs/releasenotes/template.rst b/docs/releasenotes/template.rst
new file mode 100644
index 00000000000..bf381114efa
--- /dev/null
+++ b/docs/releasenotes/template.rst
@@ -0,0 +1,45 @@
+x.y.z
+-----
+
+Backwards Incompatible Changes
+==============================
+
+TODO
+^^^^
+
+Deprecations
+============
+
+TODO
+^^^^
+
+TODO
+
+API Changes
+===========
+
+TODO
+^^^^
+
+TODO
+
+API Additions
+=============
+
+TODO
+^^^^
+
+TODO
+
+Security
+========
+
+TODO
+
+Other Changes
+=============
+
+TODO
+^^^^
+
+TODO
diff --git a/setup.cfg b/setup.cfg
index b91a522f59a..129adeee7a6 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[flake8]
-extend-ignore = E203, W503
+extend-ignore = E203
max-line-length = 88
[isort]
diff --git a/setup.py b/setup.py
index 550dc6fc5c1..a6a39e57b13 100755
--- a/setup.py
+++ b/setup.py
@@ -243,11 +243,6 @@ def _cmd_exists(cmd):
)
-def _read(file):
- with open(file, "rb") as fp:
- return fp.read()
-
-
def _pkg_config(name):
try:
command = os.environ.get("PKG_CONFIG", "pkg-config")
@@ -479,6 +474,9 @@ def build_extensions(self):
# add Homebrew's include and lib directories
_add_directory(library_dirs, os.path.join(prefix, "lib"))
_add_directory(include_dirs, os.path.join(prefix, "include"))
+ _add_directory(
+ include_dirs, os.path.join(prefix, "opt", "zlib", "include")
+ )
ft_prefix = os.path.join(prefix, "opt", "freetype")
if ft_prefix and os.path.isdir(ft_prefix):
@@ -858,12 +856,17 @@ def debug_build():
Extension("PIL._imagingmath", ["src/_imagingmath.c"]),
Extension("PIL._imagingmorph", ["src/_imagingmorph.c"]),
]
+
+with open("README.md") as f:
+ long_description = f.read()
+
try:
setup(
name=NAME,
version=PILLOW_VERSION,
description="Python Imaging Library (Fork)",
- long_description=_read("README.rst").decode("utf-8"),
+ long_description=long_description,
+ long_description_content_type="text/markdown",
license="HPND",
author="Alex Clark (PIL Fork Author)",
author_email="aclark@python-pillow.org",
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index aef101b2f1f..30ee8606a1f 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -594,7 +594,8 @@ def close(self):
try:
if hasattr(self, "_close__fp"):
self._close__fp()
- self.fp.close()
+ if self.fp:
+ self.fp.close()
self.fp = None
except Exception as msg:
logger.debug("Error closing: %s", msg)
@@ -664,7 +665,7 @@ def __repr__(self):
)
def _repr_png_(self):
- """ iPython display hook support
+ """iPython display hook support
:returns: png version of the image as bytes
"""
@@ -1180,7 +1181,7 @@ def filter(self, filter):
available filters, see the :py:mod:`~PIL.ImageFilter` module.
:param filter: Filter kernel.
- :returns: An :py:class:`~PIL.Image.Image` object. """
+ :returns: An :py:class:`~PIL.Image.Image` object."""
from . import ImageFilter
@@ -1505,7 +1506,7 @@ def paste(self, im, box=None, mask=None):
self.im.paste(im, box)
def alpha_composite(self, im, dest=(0, 0), source=(0, 0)):
- """ 'In-place' analog of Image.alpha_composite. Composites an image
+ """'In-place' analog of Image.alpha_composite. Composites an image
onto this image.
:param im: image to composite over this one
diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py
index 357256b7251..3856cb84342 100644
--- a/src/PIL/ImageCms.py
+++ b/src/PIL/ImageCms.py
@@ -277,8 +277,8 @@ def get_display_profile(handle=None):
class PyCMSError(Exception):
- """ (pyCMS) Exception class.
- This is used for all errors in the pyCMS API. """
+ """(pyCMS) Exception class.
+ This is used for all errors in the pyCMS API."""
pass
diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py
index 6b666a4e2ea..49633ef5104 100644
--- a/src/PIL/ImageFile.py
+++ b/src/PIL/ImageFile.py
@@ -509,7 +509,7 @@ def _save(im, fp, tile, bufsize=0):
try:
fh = fp.fileno()
fp.flush()
- except (AttributeError, io.UnsupportedOperation) as e:
+ except (AttributeError, io.UnsupportedOperation) as exc:
# compress to Python file-compatible object
for e, b, o, a in tile:
e = Image._getencoder(im.mode, e, a, im.encoderconfig)
@@ -526,7 +526,7 @@ def _save(im, fp, tile, bufsize=0):
if s:
break
if s < 0:
- raise OSError(f"encoder error {s} when writing image file") from e
+ raise OSError(f"encoder error {s} when writing image file") from exc
e.cleanup()
else:
# slight speedup: compress to real file object
diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py
index d1ec09eace4..b76dfa01f4d 100644
--- a/src/PIL/ImageMorph.py
+++ b/src/PIL/ImageMorph.py
@@ -28,36 +28,36 @@
class LutBuilder:
"""A class for building a MorphLut from a descriptive language
- The input patterns is a list of a strings sequences like these::
+ The input patterns is a list of a strings sequences like these::
- 4:(...
- .1.
- 111)->1
+ 4:(...
+ .1.
+ 111)->1
- (whitespaces including linebreaks are ignored). The option 4
- describes a series of symmetry operations (in this case a
- 4-rotation), the pattern is described by:
+ (whitespaces including linebreaks are ignored). The option 4
+ describes a series of symmetry operations (in this case a
+ 4-rotation), the pattern is described by:
- - . or X - Ignore
- - 1 - Pixel is on
- - 0 - Pixel is off
+ - . or X - Ignore
+ - 1 - Pixel is on
+ - 0 - Pixel is off
- The result of the operation is described after "->" string.
+ The result of the operation is described after "->" string.
- The default is to return the current pixel value, which is
- returned if no other match is found.
+ The default is to return the current pixel value, which is
+ returned if no other match is found.
- Operations:
+ Operations:
- - 4 - 4 way rotation
- - N - Negate
- - 1 - Dummy op for no other operation (an op must always be given)
- - M - Mirroring
+ - 4 - 4 way rotation
+ - N - Negate
+ - 1 - Dummy op for no other operation (an op must always be given)
+ - M - Mirroring
- Example::
+ Example::
- lb = LutBuilder(patterns = ["4:(... .1. 111)->1"])
- lut = lb.build_lut()
+ lb = LutBuilder(patterns = ["4:(... .1. 111)->1"])
+ lut = lb.build_lut()
"""
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
index 3b1b737bd78..14602a5c8cc 100644
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -61,10 +61,10 @@ def _lut(image, lut):
# actions
-def autocontrast(image, cutoff=0, ignore=None):
+def autocontrast(image, cutoff=0, ignore=None, mask=None):
"""
Maximize (normalize) image contrast. This function calculates a
- histogram of the input image, removes ``cutoff`` percent of the
+ histogram of the input image (or mask region), removes ``cutoff`` percent of the
lightest and darkest pixels from the histogram, and remaps the image
so that the darkest pixel becomes black (0), and the lightest
becomes white (255).
@@ -74,9 +74,12 @@ def autocontrast(image, cutoff=0, ignore=None):
high ends. Either a tuple of (low, high), or a single
number for both.
:param ignore: The background pixel value (use None for no background).
+ :param mask: Histogram used in contrast operation is computed using pixels
+ within the mask. If no mask is given the entire image is used
+ for histogram computation.
:return: An image.
"""
- histogram = image.histogram()
+ histogram = image.histogram(mask)
lut = []
for layer in range(0, len(histogram), 256):
h = histogram[layer : layer + 256]
diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py
index 9583f704a4d..36c8fb8494d 100644
--- a/src/PIL/PdfImagePlugin.py
+++ b/src/PIL/PdfImagePlugin.py
@@ -121,6 +121,7 @@ def _save(im, fp, filename, save_all=False):
bits = 8
params = None
+ decode = None
if im.mode == "1":
filter = "ASCIIHexDecode"
@@ -150,6 +151,7 @@ def _save(im, fp, filename, save_all=False):
filter = "DCTDecode"
colorspace = PdfParser.PdfName("DeviceCMYK")
procset = "ImageC" # color images
+ decode = [1, 0, 1, 0, 1, 0, 1, 0]
else:
raise ValueError(f"cannot save mode {im.mode}")
@@ -189,6 +191,7 @@ def _save(im, fp, filename, save_all=False):
Height=height, # * 72.0 / resolution,
Filter=PdfParser.PdfName(filter),
BitsPerComponent=bits,
+ Decode=decode,
DecodeParams=params,
ColorSpace=colorspace,
)
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
index d3d053eb2b8..bcc6b1c988b 100644
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -1108,7 +1108,10 @@ def _write_multiple_frames(im, fp, chunk, rawmode):
# animation control
chunk(
- fp, b"acTL", o32(len(im_frames)), o32(loop), # 0: num_frames # 4: num_plays
+ fp,
+ b"acTL",
+ o32(len(im_frames)), # 0: num_frames
+ o32(loop), # 4: num_plays
)
# default image IDAT (if it exists)
@@ -1153,7 +1156,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode):
else:
fdat_chunks = _fdat(fp, chunk, seq_num)
ImageFile._save(
- im_frame, fdat_chunks, [("zip", (0, 0) + im_frame.size, 0, rawmode)],
+ im_frame,
+ fdat_chunks,
+ [("zip", (0, 0) + im_frame.size, 0, rawmode)],
)
seq_num = fdat_chunks.seq_num
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index f3dba74843a..6e1c45636f7 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -286,7 +286,7 @@ def _limit_signed_rational(val, max_val, min_val):
class IFDRational(Rational):
- """ Implements a rational class where 0/0 is a legal value to match
+ """Implements a rational class where 0/0 is a legal value to match
the in the wild use of exif rationals.
e.g., DigitalZoomRatio - 0.00/0.00 indicates that no digital zoom was used
@@ -353,6 +353,8 @@ def __hash__(self):
return self._val.__hash__()
def __eq__(self, other):
+ if isinstance(other, IFDRational):
+ other = other._val
return self._val == other
def _delegate(op):
@@ -903,7 +905,7 @@ def __init__(self, *args, **kwargs):
@classmethod
def from_v2(cls, original):
- """ Returns an
+ """Returns an
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
instance with the same data as is contained in the original
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
@@ -920,7 +922,7 @@ def from_v2(cls, original):
return ifd
def to_v2(self):
- """ Returns an
+ """Returns an
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
instance with the same data as is contained in the original
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
@@ -1090,8 +1092,8 @@ def load_end(self):
self._close_exclusive_fp_after_loading = True
def _load_libtiff(self):
- """ Overload method triggered when we detect a compressed tiff
- Calls out to libtiff """
+ """Overload method triggered when we detect a compressed tiff
+ Calls out to libtiff"""
Image.Image.load(self)
diff --git a/src/decode.c b/src/decode.c
index 25ce7316bc3..f60fb490e26 100644
--- a/src/decode.c
+++ b/src/decode.c
@@ -807,7 +807,7 @@ PyImaging_XbmDecoderNew(PyObject* self, PyObject* args)
#ifdef HAVE_LIBZ
-#include "Zip.h"
+#include "ZipCodecs.h"
PyObject*
PyImaging_ZipDecoderNew(PyObject* self, PyObject* args)
diff --git a/src/encode.c b/src/encode.c
index d64f47d2b13..62a6ec38706 100644
--- a/src/encode.c
+++ b/src/encode.c
@@ -578,7 +578,7 @@ PyImaging_XbmEncoderNew(PyObject* self, PyObject* args)
#ifdef HAVE_LIBZ
-#include "Zip.h"
+#include "ZipCodecs.h"
PyObject*
PyImaging_ZipEncoderNew(PyObject* self, PyObject* args)
diff --git a/src/libImaging/Zip.h b/src/libImaging/ZipCodecs.h
similarity index 100%
rename from src/libImaging/Zip.h
rename to src/libImaging/ZipCodecs.h
diff --git a/src/libImaging/ZipDecode.c b/src/libImaging/ZipDecode.c
index b0f8ad3261e..a09ee82f7cc 100644
--- a/src/libImaging/ZipDecode.c
+++ b/src/libImaging/ZipDecode.c
@@ -20,7 +20,7 @@
#ifdef HAVE_LIBZ
-#include "Zip.h"
+#include "ZipCodecs.h"
static const int OFFSET[] = { 7, 3, 3, 1, 1, 0, 0 };
static const int STARTING_COL[] = { 0, 4, 0, 2, 0, 1, 0 };
diff --git a/src/libImaging/ZipEncode.c b/src/libImaging/ZipEncode.c
index 84ccb14ea54..4e862af57a7 100644
--- a/src/libImaging/ZipEncode.c
+++ b/src/libImaging/ZipEncode.c
@@ -19,7 +19,7 @@
#ifdef HAVE_LIBZ
-#include "Zip.h"
+#include "ZipCodecs.h"
int
ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes)
diff --git a/winbuild/build.rst b/winbuild/build.rst
index aaed2c43fa8..ba568a0303d 100644
--- a/winbuild/build.rst
+++ b/winbuild/build.rst
@@ -81,6 +81,9 @@ Pillow for the selected version of Python.
``winbuild\build\build_pillow.cmd bdist_wheel`` will build wheels
instead of installing Pillow.
+You can also use ``winbuild\build\build_pillow.cmd --inplace develop`` to build
+and install Pillow in develop mode (instead of ``pip install --editable``).
+
Testing Pillow
--------------
diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py
index e6db4b2e457..e05ac1a2970 100644
--- a/winbuild/build_prepare.py
+++ b/winbuild/build_prepare.py
@@ -251,9 +251,9 @@ def cmd_msbuild(
"libs": [r"*.lib"],
},
"harfbuzz": {
- "url": "https://github.com/harfbuzz/harfbuzz/archive/2.7.1.zip",
- "filename": "harfbuzz-2.7.1.zip",
- "dir": "harfbuzz-2.7.1",
+ "url": "https://github.com/harfbuzz/harfbuzz/archive/2.7.2.zip",
+ "filename": "harfbuzz-2.7.2.zip",
+ "dir": "harfbuzz-2.7.2",
"build": [
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
cmd_nmake(target="clean"),