diff --git a/.ci/install.sh b/.ci/install.sh index 36bce295c..2143c0613 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -30,7 +30,10 @@ pip install -U pytest-cov pip install pyroma pip install test-image-results pip install numpy -if [ "$TRAVIS_PYTHON_VERSION" == "3.9-dev" ]; then pip install setuptools==47.3.1 ; fi + +# TODO Remove when 3.9-dev includes setuptools 49.3.2+: +if [ "$GHA_PYTHON_VERSION" == "3.9-dev" ]; then pip install -U "setuptools>=49.3.2" ; fi + if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then # arm64, ppc64le, s390x CPUs: # "ERROR: Could not find a version that satisfies the requirement pyqt5" diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index 76a3ef2b7..a0418c3ba 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -15,5 +15,8 @@ pip install test-image-results echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg pip install numpy +# TODO Remove when 3.9-dev includes setuptools 49.3.2+: +if [ "$GHA_PYTHON_VERSION" == "3.9-dev" ]; then pip install -U "setuptools>=49.3.2" ; fi + # extra test images pushd depends && ./install_extra_test_images.sh && popd diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 9b04b2bd7..a90a0a954 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -63,7 +63,12 @@ jobs: - name: pip install wheel pytest pytest-cov run: python -m pip install wheel pytest pytest-cov - - name: Prepare dependencies + # TODO Remove when 3.9-dev includes setuptools 49.3.2+: + - name: Upgrade setuptools + if: "contains(matrix.python-version, '3.9-dev')" + run: python -m pip install -U "setuptools>=49.3.2" + + - name: Install dependencies run: | 7z x winbuild\depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\" Write-Host "::add-path::$env:RUNNER_WORKSPACE\nasm-2.14.02" @@ -72,41 +77,71 @@ jobs: Write-Host "::add-path::C:\Program Files (x86)\gs\gs9.50\bin" xcopy /s winbuild\depends\test_images\* Tests\images\ + shell: pwsh - & python.exe winbuild\build_prepare.py -v --python=$env:pythonLocation + - name: Cache build + id: build-cache + uses: actions/cache@v2 + with: + path: winbuild\build + key: + ${{ hashFiles('winbuild\build_prepare.py') }}-${{ hashFiles('.github\workflows\test-windows.yml') }}-${{ env.pythonLocation }} + + - name: Prepare build + if: steps.build-cache.outputs.cache-hit != 'true' + run: | + & python.exe winbuild\build_prepare.py -v --python=$env:pythonLocation --srcdir shell: pwsh - name: Build dependencies / libjpeg-turbo + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libjpeg.cmd" - name: Build dependencies / zlib + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_zlib.cmd" - name: Build dependencies / LibTiff + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libtiff.cmd" - name: Build dependencies / WebP + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libwebp.cmd" - name: Build dependencies / FreeType + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_freetype.cmd" - name: Build dependencies / LCMS2 + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_lcms2.cmd" - name: Build dependencies / OpenJPEG + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_openjpeg.cmd" - # GPL licensed; skip if building wheels + # GPL licensed - name: Build dependencies / libimagequant - if: "github.event_name != 'push'" + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libimagequant.cmd" # Raqm dependencies - name: Build dependencies / HarfBuzz + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_harfbuzz.cmd" - name: Build dependencies / FriBidi + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_fribidi.cmd" - name: Build dependencies / Raqm + if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libraqm.cmd" + # trim ~150MB x 9 + - name: Optimize build cache + if: steps.build-cache.outputs.cache-hit != 'true' + run: rmdir /S /Q winbuild\build\src + shell: cmd + - name: Build Pillow run: | - & winbuild\build\build_pillow.cmd install + $FLAGS="" + if ('${{ github.event_name }}' -eq 'push') { $FLAGS="--disable-imagequant" } + & winbuild\build\build_pillow.cmd $FLAGS install & $env:pythonLocation\python.exe selftest.py --installed shell: pwsh @@ -151,7 +186,7 @@ jobs: if: "github.event_name == 'push'" run: | for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ::set-output name=dist::dist-%%a - winbuild\\build\\build_pillow.cmd bdist_wheel" + winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel shell: cmd - uses: actions/upload-artifact@v2 @@ -169,8 +204,10 @@ jobs: 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: @@ -181,7 +218,7 @@ jobs: CHERE_INVOKING: 1 timeout-minutes: 30 - name: MSYS2 ${{ matrix.mingw }} + name: ${{ matrix.name }} steps: - uses: actions/checkout@v2 @@ -193,23 +230,22 @@ jobs: - name: Install Dependencies run: | pacman -S --noconfirm \ + ${{ matrix.package }}-python3-cffi \ + ${{ matrix.package }}-python3-numpy \ + ${{ matrix.package }}-python3-olefile \ ${{ matrix.package }}-python3-pip \ - ${{ matrix.package }}-python3-setuptools \ + ${{ matrix.package }}-python3-pyqt5 \ ${{ matrix.package }}-python3-pytest \ ${{ matrix.package }}-python3-pytest-cov \ - ${{ matrix.package }}-python3-cffi \ - ${{ matrix.package }}-python3-olefile \ - ${{ matrix.package }}-python3-numpy \ - ${{ matrix.package }}-python3-pyqt5 \ - ${{ matrix.package }}-python3-numpy \ + ${{ matrix.package }}-python3-setuptools \ ${{ matrix.package }}-freetype \ - ${{ matrix.package }}-lcms2 \ - ${{ matrix.package }}-libwebp \ - ${{ matrix.package }}-libjpeg-turbo \ - ${{ matrix.package }}-openjpeg2 \ - ${{ matrix.package }}-libimagequant \ - ${{ matrix.package }}-libraqm \ ${{ 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 @@ -231,4 +267,4 @@ jobs: python3 -m pip install codecov bash <(curl -s https://codecov.io/bash) -F GHA_Windows env: - CODECOV_NAME: MSYS2 ${{ matrix.mingw }} + CODECOV_NAME: ${{ matrix.name }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 65673b0a2..dbe0eb822 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -62,11 +62,15 @@ jobs: if: startsWith(matrix.os, 'ubuntu') run: | .ci/install.sh + env: + GHA_PYTHON_VERSION: ${{ matrix.python-version }} - name: Install macOS dependencies if: startsWith(matrix.os, 'macOS') run: | .github/workflows/macos-install.sh + env: + GHA_PYTHON_VERSION: ${{ matrix.python-version }} - name: Build run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 80a068df5..459251d77 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,35 +9,35 @@ repos: types: [] - repo: https://github.com/timothycrosley/isort - rev: 7c29dd9d55161704cfc45998c6f5c2c43d39264b # frozen: 4.3.21 + rev: 377d260ffa6f746693f97b46d95025afc4bd8275 # frozen: 5.4.2 hooks: - id: isort - repo: https://github.com/asottile/yesqa - rev: b13a51aa54142c59219c764e9f9362c049b439ed # frozen: v1.2.0 + rev: 7a009f3ee493c796827ee334f9058b110a0e0db8 # frozen: v1.2.1 hooks: - id: yesqa - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: ffbd448645bad2e7ca13f96fca5830058d27ccd5 # frozen: v1.1.7 + rev: f30f4974a08a6b2f6a1eeaf30a4d501cf909163a # frozen: v1.1.9 hooks: - id: remove-tabs exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$) - repo: https://gitlab.com/pycqa/flake8 - rev: 735cfe7e1c57a8e05f660ba75de72313005af54a # frozen: 3.8.2 + rev: 05f6544aef321e2fee03a1277ce2eef8880fb927 # frozen: 3.8.3 hooks: - id: flake8 additional_dependencies: [flake8-2020, flake8-implicit-str-concat] - repo: https://github.com/pre-commit/pygrep-hooks - rev: 0d7d077d6ed5624854f93ac601739c1804ebeb98 # frozen: v1.5.1 + rev: eae6397e4c259ed3d057511f6dd5330b92867e62 # frozen: v1.6.0 hooks: - id: python-check-blanket-noqa - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: ebc15addedad713c86ef18ae9632c88e187dd0af # frozen: v3.1.0 + rev: e1668fe86af3810fbca72b8653fe478e66a0afdc # frozen: v3.2.0 hooks: - id: check-merge-conflict - id: check-yaml diff --git a/CHANGES.rst b/CHANGES.rst index 6083cc7f3..263c2a823 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,9 +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/Makefile b/Makefile index 06208ad98..cbee5923b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ -# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: clean coverage doc docserve help inplace install install-req release-test sdist test upload upload-test .DEFAULT_GOAL := release-test +.PHONY: clean clean: python3 setup.py clean rm src/PIL/*.so || true @@ -9,28 +8,34 @@ clean: find . -name __pycache__ | xargs rm -r || true BRANCHES=`git branch -a | grep -v HEAD | grep -v master | grep remote` +.PHONY: co co: -for i in $(BRANCHES) ; do \ git checkout -t $$i ; \ done +.PHONY: coverage coverage: pytest -qq rm -r htmlcov || true coverage report +.PHONY: doc doc: $(MAKE) -C docs html +.PHONY: doccheck doccheck: $(MAKE) -C docs html # Don't make our tests rely on the links in the docs being up every single build. # We don't control them. But do check, and update them to the target of their redirects. $(MAKE) -C docs linkcheck || true +.PHONY: docserve docserve: cd docs/_build/html && python3 -mSimpleHTTPServer 2> /dev/null& +.PHONY: help help: @echo "Welcome to Pillow development. Please use \`make \` where is one of" @echo " clean remove build products" @@ -48,17 +53,21 @@ help: @echo " upload build and upload sdists to PyPI" @echo " upload-test build and upload sdists to test.pythonpackages.com" +.PHONY: inplace inplace: clean python3 setup.py develop build_ext --inplace +.PHONY: install install: python3 setup.py install python3 selftest.py +.PHONY: install-coverage install-coverage: CFLAGS="-coverage" python3 setup.py build_ext install python3 selftest.py +.PHONY: debug debug: # make a debug version if we don't have a -dbg python. Leaves in symbols # for our stuff, kills optimization, and redirects to dev null so we @@ -66,13 +75,16 @@ debug: make clean > /dev/null CFLAGS='-g -O0' python3 setup.py build_ext install > /dev/null +.PHONY: install-req install-req: python3 -m pip install -r requirements.txt +.PHONY: install-venv install-venv: virtualenv . bin/pip install -r requirements.txt +.PHONY: release-test release-test: $(MAKE) install-req python3 setup.py develop @@ -84,22 +96,14 @@ release-test: pyroma . viewdoc +.PHONY: sdist sdist: python3 setup.py sdist --format=gztar +.PHONY: test test: pytest -qq -# https://docs.python.org/3/distutils/packageindex.html#the-pypirc-file -upload-test: -# [test] -# username: -# password: -# repository = http://test.pythonpackages.com - python3 setup.py sdist --format=gztar upload -r test - -upload: - python3 setup.py sdist --format=gztar upload - +.PHONY: readme readme: viewdoc diff --git a/README.md b/README.md new file mode 100644 index 000000000..6ca6cbf83 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +

+ Pillow logo +

+ +# 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 + Documentation Status +
tests + Travis CI build status (Linux) + Travis CI build status (macOS) + AppVeyor CI build status (Windows) + GitHub Actions build status (Lint) + GitHub Actions build status (Test Linux and macOS) + GitHub Actions build status (Test Windows) + GitHub Actions build status (Test Docker) + Code coverage +
package + Zenodo + Tidelift + Newest PyPI version + Number of PyPI downloads +
social + Join the chat at https://gitter.im/python-pillow/Pillow + Follow on https://twitter.com/PythonPillow +
+ +## 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 c1d5be579..000000000 --- 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 3f62a70c4..c9a0439d8 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_imaging_leaks.py b/Tests/check_imaging_leaks.py index ae05ebade..407f3ea80 100755 --- a/Tests/check_imaging_leaks.py +++ b/Tests/check_imaging_leaks.py @@ -1,5 +1,6 @@ #!/usr/bin/env python import pytest + from PIL import Image from .helper import is_win32 @@ -11,7 +12,7 @@ pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS") def _get_mem_usage(): - from resource import getpagesize, getrusage, RUSAGE_SELF + from resource import RUSAGE_SELF, getpagesize, getrusage mem = getrusage(RUSAGE_SELF).ru_maxrss return mem * getpagesize() / 1024 / 1024 diff --git a/Tests/check_j2k_leaks.py b/Tests/check_j2k_leaks.py index 5cef4b544..afe5836f3 100755 --- a/Tests/check_j2k_leaks.py +++ b/Tests/check_j2k_leaks.py @@ -1,6 +1,7 @@ from io import BytesIO import pytest + from PIL import Image from .helper import is_win32, skip_unless_feature @@ -18,7 +19,7 @@ pytestmark = [ def test_leak_load(): - from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK + from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit setrlimit(RLIMIT_STACK, (stack_size, stack_size)) setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) @@ -28,7 +29,7 @@ def test_leak_load(): def test_leak_save(): - from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK + from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit setrlimit(RLIMIT_STACK, (stack_size, stack_size)) setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) diff --git a/Tests/check_j2k_overflow.py b/Tests/check_j2k_overflow.py index 7a0a5f948..b16412898 100644 --- a/Tests/check_j2k_overflow.py +++ b/Tests/check_j2k_overflow.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image diff --git a/Tests/check_jpeg_leaks.py b/Tests/check_jpeg_leaks.py index b63fa2a1e..ab8d77719 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: + pre patch: - MB -177.1^ # - | @@@# - | :@@@@@@# - | ::::@@@@@@# - | ::::::::@@@@@@# - | @@::::: ::::@@@@@@# - | @@@@ ::::: ::::@@@@@@# - | @@@@@@@ ::::: ::::@@@@@@# - | @@::@@@@@@@ ::::: ::::@@@@@@# - | @@@@ : @@@@@@@ ::::: ::::@@@@@@# - | @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - | @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# - 0 +----------------------------------------------------------------------->Gi - 0 11.37 + MB + 177.1^ # + | @@@# + | :@@@@@@# + | ::::@@@@@@# + | ::::::::@@@@@@# + | @@::::: ::::@@@@@@# + | @@@@ ::::: ::::@@@@@@# + | @@@@@@@ ::::: ::::@@@@@@# + | @@::@@@@@@@ ::::: ::::@@@@@@# + | @@@@ : @@@@@@@ ::::: ::::@@@@@@# + | @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + | @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# + 0 +----------------------------------------------------------------------->Gi + 0 11.37 -post patch: + post patch: - MB -21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: - 0 +----------------------------------------------------------------------->Gi - 0 11.33 - -""" + MB + 21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: + 0 +----------------------------------------------------------------------->Gi + 0 11.33 + """ im = hopper("RGB") exif = b"12345678" * 4096 @@ -183,31 +182,30 @@ post patch: 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_large_memory.py b/Tests/check_large_memory.py index f44a5a5bb..723a1a21e 100644 --- a/Tests/check_large_memory.py +++ b/Tests/check_large_memory.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image # This test is not run automatically. diff --git a/Tests/check_large_memory_numpy.py b/Tests/check_large_memory_numpy.py index de6f4571c..79d1cfd5b 100644 --- a/Tests/check_large_memory_numpy.py +++ b/Tests/check_large_memory_numpy.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image # This test is not run automatically. diff --git a/Tests/check_libtiff_segfault.py b/Tests/check_libtiff_segfault.py index 6663ac097..bd7f407e4 100644 --- a/Tests/check_libtiff_segfault.py +++ b/Tests/check_libtiff_segfault.py @@ -1,13 +1,14 @@ import pytest + from PIL import Image TEST_FILE = "Tests/images/libtiff_segfault.tif" 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/helper.py b/Tests/helper.py index 4c5f2198e..c8cbb80e1 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -11,6 +11,7 @@ import tempfile from io import BytesIO import pytest + from PIL import Image, ImageMath, features logger = logging.getLogger(__name__) @@ -175,7 +176,7 @@ class PillowLeakTestCase: :returns: memory usage in kilobytes """ - from resource import getrusage, RUSAGE_SELF + from resource import RUSAGE_SELF, getrusage mem = getrusage(RUSAGE_SELF).ru_maxrss if sys.platform == "darwin": diff --git a/Tests/images/exif_text.png b/Tests/images/exif_text.png new file mode 100644 index 000000000..e2d8dc0ff Binary files /dev/null and b/Tests/images/exif_text.png differ diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 64156a4ec..19602c1e7 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -1,6 +1,7 @@ import os import pytest + from PIL import Image from .helper import assert_image_similar @@ -15,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): @@ -31,8 +32,8 @@ def test_bad(): 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", @@ -56,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_box_blur.py b/Tests/test_box_blur.py index 44910b9ed..94f504e0b 100644 --- a/Tests/test_box_blur.py +++ b/Tests/test_box_blur.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageFilter sample = Image.new("L", (7, 5)) diff --git a/Tests/test_color_lut.py b/Tests/test_color_lut.py index b34dbadb6..99776ce58 100644 --- a/Tests/test_color_lut.py +++ b/Tests/test_color_lut.py @@ -1,6 +1,7 @@ from array import array import pytest + from PIL import Image, ImageFilter from .helper import assert_image_equal diff --git a/Tests/test_core_resources.py b/Tests/test_core_resources.py index a8fe8bfeb..6c52d25a4 100644 --- a/Tests/test_core_resources.py +++ b/Tests/test_core_resources.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image from .helper import is_pypy diff --git a/Tests/test_decompression_bomb.py b/Tests/test_decompression_bomb.py index 1704400b4..5799fc0ed 100644 --- a/Tests/test_decompression_bomb.py +++ b/Tests/test_decompression_bomb.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import hopper diff --git a/Tests/test_features.py b/Tests/test_features.py index 1e7692204..284f72205 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -2,6 +2,7 @@ import io import re import pytest + from PIL import features from .helper import skip_unless_feature diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index 7a46f4019..2d50748bd 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageSequence, PngImagePlugin @@ -358,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_bmp.py b/Tests/test_file_bmp.py index 8bb58794c..e2381df1e 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -1,6 +1,7 @@ import io import pytest + from PIL import BmpImagePlugin, Image from .helper import assert_image_equal, hopper diff --git a/Tests/test_file_bufrstub.py b/Tests/test_file_bufrstub.py index 6803a1230..11acc1c88 100644 --- a/Tests/test_file_bufrstub.py +++ b/Tests/test_file_bufrstub.py @@ -1,4 +1,5 @@ import pytest + from PIL import BufrStubImagePlugin, Image from .helper import hopper diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index 3200fd8f6..f04a20a22 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -1,4 +1,5 @@ import pytest + from PIL import CurImagePlugin, Image TEST_FILE = "Tests/images/deerstalker.cur" diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index bc76b4591..818d6ed5e 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -1,4 +1,5 @@ import pytest + from PIL import DcxImagePlugin, Image from .helper import assert_image_equal, hopper, is_pypy diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 8960edea3..0aec9ea37 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -2,6 +2,7 @@ from io import BytesIO import pytest + from PIL import DdsImagePlugin, Image from .helper import assert_image_equal diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index 504c09db1..f585a0669 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -1,6 +1,7 @@ import io import pytest + from PIL import EpsImagePlugin, Image, features from .helper import assert_image_similar, hopper, skip_unless_feature diff --git a/Tests/test_file_fitsstub.py b/Tests/test_file_fitsstub.py index 01bc2deee..6dc7c4602 100644 --- a/Tests/test_file_fitsstub.py +++ b/Tests/test_file_fitsstub.py @@ -1,4 +1,5 @@ import pytest + from PIL import FitsStubImagePlugin, Image TEST_FILE = "Tests/images/hopper.fits" diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 726e16c1a..16b3dc59a 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -1,4 +1,5 @@ import pytest + from PIL import FliImagePlugin, Image from .helper import assert_image_equal, is_pypy diff --git a/Tests/test_file_fpx.py b/Tests/test_file_fpx.py index a247de79c..c3cc37ddf 100644 --- a/Tests/test_file_fpx.py +++ b/Tests/test_file_fpx.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image FpxImagePlugin = pytest.importorskip( diff --git a/Tests/test_file_gbr.py b/Tests/test_file_gbr.py index b95970889..760f12e4d 100644 --- a/Tests/test_file_gbr.py +++ b/Tests/test_file_gbr.py @@ -1,4 +1,5 @@ import pytest + from PIL import GbrImagePlugin, Image from .helper import assert_image_equal diff --git a/Tests/test_file_gd.py b/Tests/test_file_gd.py index b6f8594be..5594e5bbb 100644 --- a/Tests/test_file_gd.py +++ b/Tests/test_file_gd.py @@ -1,4 +1,5 @@ import pytest + from PIL import GdImageFile, UnidentifiedImageError TEST_GD_FILE = "Tests/images/hopper.gd" diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 455e30f71..90943ac8f 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1,6 +1,7 @@ from io import BytesIO import pytest + from PIL import GifImagePlugin, Image, ImageDraw, ImagePalette, features from .helper import ( diff --git a/Tests/test_file_gimppalette.py b/Tests/test_file_gimppalette.py index a38c6320c..caec9cf21 100644 --- a/Tests/test_file_gimppalette.py +++ b/Tests/test_file_gimppalette.py @@ -1,4 +1,5 @@ import pytest + from PIL.GimpPaletteFile import GimpPaletteFile diff --git a/Tests/test_file_gribstub.py b/Tests/test_file_gribstub.py index 9d9def96b..e4930d8dc 100644 --- a/Tests/test_file_gribstub.py +++ b/Tests/test_file_gribstub.py @@ -1,4 +1,5 @@ import pytest + from PIL import GribStubImagePlugin, Image from .helper import hopper diff --git a/Tests/test_file_hdf5stub.py b/Tests/test_file_hdf5stub.py index 862cafa91..ff3397055 100644 --- a/Tests/test_file_hdf5stub.py +++ b/Tests/test_file_hdf5stub.py @@ -1,4 +1,5 @@ import pytest + from PIL import Hdf5StubImagePlugin, Image TEST_FILE = "Tests/images/hdf5.h5" diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 05feedb1a..a3d502d42 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -2,6 +2,7 @@ import io import sys import pytest + from PIL import IcnsImagePlugin, Image, features from .helper import assert_image_equal, assert_image_similar diff --git a/Tests/test_file_ico.py b/Tests/test_file_ico.py index 9ed1ffcb7..f7a513509 100644 --- a/Tests/test_file_ico.py +++ b/Tests/test_file_ico.py @@ -1,6 +1,7 @@ import io import pytest + from PIL import IcoImagePlugin, Image, ImageDraw from .helper import assert_image_equal, hopper diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index 30a9fd52a..afea82359 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -1,6 +1,7 @@ import filecmp import pytest + from PIL import Image, ImImagePlugin from .helper import assert_image_equal, hopper, is_pypy diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 1801cd8ff..717794614 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -3,6 +3,7 @@ import re from io import BytesIO import pytest + from PIL import ( ExifTags, Image, @@ -38,7 +39,7 @@ class TestFileJpeg: 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 @@ -98,7 +99,8 @@ class TestFileJpeg: 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): @@ -241,6 +243,16 @@ class TestFileJpeg: # 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_jpeg2k.py b/Tests/test_file_jpeg2k.py index d558843b8..c9e37f8b0 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -2,6 +2,7 @@ import re from io import BytesIO import pytest + from PIL import Image, ImageFile, Jpeg2KImagePlugin, features from .helper import ( diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 19a06a15d..da955b3de 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1,13 +1,13 @@ import base64 import io import itertools -import logging import os import re from collections import namedtuple from ctypes import c_float import pytest + from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features from .helper import ( @@ -19,8 +19,6 @@ from .helper import ( skip_unless_feature, ) -logger = logging.getLogger(__name__) - @skip_unless_feature("libtiff") class LibTiffTestCase: @@ -404,8 +402,8 @@ class TestFileLibTiff(LibTiffTestCase): 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() @@ -505,9 +503,9 @@ class TestFileLibTiff(LibTiffTestCase): 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") @@ -770,7 +768,7 @@ class TestFileLibTiff(LibTiffTestCase): 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 593a8eda8..03137c8b6 100644 --- a/Tests/test_file_libtiff_small.py +++ b/Tests/test_file_libtiff_small.py @@ -7,13 +7,13 @@ from .test_file_libtiff import LibTiffTestCase 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_mcidas.py b/Tests/test_file_mcidas.py index 516dbb208..88c8f8f4f 100644 --- a/Tests/test_file_mcidas.py +++ b/Tests/test_file_mcidas.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, McIdasImagePlugin from .helper import assert_image_equal diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index 5003090c7..464d138e2 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImagePalette from .helper import assert_image_similar, hopper, skip_unless_feature diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 893f9075d..791efcc3f 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -1,6 +1,7 @@ from io import BytesIO import pytest + from PIL import Image from .helper import assert_image_similar, is_pypy, skip_unless_feature diff --git a/Tests/test_file_msp.py b/Tests/test_file_msp.py index 9b508a4e4..293b856b0 100644 --- a/Tests/test_file_msp.py +++ b/Tests/test_file_msp.py @@ -1,6 +1,7 @@ import os import pytest + from PIL import Image, MspImagePlugin from .helper import assert_image_equal, hopper diff --git a/Tests/test_file_palm.py b/Tests/test_file_palm.py index e7afeef23..25d194b62 100644 --- a/Tests/test_file_palm.py +++ b/Tests/test_file_palm.py @@ -2,6 +2,7 @@ import os.path import subprocess import pytest + from PIL import Image from .helper import IMCONVERT, assert_image_equal, hopper, imagemagick_available diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 5af7469c7..670c03b95 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageFile, PcxImagePlugin from .helper import assert_image_equal, hopper diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 14a7a654f..3e23beae7 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -5,6 +5,7 @@ import tempfile import time import pytest + from PIL import Image, PdfParser from .helper import hopper diff --git a/Tests/test_file_pixar.py b/Tests/test_file_pixar.py index 5e83c6104..315ea4676 100644 --- a/Tests/test_file_pixar.py +++ b/Tests/test_file_pixar.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, PixarImagePlugin from .helper import assert_image_similar, hopper diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 9891b6399..f580f4ae7 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -3,6 +3,7 @@ import zlib from io import BytesIO import pytest + from PIL import Image, ImageFile, PngImagePlugin, features from .helper import ( @@ -606,6 +607,11 @@ class TestFilePng: exif = im.copy().getexif() assert exif[274] == 1 + # With a tEXt chunk + with Image.open("Tests/images/exif_text.png") as im: + exif = im._getexif() + assert exif[274] == 1 + # With XMP tags with Image.open("Tests/images/xmp_tags_orientation.png") as im: exif = im.getexif() diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 15c08e438..e7c3fb06f 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index 6b26fe442..8bb45630e 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, PsdImagePlugin from .helper import assert_image_similar, hopper, is_pypy diff --git a/Tests/test_file_sgi.py b/Tests/test_file_sgi.py index cb16276ce..a197fa775 100644 --- a/Tests/test_file_sgi.py +++ b/Tests/test_file_sgi.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, SgiImagePlugin from .helper import assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 8c69491e6..9cdb451c9 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -2,6 +2,7 @@ import tempfile from io import BytesIO import pytest + from PIL import Image, ImageSequence, SpiderImagePlugin from .helper import assert_image_equal, hopper, is_pypy diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index b2bfb5b9e..8421106a2 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -1,6 +1,7 @@ import os import pytest + from PIL import Image, SunImagePlugin from .helper import assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 3fe0cd04e..02001e5b1 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, TarIO, features from .helper import is_pypy diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index bac1b4dd6..465e13316 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -3,6 +3,7 @@ from glob import glob from itertools import product import pytest + from PIL import Image from .helper import assert_image_equal, hopper diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 7aa55dad0..594115042 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -1,8 +1,8 @@ -import logging import os from io import BytesIO import pytest + from PIL import Image, TiffImagePlugin from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION @@ -16,8 +16,6 @@ from .helper import ( is_win32, ) -logger = logging.getLogger(__name__) - class TestFileTiff: def test_sanity(self, tmp_path): @@ -227,8 +225,8 @@ class TestFileTiff: 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 179d1adf3..0f7f8adf1 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -2,6 +2,7 @@ import io import struct import pytest + from PIL import Image, TiffImagePlugin, TiffTags from PIL.TiffImagePlugin import IFDRational @@ -11,10 +12,10 @@ TAG_IDS = {info.name: info.value for info in TiffTags.TAGS_V2.values()} 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_file_webp.py b/Tests/test_file_webp.py index 25a4bb8da..11fbd9fd5 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -2,6 +2,7 @@ import io import re import pytest + from PIL import Image, WebPImagePlugin, features from .helper import ( diff --git a/Tests/test_file_webp_alpha.py b/Tests/test_file_webp_alpha.py index c624156df..362edac1a 100644 --- a/Tests/test_file_webp_alpha.py +++ b/Tests/test_file_webp_alpha.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index cd272f154..26e903488 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import ( diff --git a/Tests/test_file_webp_lossless.py b/Tests/test_file_webp_lossless.py index 4d06f53b1..2da443628 100644 --- a/Tests/test_file_webp_lossless.py +++ b/Tests/test_file_webp_lossless.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image_equal, hopper diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 3339cbfd3..d18225680 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, WmfImagePlugin from .helper import assert_image_similar, hopper diff --git a/Tests/test_file_xbm.py b/Tests/test_file_xbm.py index 23a540569..487920a92 100644 --- a/Tests/test_file_xbm.py +++ b/Tests/test_file_xbm.py @@ -1,6 +1,7 @@ from io import BytesIO import pytest + from PIL import Image from .helper import hopper diff --git a/Tests/test_file_xpm.py b/Tests/test_file_xpm.py index 187440d4e..8595b07eb 100644 --- a/Tests/test_file_xpm.py +++ b/Tests/test_file_xpm.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, XpmImagePlugin from .helper import assert_image_similar, hopper diff --git a/Tests/test_file_xvthumb.py b/Tests/test_file_xvthumb.py index 7c8c45113..ae53d2b63 100644 --- a/Tests/test_file_xvthumb.py +++ b/Tests/test_file_xvthumb.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, XVThumbImagePlugin from .helper import assert_image_similar, hopper diff --git a/Tests/test_font_bdf.py b/Tests/test_font_bdf.py index 4be39c383..1e7caee32 100644 --- a/Tests/test_font_bdf.py +++ b/Tests/test_font_bdf.py @@ -1,4 +1,5 @@ import pytest + from PIL import BdfFontFile, FontFile filename = "Tests/images/courB08.bdf" diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index a60163713..4db73e56e 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -1,6 +1,7 @@ import os import pytest + from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile from .helper import assert_image_equal, assert_image_similar, skip_unless_feature diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index d10b1acfd..3b9c8b071 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 068fb8172..6d188e740 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -3,8 +3,9 @@ import os import shutil import tempfile -import PIL import pytest + +import PIL from PIL import Image, ImageDraw, ImagePalette, ImageShow, UnidentifiedImageError from .helper import ( @@ -706,7 +707,8 @@ class TestImage: } @pytest.mark.parametrize( - "test_module", [PIL, Image], + "test_module", + [PIL, Image], ) def test_pillow_version(self, test_module): with pytest.warns(DeprecationWarning): @@ -734,7 +736,7 @@ class TestImage: 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_access.py b/Tests/test_image_access.py index af51a4fb3..3f0c6ab3b 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -2,9 +2,11 @@ import ctypes import os import subprocess import sys -from distutils import ccompiler, sysconfig +import sysconfig import pytest +from setuptools.command.build_ext import new_compiler + from PIL import Image from .helper import assert_image_equal, hopper, is_win32, on_ci @@ -15,8 +17,9 @@ if os.environ.get("PYTHONOPTIMIZE") == "2": cffi = None else: try: - from PIL import PyAccess import cffi + + from PIL import PyAccess except ImportError: cffi = None @@ -358,13 +361,12 @@ int main(int argc, char* argv[]) % sys.prefix.replace("\\", "\\\\") ) - compiler = ccompiler.new_compiler() - compiler.add_include_dir(sysconfig.get_python_inc()) + compiler = new_compiler() + compiler.add_include_dir(sysconfig.get_config_var("INCLUDEPY")) - libdir = sysconfig.get_config_var( - "LIBDIR" - ) or sysconfig.get_python_inc().replace("include", "libs") - print(libdir) + libdir = sysconfig.get_config_var("LIBDIR") or sysconfig.get_config_var( + "INCLUDEPY" + ).replace("include", "libs") compiler.add_library_dir(libdir) objects = compiler.compile(["embed_pil.c"]) compiler.link_executable(objects, "embed_pil") diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index bf6d88a97..980458407 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import hopper diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index cf83922b6..6fe1bd962 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image, assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index 3a2ce150d..e2228758c 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image_equal, hopper diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index ed71ea968..df8c353f3 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageFilter from .helper import assert_image_equal, hopper diff --git a/Tests/test_image_fromqimage.py b/Tests/test_image_fromqimage.py index 170d49ae1..5ad5b5c3c 100644 --- a/Tests/test_image_fromqimage.py +++ b/Tests/test_image_fromqimage.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageQt from .helper import assert_image_equal, hopper diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py index efb9a1452..f7fe99bb4 100644 --- a/Tests/test_image_load.py +++ b/Tests/test_image_load.py @@ -1,6 +1,8 @@ +import logging import os import pytest + from PIL import Image from .helper import hopper @@ -22,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 fe868b7c2..51108ead2 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_image_putpalette.py b/Tests/test_image_putpalette.py index 7b05e88b6..32f8de2c0 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -1,4 +1,5 @@ import pytest + from PIL import ImagePalette from .helper import hopper diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 96fa143a9..192617a52 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_image, assert_image_similar, hopper diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index b11269918..b4eebc142 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageMath, ImageMode from .helper import convert_to_comparable, skip_unless_feature diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index a6d861520..ef4ca4101 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -1,6 +1,7 @@ from contextlib import contextmanager import pytest + from PIL import Image, ImageDraw from .helper import assert_image_equal, assert_image_similar, hopper @@ -537,7 +538,10 @@ class TestCoreResampleBox: assert res.size == size # Borders should be slightly different assert_image_similar( - res, im.crop(box).resize(size, flt), 0.4, f">>> {size} {box} {flt}", + res, + im.crop(box).resize(size, flt), + 0.4, + f">>> {size} {box} {flt}", ) def test_skip_vertical(self): @@ -555,5 +559,8 @@ class TestCoreResampleBox: assert res.size == size # Borders should be slightly different assert_image_similar( - res, im.crop(box).resize(size, flt), 0.4, f">>> {size} {box} {flt}", + res, + im.crop(box).resize(size, flt), + 0.4, + f">>> {size} {box} {flt}", ) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index ad4be135a..a49abe1b9 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -4,6 +4,7 @@ Tests for resize functionality. from itertools import permutations import pytest + from PIL import Image from .helper import assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index da63efe55..c42310c32 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import ( diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 3409d86f0..3ee51178d 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -1,6 +1,7 @@ import math import pytest + from PIL import Image, ImageTransform from .helper import assert_image_equal, assert_image_similar, hopper diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index e549f0922..e9149b843 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -4,6 +4,7 @@ import re from io import BytesIO import pytest + from PIL import Image, ImageMode, features from .helper import assert_image, assert_image_equal, assert_image_similar, hopper @@ -436,7 +437,7 @@ def test_extended_information(): 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_imagecolor.py b/Tests/test_imagecolor.py index d2fd07c81..b5d693796 100644 --- a/Tests/test_imagecolor.py +++ b/Tests/test_imagecolor.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageColor diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 283006b3d..271a1629d 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,6 +1,7 @@ import os.path import pytest + from PIL import Image, ImageColor, ImageDraw, ImageFont from .helper import ( @@ -666,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_imageenhance.py b/Tests/test_imageenhance.py index 32222c1d3..8bc94401e 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -33,7 +33,9 @@ def _half_transparent_image(): def _check_alpha(im, original, op, amount): assert im.getbands() == original.getbands() assert_image_equal( - im.getchannel("A"), original.getchannel("A"), f"Diff on {op}: {amount}", + im.getchannel("A"), + original.getchannel("A"), + f"Diff on {op}: {amount}", ) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index 48fecc26e..b4107e8e3 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -1,6 +1,7 @@ from io import BytesIO import pytest + from PIL import EpsImagePlugin, Image, ImageFile, features from .helper import ( @@ -243,3 +244,8 @@ class TestPyDecoder: 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.py b/Tests/test_imagefont.py index 6668a100b..aa7ec6fa6 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -7,6 +7,7 @@ from io import BytesIO import pytest from packaging.version import parse as parse_version + from PIL import Image, ImageDraw, ImageFont, features from .helper import ( diff --git a/Tests/test_imagefont_bitmap.py b/Tests/test_imagefont_bitmap.py index c4032d55d..0ba682885 100644 --- a/Tests/test_imagefont_bitmap.py +++ b/Tests/test_imagefont_bitmap.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageDraw, ImageFont from .helper import assert_image_similar @@ -33,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_imagefontctl.py b/Tests/test_imagefontctl.py index 386dd3be6..edd6fab57 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageDraw, ImageFont from .helper import assert_image_similar, skip_unless_feature diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index ae1277ced..c36285451 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -3,6 +3,7 @@ import subprocess import sys import pytest + from PIL import Image, ImageGrab from .helper import assert_image, assert_image_equal_tofile, skip_unless_feature diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 5b0be938a..087c39e01 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -1,5 +1,6 @@ # Test the ImageMorphology functionality import pytest + from PIL import Image, ImageMorph, _imagingmorph from .helper import assert_image_equal, hopper diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 864df447e..f17bfdd2f 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -1,5 +1,6 @@ import pytest -from PIL import Image, ImageOps, features + +from PIL import Image, ImageDraw, ImageOps, ImageStat, features from .helper import ( assert_image_equal, @@ -24,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") @@ -311,3 +314,51 @@ def test_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_imageops_usm.py b/Tests/test_imageops_usm.py index 61f8dc2ba..8837ed2a2 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageFilter diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 4ef2d3ffd..a2b0d2b02 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImagePalette from .helper import assert_image_equal diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 52af16455..7cc89ae39 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -2,6 +2,7 @@ import array import struct import pytest + from PIL import Image, ImagePath diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index d723690ef..c39bb0a06 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -1,4 +1,5 @@ import pytest + from PIL import ImageQt from .helper import hopper diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index b3fe9df97..7cf237b46 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageSequence, TiffImagePlugin from .helper import assert_image_equal, hopper, skip_unless_feature diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index fddc73bd1..78e80f521 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageShow from .helper import hopper, is_win32, on_ci @@ -18,7 +19,8 @@ def test_register(): @pytest.mark.parametrize( - "order", [-1, 0], + "order", + [-1, 0], ) def test_viewer_show(order): class TestViewer(ImageShow.Viewer): @@ -40,7 +42,8 @@ def test_viewer_show(order): @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_imagestat.py b/Tests/test_imagestat.py index 6c70193ce..9474ff6f9 100644 --- a/Tests/test_imagestat.py +++ b/Tests/test_imagestat.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageStat from .helper import hopper diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index 7e87ac902..928b8cbd1 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -1,13 +1,14 @@ import pytest + from PIL import Image from .helper import assert_image_equal, hopper try: - from PIL import ImageTk - import tkinter as tk + from PIL import ImageTk + dir(ImageTk) HAS_TK = True except (OSError, ImportError): diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index b1ddc75e9..9d64d17a3 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -1,4 +1,5 @@ import pytest + from PIL import ImageWin from .helper import hopper, is_win32 diff --git a/Tests/test_lib_image.py b/Tests/test_lib_image.py index 7115e62ad..37ed3659d 100644 --- a/Tests/test_lib_image.py +++ b/Tests/test_lib_image.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image diff --git a/Tests/test_lib_pack.py b/Tests/test_lib_pack.py index 8e3c1fda9..8a1460346 100644 --- a/Tests/test_lib_pack.py +++ b/Tests/test_lib_pack.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image X = 255 diff --git a/Tests/test_locale.py b/Tests/test_locale.py index c5e54883d..7a07fbbe0 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -1,6 +1,7 @@ import locale import pytest + from PIL import Image # ref https://github.com/python-pillow/Pillow/issues/272 diff --git a/Tests/test_map.py b/Tests/test_map.py index bdb59bfe0..2b65fb3f9 100644 --- a/Tests/test_map.py +++ b/Tests/test_map.py @@ -1,6 +1,7 @@ import sys import pytest + from PIL import Image from .helper import is_win32 diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 56addca1b..da367fa46 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image from .helper import assert_deep_equal, assert_image, hopper diff --git a/Tests/test_pdfparser.py b/Tests/test_pdfparser.py index f5cd403d5..2d428e95f 100644 --- a/Tests/test_pdfparser.py +++ b/Tests/test_pdfparser.py @@ -1,6 +1,7 @@ import time import pytest + from PIL.PdfParser import ( IndirectObjectDef, IndirectReference, diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index dd241fd74..a10dcec8c 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -1,6 +1,7 @@ import pickle import pytest + from PIL import Image from .helper import skip_unless_feature diff --git a/Tests/test_pyroma.py b/Tests/test_pyroma.py index f4302350d..aa05c2cfd 100644 --- a/Tests/test_pyroma.py +++ b/Tests/test_pyroma.py @@ -1,4 +1,5 @@ import pytest + from PIL import __version__ pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed") diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py index fcb8c2f2a..8d599f9bf 100644 --- a/Tests/test_qt_image_toqimage.py +++ b/Tests/test_qt_image_toqimage.py @@ -1,4 +1,5 @@ import pytest + from PIL import Image, ImageQt from .helper import assert_image_equal, hopper @@ -12,10 +13,10 @@ if ImageQt.qt_is_installed: try: from PyQt5 import QtGui - from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication + from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget except (ImportError, RuntimeError): from PySide2 import QtGui - from PySide2.QtWidgets import QWidget, QHBoxLayout, QLabel, QApplication + from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget def test_sanity(tmp_path): diff --git a/Tests/test_sgi_crash.py b/Tests/test_sgi_crash.py index b1a3e1515..2b671244a 100644 --- a/Tests/test_sgi_crash.py +++ b/Tests/test_sgi_crash.py @@ -1,5 +1,6 @@ #!/usr/bin/env python import pytest + from PIL import Image diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 45c60fa10..d25d42dfc 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -1,6 +1,7 @@ import shutil import pytest + from PIL import GifImagePlugin, Image, JpegImagePlugin from .helper import cjpeg_available, djpeg_available, is_win32, netpbm_available diff --git a/Tests/test_tiff_ifdrational.py b/Tests/test_tiff_ifdrational.py index 707284d7b..1697a8d49 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/Tests/test_util.py b/Tests/test_util.py index 0bc8b0702..b5bfca012 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -1,4 +1,5 @@ import pytest + from PIL import _util diff --git a/docs/conf.py b/docs/conf.py index caddae327..a022e61cb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,9 +16,10 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) -import PIL import sphinx_rtd_theme +import PIL + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -76,7 +77,7 @@ language = None # 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 10ccec632..e3ad2a9e3 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/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 89e3dfd4a..f54b7acf7 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -257,6 +257,9 @@ Using the :py:meth:`~PIL.Image.Image.draft` method, you can speed things up by converting ``RGB`` images to ``L``, and resize images to 1/2, 1/4 or 1/8 of their original size while loading them. +By default Pillow doesn't allow loading of truncated JPEG files, set +:data:`.ImageFile.LOAD_TRUNCATED_IMAGES` to override this. + The :py:meth:`~PIL.Image.open` method may set the following :py:attr:`~PIL.Image.Image.info` properties if available: @@ -473,6 +476,9 @@ image formats, EXIF data is not guaranteed to be present in :py:attr:`~PIL.Image.Image.info` until :py:meth:`~PIL.Image.Image.load` has been called. +By default Pillow doesn't allow loading of truncated PNG files, set +:data:`.ImageFile.LOAD_TRUNCATED_IMAGES` to override this. + The :py:func:`~PIL.Image.open` function sets the following :py:attr:`~PIL.Image.Image.info` properties, when appropriate: diff --git a/docs/handbook/writing-your-own-file-decoder.rst b/docs/handbook/writing-your-own-file-decoder.rst index 471ae3377..97cb5abe1 100644 --- a/docs/handbook/writing-your-own-file-decoder.rst +++ b/docs/handbook/writing-your-own-file-decoder.rst @@ -87,8 +87,10 @@ true color. Image.register_open(SpamImageFile.format, SpamImageFile, _accept) - Image.register_extension(SpamImageFile.format, ".spam") - Image.register_extension(SpamImageFile.format, ".spa") # DOS version + Image.register_extensions(SpamImageFile.format, [ + ".spam", + ".spa", # DOS version + ]) The format handler must always set the @@ -103,6 +105,15 @@ Note that the image plugin must be explicitly registered using :py:func:`PIL.Image.register_open`. Although not required, it is also a good idea to register any extensions used by this format. +Once the plugin has been imported, it can be used: + +.. code-block:: python + + from PIL import Image + import SpamImagePlugin + with Image.open("hopper.spam") as im: + pass + The ``tile`` attribute ---------------------- @@ -148,6 +159,8 @@ can be used with most uncompressed file formats, such as PPM, BMP, uncompressed TIFF, and many others. To use the raw decoder with the :py:func:`PIL.Image.frombytes` function, use the following syntax:: +.. code-block:: python + image = Image.frombytes( mode, size, data, "raw", raw mode, stride, orientation @@ -258,6 +271,8 @@ image memory. To use the bit decoder with the :py:func:`PIL.Image.frombytes` function, use the following syntax:: +.. code-block:: python + image = Image.frombytes( mode, size, data, "bit", bits, pad, fill, sign, orientation @@ -350,7 +365,7 @@ interest in this object are: The target image, will be set by Pillow. **state** - An ImagingCodecStateInstance, will be set by Pillow. The **context** + An ImagingCodecStateInstance, will be set by Pillow. The ``context`` member is an opaque struct that can be used by the decoder to store any format specific state or options. @@ -414,4 +429,3 @@ Python-based file decoder: 3. Cleanup: The decoder instance's ``cleanup`` method is called. - diff --git a/docs/installation.rst b/docs/installation.rst index 706cfb1d7..4c41cc9ee 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/reference/ImageChops.rst b/docs/reference/ImageChops.rst index 772d9c983..9519361a7 100644 --- a/docs/reference/ImageChops.rst +++ b/docs/reference/ImageChops.rst @@ -39,12 +39,7 @@ operations in this module). .. autofunction:: PIL.ImageChops.soft_light .. autofunction:: PIL.ImageChops.hard_light .. autofunction:: PIL.ImageChops.overlay -.. py:method:: PIL.ImageChops.offset(image, xoffset, yoffset=None) - - Returns a copy of the image where data has been offset by the given - distances. Data wraps around the edges. If **yoffset** is omitted, it - is assumed to be equal to **xoffset**. - +.. autofunction:: PIL.ImageChops.offset .. autofunction:: PIL.ImageChops.screen .. autofunction:: PIL.ImageChops.subtract .. autofunction:: PIL.ImageChops.subtract_modulo diff --git a/docs/reference/ImageFile.rst b/docs/reference/ImageFile.rst index 333876bc8..e0ce389e8 100644 --- a/docs/reference/ImageFile.rst +++ b/docs/reference/ImageFile.rst @@ -52,3 +52,10 @@ Classes .. autoclass:: PIL.ImageFile.StubImageFile() :members: :show-inheritance: + +Constants +--------- + +.. autodata:: PIL.ImageFile.LOAD_TRUNCATED_IMAGES +.. autodata:: PIL.ImageFile.ERRORS + :annotation: diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index fc61cac13..ff79bdcd8 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -56,3 +56,19 @@ Methods .. autoclass:: PIL.ImageFont.TransposedFont :members: + +Constants +--------- + +.. data:: PIL.ImageFont.LAYOUT_BASIC + + Use basic text layout for TrueType font. + Advanced features such as text direction are not supported. + +.. data:: PIL.ImageFont.LAYOUT_RAQM + + Use Raqm text layout for TrueType font. + Advanced features are supported. + + Requires Raqm, you can check support using + :py:func:`PIL.features.check_feature` with ``feature="raqm"``. diff --git a/docs/reference/ImagePath.rst b/docs/reference/ImagePath.rst index 21a202b5e..b9bdfc507 100644 --- a/docs/reference/ImagePath.rst +++ b/docs/reference/ImagePath.rst @@ -33,7 +33,7 @@ vector data. Path objects can be passed to the methods on the method modifies the path in place, and returns the number of points left in the path. - **distance** is measured as `Manhattan distance`_ and defaults to two + ``distance`` is measured as `Manhattan distance`_ and defaults to two pixels. .. _Manhattan distance: https://en.wikipedia.org/wiki/Manhattan_distance @@ -55,7 +55,7 @@ vector data. Path objects can be passed to the methods on the :param flat: By default, this function returns a list of 2-tuples [(x, y), ...]. If this argument is ``True``, it returns a flat list [x, y, ...] instead. - :return: A list of coordinates. See **flat**. + :return: A list of coordinates. See ``flat``. .. py:method:: PIL.ImagePath.Path.transform(matrix) diff --git a/docs/reference/block_allocator.rst b/docs/reference/block_allocator.rst index 400f236dc..1abe5280f 100644 --- a/docs/reference/block_allocator.rst +++ b/docs/reference/block_allocator.rst @@ -40,7 +40,7 @@ variables: * ``PILLOW_BLOCK_SIZE``, in bytes, K, or M. Specifies the maximum block size for ``ImagingAllocateArray``. Valid values are - integers, with an optional `k` or `m` suffix. Defaults to 16M. + integers, with an optional ``k`` or ``m`` suffix. Defaults to 16M. * ``PILLOW_BLOCKS_MAX`` Specifies the number of freed blocks to retain to fill future memory requests. Any freed blocks over this diff --git a/docs/releasenotes/8.0.0.rst b/docs/releasenotes/8.0.0.rst new file mode 100644 index 000000000..bbb954e25 --- /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 2d7747c3e..ba81fbaf8 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 000000000..bf381114e --- /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 19979cf77..129adeee7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,12 +1,9 @@ [flake8] -extend-ignore = E203, W503 +extend-ignore = E203 max-line-length = 88 [isort] -combine_as_imports = True -include_trailing_comma = True -line_length = 88 -multi_line_output = 3 +profile = black [tool:pytest] addopts = -ra --color=yes diff --git a/setup.py b/setup.py index 1e6759ee5..a6a39e57b 100755 --- a/setup.py +++ b/setup.py @@ -14,10 +14,9 @@ import struct import subprocess import sys import warnings -from distutils import ccompiler -from distutils.command.build_ext import build_ext from setuptools import Extension, setup +from setuptools.command.build_ext import build_ext def get_version(): @@ -131,7 +130,7 @@ class RequiredDependencyException(Exception): pass -PLATFORM_MINGW = "mingw" in ccompiler.get_default_compiler() +PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version PLATFORM_PYPY = hasattr(sys, "pypy_version_info") if sys.platform == "win32" and PLATFORM_MINGW: @@ -244,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") @@ -352,6 +346,22 @@ class pil_build_ext(build_ext): _dbg("Requiring %s", x) self.feature.required.add(x) + def _update_extension(self, name, libraries, define_macros=None, include_dirs=None): + for extension in self.extensions: + if extension.name == name: + extension.libraries += libraries + if define_macros is not None: + extension.define_macros += define_macros + if include_dirs is not None: + extension.include_dirs += include_dirs + break + + def _remove_extension(self, name): + for extension in self.extensions: + if extension.name == name: + self.extensions.remove(extension) + break + def build_extensions(self): library_dirs = [] @@ -464,6 +474,9 @@ class pil_build_ext(build_ext): # 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): @@ -695,12 +708,6 @@ class pil_build_ext(build_ext): # # core library - files = ["src/_imaging.c"] - for src_file in _IMAGING: - files.append("src/" + src_file + ".c") - for src_file in _LIB_IMAGING: - files.append(os.path.join("src/libImaging", src_file + ".c")) - libs = self.add_imaging_libs.split() defs = [] if feature.jpeg: @@ -737,7 +744,7 @@ class pil_build_ext(build_ext): else: defs.append(("PILLOW_VERSION", f'"{PILLOW_VERSION}"')) - exts = [(Extension("PIL._imaging", files, libraries=libs, define_macros=defs))] + self._update_extension("PIL._imaging", libs, defs) # # additional libraries @@ -745,26 +752,17 @@ class pil_build_ext(build_ext): if feature.freetype: libs = ["freetype"] defs = [] - exts.append( - Extension( - "PIL._imagingft", - ["src/_imagingft.c"], - libraries=libs, - define_macros=defs, - ) - ) + self._update_extension("PIL._imagingft", libs, defs) + else: + self._remove_extension("PIL._imagingft") if feature.lcms: extra = [] if sys.platform == "win32": extra.extend(["user32", "gdi32"]) - exts.append( - Extension( - "PIL._imagingcms", - ["src/_imagingcms.c"], - libraries=[feature.lcms] + extra, - ) - ) + self._update_extension("PIL._imagingcms", [feature.lcms] + extra) + else: + self._remove_extension("PIL._imagingcms") if feature.webp: libs = [feature.webp] @@ -775,26 +773,12 @@ class pil_build_ext(build_ext): libs.append(feature.webpmux) libs.append(feature.webpmux.replace("pmux", "pdemux")) - exts.append( - Extension( - "PIL._webp", ["src/_webp.c"], libraries=libs, define_macros=defs - ) - ) + self._update_extension("PIL._webp", libs, defs) + else: + self._remove_extension("PIL._webp") tk_libs = ["psapi"] if sys.platform == "win32" else [] - exts.append( - Extension( - "PIL._imagingtk", - ["src/_imagingtk.c", "src/Tk/tkImaging.c"], - include_dirs=["src/Tk"], - libraries=tk_libs, - ) - ) - - exts.append(Extension("PIL._imagingmath", ["src/_imagingmath.c"])) - exts.append(Extension("PIL._imagingmorph", ["src/_imagingmorph.c"])) - - self.extensions[:] = exts + self._update_extension("PIL._imagingtk", tk_libs, include_dirs=["src/Tk"]) build_ext.build_extensions(self) @@ -858,12 +842,31 @@ def debug_build(): return hasattr(sys, "gettotalrefcount") +files = ["src/_imaging.c"] +for src_file in _IMAGING: + files.append("src/" + src_file + ".c") +for src_file in _LIB_IMAGING: + files.append(os.path.join("src/libImaging", src_file + ".c")) +ext_modules = [ + Extension("PIL._imaging", files), + Extension("PIL._imagingft", ["src/_imagingft.c"]), + Extension("PIL._imagingcms", ["src/_imagingcms.c"]), + Extension("PIL._webp", ["src/_webp.c"]), + Extension("PIL._imagingtk", ["src/_imagingtk.c", "src/Tk/tkImaging.c"]), + 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", @@ -892,7 +895,7 @@ try: ], python_requires=">=3.6", cmdclass={"build_ext": pil_build_ext}, - ext_modules=[Extension("PIL._imaging", ["_imaging.c"])], + ext_modules=ext_modules, include_package_data=True, packages=["PIL"], package_dir={"": "src"}, diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index c1931b791..711e030e1 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -25,7 +25,12 @@ from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16le as i16, i32le as i32, o8, o16le as o16, o32le as o32 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import i32le as i32 +from ._binary import o8 +from ._binary import o16le as o16 +from ._binary import o32le as o32 # # -------------------------------------------------------------------- diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index 3a1b6d2e5..35123f789 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -16,7 +16,9 @@ # See the README file for information on usage and redistribution. # from . import BmpImagePlugin, Image -from ._binary import i8, i16le as i16, i32le as i32 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import i32le as i32 # # -------------------------------------------------------------------- diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index 2c8d03d0b..3c88d53af 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -17,7 +17,10 @@ from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16le as i16, i32le as i32, o8 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import i32le as i32 +from ._binary import o8 # # decoder diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index 51e78bc9a..14070eebf 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -17,7 +17,8 @@ import olefile from . import Image, ImageFile -from ._binary import i8, i32le as i32 +from ._binary import i8 +from ._binary import i32le as i32 # we map from colour field tuples to (mode, rawmode) descriptors MODES = { diff --git a/src/PIL/GdImageFile.py b/src/PIL/GdImageFile.py index 0c4574f9e..8561f7b74 100644 --- a/src/PIL/GdImageFile.py +++ b/src/PIL/GdImageFile.py @@ -28,7 +28,9 @@ from . import ImageFile, ImagePalette, UnidentifiedImageError -from ._binary import i8, i16be as i16, i32be as i32 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 class GdImageFile(ImageFile.ImageFile): diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index c1a6a63ed..4ca5a697e 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -30,7 +30,10 @@ import os import subprocess from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence -from ._binary import i8, i16le as i16, o8, o16le as o16 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 # -------------------------------------------------------------------- # Identify/read GIF files diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index e4a74321b..57bb17ee9 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -28,7 +28,9 @@ from io import BytesIO from math import ceil, log from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin -from ._binary import i8, i16le as i16, i32le as i32 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import i32le as i32 # # -------------------------------------------------------------------- diff --git a/src/PIL/Image.py b/src/PIL/Image.py index b0ec86d47..30ee8606a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -594,7 +594,8 @@ class Image: 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 @@ class Image: ) def _repr_png_(self): - """ iPython display hook support + """iPython display hook support :returns: png version of the image as bytes """ @@ -855,7 +856,7 @@ class Image: and the palette can be represented without a palette. The current version supports all possible conversions between - "L", "RGB" and "CMYK." The **matrix** argument only supports "L" + "L", "RGB" and "CMYK." The ``matrix`` argument only supports "L" and "RGB". When translating a color image to greyscale (mode "L"), @@ -870,9 +871,9 @@ class Image: all other values to 0 (black). To use other thresholds, use the :py:meth:`~PIL.Image.Image.point` method. - When converting from "RGBA" to "P" without a **matrix** argument, + When converting from "RGBA" to "P" without a ``matrix`` argument, this passes the operation to :py:meth:`~PIL.Image.Image.quantize`, - and **dither** and **palette** are ignored. + and ``dither`` and ``palette`` are ignored. :param mode: The requested mode. See: :ref:`concept-modes`. :param matrix: An optional conversion matrix. If given, this @@ -880,7 +881,7 @@ class Image: :param dither: Dithering method, used when converting from mode "RGB" to "P" or from "RGB" or "L" to "1". Available methods are :data:`NONE` or :data:`FLOYDSTEINBERG` (default). - Note that this is not used when **matrix** is supplied. + Note that this is not used when ``matrix`` is supplied. :param palette: Palette to use when converting from mode "RGB" to "P". Available palettes are :data:`WEB` or :data:`ADAPTIVE`. :param colors: Number of colors to use for the :data:`ADAPTIVE` palette. @@ -1180,7 +1181,7 @@ class Image: 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 @@ -1205,7 +1206,7 @@ class Image: def getbands(self): """ Returns a tuple containing the name of each band in this image. - For example, **getbands** on an RGB image returns ("R", "G", "B"). + For example, ``getbands`` on an RGB image returns ("R", "G", "B"). :returns: A tuple containing band names. :rtype: tuple @@ -1259,7 +1260,7 @@ class Image: Note that the sequence object returned by this method is an internal PIL data type, which only supports certain sequence operations. To convert it to an ordinary sequence (e.g. for - printing), use **list(im.getdata())**. + printing), use ``list(im.getdata())``. :param band: What band to return. The default is to return all bands. To return a single band, pass in the index @@ -1505,7 +1506,7 @@ class Image: 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 @@ -1740,7 +1741,7 @@ class Image: Rewrites the image to reorder the palette. :param dest_map: A list of indexes into the original palette. - e.g. [1,0] would swap a two item palette, and list(range(256)) + e.g. ``[1,0]`` would swap a two item palette, and ``list(range(256))`` is the identity transform. :param source_palette: Bytes or None. :returns: An :py:class:`~PIL.Image.Image` object. @@ -1922,16 +1923,16 @@ class Image: def reduce(self, factor, box=None): """ - Returns a copy of the image reduced by `factor` times. - If the size of the image is not dividable by the `factor`, + Returns a copy of the image reduced ``factor`` times. + If the size of the image is not dividable by ``factor``, the resulting size will be rounded up. :param factor: A greater than 0 integer or tuple of two integers for width and height separately. :param box: An optional 4-tuple of ints providing the source image region to be reduced. - The values must be within (0, 0, width, height) rectangle. - If omitted or None, the entire source is used. + The values must be within ``(0, 0, width, height)`` rectangle. + If omitted or ``None``, the entire source is used. """ if not isinstance(factor, (list, tuple)): factor = (factor, factor) @@ -2354,7 +2355,7 @@ class Image: # Return result It may also be an object with a ``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: def getdata(self): @@ -2369,7 +2370,7 @@ class Image: interpolation in a 4x4 environment). If omitted, or if the image has mode "1" or "P", it is set to :py:data:`PIL.Image.NEAREST`. See: :ref:`concept-filters`. - :param fill: If **method** is an + :param fill: If ``method`` is an :py:class:`~PIL.Image.ImageTransformHandler` object, this is one of the arguments passed to it. Otherwise, it is unused. :param fillcolor: Optional fill color for the area outside the @@ -2668,7 +2669,7 @@ def frombuffer(mode, size, data, decoder_name="raw", *args): Note that this function decodes pixel data only, not entire images. If you have an entire image file in a string, wrap it in a - **BytesIO** object, and use :py:func:`~PIL.Image.open` to load it. + :py:class:`~io.BytesIO` object, and use :py:func:`~PIL.Image.open` to load it. In the current version, the default parameters used for the "raw" decoder differs from that used for :py:func:`~PIL.Image.frombytes`. This is a @@ -2715,7 +2716,7 @@ def fromarray(obj, mode=None): Creates an image memory from an object exporting the array interface (using the buffer protocol). - If **obj** is not contiguous, then the tobytes method is called + If ``obj`` is not contiguous, then the ``tobytes`` method is called and :py:func:`~PIL.Image.frombuffer` is used. If you have an image in NumPy:: diff --git a/src/PIL/ImageChops.py b/src/PIL/ImageChops.py index 9fccedcb2..61d3a295b 100644 --- a/src/PIL/ImageChops.py +++ b/src/PIL/ImageChops.py @@ -313,8 +313,8 @@ def composite(image1, image2, mask): def offset(image, xoffset, yoffset=None): """Returns a copy of the image where data has been offset by the given - distances. Data wraps around the edges. If **yoffset** is omitted, it - is assumed to be equal to **xoffset**. + distances. Data wraps around the edges. If ``yoffset`` is omitted, it + is assumed to be equal to ``xoffset``. :param xoffset: The horizontal distance. :param yoffset: The vertical distance. If omitted, both diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index f854959a7..3856cb843 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -250,7 +250,9 @@ class ImageCmsTransform(Image.ImagePointHandler): def get_display_profile(handle=None): - """ (experimental) Fetches the profile for the current display device. + """ + (experimental) Fetches the profile for the current display device. + :returns: ``None`` if the profile is not known. """ @@ -275,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 @@ -624,7 +626,7 @@ def applyTransform(im, transform, inPlace=False): :param im: An :py:class:`~PIL.Image.Image` object, and im.mode must be the same as the ``inMode`` supported by the transform. :param transform: A valid CmsTransform class object - :param inPlace: Bool. If ``True``, ``im` is modified in place and ``None`` is + :param inPlace: Bool. If ``True``, ``im`` is modified in place and ``None`` is returned, if ``False``, a new :py:class:`~PIL.Image.Image` object with the transform applied is returned (and ``im`` is not changed). The default is ``False``. diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index e7646a25c..6775ba43e 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -35,7 +35,6 @@ import numbers from . import Image, ImageColor - """ A simple 2D drawing interface for PIL images.

diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index bb8737d33..49633ef51 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -40,6 +40,7 @@ MAXBLOCK = 65536 SAFEBLOCK = 1024 * 1024 LOAD_TRUNCATED_IMAGES = False +"""Whether or not to load truncated image files. User code may change this.""" ERRORS = { -1: "image buffer overrun error", @@ -48,6 +49,7 @@ ERRORS = { -8: "bad configuration", -9: "out of memory error", } +"""Dict of known error codes returned from :meth:`.PyDecoder.decode`.""" # @@ -507,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) @@ -524,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 @@ -583,7 +585,7 @@ class PyCodecState: class PyDecoder: """ Python implementation of a format decoder. Override this class and - add the decoding logic in the `decode` method. + add the decoding logic in the :meth:`decode` method. See :ref:`Writing Your Own File Decoder in Python` """ @@ -615,9 +617,9 @@ class PyDecoder: Override to perform the decoding process. :param buffer: A bytes object with the data to be decoded. - :returns: A tuple of (bytes consumed, errcode). + :returns: A tuple of ``(bytes consumed, errcode)``. If finished with decoding return <0 for the bytes consumed. - Err codes are from `ERRORS` + Err codes are from :data:`.ImageFile.ERRORS`. """ raise NotImplementedError() diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index c00261e47..9ca17d9ad 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -69,7 +69,7 @@ class Kernel(BuiltinFilter): class RankFilter(Filter): """ Create a rank filter. The rank filter sorts all pixels in - a window of the given size, and returns the **rank**'th value. + a window of the given size, and returns the ``rank``'th value. :param size: The kernel size, in pixels. :param rank: What pixel value to pick. Use 0 for a min filter, diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 1e0576be0..63f819151 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -638,7 +638,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): This specifies the character set to use. It does not alter the encoding of any text provided in subsequent operations. :param layout_engine: Which layout engine to use, if available: - `ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`. + :data:`.ImageFont.LAYOUT_BASIC` or :data:`.ImageFont.LAYOUT_RAQM`. You can check support for Raqm layout using :py:func:`PIL.features.check_feature` with ``feature="raqm"``. diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 3fa338b0a..b93ec3f2a 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -21,8 +21,8 @@ from . import Image if sys.platform == "darwin": import os - import tempfile import subprocess + import tempfile def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None): diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index d1ec09eac..b76dfa01f 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -28,36 +28,36 @@ MIRROR_MATRIX = [ 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 157da0b52..14602a5c8 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] @@ -146,14 +149,14 @@ def colorize(image, black, white, mid=None, blackpoint=0, whitepoint=255, midpoi Colorize grayscale image. This function calculates a color wedge which maps all black pixels in the source image to the first color and all white pixels to the - second color. If **mid** is specified, it uses three-color mapping. - The **black** and **white** arguments should be RGB tuples or color names; - optionally you can use three-color mapping by also specifying **mid**. + second color. If ``mid`` is specified, it uses three-color mapping. + The ``black`` and ``white`` arguments should be RGB tuples or color names; + optionally you can use three-color mapping by also specifying ``mid``. Mapping positions for any of the colors can be specified - (e.g. **blackpoint**), where these parameters are the integer + (e.g. ``blackpoint``), where these parameters are the integer value corresponding to where the corresponding color should be mapped. These parameters must have logical order, such that - **blackpoint** <= **midpoint** <= **whitepoint** (if **mid** is specified). + ``blackpoint <= midpoint <= whitepoint`` (if ``mid`` is specified). :param image: The image to colorize. :param black: The color to use for black input pixels. @@ -312,7 +315,7 @@ def deform(image, deformer, resample=Image.BILINEAR): :param image: The image to deform. :param deformer: A deformer object. Any object that implements a - **getmesh** method can be used. + ``getmesh`` method can be used. :param resample: An optional resampling filter. Same values possible as in the PIL.Image.transform function. :return: An image. diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 63cc6ae5a..91be53488 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -29,11 +29,11 @@ qt_versions.sort(key=lambda qt_version: qt_version[1] in sys.modules, reverse=Tr for qt_version, qt_module in qt_versions: try: if qt_module == "PyQt5": - from PyQt5.QtGui import QImage, qRgba, QPixmap from PyQt5.QtCore import QBuffer, QIODevice + from PyQt5.QtGui import QImage, QPixmap, qRgba elif qt_module == "PySide2": - from PySide2.QtGui import QImage, qRgba, QPixmap from PySide2.QtCore import QBuffer, QIODevice + from PySide2.QtGui import QImage, QPixmap, qRgba except (ImportError, RuntimeError): continue qt_is_installed = True diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index e8148ad01..62db7a717 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -69,7 +69,7 @@ class PhotoImage: image, pixels having alpha 0 are treated as transparent. The constructor takes either a PIL image, or a mode and a size. - Alternatively, you can use the **file** or **data** options to initialize + Alternatively, you can use the ``file`` or ``data`` options to initialize the photo image object. :param image: Either a PIL image, or a mode string. If a mode string is @@ -210,7 +210,7 @@ class BitmapImage: The given image must have mode "1". Pixels having value 0 are treated as transparent. Options, if any, are passed on to Tkinter. The most commonly - used option is **foreground**, which is used to specify the color for the + used option is ``foreground``, which is used to specify the color for the non-transparent parts. See the Tkinter documentation for information on how to specify colours. diff --git a/src/PIL/ImageWin.py b/src/PIL/ImageWin.py index 2ca4acdf8..ca9b14c8a 100644 --- a/src/PIL/ImageWin.py +++ b/src/PIL/ImageWin.py @@ -59,7 +59,7 @@ class Dib: with 20 greylevels. To make sure that palettes work properly under Windows, you must call the - **palette** method upon certain events from Windows. + ``palette`` method upon certain events from Windows. :param image: Either a PIL image, or a mode string. If a mode string is used, a size must also be given. The mode can be one of "1", diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 75e7b5a2a..f407b7e5f 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -18,7 +18,10 @@ import os import tempfile from . import Image, ImageFile -from ._binary import i8, i16be as i16, i32be as i32, o8 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import o8 COMPRESSION = {1: "raw", 5: "jpeg"} @@ -181,9 +184,10 @@ def getiptcinfo(im): :returns: A dictionary containing IPTC information, or None if no IPTC information block was found. """ - from . import TiffImagePlugin, JpegImagePlugin import io + from . import JpegImagePlugin, TiffImagePlugin + data = None if isinstance(im, IptcImageFile): diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index dfd96d2df..6145e5da7 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -40,7 +40,10 @@ import tempfile import warnings from . import Image, ImageFile, TiffImagePlugin -from ._binary import i8, i16be as i16, i32be as i32, o8 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import o8 from .JpegPresets import presets # diff --git a/src/PIL/JpegPresets.py b/src/PIL/JpegPresets.py index 09691d79d..79d10ebb2 100644 --- a/src/PIL/JpegPresets.py +++ b/src/PIL/JpegPresets.py @@ -34,7 +34,7 @@ Possible subsampling values are 0, 1 and 2 that correspond to 4:4:4, 4:2:2 and 4:2:0. You can get the subsampling of a JPEG with the -`JpegImagePlugin.get_sampling(im)` function. +:func:`.JpegImagePlugin.get_sampling` function. In JPEG compressed data a JPEG marker is used instead of an EXIF tag. (ref.: https://www.exiv2.org/tags.html) @@ -64,7 +64,7 @@ The tables format between im.quantization and quantization in presets differ in 3. The zigzag order is remove in the preset (needed by libjpeg >= 6a). You can convert the dict format to the preset format with the -`JpegImagePlugin.convert_dict_qtables(dict_qtables)` function. +:func:`.JpegImagePlugin.convert_dict_qtables()` function. Libjpeg ref.: https://web.archive.org/web/20120328125543/http://www.jpegcameras.com/libjpeg/libjpeg-3.html diff --git a/src/PIL/MspImagePlugin.py b/src/PIL/MspImagePlugin.py index 03add47f4..9dd6e9f32 100644 --- a/src/PIL/MspImagePlugin.py +++ b/src/PIL/MspImagePlugin.py @@ -27,7 +27,9 @@ import io import struct from . import Image, ImageFile -from ._binary import i8, i16le as i16, o16le as o16 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import o16le as o16 # # read MSP files diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py index 3d96b481b..c1bd933d3 100644 --- a/src/PIL/PSDraw.py +++ b/src/PIL/PSDraw.py @@ -25,7 +25,7 @@ from . import EpsImagePlugin class PSDraw: """ - Sets up printing to the given file. If **fp** is omitted, + Sets up printing to the given file. If ``fp`` is omitted, :py:data:`sys.stdout` is assumed. """ diff --git a/src/PIL/PalmImagePlugin.py b/src/PIL/PalmImagePlugin.py index 553fe7b77..700f10e3f 100644 --- a/src/PIL/PalmImagePlugin.py +++ b/src/PIL/PalmImagePlugin.py @@ -8,7 +8,8 @@ ## from . import Image, ImageFile -from ._binary import o8, o16be as o16b +from ._binary import o8 +from ._binary import o16be as o16b # fmt: off _Palm8BitColormapValues = ( diff --git a/src/PIL/PcfFontFile.py b/src/PIL/PcfFontFile.py index f8836ad88..6a4eb22a6 100644 --- a/src/PIL/PcfFontFile.py +++ b/src/PIL/PcfFontFile.py @@ -19,7 +19,11 @@ import io from . import FontFile, Image -from ._binary import i8, i16be as b16, i16le as l16, i32be as b32, i32le as l32 +from ._binary import i8 +from ._binary import i16be as b16 +from ._binary import i16le as l16 +from ._binary import i32be as b32 +from ._binary import i32le as l32 # -------------------------------------------------------------------- # declarations diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 0306237dc..767f9945a 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -29,7 +29,10 @@ import io import logging from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16le as i16, o8, o16le as o16 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 logger = logging.getLogger(__name__) diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 9583f704a..36c8fb849 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 9900a5e12..bcc6b1c98 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -39,7 +39,12 @@ import warnings import zlib from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence -from ._binary import i8, i16be as i16, i32be as i32, o8, o16be as o16, o32be as o32 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 +from ._binary import o8 +from ._binary import o16be as o16 +from ._binary import o32be as o32 logger = logging.getLogger(__name__) @@ -509,10 +514,11 @@ class PngStream(ChunkStream): v = b"" if k: k = k.decode("latin-1", "strict") - v = v.decode("latin-1", "replace") + v_str = v.decode("latin-1", "replace") - self.im_info[k] = self.im_text[k] = v - self.check_text_memory(len(v)) + self.im_info[k] = v if k == "exif" else v_str + self.im_text[k] = v_str + self.check_text_memory(len(v_str)) return s @@ -1102,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) @@ -1147,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/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index 80bc116fc..8d1dbf2b2 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -19,7 +19,9 @@ import io from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16be as i16, i32be as i32 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import i32be as i32 MODES = { # (photoshop mode, bits) -> (pil mode, required channels) diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index 612ebe5a8..f878fefa9 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -26,7 +26,9 @@ import os import struct from . import Image, ImageFile -from ._binary import i8, i16be as i16, o8 +from ._binary import i8 +from ._binary import i16be as i16 +from ._binary import o8 def _accept(prefix): diff --git a/src/PIL/TgaImagePlugin.py b/src/PIL/TgaImagePlugin.py index d3f780d4d..69b3e0678 100644 --- a/src/PIL/TgaImagePlugin.py +++ b/src/PIL/TgaImagePlugin.py @@ -20,7 +20,10 @@ import warnings from . import Image, ImageFile, ImagePalette -from ._binary import i8, i16le as i16, o8, o16le as o16 +from ._binary import i8 +from ._binary import i16le as i16 +from ._binary import o8 +from ._binary import o16le as o16 # # -------------------------------------------------------------------- diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 453cc8660..6e1c45636 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -286,7 +286,7 @@ _write_dispatch = {} 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 @@ class IFDRational(Rational): return self._val.__hash__() def __eq__(self, other): + if isinstance(other, IFDRational): + other = other._val return self._val == other def _delegate(op): @@ -414,7 +416,7 @@ class ImageFileDirectory_v2(MutableMapping): The tiff metadata type of each item is stored in a dictionary of tag types in - `~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types + :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types are read from a tiff file, guessed from the type added, or added manually. @@ -883,7 +885,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): ('Some Data',) Also contains a dictionary of tag types as read from the tiff image file, - `~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`. + :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`. Values are returned as a tuple. @@ -897,9 +899,13 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): tags = property(lambda self: self._tags_v1) tagdata = property(lambda self: self._tagdata) + # defined in ImageFileDirectory_v2 + tagtype: dict + """Dictionary of tag types""" + @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` @@ -916,7 +922,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): 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` @@ -1086,8 +1092,8 @@ class TiffImageFile(ImageFile.ImageFile): 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/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 024222c9b..87847a107 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -20,7 +20,10 @@ # http://wvware.sourceforge.net/caolan/ora-wmf.html from . import Image, ImageFile -from ._binary import i16le as word, i32le as dword, si16le as short, si32le as _long +from ._binary import i16le as word +from ._binary import i32le as dword +from ._binary import si16le as short +from ._binary import si32le as _long _handler = None diff --git a/src/PIL/features.py b/src/PIL/features.py index d2e861372..fa86b9cb0 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -237,10 +237,12 @@ def pilinfo(out=None, supported_formats=True): print(f" {py_version.strip()}", file=out) print("-" * 68, file=out) print( - f"Python modules loaded from {os.path.dirname(Image.__file__)}", file=out, + f"Python modules loaded from {os.path.dirname(Image.__file__)}", + file=out, ) print( - f"Binary modules loaded from {os.path.dirname(Image.core.__file__)}", file=out, + f"Binary modules loaded from {os.path.dirname(Image.core.__file__)}", + file=out, ) print("-" * 68, file=out) diff --git a/src/decode.c b/src/decode.c index 25ce7316b..f60fb490e 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 d64f47d2b..62a6ec387 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 b0f8ad326..a09ee82f7 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 84ccb14ea..4e862af57 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/appveyor_build_msys2.sh b/winbuild/appveyor_build_msys2.sh deleted file mode 100644 index 489f9411e..000000000 --- a/winbuild/appveyor_build_msys2.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -cd /c/pillow && /mingw32/$EXECUTABLE setup.py install diff --git a/winbuild/appveyor_install_msys2_deps.sh b/winbuild/appveyor_install_msys2_deps.sh deleted file mode 100644 index 4cc01082d..000000000 --- a/winbuild/appveyor_install_msys2_deps.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -mkdir /var/cache/pacman/pkg -pacman -S --noconfirm mingw32/mingw-w64-i686-python3-pip \ - mingw32/mingw-w64-i686-python3-setuptools \ - mingw32/mingw-w64-i686-python3-pytest \ - mingw32/mingw-w64-i686-python3-pytest-cov \ - mingw-w64-i686-libjpeg-turbo \ - mingw-w64-i686-libimagequant - -C:/msys64/mingw32/bin/python3 -m pip install --upgrade pip - -/mingw32/bin/pip install olefile -/mingw32/bin/pip3 install olefile diff --git a/winbuild/build.rst b/winbuild/build.rst index aaed2c43f..ba568a030 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 ec292b14d..e05ac1a29 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -205,7 +205,7 @@ deps = { # retarget to default toolset (selected by vcvarsall.bat) "v141": "$(DefaultPlatformToolset)", # noqa: E501 # retarget to latest (selected by vcvarsall.bat) - "8.1": "$(WindowsSDKVersion)", # noqa: E501 + "10.0.17134.0": "$(WindowsSDKVersion)", # noqa: E501 } }, "build": [ @@ -251,9 +251,9 @@ deps = { "libs": [r"*.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/2.7.0.zip", - "filename": "harfbuzz-2.7.0.zip", - "dir": "harfbuzz-2.7.0", + "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"), @@ -356,8 +356,8 @@ def find_msvs(): def extract_dep(url, filename): - import urllib.request import tarfile + import urllib.request import zipfile file = os.path.join(depends_dir, filename) @@ -378,10 +378,10 @@ def extract_dep(url, filename): print("Extracting " + filename) if filename.endswith(".zip"): with zipfile.ZipFile(file) as zf: - zf.extractall(build_dir) + zf.extractall(sources_dir) elif filename.endswith(".tar.gz") or filename.endswith(".tgz"): with tarfile.open(file, "r:gz") as tgz: - tgz.extractall(build_dir) + tgz.extractall(sources_dir) else: raise RuntimeError("Unknown archive type: " + filename) @@ -416,11 +416,14 @@ def build_dep(name): extract_dep(dep["url"], dep["filename"]) for patch_file, patch_list in dep.get("patch", {}).items(): - patch_file = os.path.join(build_dir, dir, patch_file.format(**prefs)) + patch_file = os.path.join(sources_dir, dir, patch_file.format(**prefs)) with open(patch_file) as f: text = f.read() for patch_from, patch_to in patch_list.items(): - text = text.replace(patch_from.format(**prefs), patch_to.format(**prefs)) + patch_from = patch_from.format(**prefs) + patch_to = patch_to.format(**prefs) + assert patch_from in text + text = text.replace(patch_from, patch_to) with open(patch_file, "w") as f: f.write(text) @@ -429,7 +432,7 @@ def build_dep(name): "@echo " + ("=" * 70), f"@echo ==== {banner:<60} ====", "@echo " + ("=" * 70), - "cd /D %s" % os.path.join(build_dir, dir), + "cd /D %s" % os.path.join(sources_dir, dir), *prefs["header"], *dep.get("build", []), *get_footer(dep), @@ -478,6 +481,7 @@ if __name__ == "__main__": "ARCHITECTURE", "x86" if struct.calcsize("P") == 4 else "x64" ) build_dir = os.environ.get("PILLOW_BUILD", os.path.join(winbuild_dir, "build")) + sources_dir = "" for arg in sys.argv[1:]: if arg == "-v": verbose = True @@ -495,6 +499,8 @@ if __name__ == "__main__": architecture = arg[15:] elif arg.startswith("--dir="): build_dir = arg[6:] + elif arg == "--srcdir": + sources_dir = os.path.sep + "src" else: raise ValueError("Unknown parameter: " + arg) @@ -525,10 +531,13 @@ if __name__ == "__main__": lib_dir = os.path.join(build_dir, "lib") # build directory for *.bin files bin_dir = os.path.join(build_dir, "bin") + # directory for storing project files + sources_dir = build_dir + sources_dir shutil.rmtree(build_dir, ignore_errors=True) - for path in [build_dir, inc_dir, lib_dir, bin_dir]: - os.makedirs(path) + os.makedirs(build_dir, exist_ok=False) + for path in [inc_dir, lib_dir, bin_dir, sources_dir]: + os.makedirs(path, exist_ok=True) prefs = { # Python paths / preferences @@ -544,6 +553,7 @@ if __name__ == "__main__": "inc_dir": inc_dir, "lib_dir": lib_dir, "bin_dir": bin_dir, + "src_dir": sources_dir, # Compilers / Tools **msvs, "cmake": "cmake.exe", # TODO find CMAKE automatically @@ -554,5 +564,6 @@ if __name__ == "__main__": print() + write_script(".gitignore", ["*"]) build_dep_all() build_pillow()