Merge branch 'master' into winbuild-cache

This commit is contained in:
Hugo van Kemenade 2020-08-14 00:19:49 +03:00 committed by GitHub
commit e7a29da517
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
268 changed files with 3059 additions and 1968 deletions

View File

@ -14,7 +14,7 @@ environment:
matrix: matrix:
- PYTHON: C:/Python38 - PYTHON: C:/Python38
ARCHITECTURE: x86 ARCHITECTURE: x86
- PYTHON: C:/Python35-x64 - PYTHON: C:/Python36-x64
ARCHITECTURE: x64 ARCHITECTURE: x64

View File

@ -30,6 +30,11 @@ 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
# TODO Remove when 3.9-dev includes setuptools 49.3.2+:
if [ "$TRAVIS_PYTHON_VERSION" == "3.9-dev" ]; then pip install -U "setuptools>=49.3.2" ; fi
if [ "$GHA_PYTHON_VERSION" == "3.9-dev" ]; then pip install -U "setuptools>=49.3.2" ; fi
if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then 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

@ -6,17 +6,14 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8"]
name: Python ${{ matrix.python-version }} name: Lint
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: pip cache - name: pip cache
uses: actions/cache@v1 uses: actions/cache@v2
with: with:
path: ~/.cache/pip path: ~/.cache/pip
key: lint-pip-${{ hashFiles('**/setup.py') }} key: lint-pip-${{ hashFiles('**/setup.py') }}
@ -24,26 +21,28 @@ jobs:
lint-pip- lint-pip-
- name: pre-commit cache - name: pre-commit cache
uses: actions/cache@v1 uses: actions/cache@v2
with: with:
path: ~/.cache/pre-commit path: ~/.cache/pre-commit
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
restore-keys: | restore-keys: |
lint-pre-commit- lint-pre-commit-
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python
uses: actions/setup-python@v1 uses: actions/setup-python@v2
with: with:
python-version: ${{ matrix.python-version }} python-version: 3.8
- name: Build system information - name: Build system information
run: python .github/workflows/system-info.py run: python .github/workflows/system-info.py
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install -U pip
python -m pip install --upgrade tox python -m pip install -U tox
- name: Lint - name: Lint
run: tox -e lint run: tox -e lint
env:
PRE_COMMIT_COLOR: always

View File

@ -2,7 +2,7 @@
set -e set -e
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype openblas
PYTHONOPTIMIZE=0 pip install cffi PYTHONOPTIMIZE=0 pip install cffi
pip install coverage pip install coverage
@ -11,7 +11,12 @@ pip install -U pytest
pip install -U pytest-cov pip install -U pytest-cov
pip install pyroma pip install pyroma
pip install test-image-results pip install test-image-results
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

@ -12,10 +12,8 @@ jobs:
docker: [ docker: [
alpine, alpine,
arch, arch,
ubuntu-16.04-xenial-amd64,
ubuntu-18.04-bionic-amd64, ubuntu-18.04-bionic-amd64,
ubuntu-20.04-focal-amd64, ubuntu-20.04-focal-amd64,
debian-9-stretch-x86,
debian-10-buster-x86, debian-10-buster-x86,
centos-6-amd64, centos-6-amd64,
centos-7-amd64, centos-7-amd64,

View File

@ -8,7 +8,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["3.5", "3.6", "3.7", "3.8", "pypy3"] python-version: ["3.6", "3.7", "3.8", "3.9-dev", "pypy3"]
architecture: ["x86", "x64"] architecture: ["x86", "x64"]
include: include:
- architecture: "x86" - architecture: "x86"
@ -47,17 +47,27 @@ jobs:
# sets env: pythonLocation # sets env: pythonLocation
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v1 uses: actions/setup-python@v2
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.architecture }} architecture: ${{ matrix.architecture }}
- name: Set up TCL
if: "contains(matrix.python-version, 'pypy')"
run: Write-Host "::set-env name=TCL_LIBRARY::$env:pythonLocation\tcl\tcl8.5"
shell: pwsh
- name: Print build system information - name: Print build system information
run: python .github/workflows/system-info.py run: python .github/workflows/system-info.py
- 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
# 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 - 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\"
@ -153,7 +163,7 @@ jobs:
shell: pwsh shell: pwsh
- name: Upload errors - name: Upload errors
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v2
if: failure() if: failure()
with: with:
name: errors name: errors
@ -188,25 +198,15 @@ jobs:
msys: msys:
runs-on: windows-2019 runs-on: windows-2019
strategy:
fail-fast: false
matrix:
mingw: ["MINGW32", "MINGW64"]
include:
- mingw: "MINGW32"
package: "mingw-w64-i686"
- mingw: "MINGW64"
package: "mingw-w64-x86_64"
defaults: defaults:
run: run:
shell: bash.exe --login -eo pipefail "{0}" shell: bash.exe --login -eo pipefail "{0}"
env: env:
MSYSTEM: ${{ matrix.mingw }} MSYSTEM: MINGW64
CHERE_INVOKING: 1 CHERE_INVOKING: 1
timeout-minutes: 30 timeout-minutes: 30
name: MSYS2 ${{ matrix.mingw }} name: MSYS2 MinGW 64-bit
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -218,23 +218,23 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
run: | run: |
pacman -S --noconfirm \ pacman -S --noconfirm \
${{ matrix.package }}-python3-pip \ mingw-w64-x86_64-python3-pip \
${{ matrix.package }}-python3-setuptools \ mingw-w64-x86_64-python3-setuptools \
${{ matrix.package }}-python3-pytest \ mingw-w64-x86_64-python3-pytest \
${{ matrix.package }}-python3-pytest-cov \ mingw-w64-x86_64-python3-pytest-cov \
${{ matrix.package }}-python3-cffi \ mingw-w64-x86_64-python3-cffi \
${{ matrix.package }}-python3-olefile \ mingw-w64-x86_64-python3-olefile \
${{ matrix.package }}-python3-numpy \ mingw-w64-x86_64-python3-numpy \
${{ matrix.package }}-python3-pyqt5 \ mingw-w64-x86_64-python3-pyqt5 \
${{ matrix.package }}-python3-numpy \ mingw-w64-x86_64-python3-numpy \
${{ matrix.package }}-freetype \ mingw-w64-x86_64-freetype \
${{ matrix.package }}-lcms2 \ mingw-w64-x86_64-lcms2 \
${{ matrix.package }}-libwebp \ mingw-w64-x86_64-libwebp \
${{ matrix.package }}-libjpeg-turbo \ mingw-w64-x86_64-libjpeg-turbo \
${{ matrix.package }}-openjpeg2 \ mingw-w64-x86_64-openjpeg2 \
${{ matrix.package }}-libimagequant \ mingw-w64-x86_64-libimagequant \
${{ matrix.package }}-libraqm \ mingw-w64-x86_64-libraqm \
${{ matrix.package }}-ghostscript \ mingw-w64-x86_64-ghostscript \
subversion subversion
python3 -m pip install pyroma python3 -m pip install pyroma
@ -256,4 +256,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: MSYS2 MinGW 64-bit

View File

@ -14,16 +14,16 @@ jobs:
] ]
python-version: [ python-version: [
"pypy3", "pypy3",
"3.9-dev",
"3.8", "3.8",
"3.7", "3.7",
"3.6", "3.6",
"3.5",
] ]
include: include:
- python-version: "3.5"
env: PYTHONOPTIMIZE=2
- python-version: "3.6" - python-version: "3.6"
env: PYTHONOPTIMIZE=1 env: PYTHONOPTIMIZE=1
- python-version: "3.7"
env: PYTHONOPTIMIZE=2
# Include new variables for Codecov # Include new variables for Codecov
- os: ubuntu-latest - os: ubuntu-latest
codecov-flag: GHA_Ubuntu codecov-flag: GHA_Ubuntu
@ -36,31 +36,25 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Ubuntu cache
uses: actions/cache@v1
if: startsWith(matrix.os, 'ubuntu')
with:
path: ~/.cache/pip
key:
${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.ci/*.sh') }}
restore-keys: |
${{ matrix.os }}-${{ matrix.python-version }}-
- name: macOS cache
uses: actions/cache@v1
if: startsWith(matrix.os, 'macOS')
with:
path: ~/Library/Caches/pip
key:
${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.ci/*.sh') }}
restore-keys: |
${{ matrix.os }}-${{ matrix.python-version }}-
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1 uses: actions/setup-python@v2
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(pip cache dir)"
- name: pip cache
uses: actions/cache@v2
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:
${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.ci/*.sh') }}
restore-keys: |
${{ matrix.os }}-${{ matrix.python-version }}-
- name: Build system information - name: Build system information
run: python .github/workflows/system-info.py run: python .github/workflows/system-info.py
@ -68,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: |
@ -89,7 +87,7 @@ jobs:
shell: pwsh shell: pwsh
- name: Upload errors - name: Upload errors
uses: actions/upload-artifact@v1 uses: actions/upload-artifact@v2
if: failure() if: failure()
with: with:
name: errors name: errors
@ -98,7 +96,7 @@ jobs:
- name: Docs - name: Docs
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.8 if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.8
run: | run: |
pip install sphinx-rtd-theme pip install sphinx-removed-in sphinx-rtd-theme
make doccheck make doccheck
- name: After success - name: After success

View File

@ -9,35 +9,35 @@ repos:
types: [] types: []
- repo: https://github.com/timothycrosley/isort - repo: https://github.com/timothycrosley/isort
rev: 7c29dd9d55161704cfc45998c6f5c2c43d39264b # frozen: 4.3.21 rev: 9ae09866e278fbc6ec0383ccb16b5c84e78e6e4d # frozen: 5.3.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: 20b9ac745c5adaab12b845b3564c773dcc051d0e # frozen: v1.5.2
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

@ -23,7 +23,7 @@ matrix:
arch: arm64 arch: arm64
- python: "3.7" - python: "3.7"
arch: ppc64le arch: ppc64le
- python: "3.5" - python: "3.8"
arch: s390x arch: s390x
- python: "pypy3" - python: "pypy3"
@ -35,16 +35,16 @@ matrix:
name: "3.8 Xenial" name: "3.8 Xenial"
services: xvfb services: xvfb
- python: '3.7' - python: '3.7'
name: "3.7 Xenial" name: "3.7 Xenial PYTHONOPTIMIZE=2"
env: PYTHONOPTIMIZE=2
services: xvfb services: xvfb
- python: '3.6' - python: '3.6'
name: "3.6 Xenial PYTHONOPTIMIZE=1" name: "3.6 Xenial PYTHONOPTIMIZE=1"
env: PYTHONOPTIMIZE=1 env: PYTHONOPTIMIZE=1
services: xvfb services: xvfb
- python: '3.5'
name: "3.5 Xenial PYTHONOPTIMIZE=2" allow_failures:
env: PYTHONOPTIMIZE=2 - python: "3.9-dev"
services: xvfb
install: install:
- | - |

View File

@ -2,9 +2,66 @@
Changelog (Pillow) Changelog (Pillow)
================== ==================
7.2.0 (unreleased) 8.0.0 (unreleased)
------------------ ------------------
- Read EXIF data tEXt chunk into info as bytes instead of string #4828
[radarhere]
- Remove long-deprecated Image.py functions #4798
[hugovk, nulano, radarhere]
- Add MIME type to PsdImagePlugin #4788
[samamorgan]
- Drop support for EOL Python 3.5 #4746
[hugovk, radarhere, nulano]
- Remove ImageCms.CmsProfile attributes deprecated since 3.2.0 #4768
[hugovk, radarhere]
- Allow ImageOps.autocontrast to specify low and high cutoffs separately #4749
[millionhz, radarhere]
7.2.0 (2020-07-01)
------------------
- Do not convert I;16 images when showing PNGs #4744
[radarhere]
- Fixed ICNS file pointer saving #4741
[radarhere]
- Fixed loading non-RGBA mode APNGs with dispose background #4742
[radarhere]
- Deprecated _showxv #4714
[radarhere]
- Deprecate Image.show(command="...") #4646
[nulano, hugovk, radarhere]
- Updated JPEG magic number #4707
[Cykooz, radarhere]
- Change STRIPBYTECOUNTS to LONG if necessary when saving #4626
[radarhere, hugovk]
- Write JFIF header when saving JPEG #4639
[radarhere]
- Replaced tiff_jpeg with jpeg compression when saving TIFF images #4627
[radarhere]
- Writing TIFF tags: improved BYTE, added UNDEFINED #4605
[radarhere]
- Consider transparency when pasting text on an RGBA image #4566
[radarhere]
- Added method argument to single frame WebP saving #4547
[radarhere]
- Use ImageFileDirectory_v2 in Image.Exif #4637 - Use ImageFileDirectory_v2 in Image.Exif #4637
[radarhere] [radarhere]
@ -44,6 +101,9 @@ Changelog (Pillow)
- Fix pickling WebP #4561 - Fix pickling WebP #4561
[hugovk, radarhere] [hugovk, radarhere]
- Replace IOError and WindowsError aliases with OSError #4536
[hugovk, radarhere]
7.1.2 (2020-04-25) 7.1.2 (2020-04-25)
------------------ ------------------
@ -5542,7 +5602,7 @@ Pre-fork
any other pixel value means opaque. This is faster than using an any other pixel value means opaque. This is faster than using an
"L" transparency mask. "L" transparency mask.
+ Properly writes EPS files (and properly prints images to postscript + Properly writes EPS files (and properly prints images to PostScript
printers as well). printers as well).
+ Reads 4-bit BMP files, as well as 4 and 8-bit Windows ICO and CUR + Reads 4-bit BMP files, as well as 4 and 8-bit Windows ICO and CUR
@ -5625,7 +5685,7 @@ Pre-fork
+ Added the "pilfile" utility, which quickly identifies image files + Added the "pilfile" utility, which quickly identifies image files
(without loading them, in most cases). (without loading them, in most cases).
+ Added the "pilprint" utility, which prints image files to Postscript + Added the "pilprint" utility, which prints image files to PostScript
printers. printers.
+ Added a rudimentary version of the "pilview" utility, which is + Added a rudimentary version of the "pilview" utility, which is
@ -5639,5 +5699,5 @@ Pre-fork
Jack). This allows you to read images through the Img extensions file Jack). This allows you to read images through the Img extensions file
format handlers. See the file "Lib/ImgExtImagePlugin.py" for details. format handlers. See the file "Lib/ImgExtImagePlugin.py" for details.
+ Postscript printing is provided through the PSDraw module. See the + PostScript printing is provided through the PSDraw module. See the
handbook for details. handbook for details.

View File

@ -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 .DEFAULT_GOAL := release-test
.PHONY: clean
clean: clean:
python3 setup.py clean python3 setup.py clean
rm src/PIL/*.so || true rm src/PIL/*.so || true
@ -9,28 +8,34 @@ clean:
find . -name __pycache__ | xargs rm -r || true find . -name __pycache__ | xargs rm -r || true
BRANCHES=`git branch -a | grep -v HEAD | grep -v master | grep remote` BRANCHES=`git branch -a | grep -v HEAD | grep -v master | grep remote`
.PHONY: co
co: co:
-for i in $(BRANCHES) ; do \ -for i in $(BRANCHES) ; do \
git checkout -t $$i ; \ git checkout -t $$i ; \
done done
.PHONY: coverage
coverage: coverage:
pytest -qq pytest -qq
rm -r htmlcov || true rm -r htmlcov || true
coverage report coverage report
.PHONY: doc
doc: doc:
$(MAKE) -C docs html $(MAKE) -C docs html
.PHONY: doccheck
doccheck: doccheck:
$(MAKE) -C docs html $(MAKE) -C docs html
# Don't make our tests rely on the links in the docs being up every single build. # 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. # We don't control them. But do check, and update them to the target of their redirects.
$(MAKE) -C docs linkcheck || true $(MAKE) -C docs linkcheck || true
.PHONY: docserve
docserve: docserve:
cd docs/_build/html && python3 -mSimpleHTTPServer 2> /dev/null& cd docs/_build/html && python3 -mSimpleHTTPServer 2> /dev/null&
.PHONY: help
help: help:
@echo "Welcome to Pillow development. Please use \`make <target>\` where <target> is one of" @echo "Welcome to Pillow development. Please use \`make <target>\` where <target> is one of"
@echo " clean remove build products" @echo " clean remove build products"
@ -48,17 +53,21 @@ help:
@echo " upload build and upload sdists to PyPI" @echo " upload build and upload sdists to PyPI"
@echo " upload-test build and upload sdists to test.pythonpackages.com" @echo " upload-test build and upload sdists to test.pythonpackages.com"
.PHONY: inplace
inplace: clean inplace: clean
python3 setup.py develop build_ext --inplace python3 setup.py develop build_ext --inplace
.PHONY: install
install: install:
python3 setup.py install python3 setup.py install
python3 selftest.py python3 selftest.py
.PHONY: install-coverage
install-coverage: install-coverage:
CFLAGS="-coverage" python3 setup.py build_ext install CFLAGS="-coverage" python3 setup.py build_ext install
python3 selftest.py python3 selftest.py
.PHONY: debug
debug: debug:
# make a debug version if we don't have a -dbg python. Leaves in symbols # 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 # for our stuff, kills optimization, and redirects to dev null so we
@ -66,13 +75,16 @@ debug:
make clean > /dev/null make clean > /dev/null
CFLAGS='-g -O0' python3 setup.py build_ext install > /dev/null CFLAGS='-g -O0' python3 setup.py build_ext install > /dev/null
.PHONY: install-req
install-req: install-req:
python3 -m pip install -r requirements.txt python3 -m pip install -r requirements.txt
.PHONY: install-venv
install-venv: install-venv:
virtualenv . virtualenv .
bin/pip install -r requirements.txt bin/pip install -r requirements.txt
.PHONY: release-test
release-test: release-test:
$(MAKE) install-req $(MAKE) install-req
python3 setup.py develop python3 setup.py develop
@ -84,22 +96,14 @@ release-test:
pyroma . pyroma .
viewdoc viewdoc
.PHONY: sdist
sdist: sdist:
python3 setup.py sdist --format=gztar python3 setup.py sdist --format=gztar
.PHONY: test
test: test:
pytest -qq pytest -qq
# https://docs.python.org/3/distutils/packageindex.html#the-pypirc-file .PHONY: readme
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
readme: readme:
viewdoc viewdoc

View File

@ -114,3 +114,12 @@ Released as needed privately to individual vendors for critical security-related
## Documentation ## Documentation
* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes * [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes
## Docker Images
* [ ] Update Pillow in the Docker Images repository
```bash
git clone https://github.com/python-pillow/docker-images
cd docker-images
./update-pillow-tag.sh [[release tag]]
```

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

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

@ -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,4 +1,5 @@
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"

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__)
@ -165,12 +166,6 @@ def assert_tuple_approx_equal(actuals, targets, threshold, msg):
assert value, msg + ": " + repr(actuals) + " != " + repr(targets) assert value, msg + ": " + repr(actuals) + " != " + repr(targets)
def skip_known_bad_test(msg=None):
# Skip if PILLOW_RUN_KNOWN_BAD is not true in the environment.
if not os.environ.get("PILLOW_RUN_KNOWN_BAD", False):
pytest.skip(msg or "Known bad test")
def skip_unless_feature(feature): def skip_unless_feature(feature):
reason = "%s not available" % feature reason = "%s not available" % feature
return pytest.mark.skipif(not features.check(feature), reason=reason) return pytest.mark.skipif(not features.check(feature), reason=reason)
@ -190,7 +185,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":

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
Tests/images/exif_text.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

BIN
Tests/images/text_mono.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 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

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

@ -1,6 +1,8 @@
import io import io
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
@ -21,6 +23,27 @@ def test_check():
assert features.check_feature(feature) == features.check(feature) assert features.check_feature(feature) == features.check(feature)
def test_version():
# Check the correctness of the convenience function
# and the format of version numbers
def test(name, function):
version = features.version(name)
if not features.check(name):
assert version is None
else:
assert function(name) == version
if name != "PIL":
assert version is None or re.search(r"\d+(\.\d+)*$", version)
for module in features.modules:
test(module, features.version_module)
for codec in features.codecs:
test(codec, features.version_codec)
for feature in features.features:
test(feature, features.version_feature)
@skip_unless_feature("webp") @skip_unless_feature("webp")
def test_webp_transparency(): def test_webp_transparency():
assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha() assert features.check("transp_webp") != _webp.WebPDecoderBuggyAlpha()
@ -37,9 +60,22 @@ def test_webp_anim():
assert features.check("webp_anim") == _webp.HAVE_WEBPANIM assert features.check("webp_anim") == _webp.HAVE_WEBPANIM
@skip_unless_feature("libjpeg_turbo")
def test_libjpeg_turbo_version():
assert re.search(r"\d+\.\d+\.\d+$", features.version("libjpeg_turbo"))
@skip_unless_feature("libimagequant")
def test_libimagequant_version():
assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant"))
def test_check_modules(): def test_check_modules():
for feature in features.modules: for feature in features.modules:
assert features.check_module(feature) in [True, False] assert features.check_module(feature) in [True, False]
def test_check_codecs():
for feature in features.codecs: for feature in features.codecs:
assert features.check_codec(feature) in [True, False] assert features.check_codec(feature) in [True, False]
@ -64,6 +100,8 @@ def test_unsupported_codec():
# Act / Assert # Act / Assert
with pytest.raises(ValueError): with pytest.raises(ValueError):
features.check_codec(codec) features.check_codec(codec)
with pytest.raises(ValueError):
features.version_codec(codec)
def test_unsupported_module(): def test_unsupported_module():
@ -72,6 +110,8 @@ def test_unsupported_module():
# Act / Assert # Act / Assert
with pytest.raises(ValueError): with pytest.raises(ValueError):
features.check_module(module) features.check_module(module)
with pytest.raises(ValueError):
features.version_module(module)
def test_pilinfo(): def test_pilinfo():

View File

@ -1,4 +1,5 @@
import pytest import pytest
from PIL import Image, ImageSequence, PngImagePlugin from PIL import Image, ImageSequence, PngImagePlugin
@ -104,6 +105,13 @@ def test_apng_dispose_region():
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
def test_apng_dispose_op_background_p_mode():
with Image.open("Tests/images/apng/dispose_op_background_p_mode.png") as im:
im.seek(1)
im.load()
assert im.size == (128, 64)
def test_apng_blend(): def test_apng_blend():
with Image.open("Tests/images/apng/blend_op_source_solid.png") as im: with Image.open("Tests/images/apng/blend_op_source_solid.png") as im:
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
@ -494,6 +502,26 @@ def test_apng_save_disposal(tmp_path):
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
def test_apng_save_disposal_previous(tmp_path):
test_file = str(tmp_path / "temp.png")
size = (128, 64)
transparent = Image.new("RGBA", size, (0, 0, 0, 0))
red = Image.new("RGBA", size, (255, 0, 0, 255))
green = Image.new("RGBA", size, (0, 255, 0, 255))
# test APNG_DISPOSE_OP_NONE
transparent.save(
test_file,
save_all=True,
append_images=[red, green],
disposal=PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
)
with Image.open(test_file) as im:
im.seek(2)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
def test_apng_save_blend(tmp_path): def test_apng_save_blend(tmp_path):
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")
size = (128, 64) size = (128, 64)

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,14 +2,15 @@ import io
import sys import sys
import pytest import pytest
from PIL import IcnsImagePlugin, Image
from PIL import IcnsImagePlugin, Image, features
from .helper import assert_image_equal, assert_image_similar from .helper import assert_image_equal, assert_image_similar
# sample icon file # sample icon file
TEST_FILE = "Tests/images/pillow.icns" TEST_FILE = "Tests/images/pillow.icns"
ENABLE_JPEG2K = hasattr(Image.core, "jp2klib_version") ENABLE_JPEG2K = features.check_codec("jpg_2000")
def test_sanity(): def test_sanity():
@ -55,6 +56,19 @@ def test_save_append_images(tmp_path):
assert_image_equal(reread, provided_im) assert_image_equal(reread, provided_im)
@pytest.mark.skipif(sys.platform != "darwin", reason="Requires macOS")
def test_save_fp():
fp = io.BytesIO()
with Image.open(TEST_FILE) as im:
im.save(fp, format="ICNS")
with Image.open(fp) as reread:
assert reread.mode == "RGBA"
assert reread.size == (1024, 1024)
assert reread.format == "ICNS"
def test_sizes(): def test_sizes():
# Check that we can load all of the sizes, and that the final pixel # Check that we can load all of the sizes, and that the final pixel
# dimensions are as expected # dimensions are as expected

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,7 +3,15 @@ import re
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import ExifTags, Image, ImageFile, JpegImagePlugin
from PIL import (
ExifTags,
Image,
ImageFile,
JpegImagePlugin,
UnidentifiedImageError,
features,
)
from .helper import ( from .helper import (
assert_image, assert_image,
@ -41,7 +49,7 @@ class TestFileJpeg:
def test_sanity(self): def test_sanity(self):
# internal version number # internal version number
assert re.search(r"\d+\.\d+$", Image.core.jpeglib_version) assert re.search(r"\d+\.\d+$", features.version_codec("jpg"))
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
im.load() im.load()
@ -90,9 +98,12 @@ class TestFileJpeg:
] ]
assert k > 0.9 assert k > 0.9
def test_dpi(self): @pytest.mark.parametrize(
"test_image_path", [TEST_FILE, "Tests/images/pil_sample_cmyk.jpg"],
)
def test_dpi(self, test_image_path):
def test(xdpi, ydpi=None): def test(xdpi, ydpi=None):
with Image.open(TEST_FILE) as im: with Image.open(test_image_path) as im:
im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi)) im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi))
return im.info.get("dpi") return im.info.get("dpi")
@ -706,6 +717,24 @@ class TestFileJpeg:
with Image.open("Tests/images/icc-after-SOF.jpg") as im: with Image.open("Tests/images/icc-after-SOF.jpg") as im:
assert im.info["icc_profile"] == b"profile" assert im.info["icc_profile"] == b"profile"
def test_jpeg_magic_number(self):
size = 4097
buffer = BytesIO(b"\xFF" * size) # Many xFF bytes
buffer.max_pos = 0
orig_read = buffer.read
def read(n=-1):
res = orig_read(n)
buffer.max_pos = max(buffer.max_pos, buffer.tell())
return res
buffer.read = read
with pytest.raises(UnidentifiedImageError):
Image.open(buffer)
# Assert the entire file has not been read
assert 0 < buffer.max_pos < size
@pytest.mark.skipif(not is_win32(), reason="Windows only") @pytest.mark.skipif(not is_win32(), reason="Windows only")
@skip_unless_feature("jpg") @skip_unless_feature("jpg")

View File

@ -2,13 +2,13 @@ import re
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, ImageFile, Jpeg2KImagePlugin
from PIL import Image, ImageFile, Jpeg2KImagePlugin, features
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
assert_image_similar, assert_image_similar,
is_big_endian, is_big_endian,
on_ci,
skip_unless_feature, skip_unless_feature,
) )
@ -35,7 +35,7 @@ def roundtrip(im, **options):
def test_sanity(): def test_sanity():
# Internal version number # Internal version number
assert re.search(r"\d+\.\d+\.\d+$", Image.core.jp2klib_version) assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("jpg_2000"))
with Image.open("Tests/images/test-card-lossless.jp2") as im: with Image.open("Tests/images/test-card-lossless.jp2") as im:
px = im.load() px = im.load()
@ -190,14 +190,14 @@ def test_16bit_monochrome_has_correct_mode():
assert jp2.mode == "I;16" assert jp2.mode == "I;16"
@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
def test_16bit_monochrome_jp2_like_tiff(): def test_16bit_monochrome_jp2_like_tiff():
with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit:
with Image.open("Tests/images/16bit.cropped.jp2") as jp2: with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
assert_image_similar(jp2, tiff_16bit, 1e-3) assert_image_similar(jp2, tiff_16bit, 1e-3)
@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
def test_16bit_monochrome_j2k_like_tiff(): def test_16bit_monochrome_j2k_like_tiff():
with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit: with Image.open("Tests/images/16bit.cropped.tif") as tiff_16bit:
with Image.open("Tests/images/16bit.cropped.j2k") as j2k: with Image.open("Tests/images/16bit.cropped.j2k") as j2k:

View File

@ -1,13 +1,14 @@
import base64 import base64
import io import io
import itertools import itertools
import logging
import os import os
import re
from collections import namedtuple 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
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
@ -18,8 +19,6 @@ from .helper import (
skip_unless_feature, skip_unless_feature,
) )
logger = logging.getLogger(__name__)
@skip_unless_feature("libtiff") @skip_unless_feature("libtiff")
class LibTiffTestCase: class LibTiffTestCase:
@ -47,6 +46,9 @@ class LibTiffTestCase:
class TestFileLibTiff(LibTiffTestCase): class TestFileLibTiff(LibTiffTestCase):
def test_version(self):
assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff"))
def test_g4_tiff(self, tmp_path): def test_g4_tiff(self, tmp_path):
"""Test the ordinary file path load path""" """Test the ordinary file path load path"""
@ -203,6 +205,7 @@ class TestFileLibTiff(LibTiffTestCase):
del core_items[tag] del core_items[tag]
except KeyError: except KeyError:
pass pass
del core_items[320] # colormap is special, tested below
# Type codes: # Type codes:
# 2: "ascii", # 2: "ascii",
@ -299,9 +302,6 @@ class TestFileLibTiff(LibTiffTestCase):
) )
continue continue
if libtiff and isinstance(value, bytes):
value = value.decode()
assert reloaded_value == value assert reloaded_value == value
# Test with types # Test with types
@ -322,6 +322,17 @@ class TestFileLibTiff(LibTiffTestCase):
) )
TiffImagePlugin.WRITE_LIBTIFF = False TiffImagePlugin.WRITE_LIBTIFF = False
def test_xmlpacket_tag(self, tmp_path):
TiffImagePlugin.WRITE_LIBTIFF = True
out = str(tmp_path / "temp.tif")
hopper().save(out, tiffinfo={700: b"xmlpacket tag"})
TiffImagePlugin.WRITE_LIBTIFF = False
with Image.open(out) as reloaded:
if 700 in reloaded.tag_v2:
assert reloaded.tag_v2[700] == b"xmlpacket tag"
def test_int_dpi(self, tmp_path): def test_int_dpi(self, tmp_path):
# issue #1765 # issue #1765
im = hopper("RGB") im = hopper("RGB")
@ -448,6 +459,14 @@ class TestFileLibTiff(LibTiffTestCase):
assert size_compressed > size_jpeg assert size_compressed > size_jpeg
assert size_jpeg > size_jpeg_30 assert size_jpeg > size_jpeg_30
def test_tiff_jpeg_compression(self, tmp_path):
im = hopper("RGB")
out = str(tmp_path / "temp.tif")
im.save(out, compression="tiff_jpeg")
with Image.open(out) as reloaded:
assert reloaded.info["compression"] == "jpeg"
def test_quality(self, tmp_path): def test_quality(self, tmp_path):
im = hopper("RGB") im = hopper("RGB")
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
@ -471,6 +490,18 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open(out) as im2: with Image.open(out) as im2:
assert_image_equal(im, im2) assert_image_equal(im, im2)
def test_palette_save(self, tmp_path):
im = hopper("P")
out = str(tmp_path / "temp.tif")
TiffImagePlugin.WRITE_LIBTIFF = True
im.save(out)
TiffImagePlugin.WRITE_LIBTIFF = False
with Image.open(out) as reloaded:
# colormap/palette tag
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
@ -667,6 +698,26 @@ class TestFileLibTiff(LibTiffTestCase):
TiffImagePlugin.READ_LIBTIFF = False TiffImagePlugin.READ_LIBTIFF = False
assert icc == icc_libtiff assert icc == icc_libtiff
def test_write_icc(self, tmp_path):
def check_write(libtiff):
TiffImagePlugin.WRITE_LIBTIFF = libtiff
with Image.open("Tests/images/hopper.iccprofile.tif") as img:
icc_profile = img.info["icc_profile"]
out = str(tmp_path / "temp.tif")
img.save(out, icc_profile=icc_profile)
with Image.open(out) as reloaded:
assert icc_profile == reloaded.info["icc_profile"]
libtiffs = []
if Image.core.libtiff_support_custom_tags:
libtiffs.append(True)
libtiffs.append(False)
for libtiff in libtiffs:
check_write(libtiff)
def test_multipage_compression(self): def test_multipage_compression(self):
with Image.open("Tests/images/compression.tif") as im: with Image.open("Tests/images/compression.tif") as im:

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,15 +2,10 @@ import os.path
import subprocess import subprocess
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import ( from .helper import IMCONVERT, assert_image_equal, hopper, imagemagick_available
IMCONVERT,
assert_image_equal,
hopper,
imagemagick_available,
skip_known_bad_test,
)
_roundtrip = imagemagick_available() _roundtrip = imagemagick_available()
@ -62,13 +57,13 @@ def test_monochrome(tmp_path):
roundtrip(tmp_path, mode) roundtrip(tmp_path, mode)
@pytest.mark.xfail(reason="Palm P image is wrong")
def test_p_mode(tmp_path): def test_p_mode(tmp_path):
# Arrange # Arrange
mode = "P" mode = "P"
# Act / Assert # Act / Assert
helper_save_as_palm(tmp_path, mode) helper_save_as_palm(tmp_path, mode)
skip_known_bad_test("Palm P image is wrong")
roundtrip(tmp_path, mode) roundtrip(tmp_path, mode)

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,7 +3,8 @@ import zlib
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, ImageFile, PngImagePlugin
from PIL import Image, ImageFile, PngImagePlugin, features
from .helper import ( from .helper import (
PillowLeakTestCase, PillowLeakTestCase,
@ -12,7 +13,6 @@ from .helper import (
hopper, hopper,
is_big_endian, is_big_endian,
is_win32, is_win32,
on_ci,
skip_unless_feature, skip_unless_feature,
) )
@ -69,11 +69,11 @@ class TestFilePng:
png.crc(cid, s) png.crc(cid, s)
return chunks return chunks
@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
def test_sanity(self, tmp_path): def test_sanity(self, tmp_path):
# internal version number # internal version number
assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", Image.core.zlib_version) assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib"))
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")
@ -607,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
@ -12,6 +13,7 @@ def test_sanity():
assert im.mode == "RGB" assert im.mode == "RGB"
assert im.size == (128, 128) assert im.size == (128, 128)
assert im.format == "PSD" assert im.format == "PSD"
assert im.get_format_mimetype() == "image/vnd.adobe.photoshop"
im2 = hopper() im2 = hopper()
assert_image_similar(im, im2, 4.8) assert_image_similar(im, im2, 4.8)

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

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

View File

@ -1,8 +1,8 @@
import logging
import os 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
@ -16,8 +16,6 @@ from .helper import (
is_win32, is_win32,
) )
logger = logging.getLogger(__name__)
class TestFileTiff: class TestFileTiff:
def test_sanity(self, tmp_path): def test_sanity(self, tmp_path):

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
@ -156,6 +157,23 @@ def test_write_metadata(tmp_path):
assert value == reloaded[tag], "%s didn't roundtrip" % tag assert value == reloaded[tag], "%s didn't roundtrip" % tag
def test_change_stripbytecounts_tag_type(tmp_path):
out = str(tmp_path / "temp.tiff")
with Image.open("Tests/images/hopper.tif") as im:
info = im.tag_v2
# Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT
im = im.resize((500, 500))
# STRIPBYTECOUNTS can be a SHORT or a LONG
info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT
im.save(out, tiffinfo=info)
with Image.open(out) as reloaded:
assert reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] == TiffTags.LONG
def test_no_duplicate_50741_tag(): def test_no_duplicate_50741_tag():
assert TAG_IDS["MakerNoteSafety"] == 50741 assert TAG_IDS["MakerNoteSafety"] == 50741
assert TAG_IDS["BestQualityScale"] == 50780 assert TAG_IDS["BestQualityScale"] == 50780
@ -319,13 +337,13 @@ def test_empty_values():
def test_PhotoshopInfo(tmp_path): def test_PhotoshopInfo(tmp_path):
with Image.open("Tests/images/issue_2278.tif") as im: with Image.open("Tests/images/issue_2278.tif") as im:
assert len(im.tag_v2[34377]) == 1 assert len(im.tag_v2[34377]) == 70
assert isinstance(im.tag_v2[34377][0], bytes) assert isinstance(im.tag_v2[34377], bytes)
out = str(tmp_path / "temp.tiff") out = str(tmp_path / "temp.tiff")
im.save(out) im.save(out)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert len(reloaded.tag_v2[34377]) == 1 assert len(reloaded.tag_v2[34377]) == 70
assert isinstance(reloaded.tag_v2[34377][0], bytes) assert isinstance(reloaded.tag_v2[34377], bytes)
def test_too_many_entries(): def test_too_many_entries():

View File

@ -1,5 +1,9 @@
import io
import re
import pytest import pytest
from PIL import Image, WebPImagePlugin
from PIL import Image, WebPImagePlugin, features
from .helper import ( from .helper import (
assert_image_similar, assert_image_similar,
@ -36,6 +40,7 @@ class TestFileWebp:
def test_version(self): def test_version(self):
_webp.WebPDecoderVersion() _webp.WebPDecoderVersion()
_webp.WebPDecoderBuggyAlpha() _webp.WebPDecoderBuggyAlpha()
assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp"))
def test_read_rgb(self): def test_read_rgb(self):
""" """
@ -54,15 +59,10 @@ class TestFileWebp:
# dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm # dwebp -ppm ../../Tests/images/hopper.webp -o hopper_webp_bits.ppm
assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0) assert_image_similar_tofile(image, "Tests/images/hopper_webp_bits.ppm", 1.0)
def test_write_rgb(self, tmp_path): def _roundtrip(self, tmp_path, mode, epsilon, args={}):
"""
Can we write a RGB mode file to webp without error.
Does it have the bits we expect?
"""
temp_file = str(tmp_path / "temp.webp") temp_file = str(tmp_path / "temp.webp")
hopper(self.rgb_mode).save(temp_file) hopper(mode).save(temp_file, **args)
with Image.open(temp_file) as image: with Image.open(temp_file) as image:
assert image.mode == self.rgb_mode assert image.mode == self.rgb_mode
assert image.size == (128, 128) assert image.size == (128, 128)
@ -70,6 +70,7 @@ class TestFileWebp:
image.load() image.load()
image.getdata() image.getdata()
if mode == self.rgb_mode:
# generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm # generated with: dwebp -ppm temp.webp -o hopper_webp_write.ppm
assert_image_similar_tofile( assert_image_similar_tofile(
image, "Tests/images/hopper_webp_write.ppm", 12.0 image, "Tests/images/hopper_webp_write.ppm", 12.0
@ -78,10 +79,29 @@ class TestFileWebp:
# This test asserts that the images are similar. If the average pixel # This test asserts that the images are similar. If the average pixel
# difference between the two images is less than the epsilon value, # difference between the two images is less than the epsilon value,
# then we're going to accept that it's a reasonable lossy version of # then we're going to accept that it's a reasonable lossy version of
# the image. The old lena images for WebP are showing ~16 on # the image.
# Ubuntu, the jpegs are showing ~18. target = hopper(mode)
target = hopper(self.rgb_mode) if mode != self.rgb_mode:
assert_image_similar(image, target, 12.0) target = target.convert(self.rgb_mode)
assert_image_similar(image, target, epsilon)
def test_write_rgb(self, tmp_path):
"""
Can we write a RGB mode file to webp without error?
Does it have the bits we expect?
"""
self._roundtrip(tmp_path, self.rgb_mode, 12.5)
def test_write_method(self, tmp_path):
self._roundtrip(tmp_path, self.rgb_mode, 12.0, {"method": 6})
buffer_no_args = io.BytesIO()
hopper().save(buffer_no_args, format="WEBP")
buffer_method = io.BytesIO()
hopper().save(buffer_method, format="WEBP", method=6)
assert buffer_no_args.getbuffer() != buffer_method.getbuffer()
def test_write_unsupported_mode_L(self, tmp_path): def test_write_unsupported_mode_L(self, tmp_path):
""" """
@ -89,18 +109,7 @@ class TestFileWebp:
similar to the original file. similar to the original file.
""" """
temp_file = str(tmp_path / "temp.webp") self._roundtrip(tmp_path, "L", 10.0)
hopper("L").save(temp_file)
with Image.open(temp_file) as image:
assert image.mode == self.rgb_mode
assert image.size == (128, 128)
assert image.format == "WEBP"
image.load()
image.getdata()
target = hopper("L").convert(self.rgb_mode)
assert_image_similar(image, target, 10.0)
def test_write_unsupported_mode_P(self, tmp_path): def test_write_unsupported_mode_P(self, tmp_path):
""" """
@ -108,18 +117,7 @@ class TestFileWebp:
similar to the original file. similar to the original file.
""" """
temp_file = str(tmp_path / "temp.webp") self._roundtrip(tmp_path, "P", 50.0)
hopper("P").save(temp_file)
with Image.open(temp_file) as image:
assert image.mode == self.rgb_mode
assert image.size == (128, 128)
assert image.format == "WEBP"
image.load()
image.getdata()
target = hopper("P").convert(self.rgb_mode)
assert_image_similar(image, target, 50.0)
def test_WebPEncode_with_invalid_args(self): def test_WebPEncode_with_invalid_args(self):
""" """

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,11 +1,11 @@
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
assert_image_similar, assert_image_similar,
is_big_endian, is_big_endian,
on_ci,
skip_unless_feature, skip_unless_feature,
) )
@ -27,7 +27,7 @@ def test_n_frames():
assert im.is_animated assert im.is_animated
@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
def test_write_animation_L(tmp_path): def test_write_animation_L(tmp_path):
""" """
Convert an animated GIF to animated WebP, then compare the frame count, and first Convert an animated GIF to animated WebP, then compare the frame count, and first
@ -53,7 +53,7 @@ def test_write_animation_L(tmp_path):
assert_image_similar(im, orig.convert("RGBA"), 25.0) assert_image_similar(im, orig.convert("RGBA"), 25.0)
@pytest.mark.xfail(is_big_endian() and on_ci(), reason="Fails on big-endian") @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
def test_write_animation_RGB(tmp_path): def test_write_animation_RGB(tmp_path):
""" """
Write an animated WebP from RGB frames, and ensure the frames Write an animated WebP from RGB frames, and ensure the frames

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

@ -3,9 +3,10 @@ import os
import shutil import shutil
import tempfile import tempfile
import PIL
import pytest import pytest
from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError
import PIL
from PIL import Image, ImageDraw, ImagePalette, ImageShow, UnidentifiedImageError
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
@ -13,6 +14,7 @@ from .helper import (
assert_not_all_same, assert_not_all_same,
hopper, hopper,
is_win32, is_win32,
skip_unless_feature,
) )
@ -465,18 +467,6 @@ class TestImage:
with pytest.raises(ValueError): with pytest.raises(ValueError):
Image.core.fill("RGB", (2, -2), (0, 0, 0)) Image.core.fill("RGB", (2, -2), (0, 0, 0))
def test_offset_not_implemented(self):
# Arrange
with hopper() as im:
# Act / Assert
with pytest.raises(NotImplementedError):
im.offset(None)
def test_fromstring(self):
with pytest.raises(NotImplementedError):
Image.fromstring()
def test_linear_gradient_wrong_mode(self): def test_linear_gradient_wrong_mode(self):
# Arrange # Arrange
wrong_mode = "RGB" wrong_mode = "RGB"
@ -585,6 +575,22 @@ class TestImage:
expected = Image.new(mode, (100, 100), color) expected = Image.new(mode, (100, 100), color)
assert_image_equal(im.convert(mode), expected) assert_image_equal(im.convert(mode), expected)
def test_showxv_deprecation(self):
class TestViewer(ImageShow.Viewer):
def show_image(self, image, **options):
return True
viewer = TestViewer()
ImageShow.register(viewer, -1)
im = Image.new("RGB", (50, 50), "white")
with pytest.warns(DeprecationWarning):
Image._showxv(im)
# Restore original state
ImageShow._viewers.pop(0)
def test_no_resource_warning_on_save(self, tmp_path): def test_no_resource_warning_on_save(self, tmp_path):
# https://github.com/python-pillow/Pillow/issues/835 # https://github.com/python-pillow/Pillow/issues/835
# Arrange # Arrange
@ -609,6 +615,97 @@ class TestImage:
assert not fp.closed assert not fp.closed
def test_exif_jpeg(self, tmp_path):
with Image.open("Tests/images/exif-72dpi-int.jpg") as im: # Little endian
exif = im.getexif()
assert 258 not in exif
assert 40960 in exif
assert exif[40963] == 450
assert exif[11] == "gThumb 3.0.1"
out = str(tmp_path / "temp.jpg")
exif[258] = 8
del exif[40960]
exif[40963] = 455
exif[11] = "Pillow test"
im.save(out, exif=exif)
with Image.open(out) as reloaded:
reloaded_exif = reloaded.getexif()
assert reloaded_exif[258] == 8
assert 40960 not in reloaded_exif
assert reloaded_exif[40963] == 455
assert reloaded_exif[11] == "Pillow test"
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: # Big endian
exif = im.getexif()
assert 258 not in exif
assert 40962 in exif
assert exif[40963] == 200
assert exif[305] == "Adobe Photoshop CC 2017 (Macintosh)"
out = str(tmp_path / "temp.jpg")
exif[258] = 8
del exif[34665]
exif[40963] = 455
exif[305] = "Pillow test"
im.save(out, exif=exif)
with Image.open(out) as reloaded:
reloaded_exif = reloaded.getexif()
assert reloaded_exif[258] == 8
assert 34665 not in reloaded_exif
assert reloaded_exif[40963] == 455
assert reloaded_exif[305] == "Pillow test"
@skip_unless_feature("webp")
@skip_unless_feature("webp_anim")
def test_exif_webp(self, tmp_path):
with Image.open("Tests/images/hopper.webp") as im:
exif = im.getexif()
assert exif == {}
out = str(tmp_path / "temp.webp")
exif[258] = 8
exif[40963] = 455
exif[305] = "Pillow test"
def check_exif():
with Image.open(out) as reloaded:
reloaded_exif = reloaded.getexif()
assert reloaded_exif[258] == 8
assert reloaded_exif[40963] == 455
assert reloaded_exif[305] == "Pillow test"
im.save(out, exif=exif)
check_exif()
im.save(out, exif=exif, save_all=True)
check_exif()
def test_exif_png(self, tmp_path):
with Image.open("Tests/images/exif.png") as im:
exif = im.getexif()
assert exif == {274: 1}
out = str(tmp_path / "temp.png")
exif[258] = 8
del exif[274]
exif[40963] = 455
exif[305] = "Pillow test"
im.save(out, exif=exif)
with Image.open(out) as reloaded:
reloaded_exif = reloaded.getexif()
assert reloaded_exif == {258: 8, 40963: 455, 305: "Pillow test"}
def test_exif_interop(self):
with Image.open("Tests/images/flower.jpg") as im:
exif = im.getexif()
assert exif.get_ifd(0xA005) == {
1: "R98",
2: b"0100",
4097: 2272,
4098: 1704,
}
@pytest.mark.parametrize( @pytest.mark.parametrize(
"test_module", [PIL, Image], "test_module", [PIL, Image],
) )
@ -664,6 +761,18 @@ class TestImage:
except OSError as e: except OSError as e:
assert str(e) == "buffer overrun when reading image file" assert str(e) == "buffer overrun when reading image file"
def test_show_deprecation(self, monkeypatch):
monkeypatch.setattr(Image, "_show", lambda *args, **kwargs: None)
im = Image.new("RGB", (50, 50), "white")
with pytest.warns(None) as raised:
im.show()
assert not raised
with pytest.warns(DeprecationWarning):
im.show(command="mock")
class MockEncoder: class MockEncoder:
pass pass

View File

@ -2,9 +2,11 @@ import ctypes
import os import os
import subprocess import subprocess
import sys import sys
from distutils import ccompiler, sysconfig import sysconfig
import pytest import pytest
from setuptools.command.build_ext import new_compiler
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
@ -15,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
@ -359,13 +362,12 @@ int main(int argc, char* argv[])
% sys.prefix.replace("\\", "\\\\") % sys.prefix.replace("\\", "\\\\")
) )
compiler = ccompiler.new_compiler() compiler = new_compiler()
compiler.add_include_dir(sysconfig.get_python_inc()) compiler.add_include_dir(sysconfig.get_config_var("INCLUDEPY"))
libdir = sysconfig.get_config_var( libdir = sysconfig.get_config_var("LIBDIR") or sysconfig.get_config_var(
"LIBDIR" "INCLUDEPY"
) or sysconfig.get_python_inc().replace("include", "libs") ).replace("include", "libs")
print(libdir)
compiler.add_library_dir(libdir) compiler.add_library_dir(libdir)
objects = compiler.compile(["embed_pil.c"]) objects = compiler.compile(["embed_pil.c"])
compiler.link_executable(objects, "embed_pil") compiler.link_executable(objects, "embed_pil")

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,3 @@
import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper
@ -9,8 +8,3 @@ def test_sanity():
im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes()) im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes())
assert_image_equal(im1, im2) assert_image_equal(im1, im2)
def test_not_implemented():
with pytest.raises(NotImplementedError):
Image.fromstring()

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,7 @@
import os import os
import pytest import pytest
from PIL import Image from PIL import Image
from .helper import hopper from .helper import hopper

View File

@ -236,7 +236,7 @@ class TestImagingPaste:
[ [
(127, 191, 254, 191), (127, 191, 254, 191),
(111, 207, 206, 110), (111, 207, 206, 110),
(127, 254, 127, 0), (255, 255, 255, 0) if mode == "RGBA" else (127, 254, 127, 0),
(207, 207, 239, 239), (207, 207, 239, 239),
(191, 191, 190, 191), (191, 191, 190, 191),
(207, 206, 111, 112), (207, 206, 111, 112),

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,7 +1,8 @@
import pytest import pytest
from PIL import Image, ImageMath, ImageMode from PIL import Image, ImageMath, ImageMode
from .helper import convert_to_comparable from .helper import convert_to_comparable, skip_unless_feature
codecs = dir(Image.core) codecs = dir(Image.core)
@ -254,9 +255,7 @@ def test_mode_F():
compare_reduce_with_box(im, factor) compare_reduce_with_box(im, factor)
@pytest.mark.skipif( @skip_unless_feature("jpg_2000")
"jpeg2k_decoder" not in codecs, reason="JPEG 2000 support not available"
)
def test_jpeg2k(): def test_jpeg2k():
with Image.open("Tests/images/test-card-lossless.jp2") as im: with Image.open("Tests/images/test-card-lossless.jp2") as im:
assert im.reduce(2).size == (320, 240) assert im.reduce(2).size == (320, 240)

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
@ -218,7 +219,7 @@ class TestImagingCoreResampleAccuracy:
assert_image_equal(im, ref) assert_image_equal(im, ref)
class CoreResampleConsistencyTest: class TestCoreResampleConsistency:
def make_case(self, mode, fill): def make_case(self, mode, fill):
im = Image.new(mode, (512, 9), fill) im = Image.new(mode, (512, 9), fill)
return im.resize((9, 512), Image.LANCZOS), im.load()[0, 0] return im.resize((9, 512), Image.LANCZOS), im.load()[0, 0]
@ -253,7 +254,7 @@ class CoreResampleConsistencyTest:
self.run_case(self.make_case("F", 1.192093e-07)) self.run_case(self.make_case("F", 1.192093e-07))
class CoreResampleAlphaCorrectTest: class TestCoreResampleAlphaCorrect:
def make_levels_case(self, mode): def make_levels_case(self, mode):
i = Image.new(mode, (256, 16)) i = Image.new(mode, (256, 16))
px = i.load() px = i.load()
@ -274,7 +275,7 @@ class CoreResampleAlphaCorrectTest:
len(used_colors), y len(used_colors), y
) )
@pytest.mark.skip("Current implementation isn't precise enough") @pytest.mark.xfail(reason="Current implementation isn't precise enough")
def test_levels_rgba(self): def test_levels_rgba(self):
case = self.make_levels_case("RGBA") case = self.make_levels_case("RGBA")
self.run_levels_case(case.resize((512, 32), Image.BOX)) self.run_levels_case(case.resize((512, 32), Image.BOX))
@ -283,7 +284,7 @@ class CoreResampleAlphaCorrectTest:
self.run_levels_case(case.resize((512, 32), Image.BICUBIC)) self.run_levels_case(case.resize((512, 32), Image.BICUBIC))
self.run_levels_case(case.resize((512, 32), Image.LANCZOS)) self.run_levels_case(case.resize((512, 32), Image.LANCZOS))
@pytest.mark.skip("Current implementation isn't precise enough") @pytest.mark.xfail(reason="Current implementation isn't precise enough")
def test_levels_la(self): def test_levels_la(self):
case = self.make_levels_case("LA") case = self.make_levels_case("LA")
self.run_levels_case(case.resize((512, 32), Image.BOX)) self.run_levels_case(case.resize((512, 32), Image.BOX))
@ -329,7 +330,7 @@ class CoreResampleAlphaCorrectTest:
self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255,)) self.run_dirty_case(case.resize((20, 20), Image.LANCZOS), (255,))
class CoreResamplePassesTest: class TestCoreResamplePasses:
@contextmanager @contextmanager
def count(self, diff): def count(self, diff):
count = Image.core.get_stats()["new_count"] count = Image.core.get_stats()["new_count"]
@ -372,7 +373,7 @@ class CoreResamplePassesTest:
assert_image_similar(with_box, cropped, 0.1) assert_image_similar(with_box, cropped, 0.1)
class CoreResampleCoefficientsTest: class TestCoreResampleCoefficients:
def test_reduce(self): def test_reduce(self):
test_color = 254 test_color = 254
@ -401,7 +402,7 @@ class CoreResampleCoefficientsTest:
assert histogram[0x100 * 3 + 0xFF] == 0x10000 assert histogram[0x100 * 3 + 0xFF] == 0x10000
class CoreResampleBoxTest: class TestCoreResampleBox:
def test_wrong_arguments(self): def test_wrong_arguments(self):
im = hopper() im = hopper()
for resample in ( for resample in (

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,7 +4,8 @@ import re
from io import BytesIO from io import BytesIO
import pytest import pytest
from PIL import Image, ImageMode
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
@ -46,7 +47,7 @@ def test_sanity():
assert list(map(type, v)) == [str, str, str, str] assert list(map(type, v)) == [str, str, str, str]
# internal version number # internal version number
assert re.search(r"\d+\.\d+$", ImageCms.core.littlecms_version) assert re.search(r"\d+\.\d+$", features.version_module("littlecms2"))
skip_missing() skip_missing()
i = ImageCms.profileToProfile(hopper(), SRGB, SRGB) i = ImageCms.profileToProfile(hopper(), SRGB, SRGB)
@ -435,39 +436,6 @@ def test_extended_information():
assert p.xcolor_space == "RGB " assert p.xcolor_space == "RGB "
def test_deprecations():
skip_missing()
o = ImageCms.getOpenProfile(SRGB)
p = o.profile
def helper_deprecated(attr, expected):
result = pytest.warns(DeprecationWarning, getattr, p, attr)
assert result == expected
# p.color_space
helper_deprecated("color_space", "RGB")
# p.pcs
helper_deprecated("pcs", "XYZ")
# p.product_copyright
helper_deprecated(
"product_copyright", "Copyright International Color Consortium, 2009"
)
# p.product_desc
helper_deprecated("product_desc", "sRGB IEC61966-2-1 black scaled")
# p.product_description
helper_deprecated("product_description", "sRGB IEC61966-2-1 black scaled")
# p.product_manufacturer
helper_deprecated("product_manufacturer", "")
# p.product_model
helper_deprecated("product_model", "IEC 61966-2-1 Default RGB Colour Space - sRGB")
def test_profile_typesafety(): def test_profile_typesafety():
""" Profile init type safety """ Profile init type safety

View File

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

View File

@ -1,11 +1,12 @@
import os.path import os.path
import pytest import pytest
from PIL import Image, ImageColor, ImageDraw, ImageFont from PIL import Image, ImageColor, ImageDraw, ImageFont
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
assert_image_similar, assert_image_similar_tofile,
hopper, hopper,
skip_unless_feature, skip_unless_feature,
) )
@ -71,7 +72,7 @@ def helper_arc(bbox, start, end):
draw.arc(bbox, start, end) draw.arc(bbox, start, end)
# Assert # Assert
assert_image_similar(im, Image.open("Tests/images/imagedraw_arc.png"), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_arc.png", 1)
def test_arc1(): def test_arc1():
@ -110,20 +111,19 @@ def test_arc_no_loops():
draw.arc(BBOX1, start=start, end=end) draw.arc(BBOX1, start=start, end=end)
# Assert # Assert
assert_image_similar(im, Image.open("Tests/images/imagedraw_arc_no_loops.png"), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_no_loops.png", 1)
def test_arc_width(): def test_arc_width():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_arc_width.png"
# Act # Act
draw.arc(BBOX1, 10, 260, width=5) draw.arc(BBOX1, 10, 260, width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width.png", 1)
def test_arc_width_pieslice_large(): def test_arc_width_pieslice_large():
@ -131,26 +131,24 @@ def test_arc_width_pieslice_large():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_arc_width_pieslice.png"
# Act # Act
draw.arc(BBOX1, 10, 260, fill="yellow", width=100) draw.arc(BBOX1, 10, 260, fill="yellow", width=100)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_pieslice.png", 1)
def test_arc_width_fill(): def test_arc_width_fill():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_arc_width_fill.png"
# Act # Act
draw.arc(BBOX1, 10, 260, fill="yellow", width=5) draw.arc(BBOX1, 10, 260, fill="yellow", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_arc_width_fill.png", 1)
def test_arc_width_non_whole_angle(): def test_arc_width_non_whole_angle():
@ -163,7 +161,7 @@ def test_arc_width_non_whole_angle():
draw.arc(BBOX1, 10, 259.5, width=5) draw.arc(BBOX1, 10, 259.5, width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, expected, 1)
def test_bitmap(): def test_bitmap():
@ -190,7 +188,7 @@ def helper_chord(mode, bbox, start, end):
draw.chord(bbox, start, end, fill="red", outline="yellow") draw.chord(bbox, start, end, fill="red", outline="yellow")
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, expected, 1)
def test_chord1(): def test_chord1():
@ -209,26 +207,24 @@ def test_chord_width():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_chord_width.png"
# Act # Act
draw.chord(BBOX1, 10, 260, outline="yellow", width=5) draw.chord(BBOX1, 10, 260, outline="yellow", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width.png", 1)
def test_chord_width_fill(): def test_chord_width_fill():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_chord_width_fill.png"
# Act # Act
draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=5) draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_chord_width_fill.png", 1)
def test_chord_zero_width(): def test_chord_zero_width():
@ -254,7 +250,7 @@ def helper_ellipse(mode, bbox):
draw.ellipse(bbox, fill="green", outline="blue") draw.ellipse(bbox, fill="green", outline="blue")
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, expected, 1)
def test_ellipse1(): def test_ellipse1():
@ -276,8 +272,8 @@ def test_ellipse_translucent():
draw.ellipse(BBOX1, fill=(0, 255, 0, 127)) draw.ellipse(BBOX1, fill=(0, 255, 0, 127))
# Assert # Assert
expected = Image.open("Tests/images/imagedraw_ellipse_translucent.png") expected = "Tests/images/imagedraw_ellipse_translucent.png"
assert_image_similar(im, expected, 1) assert_image_similar_tofile(im, expected, 1)
def test_ellipse_edge(): def test_ellipse_edge():
@ -289,7 +285,7 @@ def test_ellipse_edge():
draw.ellipse(((0, 0), (W - 1, H)), fill="white") draw.ellipse(((0, 0), (W - 1, H)), fill="white")
# Assert # Assert
assert_image_similar(im, Image.open("Tests/images/imagedraw_ellipse_edge.png"), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1)
def test_ellipse_symmetric(): def test_ellipse_symmetric():
@ -304,39 +300,36 @@ def test_ellipse_width():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_ellipse_width.png"
# Act # Act
draw.ellipse(BBOX1, outline="blue", width=5) draw.ellipse(BBOX1, outline="blue", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width.png", 1)
def test_ellipse_width_large(): def test_ellipse_width_large():
# Arrange # Arrange
im = Image.new("RGB", (500, 500)) im = Image.new("RGB", (500, 500))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_ellipse_width_large.png"
# Act # Act
draw.ellipse((25, 25, 475, 475), outline="blue", width=75) draw.ellipse((25, 25, 475, 475), outline="blue", width=75)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_large.png", 1)
def test_ellipse_width_fill(): def test_ellipse_width_fill():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_ellipse_width_fill.png"
# Act # Act
draw.ellipse(BBOX1, fill="green", outline="blue", width=5) draw.ellipse(BBOX1, fill="green", outline="blue", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_width_fill.png", 1)
def test_ellipse_zero_width(): def test_ellipse_zero_width():
@ -423,7 +416,7 @@ def helper_pieslice(bbox, start, end):
draw.pieslice(bbox, start, end, fill="white", outline="blue") draw.pieslice(bbox, start, end, fill="white", outline="blue")
# Assert # Assert
assert_image_similar(im, Image.open("Tests/images/imagedraw_pieslice.png"), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice.png", 1)
def test_pieslice1(): def test_pieslice1():
@ -440,13 +433,12 @@ def test_pieslice_width():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_pieslice_width.png"
# Act # Act
draw.pieslice(BBOX1, 10, 260, outline="blue", width=5) draw.pieslice(BBOX1, 10, 260, outline="blue", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice_width.png", 1)
def test_pieslice_width_fill(): def test_pieslice_width_fill():
@ -459,7 +451,7 @@ def test_pieslice_width_fill():
draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5) draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, expected, 1)
def test_pieslice_zero_width(): def test_pieslice_zero_width():
@ -571,13 +563,12 @@ def test_big_rectangle():
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
bbox = [(-1, -1), (W + 1, H + 1)] bbox = [(-1, -1), (W + 1, H + 1)]
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_big_rectangle.png"
# Act # Act
draw.rectangle(bbox, fill="orange") draw.rectangle(bbox, fill="orange")
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_big_rectangle.png", 1)
def test_rectangle_width(): def test_rectangle_width():
@ -878,13 +869,25 @@ def test_wide_line_dot():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_wide_line_dot.png"
# Act # Act
draw.line([(50, 50), (50, 50)], width=3) draw.line([(50, 50), (50, 50)], width=3)
# Assert # Assert
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, "Tests/images/imagedraw_wide_line_dot.png", 1)
def test_wide_line_larger_than_int():
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_wide_line_larger_than_int.png"
# Act
draw.line([(0, 0), (32768, 32768)], width=3)
# Assert
assert_image_similar_tofile(im, expected, 1)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -971,13 +974,12 @@ def test_wide_line_dot():
def test_line_joint(xy): def test_line_joint(xy):
im = Image.new("RGB", (500, 325)) im = Image.new("RGB", (500, 325))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_line_joint_curve.png"
# Act # Act
draw.line(xy, GRAY, 50, "curve") draw.line(xy, GRAY, 50, "curve")
# Assert # Assert
assert_image_similar(im, Image.open(expected), 3) assert_image_similar_tofile(im, "Tests/images/imagedraw_line_joint_curve.png", 3)
def test_textsize_empty_string(): def test_textsize_empty_string():
@ -1018,8 +1020,8 @@ def test_stroke():
draw.text((10, 10), "A", "#f00", font, stroke_width=2, stroke_fill=stroke_fill) draw.text((10, 10), "A", "#f00", font, stroke_width=2, stroke_fill=stroke_fill)
# Assert # Assert
assert_image_similar( assert_image_similar_tofile(
im, Image.open("Tests/images/imagedraw_stroke_" + suffix + ".png"), 3.1 im, "Tests/images/imagedraw_stroke_" + suffix + ".png", 3.1
) )
@ -1034,9 +1036,7 @@ def test_stroke_descender():
draw.text((10, 0), "y", "#f00", font, stroke_width=2, stroke_fill="#0f0") draw.text((10, 0), "y", "#f00", font, stroke_width=2, stroke_fill="#0f0")
# Assert # Assert
assert_image_similar( assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_descender.png", 6.76)
im, Image.open("Tests/images/imagedraw_stroke_descender.png"), 6.76
)
@skip_unless_feature("freetype2") @skip_unless_feature("freetype2")
@ -1052,9 +1052,7 @@ def test_stroke_multiline():
) )
# Assert # Assert
assert_image_similar( assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3)
im, Image.open("Tests/images/imagedraw_stroke_multiline.png"), 3.3
)
def test_same_color_outline(): def test_same_color_outline():
@ -1093,4 +1091,4 @@ def test_same_color_outline():
expected = "Tests/images/imagedraw_outline_{}_{}.png".format( expected = "Tests/images/imagedraw_outline_{}_{}.png".format(
operation, mode operation, mode
) )
assert_image_similar(im, Image.open(expected), 1) assert_image_similar_tofile(im, expected, 1)

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