Merge remote-tracking branch 'upstream/master' into formats

This commit is contained in:
nulano 2020-09-04 21:07:34 +02:00
commit a340dc5fd3
221 changed files with 1507 additions and 923 deletions

View File

@ -30,7 +30,10 @@ pip install -U pytest-cov
pip install pyroma pip install pyroma
pip install test-image-results pip install test-image-results
pip install numpy 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 if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then
# arm64, ppc64le, s390x CPUs: # arm64, ppc64le, s390x CPUs:
# "ERROR: Could not find a version that satisfies the requirement pyqt5" # "ERROR: Could not find a version that satisfies the requirement pyqt5"

View File

@ -2,7 +2,7 @@
set -e set -e
python -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests python -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests
# Docs # Docs
if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ] && [ "$TRAVIS_CPU_ARCH" == "amd64" ]; then if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ] && [ "$TRAVIS_CPU_ARCH" == "amd64" ]; then

View File

@ -15,5 +15,8 @@ pip install test-image-results
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
pip install numpy 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 # extra test images
pushd depends && ./install_extra_test_images.sh && popd pushd depends && ./install_extra_test_images.sh && popd

View File

@ -63,7 +63,12 @@ jobs:
- name: pip install wheel pytest pytest-cov - name: pip install wheel pytest pytest-cov
run: python -m 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: | run: |
7z x winbuild\depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\" 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" 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" Write-Host "::add-path::C:\Program Files (x86)\gs\gs9.50\bin"
xcopy /s winbuild\depends\test_images\* Tests\images\ 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 shell: pwsh
- name: Build dependencies / libjpeg-turbo - name: Build dependencies / libjpeg-turbo
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libjpeg.cmd" run: "& winbuild\\build\\build_dep_libjpeg.cmd"
- name: Build dependencies / zlib - name: Build dependencies / zlib
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_zlib.cmd" run: "& winbuild\\build\\build_dep_zlib.cmd"
- name: Build dependencies / LibTiff - name: Build dependencies / LibTiff
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libtiff.cmd" run: "& winbuild\\build\\build_dep_libtiff.cmd"
- name: Build dependencies / WebP - name: Build dependencies / WebP
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libwebp.cmd" run: "& winbuild\\build\\build_dep_libwebp.cmd"
- name: Build dependencies / FreeType - name: Build dependencies / FreeType
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_freetype.cmd" run: "& winbuild\\build\\build_dep_freetype.cmd"
- name: Build dependencies / LCMS2 - name: Build dependencies / LCMS2
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_lcms2.cmd" run: "& winbuild\\build\\build_dep_lcms2.cmd"
- name: Build dependencies / OpenJPEG - name: Build dependencies / OpenJPEG
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_openjpeg.cmd" run: "& winbuild\\build\\build_dep_openjpeg.cmd"
# GPL licensed; skip if building wheels # GPL licensed
- name: Build dependencies / libimagequant - name: Build dependencies / libimagequant
if: "github.event_name != 'push'" if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libimagequant.cmd" run: "& winbuild\\build\\build_dep_libimagequant.cmd"
# Raqm dependencies # Raqm dependencies
- name: Build dependencies / HarfBuzz - name: Build dependencies / HarfBuzz
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_harfbuzz.cmd" run: "& winbuild\\build\\build_dep_harfbuzz.cmd"
- name: Build dependencies / FriBidi - name: Build dependencies / FriBidi
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_fribidi.cmd" run: "& winbuild\\build\\build_dep_fribidi.cmd"
- name: Build dependencies / Raqm - name: Build dependencies / Raqm
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libraqm.cmd" 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 - name: Build Pillow
run: | 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 & $env:pythonLocation\python.exe selftest.py --installed
shell: pwsh shell: pwsh
@ -151,7 +186,7 @@ jobs:
if: "github.event_name == 'push'" if: "github.event_name == 'push'"
run: | run: |
for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ::set-output name=dist::dist-%%a 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 shell: cmd
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
@ -169,8 +204,10 @@ jobs:
mingw: ["MINGW32", "MINGW64"] mingw: ["MINGW32", "MINGW64"]
include: include:
- mingw: "MINGW32" - mingw: "MINGW32"
name: "MSYS2 MinGW 32-bit"
package: "mingw-w64-i686" package: "mingw-w64-i686"
- mingw: "MINGW64" - mingw: "MINGW64"
name: "MSYS2 MinGW 64-bit"
package: "mingw-w64-x86_64" package: "mingw-w64-x86_64"
defaults: defaults:
@ -181,7 +218,7 @@ jobs:
CHERE_INVOKING: 1 CHERE_INVOKING: 1
timeout-minutes: 30 timeout-minutes: 30
name: MSYS2 ${{ matrix.mingw }} name: ${{ matrix.name }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -193,23 +230,23 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
run: | run: |
pacman -S --noconfirm \ pacman -S --noconfirm \
${{ matrix.package }}-python3-cffi \
${{ matrix.package }}-python3-numpy \
${{ matrix.package }}-python3-olefile \
${{ matrix.package }}-python3-pip \ ${{ matrix.package }}-python3-pip \
${{ matrix.package }}-python3-setuptools \ ${{ matrix.package }}-python3-pyqt5 \
${{ matrix.package }}-python3-pytest \ ${{ matrix.package }}-python3-pytest \
${{ matrix.package }}-python3-pytest-cov \ ${{ matrix.package }}-python3-pytest-cov \
${{ matrix.package }}-python3-cffi \ ${{ matrix.package }}-python3-setuptools \
${{ matrix.package }}-python3-olefile \
${{ matrix.package }}-python3-numpy \
${{ matrix.package }}-python3-pyqt5 \
${{ matrix.package }}-python3-numpy \
${{ matrix.package }}-freetype \ ${{ 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 }}-ghostscript \
${{ matrix.package }}-lcms2 \
${{ matrix.package }}-libimagequant \
${{ matrix.package }}-libjpeg-turbo \
${{ matrix.package }}-libraqm \
${{ matrix.package }}-libtiff \
${{ matrix.package }}-libwebp \
${{ matrix.package }}-openjpeg2 \
subversion subversion
python3 -m pip install pyroma python3 -m pip install pyroma
@ -217,9 +254,7 @@ jobs:
pushd depends && ./install_extra_test_images.sh && popd pushd depends && ./install_extra_test_images.sh && popd
- name: Build Pillow - name: Build Pillow
run: | run: CFLAGS="-coverage" python3 setup.py build_ext install
# libtiff is unable to open files
CFLAGS="-coverage" python3 setup.py build_ext --disable-tiff install
- name: Test Pillow - name: Test Pillow
run: | run: |
@ -231,4 +266,4 @@ jobs:
python3 -m pip install codecov python3 -m pip install codecov
bash <(curl -s https://codecov.io/bash) -F GHA_Windows bash <(curl -s https://codecov.io/bash) -F GHA_Windows
env: env:
CODECOV_NAME: MSYS2 ${{ matrix.mingw }} CODECOV_NAME: ${{ matrix.name }}

View File

@ -62,11 +62,15 @@ jobs:
if: startsWith(matrix.os, 'ubuntu') if: startsWith(matrix.os, 'ubuntu')
run: | run: |
.ci/install.sh .ci/install.sh
env:
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
- name: Install macOS dependencies - name: Install macOS dependencies
if: startsWith(matrix.os, 'macOS') if: startsWith(matrix.os, 'macOS')
run: | run: |
.github/workflows/macos-install.sh .github/workflows/macos-install.sh
env:
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
- name: Build - name: Build
run: | run: |

View File

@ -1,43 +1,43 @@
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 6bedb5c58a7d8c25aa9509f8217bc24e9797e90d # frozen: 19.10b0 rev: e66be67b9b6811913470f70c28b4d50f94d05b22 # frozen: 20.8b1
hooks: hooks:
- id: black - id: black
args: ["--target-version", "py35"] args: ["--target-version", "py36"]
# Only .py files, until https://github.com/psf/black/issues/402 resolved # Only .py files, until https://github.com/psf/black/issues/402 resolved
files: \.py$ files: \.py$
types: [] types: []
- repo: https://github.com/timothycrosley/isort - repo: https://github.com/timothycrosley/isort
rev: 7c29dd9d55161704cfc45998c6f5c2c43d39264b # frozen: 4.3.21 rev: 377d260ffa6f746693f97b46d95025afc4bd8275 # frozen: 5.4.2
hooks: hooks:
- id: isort - id: isort
- repo: https://github.com/asottile/yesqa - repo: https://github.com/asottile/yesqa
rev: b13a51aa54142c59219c764e9f9362c049b439ed # frozen: v1.2.0 rev: 7a009f3ee493c796827ee334f9058b110a0e0db8 # frozen: v1.2.1
hooks: hooks:
- id: yesqa - id: yesqa
- repo: https://github.com/Lucas-C/pre-commit-hooks - repo: https://github.com/Lucas-C/pre-commit-hooks
rev: ffbd448645bad2e7ca13f96fca5830058d27ccd5 # frozen: v1.1.7 rev: f30f4974a08a6b2f6a1eeaf30a4d501cf909163a # frozen: v1.1.9
hooks: hooks:
- id: remove-tabs - id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$) exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
- repo: https://gitlab.com/pycqa/flake8 - repo: https://gitlab.com/pycqa/flake8
rev: 735cfe7e1c57a8e05f660ba75de72313005af54a # frozen: 3.8.2 rev: 05f6544aef321e2fee03a1277ce2eef8880fb927 # frozen: 3.8.3
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: [flake8-2020, flake8-implicit-str-concat] additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
- repo: https://github.com/pre-commit/pygrep-hooks - repo: https://github.com/pre-commit/pygrep-hooks
rev: 0d7d077d6ed5624854f93ac601739c1804ebeb98 # frozen: v1.5.1 rev: eae6397e4c259ed3d057511f6dd5330b92867e62 # frozen: v1.6.0
hooks: hooks:
- id: python-check-blanket-noqa - id: python-check-blanket-noqa
- id: rst-backticks - id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: ebc15addedad713c86ef18ae9632c88e187dd0af # frozen: v3.1.0 rev: e1668fe86af3810fbca72b8653fe478e66a0afdc # frozen: v3.2.0
hooks: hooks:
- id: check-merge-conflict - id: check-merge-conflict
- id: check-yaml - id: check-yaml

View File

@ -5,13 +5,43 @@ Changelog (Pillow)
8.0.0 (unreleased) 8.0.0 (unreleased)
------------------ ------------------
- Raise proper TypeError in putpixel #4882
[nulano, hugovk]
- Added writing of subIFDs #4862
[radarhere]
- 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 - Remove long-deprecated Image.py functions #4798
[hugovk, nulano, radarhere] [hugovk, nulano, radarhere]
- Replaced distutils with setuptools #4797, #4809, #4814, #4817, #4829, #4890
[hugovk, radarhere]
- Add MIME type to PsdImagePlugin #4788 - Add MIME type to PsdImagePlugin #4788
[samamorgan] [samamorgan]
- Drop support for EOL Python 3.5 #4746 - Drop support for EOL Python 3.5 #4746, #4794
[hugovk, radarhere, nulano] [hugovk, radarhere, nulano]
- Remove ImageCms.CmsProfile attributes deprecated since 3.2.0 #4768 - Remove ImageCms.CmsProfile attributes deprecated since 3.2.0 #4768

96
README.md Normal file
View File

@ -0,0 +1,96 @@
<p align="center">
<img width="248" height="250" src="https://raw.githubusercontent.com/python-pillow/pillow-logo/master/pillow-logo-248x250.png" alt="Pillow logo">
</p>
# 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).
<table>
<tr>
<th>docs</th>
<td>
<a href="https://pillow.readthedocs.io/?badge=latest"><img
alt="Documentation Status"
src="https://readthedocs.org/projects/pillow/badge/?version=latest"></a>
</td>
</tr>
<tr>
<th>tests</th>
<td>
<a href="https://travis-ci.org/python-pillow/Pillow"><img
alt="Travis CI build status (Linux)"
src="https://img.shields.io/travis/python-pillow/Pillow/master.svg?label=Linux%20build"></a>
<a href="https://travis-ci.org/python-pillow/pillow-wheels"><img
alt="Travis CI build status (macOS)"
src="https://img.shields.io/travis/python-pillow/pillow-wheels/master.svg?label=macOS%20build"></a>
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
alt="AppVeyor CI build status (Windows)"
src="https://img.shields.io/appveyor/build/python-pillow/Pillow/master.svg?label=Windows%20build"></a>
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint"><img
alt="GitHub Actions build status (Lint)"
src="https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3ATest"><img
alt="GitHub Actions build status (Test Linux and macOS)"
src="https://github.com/python-pillow/Pillow/workflows/Test/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Windows%22"><img
alt="GitHub Actions build status (Test Windows)"
src="https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Docker%22"><img
alt="GitHub Actions build status (Test Docker)"
src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
<a href="https://codecov.io/gh/python-pillow/Pillow"><img
alt="Code coverage"
src="https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg"></a>
</td>
</tr>
<tr>
<th>package</th>
<td>
<a href="https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow"><img
alt="Zenodo"
src="https://zenodo.org/badge/17549/python-pillow/Pillow.svg"></a>
<a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
alt="Tidelift"
src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a>
<a href="https://pypi.org/project/Pillow/"><img
alt="Newest PyPI version"
src="https://img.shields.io/pypi/v/pillow.svg"></a>
<a href="https://pypi.org/project/Pillow/"><img
alt="Number of PyPI downloads"
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
</td>
</tr>
<tr>
<th>social</th>
<td>
<a href="https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img
alt="Join the chat at https://gitter.im/python-pillow/Pillow"
src="https://badges.gitter.im/python-pillow/Pillow.svg"></a>
<a href="https://twitter.com/PythonPillow"><img
alt="Follow on https://twitter.com/PythonPillow"
src="https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg"></a>
</td>
</tr>
</table>
## 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).

View File

@ -1,103 +0,0 @@
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>`_.
.. 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 <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>`_.
.. |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

View File

@ -101,11 +101,7 @@ Released as needed privately to individual vendors for critical security-related
cd pillow-wheels cd pillow-wheels
./update-pillow-tag.sh [[release tag]] ./update-pillow-tag.sh [[release tag]]
``` ```
* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/). * [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases).
```bash
wget -m -A 'Pillow-<VERSION>-*' \
http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com
```
## Publicize Release ## Publicize Release

View File

@ -28,15 +28,17 @@ def timer(func, label, *args):
func(*args) func(*args)
if time.time() - starttime > 10: if time.time() - starttime > 10:
print( print(
"%s: breaking at %s iterations, %.6f per iteration" "{}: breaking at {} iterations, {:.6f} per iteration".format(
% (label, x + 1, (time.time() - starttime) / (x + 1.0)) label, x + 1, (time.time() - starttime) / (x + 1.0)
)
) )
break break
if x == iterations - 1: if x == iterations - 1:
endtime = time.time() endtime = time.time()
print( print(
"%s: %.4f s %.6f per iteration" "{}: {:.4f} s {:.6f} per iteration".format(
% (label, endtime - starttime, (endtime - starttime) / (x + 1.0)) label, endtime - starttime, (endtime - starttime) / (x + 1.0)
)
) )

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import is_win32 from .helper import is_win32
@ -11,7 +12,7 @@ pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
def _get_mem_usage(): def _get_mem_usage():
from resource import getpagesize, getrusage, RUSAGE_SELF from resource import RUSAGE_SELF, getpagesize, getrusage
mem = getrusage(RUSAGE_SELF).ru_maxrss mem = getrusage(RUSAGE_SELF).ru_maxrss
return mem * getpagesize() / 1024 / 1024 return mem * getpagesize() / 1024 / 1024
@ -25,7 +26,7 @@ def _test_leak(min_iterations, max_iterations, fn, *args, **kwargs):
if i < min_iterations: if i < min_iterations:
mem_limit = mem + 1 mem_limit = mem + 1
continue continue
msg = "memory usage limit exceeded after %d iterations" % (i + 1) msg = f"memory usage limit exceeded after {i + 1} iterations"
assert mem <= mem_limit, msg assert mem <= mem_limit, msg

View File

@ -1,6 +1,7 @@
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import is_win32, skip_unless_feature from .helper import is_win32, skip_unless_feature
@ -18,7 +19,7 @@ pytestmark = [
def test_leak_load(): 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_STACK, (stack_size, stack_size))
setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
@ -28,7 +29,7 @@ def test_leak_load():
def test_leak_save(): 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_STACK, (stack_size, stack_size))
setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) setrlimit(RLIMIT_AS, (mem_limit, mem_limit))

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image from PIL import Image

View File

@ -119,60 +119,59 @@ def test_qtables_leak():
def test_exif_leak(): def test_exif_leak():
""" """
pre patch: pre patch:
MB MB
177.1^ # 177.1^ #
| @@@# | @@@#
| :@@@@@@# | :@@@@@@#
| ::::@@@@@@# | ::::@@@@@@#
| ::::::::@@@@@@# | ::::::::@@@@@@#
| @@::::: ::::@@@@@@# | @@::::: ::::@@@@@@#
| @@@@ ::::: ::::@@@@@@# | @@@@ ::::: ::::@@@@@@#
| @@@@@@@ ::::: ::::@@@@@@# | @@@@@@@ ::::: ::::@@@@@@#
| @@::@@@@@@@ ::::: ::::@@@@@@# | @@::@@@@@@@ ::::: ::::@@@@@@#
| @@@@ : @@@@@@@ ::::: ::::@@@@@@# | @@@@ : @@@@@@@ ::::: ::::@@@@@@#
| @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@# | @@@@@@ @@ : @@@@@@@ ::::: ::::@@@@@@#
| @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# | @@@@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# | @::@@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# | ::::@: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# | :@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# | ::@@::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# | @@::: @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# | @::@ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# | :::@: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
| @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@# | @@@:: @: @ : : @ ::@@: : @: @@ @@ @@ @ @@ : @@@@@@@ ::::: ::::@@@@@@#
0 +----------------------------------------------------------------------->Gi 0 +----------------------------------------------------------------------->Gi
0 11.37 0 11.37
post patch: post patch:
MB MB
21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: 21.06^ ::::::::::::::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | ##::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | # ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @@@@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
| @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
0 +----------------------------------------------------------------------->Gi 0 +----------------------------------------------------------------------->Gi
0 11.33 0 11.33
"""
"""
im = hopper("RGB") im = hopper("RGB")
exif = b"12345678" * 4096 exif = b"12345678" * 4096
@ -183,31 +182,30 @@ post patch:
def test_base_save(): def test_base_save():
""" """
base case: base case:
MB MB
20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@::: 20.99^ ::::: :::::::::::::::::::::::::::::::::::::::::::@:::
| ##: : ::::::@::::::: :::: :::: : : : : : : :::::::::::: :::@::: | ##: : ::::::@::::::: :::: :::: : : : : : : :::::::::::: :::@:::
| # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@# : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | @@# : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@@ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | @@@ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | @ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| :@@@@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | :@@@@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
0 +----------------------------------------------------------------------->Gi 0 +----------------------------------------------------------------------->Gi
0 7.882 0 7.882"""
"""
im = hopper("RGB") im = hopper("RGB")
for _ in range(iterations): for _ in range(iterations):

View File

@ -1,6 +1,7 @@
import sys import sys
import pytest import pytest
from PIL import Image from PIL import Image
# This test is not run automatically. # This test is not run automatically.

View File

@ -1,6 +1,7 @@
import sys import sys
import pytest import pytest
from PIL import Image from PIL import Image
# This test is not run automatically. # This test is not run automatically.

View File

@ -1,13 +1,14 @@
import pytest import pytest
from PIL import Image from PIL import Image
TEST_FILE = "Tests/images/libtiff_segfault.tif" TEST_FILE = "Tests/images/libtiff_segfault.tif"
def test_libtiff_segfault(): def test_libtiff_segfault():
""" This test should not segfault. It will on Pillow <= 3.1.0 and """This test should not segfault. It will on Pillow <= 3.1.0 and
libtiff >= 4.0.0 libtiff >= 4.0.0
""" """
with pytest.raises(OSError): with pytest.raises(OSError):
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:

View File

@ -42,8 +42,8 @@ def test_dos_total_memory():
info = PngImagePlugin.PngInfo() info = PngImagePlugin.PngInfo()
for x in range(64): for x in range(64):
info.add_text("t%s" % x, compressed_data, zip=True) info.add_text(f"t{x}", compressed_data, zip=True)
info.add_itxt("i%s" % x, compressed_data, zip=True) info.add_itxt(f"i{x}", compressed_data, zip=True)
b = BytesIO() b = BytesIO()
im.save(b, "PNG", pnginfo=info) im.save(b, "PNG", pnginfo=info)

View File

@ -9,4 +9,4 @@ def pytest_report_header(config):
features.pilinfo(out=out, supported_formats=False) features.pilinfo(out=out, supported_formats=False)
return out.getvalue() return out.getvalue()
except Exception as e: except Exception as e:
return "pytest_report_header failed: %s" % e return f"pytest_report_header failed: {e}"

View File

@ -6,7 +6,7 @@ if __name__ == "__main__":
# create font data chunk for embedding # create font data chunk for embedding
font = "Tests/images/courB08" font = "Tests/images/courB08"
print(" f._load_pilfont_data(") print(" f._load_pilfont_data(")
print(" # %s" % os.path.basename(font)) print(f" # {os.path.basename(font)}")
print(" BytesIO(base64.decodestring(b'''") print(" BytesIO(base64.decodestring(b'''")
with open(font + ".pil", "rb") as fp: with open(font + ".pil", "rb") as fp:
print(base64.b64encode(fp.read()).decode()) print(base64.b64encode(fp.read()).decode())

View File

@ -11,6 +11,7 @@ import tempfile
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, ImageMath, features from PIL import Image, ImageMath, features
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -67,37 +68,31 @@ def convert_to_comparable(a, b):
def assert_deep_equal(a, b, msg=None): def assert_deep_equal(a, b, msg=None):
try: try:
assert len(a) == len(b), msg or "got length {}, expected {}".format( assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
len(a), len(b)
)
except Exception: except Exception:
assert a == b, msg assert a == b, msg
def assert_image(im, mode, size, msg=None): def assert_image(im, mode, size, msg=None):
if mode is not None: if mode is not None:
assert im.mode == mode, msg or "got mode {!r}, expected {!r}".format( assert im.mode == mode, (
im.mode, mode msg or f"got mode {repr(im.mode)}, expected {repr(mode)}"
) )
if size is not None: if size is not None:
assert im.size == size, msg or "got size {!r}, expected {!r}".format( assert im.size == size, (
im.size, size msg or f"got size {repr(im.size)}, expected {repr(size)}"
) )
def assert_image_equal(a, b, msg=None): def assert_image_equal(a, b, msg=None):
assert a.mode == b.mode, msg or "got mode {!r}, expected {!r}".format( assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
a.mode, b.mode assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
)
assert a.size == b.size, msg or "got size {!r}, expected {!r}".format(
a.size, b.size
)
if a.tobytes() != b.tobytes(): if a.tobytes() != b.tobytes():
if HAS_UPLOADER: if HAS_UPLOADER:
try: try:
url = test_image_results.upload(a, b) url = test_image_results.upload(a, b)
logger.error("Url for test images: %s" % url) logger.error(f"Url for test images: {url}")
except Exception: except Exception:
pass pass
@ -112,12 +107,8 @@ def assert_image_equal_tofile(a, filename, msg=None, mode=None):
def assert_image_similar(a, b, epsilon, msg=None): def assert_image_similar(a, b, epsilon, msg=None):
assert a.mode == b.mode, msg or "got mode {!r}, expected {!r}".format( assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
a.mode, b.mode assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
)
assert a.size == b.size, msg or "got size {!r}, expected {!r}".format(
a.size, b.size
)
a, b = convert_to_comparable(a, b) a, b = convert_to_comparable(a, b)
@ -129,13 +120,14 @@ def assert_image_similar(a, b, epsilon, msg=None):
ave_diff = diff / (a.size[0] * a.size[1]) ave_diff = diff / (a.size[0] * a.size[1])
try: try:
assert epsilon >= ave_diff, ( assert epsilon >= ave_diff, (
msg or "" (msg or "")
) + " average pixel value difference %.4f > epsilon %.4f" % (ave_diff, epsilon) + f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}"
)
except Exception as e: except Exception as e:
if HAS_UPLOADER: if HAS_UPLOADER:
try: try:
url = test_image_results.upload(a, b) url = test_image_results.upload(a, b)
logger.error("Url for test images: %s" % url) logger.error(f"Url for test images: {url}")
except Exception: except Exception:
pass pass
raise e raise e
@ -166,7 +158,7 @@ def assert_tuple_approx_equal(actuals, targets, threshold, msg):
def skip_unless_feature(feature): def skip_unless_feature(feature):
reason = "%s not available" % feature reason = f"{feature} not available"
return pytest.mark.skipif(not features.check(feature), reason=reason) return pytest.mark.skipif(not features.check(feature), reason=reason)
@ -184,7 +176,7 @@ class PillowLeakTestCase:
:returns: memory usage in kilobytes :returns: memory usage in kilobytes
""" """
from resource import getrusage, RUSAGE_SELF from resource import RUSAGE_SELF, getrusage
mem = getrusage(RUSAGE_SELF).ru_maxrss mem = getrusage(RUSAGE_SELF).ru_maxrss
if sys.platform == "darwin": if sys.platform == "darwin":
@ -204,7 +196,7 @@ class PillowLeakTestCase:
for cycle in range(self.iterations): for cycle in range(self.iterations):
core() core()
mem = self._get_mem_usage() - start_mem mem = self._get_mem_usage() - start_mem
msg = "memory usage limit exceeded in iteration %d" % cycle msg = f"memory usage limit exceeded in iteration {cycle}"
assert mem < self.mem_limit, msg assert mem < self.mem_limit, msg

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
Tests/images/exif_text.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View File

@ -1,6 +1,7 @@
import os import os
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_similar from .helper import assert_image_similar
@ -15,8 +16,8 @@ def get_files(d, ext=".bmp"):
def test_bad(): def test_bad():
""" These shouldn't crash/dos, but they shouldn't return anything """These shouldn't crash/dos, but they shouldn't return anything
either """ either"""
for f in get_files("b"): for f in get_files("b"):
def open(f): def open(f):
@ -31,8 +32,8 @@ def test_bad():
def test_questionable(): def test_questionable():
""" These shouldn't crash/dos, but it's not well defined that these """These shouldn't crash/dos, but it's not well defined that these
are in spec """ are in spec"""
supported = [ supported = [
"pal8os2v2.bmp", "pal8os2v2.bmp",
"rgb24prof.bmp", "rgb24prof.bmp",
@ -49,15 +50,15 @@ def test_questionable():
with Image.open(f) as im: with Image.open(f) as im:
im.load() im.load()
if os.path.basename(f) not in supported: if os.path.basename(f) not in supported:
print("Please add %s to the partially supported bmp specs." % f) print(f"Please add {f} to the partially supported bmp specs.")
except Exception: # as msg: except Exception: # as msg:
if os.path.basename(f) in supported: if os.path.basename(f) in supported:
raise raise
def test_good(): def test_good():
""" These should all work. There's a set of target files in the """These should all work. There's a set of target files in the
html directory that we can compare against. """ html directory that we can compare against."""
# Target files, if they're not just replacing the extension # Target files, if they're not just replacing the extension
file_map = { file_map = {
@ -84,7 +85,7 @@ def test_good():
if name in file_map: if name in file_map:
return os.path.join(base, "html", file_map[name]) return os.path.join(base, "html", file_map[name])
name = os.path.splitext(name)[0] name = os.path.splitext(name)[0]
return os.path.join(base, "html", "%s.png" % name) return os.path.join(base, "html", f"{name}.png")
for f in get_files("g"): for f in get_files("g"):
try: try:
@ -107,4 +108,4 @@ def test_good():
os.path.join(base, "g", "pal8rle.bmp"), os.path.join(base, "g", "pal8rle.bmp"),
os.path.join(base, "g", "pal4rle.bmp"), os.path.join(base, "g", "pal4rle.bmp"),
) )
assert f in unsupported, "Unsupported Image {}: {}".format(f, msg) assert f in unsupported, f"Unsupported Image {f}: {msg}"

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, ImageFilter from PIL import Image, ImageFilter
sample = Image.new("L", (7, 5)) sample = Image.new("L", (7, 5))

View File

@ -1,6 +1,7 @@
from array import array from array import array
import pytest import pytest
from PIL import Image, ImageFilter from PIL import Image, ImageFilter
from .helper import assert_image_equal from .helper import assert_image_equal

View File

@ -1,6 +1,7 @@
import sys import sys
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import is_pypy from .helper import is_pypy

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import hopper from .helper import hopper

View File

@ -2,6 +2,7 @@ import io
import re import re
import pytest import pytest
from PIL import features from PIL import features
from .helper import skip_unless_feature from .helper import skip_unless_feature

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, ImageSequence, PngImagePlugin from PIL import Image, ImageSequence, PngImagePlugin
@ -311,7 +312,7 @@ def test_apng_sequence_errors():
] ]
for f in test_files: for f in test_files:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
with Image.open("Tests/images/apng/{0}".format(f)) as im: with Image.open(f"Tests/images/apng/{f}") as im:
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
im.load() im.load()
@ -358,7 +359,10 @@ def test_apng_save_split_fdat(tmp_path):
with Image.open("Tests/images/old-style-jpeg-compression.png") as im: with Image.open("Tests/images/old-style-jpeg-compression.png") as im:
frames = [im.copy(), Image.new("RGBA", im.size, (255, 0, 0, 255))] frames = [im.copy(), Image.new("RGBA", im.size, (255, 0, 0, 255))]
im.save( 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: with Image.open(test_file) as im:
exception = None exception = None

View File

@ -1,6 +1,7 @@
import io import io
import pytest import pytest
from PIL import BmpImagePlugin, Image from PIL import BmpImagePlugin, Image
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import BufrStubImagePlugin, Image from PIL import BufrStubImagePlugin, Image
from .helper import hopper from .helper import hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import CurImagePlugin, Image from PIL import CurImagePlugin, Image
TEST_FILE = "Tests/images/deerstalker.cur" TEST_FILE = "Tests/images/deerstalker.cur"

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import DcxImagePlugin, Image from PIL import DcxImagePlugin, Image
from .helper import assert_image_equal, hopper, is_pypy from .helper import assert_image_equal, hopper, is_pypy

View File

@ -2,6 +2,7 @@
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import DdsImagePlugin, Image from PIL import DdsImagePlugin, Image
from .helper import assert_image_equal from .helper import assert_image_equal

View File

@ -1,6 +1,7 @@
import io import io
import pytest import pytest
from PIL import EpsImagePlugin, Image, features from PIL import EpsImagePlugin, Image, features
from .helper import assert_image_similar, hopper, skip_unless_feature from .helper import assert_image_similar, hopper, skip_unless_feature

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import FitsStubImagePlugin, Image from PIL import FitsStubImagePlugin, Image
TEST_FILE = "Tests/images/hopper.fits" TEST_FILE = "Tests/images/hopper.fits"

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import FliImagePlugin, Image from PIL import FliImagePlugin, Image
from .helper import assert_image_equal, is_pypy from .helper import assert_image_equal, is_pypy

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image from PIL import Image
FpxImagePlugin = pytest.importorskip( FpxImagePlugin = pytest.importorskip(

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import GbrImagePlugin, Image from PIL import GbrImagePlugin, Image
from .helper import assert_image_equal from .helper import assert_image_equal

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import GdImageFile, UnidentifiedImageError from PIL import GdImageFile, UnidentifiedImageError
TEST_GD_FILE = "Tests/images/hopper.gd" TEST_GD_FILE = "Tests/images/hopper.gd"

View File

@ -1,6 +1,7 @@
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import GifImagePlugin, Image, ImageDraw, ImagePalette, features from PIL import GifImagePlugin, Image, ImageDraw, ImagePalette, features
from .helper import ( from .helper import (

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL.GimpPaletteFile import GimpPaletteFile from PIL.GimpPaletteFile import GimpPaletteFile

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import GribStubImagePlugin, Image from PIL import GribStubImagePlugin, Image
from .helper import hopper from .helper import hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Hdf5StubImagePlugin, Image from PIL import Hdf5StubImagePlugin, Image
TEST_FILE = "Tests/images/hdf5.h5" TEST_FILE = "Tests/images/hdf5.h5"

View File

@ -2,6 +2,7 @@ import io
import sys import sys
import pytest import pytest
from PIL import IcnsImagePlugin, Image, features from PIL import IcnsImagePlugin, Image, features
from .helper import assert_image_equal, assert_image_similar from .helper import assert_image_equal, assert_image_similar

View File

@ -1,6 +1,7 @@
import io import io
import pytest import pytest
from PIL import IcoImagePlugin, Image, ImageDraw from PIL import IcoImagePlugin, Image, ImageDraw
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper

View File

@ -1,6 +1,7 @@
import filecmp import filecmp
import pytest import pytest
from PIL import Image, ImImagePlugin from PIL import Image, ImImagePlugin
from .helper import assert_image_equal, hopper, is_pypy from .helper import assert_image_equal, hopper, is_pypy

View File

@ -3,10 +3,12 @@ import re
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import ( from PIL import (
ExifTags, ExifTags,
Image, Image,
ImageFile, ImageFile,
ImageOps,
JpegImagePlugin, JpegImagePlugin,
UnidentifiedImageError, UnidentifiedImageError,
features, features,
@ -38,7 +40,7 @@ class TestFileJpeg:
return im return im
def gen_random_image(self, size, mode="RGB"): 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 size: tuple
:param mode: optional image mode :param mode: optional image mode
@ -98,7 +100,8 @@ class TestFileJpeg:
assert k > 0.9 assert k > 0.9
@pytest.mark.parametrize( @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_dpi(self, test_image_path):
def test(xdpi, ydpi=None): def test(xdpi, ydpi=None):
@ -223,23 +226,58 @@ class TestFileJpeg:
# Should not raise a TypeError # Should not raise a TypeError
im._getexif() im._getexif()
def test_exif_gps(self): def test_exif_gps(self, tmp_path):
# Arrange expected_exif_gps = {
0: b"\x00\x00\x00\x01",
2: 4294967295,
5: b"\x01",
30: 65535,
29: "1999:99:99 99:99:99",
}
gps_index = 34853
# Reading
with Image.open("Tests/images/exif_gps.jpg") as im: with Image.open("Tests/images/exif_gps.jpg") as im:
gps_index = 34853
expected_exif_gps = {
0: b"\x00\x00\x00\x01",
2: 4294967295,
5: b"\x01",
30: 65535,
29: "1999:99:99 99:99:99",
}
# Act
exif = im._getexif() exif = im._getexif()
assert exif[gps_index] == expected_exif_gps
# Assert # Writing
assert exif[gps_index] == expected_exif_gps f = str(tmp_path / "temp.jpg")
exif = Image.Exif()
exif[gps_index] = expected_exif_gps
hopper().save(f, exif=exif)
with Image.open(f) as reloaded:
exif = reloaded._getexif()
assert exif[gps_index] == expected_exif_gps
def test_empty_exif_gps(self):
with Image.open("Tests/images/empty_gps_ifd.jpg") as im:
exif = im.getexif()
del exif[0x8769]
# Assert that it needs to be transposed
assert exif[0x0112] == Image.TRANSVERSE
# Assert that the GPS IFD is present and empty
assert exif[0x8825] == {}
transposed = ImageOps.exif_transpose(im)
exif = transposed.getexif()
assert exif[0x8825] == {}
# Assert that it was transposed
assert 0x0112 not in exif
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): def test_exif_rollback(self):
# rolling back exif support in 3.1 to pre-3.0 formatting. # rolling back exif support in 3.1 to pre-3.0 formatting.

View File

@ -2,6 +2,7 @@ import re
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, ImageFile, Jpeg2KImagePlugin, features from PIL import Image, ImageFile, Jpeg2KImagePlugin, features
from .helper import ( from .helper import (

View File

@ -7,6 +7,7 @@ from collections import namedtuple
from ctypes import c_float from ctypes import c_float
import pytest import pytest
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features
from .helper import ( from .helper import (
@ -170,18 +171,18 @@ class TestFileLibTiff(LibTiffTestCase):
assert ( assert (
c_float(val[0][0] / val[0][1]).value c_float(val[0][0] / val[0][1]).value
== c_float(value[0][0] / value[0][1]).value == c_float(value[0][0] / value[0][1]).value
), ("%s didn't roundtrip" % tag) ), f"{tag} didn't roundtrip"
else: else:
assert c_float(val).value == c_float(value).value, ( assert (
"%s didn't roundtrip" % tag c_float(val).value == c_float(value).value
) ), f"{tag} didn't roundtrip"
else: else:
assert val == value, "%s didn't roundtrip" % tag assert val == value, f"{tag} didn't roundtrip"
# https://github.com/python-pillow/Pillow/issues/1561 # https://github.com/python-pillow/Pillow/issues/1561
requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"] requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"]
for field in requested_fields: for field in requested_fields:
assert field in reloaded, "%s not in metadata" % field assert field in reloaded, f"{field} not in metadata"
def test_additional_metadata(self, tmp_path): def test_additional_metadata(self, tmp_path):
# these should not crash. Seriously dummy data, most of it doesn't make # these should not crash. Seriously dummy data, most of it doesn't make
@ -401,8 +402,8 @@ class TestFileLibTiff(LibTiffTestCase):
assert "temp.tif" == reread.tag[269][0] assert "temp.tif" == reread.tag[269][0]
def test_12bit_rawmode(self): def test_12bit_rawmode(self):
""" Are we generating the same interpretation """Are we generating the same interpretation
of the image as Imagemagick is? """ of the image as Imagemagick is?"""
TiffImagePlugin.READ_LIBTIFF = True TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/12bit.cropped.tif") as im: with Image.open("Tests/images/12bit.cropped.tif") as im:
im.load() im.load()
@ -502,9 +503,9 @@ class TestFileLibTiff(LibTiffTestCase):
assert len(reloaded.tag_v2[320]) == 768 assert len(reloaded.tag_v2[320]) == 768
def xtest_bw_compression_w_rgb(self, tmp_path): def xtest_bw_compression_w_rgb(self, tmp_path):
""" This test passes, but when running all tests causes a failure due """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 to output on stderr from the error thrown by libtiff. We need to
capture that but not now""" capture that but not now"""
im = hopper("RGB") im = hopper("RGB")
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
@ -767,7 +768,7 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.mode == "RGBA" assert im.mode == "RGBA"
assert im.size == (100, 40) assert im.size == (100, 40)
assert im.tile, [ 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() im.load()

View File

@ -7,13 +7,13 @@ from .test_file_libtiff import LibTiffTestCase
class TestFileLibTiffSmall(LibTiffTestCase): class TestFileLibTiffSmall(LibTiffTestCase):
""" The small lena image was failing on open in the libtiff """The small lena image was failing on open in the libtiff
decoder because the file pointer was set to the wrong place decoder because the file pointer was set to the wrong place
by a spurious seek. It wasn't failing with the byteio method. by a spurious seek. It wasn't failing with the byteio method.
It was fixed by forcing an lseek to the beginning of the It was fixed by forcing an lseek to the beginning of the
file just before reading in libtiff. These tests remain file just before reading in libtiff. These tests remain
to ensure that it stays fixed. """ to ensure that it stays fixed."""
def test_g4_hopper_file(self, tmp_path): def test_g4_hopper_file(self, tmp_path):
"""Testing the open file load path""" """Testing the open file load path"""

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, McIdasImagePlugin from PIL import Image, McIdasImagePlugin
from .helper import assert_image_equal from .helper import assert_image_equal

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, ImagePalette from PIL import Image, ImagePalette
from .helper import assert_image_similar, hopper, skip_unless_feature from .helper import assert_image_similar, hopper, skip_unless_feature

View File

@ -1,6 +1,7 @@
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_similar, is_pypy, skip_unless_feature from .helper import assert_image_similar, is_pypy, skip_unless_feature

View File

@ -1,6 +1,7 @@
import os import os
import pytest import pytest
from PIL import Image, MspImagePlugin from PIL import Image, MspImagePlugin
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper

View File

@ -2,6 +2,7 @@ import os.path
import subprocess import subprocess
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import IMCONVERT, assert_image_equal, hopper, imagemagick_available from .helper import IMCONVERT, assert_image_equal, hopper, imagemagick_available

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, ImageFile, PcxImagePlugin from PIL import Image, ImageFile, PcxImagePlugin
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper

View File

@ -5,6 +5,7 @@ import tempfile
import time import time
import pytest import pytest
from PIL import Image, PdfParser from PIL import Image, PdfParser
from .helper import hopper from .helper import hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, PixarImagePlugin from PIL import Image, PixarImagePlugin
from .helper import assert_image_similar, hopper from .helper import assert_image_similar, hopper

View File

@ -3,6 +3,7 @@ import zlib
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, ImageFile, PngImagePlugin, features from PIL import Image, ImageFile, PngImagePlugin, features
from .helper import ( from .helper import (
@ -606,6 +607,11 @@ class TestFilePng:
exif = im.copy().getexif() exif = im.copy().getexif()
assert exif[274] == 1 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 XMP tags
with Image.open("Tests/images/xmp_tags_orientation.png") as im: with Image.open("Tests/images/xmp_tags_orientation.png") as im:
exif = im.getexif() exif = im.getexif()

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal, assert_image_similar, hopper from .helper import assert_image_equal, assert_image_similar, hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, PsdImagePlugin from PIL import Image, PsdImagePlugin
from .helper import assert_image_similar, hopper, is_pypy from .helper import assert_image_similar, hopper, is_pypy

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, SgiImagePlugin from PIL import Image, SgiImagePlugin
from .helper import assert_image_equal, assert_image_similar, hopper from .helper import assert_image_equal, assert_image_similar, hopper

View File

@ -2,6 +2,7 @@ import tempfile
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, ImageSequence, SpiderImagePlugin from PIL import Image, ImageSequence, SpiderImagePlugin
from .helper import assert_image_equal, hopper, is_pypy from .helper import assert_image_equal, hopper, is_pypy

View File

@ -1,6 +1,7 @@
import os import os
import pytest import pytest
from PIL import Image, SunImagePlugin from PIL import Image, SunImagePlugin
from .helper import assert_image_equal, assert_image_similar, hopper from .helper import assert_image_equal, assert_image_similar, hopper
@ -45,7 +46,7 @@ def test_others():
with Image.open(path) as im: with Image.open(path) as im:
im.load() im.load()
assert isinstance(im, SunImagePlugin.SunImageFile) assert isinstance(im, SunImagePlugin.SunImageFile)
target_path = "%s.png" % os.path.splitext(path)[0] target_path = f"{os.path.splitext(path)[0]}.png"
# im.save(target_file) # im.save(target_file)
with Image.open(target_path) as target: with Image.open(target_path) as target:
assert_image_equal(im, target) assert_image_equal(im, target)

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, TarIO, features from PIL import Image, TarIO, features
from .helper import is_pypy from .helper import is_pypy

View File

@ -3,6 +3,7 @@ from glob import glob
from itertools import product from itertools import product
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper
@ -35,9 +36,7 @@ def test_sanity(tmp_path):
assert_image_equal(saved_im, original_im) assert_image_equal(saved_im, original_im)
png_paths = glob( png_paths = glob(os.path.join(_TGA_DIR_COMMON, f"*x*_{mode.lower()}.png"))
os.path.join(_TGA_DIR_COMMON, "*x*_{}.png".format(mode.lower()))
)
for png_path in png_paths: for png_path in png_paths:
with Image.open(png_path) as reference_im: with Image.open(png_path) as reference_im:

View File

@ -2,6 +2,7 @@ import os
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, TiffImagePlugin from PIL import Image, TiffImagePlugin
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
@ -224,8 +225,8 @@ class TestFileTiff:
assert im.getpixel((0, 1)) == 0 assert im.getpixel((0, 1)) == 0
def test_12bit_rawmode(self): def test_12bit_rawmode(self):
""" Are we generating the same interpretation """Are we generating the same interpretation
of the image as Imagemagick is? """ of the image as Imagemagick is?"""
with Image.open("Tests/images/12bit.cropped.tif") as im: with Image.open("Tests/images/12bit.cropped.tif") as im:
# to make the target -- # to make the target --

View File

@ -2,6 +2,7 @@ import io
import struct import struct
import pytest import pytest
from PIL import Image, TiffImagePlugin, TiffTags from PIL import Image, TiffImagePlugin, TiffTags
from PIL.TiffImagePlugin import IFDRational 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): def test_rt_metadata(tmp_path):
""" Test writing arbitrary metadata into the tiff image directory """Test writing arbitrary metadata into the tiff image directory
Use case is ImageJ private tags, one numeric, one arbitrary Use case is ImageJ private tags, one numeric, one arbitrary
data. https://github.com/python-pillow/Pillow/issues/291 data. https://github.com/python-pillow/Pillow/issues/291
""" """
img = hopper() img = hopper()
@ -144,16 +145,16 @@ def test_write_metadata(tmp_path):
assert_deep_equal( assert_deep_equal(
original[tag], original[tag],
value, value,
"{} didn't roundtrip, {}, {}".format(tag, original[tag], value), f"{tag} didn't roundtrip, {original[tag]}, {value}",
) )
else: else:
assert original[tag] == value, "{} didn't roundtrip, {}, {}".format( assert (
tag, original[tag], value original[tag] == value
) ), f"{tag} didn't roundtrip, {original[tag]}, {value}"
for tag, value in original.items(): for tag, value in original.items():
if tag not in ignored: if tag not in ignored:
assert value == reloaded[tag], "%s didn't roundtrip" % tag assert value == reloaded[tag], f"{tag} didn't roundtrip"
def test_change_stripbytecounts_tag_type(tmp_path): def test_change_stripbytecounts_tag_type(tmp_path):

View File

@ -2,6 +2,7 @@ import io
import re import re
import pytest import pytest
from PIL import Image, WebPImagePlugin, features from PIL import Image, WebPImagePlugin, features
from .helper import ( from .helper import (

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal, assert_image_similar, hopper from .helper import assert_image_equal, assert_image_similar, hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import ( from .helper import (

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, WmfImagePlugin from PIL import Image, WmfImagePlugin
from .helper import assert_image_similar, hopper from .helper import assert_image_similar, hopper

View File

@ -1,6 +1,7 @@
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import hopper from .helper import hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, XpmImagePlugin from PIL import Image, XpmImagePlugin
from .helper import assert_image_similar, hopper from .helper import assert_image_similar, hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, XVThumbImagePlugin from PIL import Image, XVThumbImagePlugin
from .helper import assert_image_similar, hopper from .helper import assert_image_similar, hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import BdfFontFile, FontFile from PIL import BdfFontFile, FontFile
filename = "Tests/images/courB08.bdf" filename = "Tests/images/courB08.bdf"

View File

@ -1,6 +1,7 @@
import os import os
import pytest import pytest
from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile
from .helper import assert_image_equal, assert_image_similar, skip_unless_feature from .helper import assert_image_equal, assert_image_similar, skip_unless_feature

View File

@ -47,11 +47,11 @@ def save_font(request, tmp_path, encoding):
font.save(tempname) font.save(tempname)
with Image.open(tempname.replace(".pil", ".pbm")) as loaded: with Image.open(tempname.replace(".pil", ".pbm")) as loaded:
with Image.open("Tests/fonts/ter-x20b-%s.pbm" % encoding) as target: with Image.open(f"Tests/fonts/ter-x20b-{encoding}.pbm") as target:
assert_image_equal(loaded, target) assert_image_equal(loaded, target)
with open(tempname, "rb") as f_loaded: with open(tempname, "rb") as f_loaded:
with open("Tests/fonts/ter-x20b-%s.pil" % encoding, "rb") as f_target: with open(f"Tests/fonts/ter-x20b-{encoding}.pil", "rb") as f_target:
assert f_loaded.read() == f_target.read() assert f_loaded.read() == f_target.read()
return tempname return tempname

View File

@ -85,7 +85,10 @@ def test_wedge():
im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong" im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong"
) )
assert_image_similar( 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( assert_image_similar(
im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong" 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" im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong"
) )
assert_image_similar( 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( assert_image_similar(
im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong" 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) comparable = to_rgb_colorsys(comparable)
assert_image_similar( 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( 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( 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",
) )

View File

@ -3,8 +3,9 @@ import os
import shutil import shutil
import tempfile import tempfile
import PIL
import pytest import pytest
import PIL
from PIL import Image, ImageDraw, ImagePalette, ImageShow, UnidentifiedImageError from PIL import Image, ImageDraw, ImagePalette, ImageShow, UnidentifiedImageError
from .helper import ( from .helper import (
@ -726,7 +727,8 @@ class TestImage:
} }
@pytest.mark.parametrize( @pytest.mark.parametrize(
"test_module", [PIL, Image], "test_module",
[PIL, Image],
) )
def test_pillow_version(self, test_module): def test_pillow_version(self, test_module):
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
@ -754,7 +756,7 @@ class TestImage:
assert test_module.PILLOW_VERSION > "7.0.0" assert test_module.PILLOW_VERSION > "7.0.0"
def test_overrun(self): 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 valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c
""" """
for file in [ for file in [

View File

@ -4,9 +4,9 @@ import subprocess
import sys import sys
import sysconfig import sysconfig
import pytest
from setuptools.command.build_ext import new_compiler from setuptools.command.build_ext import new_compiler
import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal, hopper, is_win32, on_ci from .helper import assert_image_equal, hopper, is_win32, on_ci
@ -17,8 +17,9 @@ if os.environ.get("PYTHONOPTIMIZE") == "2":
cffi = None cffi = None
else: else:
try: try:
from PIL import PyAccess
import cffi import cffi
from PIL import PyAccess
except ImportError: except ImportError:
cffi = None cffi = None
@ -127,14 +128,13 @@ class TestImageGetPixel(AccessTest):
im.putpixel((0, 0), c) im.putpixel((0, 0), c)
assert ( assert (
im.getpixel((0, 0)) == c im.getpixel((0, 0)) == c
), "put/getpixel roundtrip failed for mode {}, color {}".format(mode, c) ), f"put/getpixel roundtrip failed for mode {mode}, color {c}"
# check putpixel negative index # check putpixel negative index
im.putpixel((-1, -1), c) im.putpixel((-1, -1), c)
assert im.getpixel((-1, -1)) == c, ( assert (
"put/getpixel roundtrip negative index failed for mode %s, color %s" im.getpixel((-1, -1)) == c
% (mode, c) ), f"put/getpixel roundtrip negative index failed for mode {mode}, color {c}"
)
# Check 0 # Check 0
im = Image.new(mode, (0, 0), None) im = Image.new(mode, (0, 0), None)
@ -152,11 +152,11 @@ class TestImageGetPixel(AccessTest):
im = Image.new(mode, (1, 1), c) im = Image.new(mode, (1, 1), c)
assert ( assert (
im.getpixel((0, 0)) == c im.getpixel((0, 0)) == c
), "initial color failed for mode {}, color {} ".format(mode, c) ), f"initial color failed for mode {mode}, color {c} "
# check initial color negative index # check initial color negative index
assert ( assert (
im.getpixel((-1, -1)) == c im.getpixel((-1, -1)) == c
), "initial color failed with negative index for mode %s, color %s " % (mode, c) ), f"initial color failed with negative index for mode {mode}, color {c} "
# Check 0 # Check 0
im = Image.new(mode, (0, 0), c) im = Image.new(mode, (0, 0), c)
@ -327,6 +327,38 @@ class TestCffi(AccessTest):
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0) assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
class TestImagePutPixelError(AccessTest):
IMAGE_MODES1 = ["L", "LA", "RGB", "RGBA"]
IMAGE_MODES2 = ["I", "I;16", "BGR;15"]
INVALID_TYPES1 = ["foo", 1.0, None]
INVALID_TYPES2 = [*INVALID_TYPES1, (10,)]
@pytest.mark.parametrize("mode", IMAGE_MODES1)
def test_putpixel_type_error1(self, mode):
im = hopper(mode)
for v in self.INVALID_TYPES1:
with pytest.raises(TypeError, match="color must be int or tuple"):
im.putpixel((0, 0), v)
@pytest.mark.parametrize("mode", IMAGE_MODES2)
def test_putpixel_type_error2(self, mode):
im = hopper(mode)
for v in self.INVALID_TYPES2:
with pytest.raises(TypeError, match="color must be int"):
im.putpixel((0, 0), v)
@pytest.mark.parametrize("mode", IMAGE_MODES1 + IMAGE_MODES2)
def test_putpixel_overflow_error(self, mode):
im = hopper(mode)
with pytest.raises(OverflowError):
im.putpixel((0, 0), 2 ** 80)
def test_putpixel_unrecognized_mode(self):
im = hopper("BGR;15")
with pytest.raises(ValueError, match="unrecognized image mode"):
im.putpixel((0, 0), 0)
class TestEmbeddable: class TestEmbeddable:
@pytest.mark.skipif( @pytest.mark.skipif(
not is_win32() or on_ci(), not is_win32() or on_ci(),

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import hopper from .helper import hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import assert_image, assert_image_equal, assert_image_similar, hopper from .helper import assert_image, assert_image_equal, assert_image_similar, hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, ImageFilter from PIL import Image, ImageFilter
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, ImageQt from PIL import Image, ImageQt
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper

View File

@ -1,6 +1,8 @@
import logging
import os import os
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import hopper from .helper import hopper
@ -22,6 +24,14 @@ def test_close():
im.getpixel((0, 0)) 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(): def test_contextmanager():
fn = None fn = None
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:

View File

@ -24,9 +24,9 @@ def test_sanity():
def test_16bit_lut(): def test_16bit_lut():
""" Tests for 16 bit -> 8 bit lut for converting I->L images """Tests for 16 bit -> 8 bit lut for converting I->L images
see https://github.com/python-pillow/Pillow/issues/440 see https://github.com/python-pillow/Pillow/issues/440
""" """
im = hopper("I") im = hopper("I")
im.point(list(range(256)) * 256, "L") im.point(list(range(256)) * 256, "L")

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import ImagePalette from PIL import ImagePalette
from .helper import hopper from .helper import hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import assert_image, assert_image_similar, hopper from .helper import assert_image, assert_image_similar, hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, ImageMath, ImageMode from PIL import Image, ImageMath, ImageMode
from .helper import convert_to_comparable, skip_unless_feature from .helper import convert_to_comparable, skip_unless_feature
@ -161,8 +162,8 @@ def compare_reduce_with_reference(im, factor, average_diff=0.4, max_diff=1):
def assert_compare_images(a, b, max_average_diff, max_diff=255): def assert_compare_images(a, b, max_average_diff, max_diff=255):
assert a.mode == b.mode, "got mode %r, expected %r" % (a.mode, b.mode) assert a.mode == b.mode, f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
assert a.size == b.size, "got size %r, expected %r" % (a.size, b.size) assert a.size == b.size, f"got size {repr(a.size)}, expected {repr(b.size)}"
a, b = convert_to_comparable(a, b) a, b = convert_to_comparable(a, b)
@ -175,16 +176,15 @@ def assert_compare_images(a, b, max_average_diff, max_diff=255):
a.size[0] * a.size[1] a.size[0] * a.size[1]
) )
msg = ( msg = (
"average pixel value difference {:.4f} > expected {:.4f} " f"average pixel value difference {average_diff:.4f} > "
"for '{}' band".format(average_diff, max_average_diff, band) f"expected {max_average_diff:.4f} for '{band}' band"
) )
assert max_average_diff >= average_diff, msg assert max_average_diff >= average_diff, msg
last_diff = [i for i, num in enumerate(ch_hist) if num > 0][-1] last_diff = [i for i, num in enumerate(ch_hist) if num > 0][-1]
assert ( assert max_diff >= last_diff, (
max_diff >= last_diff f"max pixel value difference {last_diff} > expected {max_diff} "
), "max pixel value difference {} > expected {} for '{}' band".format( f"for '{band}' band"
last_diff, max_diff, band
) )

View File

@ -1,6 +1,7 @@
from contextlib import contextmanager from contextlib import contextmanager
import pytest import pytest
from PIL import Image, ImageDraw from PIL import Image, ImageDraw
from .helper import assert_image_equal, assert_image_similar, hopper from .helper import assert_image_equal, assert_image_similar, hopper
@ -81,15 +82,16 @@ class TestImagingCoreResampleAccuracy:
for y in range(case.size[1]): for y in range(case.size[1]):
for x in range(case.size[0]): for x in range(case.size[0]):
if c_px[x, y] != s_px[x, y]: if c_px[x, y] != s_px[x, y]:
message = "\nHave: \n{}\n\nExpected: \n{}".format( message = (
self.serialize_image(case), self.serialize_image(sample) f"\nHave: \n{self.serialize_image(case)}\n"
f"\nExpected: \n{self.serialize_image(sample)}"
) )
assert s_px[x, y] == c_px[x, y], message assert s_px[x, y] == c_px[x, y], message
def serialize_image(self, image): def serialize_image(self, image):
s_px = image.load() s_px = image.load()
return "\n".join( return "\n".join(
" ".join("{:02x}".format(s_px[x, y]) for x in range(image.size[0])) " ".join(f"{s_px[x, y]:02x}" for x in range(image.size[0]))
for y in range(image.size[1]) for y in range(image.size[1])
) )
@ -229,7 +231,7 @@ class TestCoreResampleConsistency:
for x in range(channel.size[0]): for x in range(channel.size[0]):
for y in range(channel.size[1]): for y in range(channel.size[1]):
if px[x, y] != color: if px[x, y] != color:
message = "{} != {} for pixel {}".format(px[x, y], color, (x, y)) message = f"{px[x, y]} != {color} for pixel {(x, y)}"
assert px[x, y] == color, message assert px[x, y] == color, message
def test_8u(self): def test_8u(self):
@ -268,10 +270,9 @@ class TestCoreResampleAlphaCorrect:
px = i.load() px = i.load()
for y in range(i.size[1]): for y in range(i.size[1]):
used_colors = {px[x, y][0] for x in range(i.size[0])} used_colors = {px[x, y][0] for x in range(i.size[0])}
assert 256 == len( assert 256 == len(used_colors), (
used_colors "All colors should be present in resized image. "
), "All colors should present in resized image. Only {} on {} line.".format( f"Only {len(used_colors)} on {y} line."
len(used_colors), y
) )
@pytest.mark.xfail(reason="Current implementation isn't precise enough") @pytest.mark.xfail(reason="Current implementation isn't precise enough")
@ -307,8 +308,9 @@ class TestCoreResampleAlphaCorrect:
for y in range(i.size[1]): for y in range(i.size[1]):
for x in range(i.size[0]): for x in range(i.size[0]):
if px[x, y][-1] != 0 and px[x, y][:-1] != clean_pixel: if px[x, y][-1] != 0 and px[x, y][:-1] != clean_pixel:
message = "pixel at ({}, {}) is differ:\n{}\n{}".format( message = (
x, y, px[x, y], clean_pixel f"pixel at ({x}, {y}) is different:\n"
f"{px[x, y]}\n{clean_pixel}"
) )
assert px[x, y][:3] == clean_pixel, message assert px[x, y][:3] == clean_pixel, message
@ -503,7 +505,7 @@ class TestCoreResampleBox:
]: ]:
res = im.resize(size, Image.LANCZOS, box) res = im.resize(size, Image.LANCZOS, box)
assert res.size == size assert res.size == size
assert_image_equal(res, im.crop(box), ">>> {} {}".format(size, box)) assert_image_equal(res, im.crop(box), f">>> {size} {box}")
def test_no_passthrough(self): def test_no_passthrough(self):
# When resize is required # When resize is required
@ -519,9 +521,7 @@ class TestCoreResampleBox:
assert res.size == size assert res.size == size
with pytest.raises(AssertionError, match=r"difference \d"): with pytest.raises(AssertionError, match=r"difference \d"):
# check that the difference at least that much # check that the difference at least that much
assert_image_similar( assert_image_similar(res, im.crop(box), 20, f">>> {size} {box}")
res, im.crop(box), 20, ">>> {} {}".format(size, box)
)
def test_skip_horizontal(self): def test_skip_horizontal(self):
# Can skip resize for one dimension # Can skip resize for one dimension
@ -541,7 +541,7 @@ class TestCoreResampleBox:
res, res,
im.crop(box).resize(size, flt), im.crop(box).resize(size, flt),
0.4, 0.4,
">>> {} {} {}".format(size, box, flt), f">>> {size} {box} {flt}",
) )
def test_skip_vertical(self): def test_skip_vertical(self):
@ -562,5 +562,5 @@ class TestCoreResampleBox:
res, res,
im.crop(box).resize(size, flt), im.crop(box).resize(size, flt),
0.4, 0.4,
">>> {} {} {}".format(size, box, flt), f">>> {size} {box} {flt}",
) )

View File

@ -4,6 +4,7 @@ Tests for resize functionality.
from itertools import permutations from itertools import permutations
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal, assert_image_similar, hopper from .helper import assert_image_equal, assert_image_similar, hopper

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import ( from .helper import (

View File

@ -1,6 +1,7 @@
import math import math
import pytest import pytest
from PIL import Image, ImageTransform from PIL import Image, ImageTransform
from .helper import assert_image_equal, assert_image_similar, hopper from .helper import assert_image_equal, assert_image_similar, hopper

View File

@ -4,6 +4,7 @@ import re
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, ImageMode, features from PIL import Image, ImageMode, features
from .helper import assert_image, assert_image_equal, assert_image_similar, hopper from .helper import assert_image, assert_image_equal, assert_image_similar, hopper
@ -436,7 +437,7 @@ def test_extended_information():
def test_profile_typesafety(): def test_profile_typesafety():
""" Profile init type safety """Profile init type safety
prepatch, these would segfault, postpatch they should emit a typeerror prepatch, these would segfault, postpatch they should emit a typeerror
""" """

Some files were not shown because too many files have changed in this diff Show More