Merge branch 'python-pillow:main' into test_lists_and_tuples

This commit is contained in:
Yay295 2023-04-13 00:20:44 -05:00 committed by GitHub
commit 886ed7a7d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
214 changed files with 2756 additions and 2488 deletions

View File

@ -13,7 +13,7 @@ environment:
- PYTHON: C:/Python311 - PYTHON: C:/Python311
ARCHITECTURE: x86 ARCHITECTURE: x86
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
- PYTHON: C:/Python37-x64 - PYTHON: C:/Python38-x64
ARCHITECTURE: x64 ARCHITECTURE: x64
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
@ -21,15 +21,17 @@ environment:
install: install:
- '%PYTHON%\%EXECUTABLE% --version' - '%PYTHON%\%EXECUTABLE% --version'
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip - curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
- 7z x pillow-depends.zip -oc:\ - 7z x pillow-depends.zip -oc:\
- 7z x pillow-test-images.zip -oc:\
- mv c:\pillow-depends-main c:\pillow-depends - mv c:\pillow-depends-main c:\pillow-depends
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images - xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\ - 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
- ..\pillow-depends\gs1000w32.exe /S - choco install ghostscript --version=10.0.0.20230317
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH% - path c:\nasm-2.15.05;C:\Program Files\gs\gs10.00.0\bin;%PATH%
- cd c:\pillow\winbuild\ - cd c:\pillow\winbuild\
- ps: | - ps: |
c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\ c:\python38\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
c:\pillow\winbuild\build\build_dep_all.cmd c:\pillow\winbuild\build\build_dep_all.cmd
$host.SetShouldExit(0) $host.SetShouldExit(0)
- path C:\pillow\winbuild\build\bin;%PATH% - path C:\pillow\winbuild\build\bin;%PATH%
@ -50,8 +52,8 @@ test_script:
#- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest? #- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest?
after_test: after_test:
- python -m pip install codecov - curl -Os https://uploader.codecov.io/latest/windows/codecov.exe
- codecov --file coverage.xml --name %PYTHON% --flags AppVeyor - .\codecov.exe --file coverage.xml --name %PYTHON% --flags AppVeyor
matrix: matrix:
fast_finish: true fast_finish: true

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# gather the coverage data # gather the coverage data
python3 -m pip install codecov python3 -m pip install coverage
if [[ $MATRIX_DOCKER ]]; then if [[ $MATRIX_DOCKER ]]; then
python3 -m coverage xml --ignore-errors python3 -m coverage xml --ignore-errors
else else

View File

@ -37,11 +37,12 @@ python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma python3 -m pip install pyroma
if [[ $(uname) != CYGWIN* ]]; then if [[ $(uname) != CYGWIN* ]]; then
python3 -m pip install numpy # TODO Remove condition when NumPy supports 3.12
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
# PyQt6 doesn't support PyPy3 # PyQt6 doesn't support PyPy3
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
sudo apt-get -qq install libegl1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0 sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
python3 -m pip install pyqt6 python3 -m pip install pyqt6
fi fi

View File

@ -3,10 +3,12 @@ name: CIFuzz
on: on:
push: push:
paths: paths:
- ".github/workflows/cifuzz.yml"
- "**.c" - "**.c"
- "**.h" - "**.h"
pull_request: pull_request:
paths: paths:
- ".github/workflows/cifuzz.yml"
- "**.c" - "**.c"
- "**.h" - "**.h"
workflow_dispatch: workflow_dispatch:
@ -14,7 +16,7 @@ on:
permissions: permissions:
contents: read contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true

55
.github/workflows/docs.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: Docs
on:
push:
paths:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
FORCE_COLOR: 1
jobs:
build:
runs-on: ubuntu-latest
name: Docs
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.x"
cache: pip
cache-dependency-path: ".ci/*.sh"
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Install Linux dependencies
run: |
.ci/install.sh
env:
GHA_PYTHON_VERSION: "3.x"
- name: Build
run: |
.ci/build.sh
- name: Docs
run: |
make doccheck

View File

@ -13,7 +13,8 @@ python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma python3 -m pip install pyroma
python3 -m pip install numpy # TODO Remove condition when NumPy supports 3.12
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; 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

@ -20,7 +20,7 @@ jobs:
steps: steps:
- name: "Check issues" - name: "Check issues"
uses: actions/stale@v7 uses: actions/stale@v8
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
only-labels: "Awaiting OP Action" only-labels: "Awaiting OP Action"

View File

@ -1,6 +1,15 @@
name: Test Cygwin name: Test Cygwin
on: [push, pull_request, workflow_dispatch] on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions: permissions:
contents: read contents: read
@ -58,8 +67,7 @@ jobs:
python3${{ matrix.python-minor-version }}-numpy python3${{ matrix.python-minor-version }}-numpy
python3${{ matrix.python-minor-version }}-sip python3${{ matrix.python-minor-version }}-sip
python3${{ matrix.python-minor-version }}-tkinter python3${{ matrix.python-minor-version }}-tkinter
qt5-devel-tools wget
subversion
xorg-server-extra xorg-server-extra
zlib-devel zlib-devel

View File

@ -1,6 +1,15 @@
name: Test Docker name: Test Docker
on: [push, pull_request, workflow_dispatch] on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions: permissions:
contents: read contents: read
@ -24,11 +33,11 @@ jobs:
# Then run the remainder # Then run the remainder
alpine, alpine,
amazon-2-amd64, amazon-2-amd64,
amazon-2023-amd64,
arch, arch,
centos-7-amd64, centos-7-amd64,
centos-stream-8-amd64, centos-stream-8-amd64,
centos-stream-9-amd64, centos-stream-9-amd64,
debian-10-buster-x86,
debian-11-bullseye-x86, debian-11-bullseye-x86,
fedora-36-amd64, fedora-36-amd64,
fedora-37-amd64, fedora-37-amd64,
@ -87,6 +96,7 @@ jobs:
with: with:
flags: GHA_Docker flags: GHA_Docker
name: ${{ matrix.docker }} name: ${{ matrix.docker }}
gcov: true
success: success:
permissions: permissions:

View File

@ -1,6 +1,15 @@
name: Test MinGW name: Test MinGW
on: [push, pull_request, workflow_dispatch] on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions: permissions:
contents: read contents: read
@ -59,8 +68,7 @@ jobs:
${{ matrix.package }}-python3-numpy \ ${{ matrix.package }}-python3-numpy \
${{ matrix.package }}-python3-olefile \ ${{ matrix.package }}-python3-olefile \
${{ matrix.package }}-python3-pip \ ${{ matrix.package }}-python3-pip \
${{ matrix.package }}-python3-setuptools \ ${{ matrix.package }}-python3-setuptools
subversion
if [ ${{ matrix.package }} == "mingw-w64-x86_64" ]; then if [ ${{ matrix.package }} == "mingw-w64-x86_64" ]; then
pacman -S --noconfirm \ pacman -S --noconfirm \

View File

@ -5,10 +5,12 @@ name: Test Valgrind
on: on:
push: push:
paths: paths:
- ".github/workflows/test-valgrind.yml"
- "**.c" - "**.c"
- "**.h" - "**.h"
pull_request: pull_request:
paths: paths:
- ".github/workflows/test-valgrind.yml"
- "**.c" - "**.c"
- "**.h" - "**.h"
workflow_dispatch: workflow_dispatch:
@ -16,7 +18,7 @@ on:
permissions: permissions:
contents: read contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true

View File

@ -1,6 +1,15 @@
name: Test Windows name: Test Windows
on: [push, pull_request, workflow_dispatch] on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions: permissions:
contents: read contents: read
@ -15,7 +24,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12-dev"]
architecture: ["x86", "x64"] architecture: ["x86", "x64"]
include: include:
# PyPy 7.3.4+ only ships 64-bit binaries for Windows # PyPy 7.3.4+ only ships 64-bit binaries for Windows
@ -38,6 +47,12 @@ jobs:
repository: python-pillow/pillow-depends repository: python-pillow/pillow-depends
path: winbuild\depends path: winbuild\depends
- name: Checkout extra test images
uses: actions/checkout@v3
with:
repository: python-pillow/test-images
path: Tests\test-images
# sets env: pythonLocation # sets env: pythonLocation
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
@ -59,10 +74,11 @@ jobs:
7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\" 7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\"
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH
winbuild\depends\gs1000w32.exe /S choco install ghostscript --version=10.0.0.20230317
echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
xcopy /S /Y winbuild\depends\test_images\* Tests\images\ # Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images
# make cache key depend on VS version # make cache key depend on VS version
& "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" ` & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" `
@ -81,7 +97,7 @@ jobs:
- name: Prepare build - name: Prepare build
if: steps.build-cache.outputs.cache-hit != 'true' if: steps.build-cache.outputs.cache-hit != 'true'
run: | run: |
& python.exe winbuild\build_prepare.py -v --python=$env:pythonLocation --srcdir & python.exe winbuild\build_prepare.py -v --python $env:pythonLocation
shell: pwsh shell: pwsh
- name: Build dependencies / libjpeg-turbo - name: Build dependencies / libjpeg-turbo

View File

@ -1,6 +1,15 @@
name: Test name: Test
on: [push, pull_request, workflow_dispatch] on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions: permissions:
contents: read contents: read
@ -22,14 +31,14 @@ jobs:
python-version: [ python-version: [
"pypy3.9", "pypy3.9",
"pypy3.8", "pypy3.8",
"3.12-dev",
"3.11", "3.11",
"3.10", "3.10",
"3.9", "3.9",
"3.8", "3.8",
"3.7",
] ]
include: include:
- python-version: "3.7" - python-version: "3.9"
PYTHONOPTIMIZE: 1 PYTHONOPTIMIZE: 1
REVERSE: "--reverse" REVERSE: "--reverse"
- python-version: "3.8" - python-version: "3.8"
@ -95,11 +104,6 @@ jobs:
name: errors name: errors
path: Tests/errors path: Tests/errors
- name: Docs
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.11
run: |
make doccheck
- name: After success - name: After success
run: | run: |
.ci/after_success.sh .ci/after_success.sh
@ -107,9 +111,9 @@ jobs:
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
with: with:
file: ./coverage.xml
flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }} flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }} name: ${{ matrix.os }} Python ${{ matrix.python-version }}
gcov: true
success: success:
permissions: permissions:

2
.gitignore vendored
View File

@ -79,7 +79,7 @@ docs/_build/
# JetBrains # JetBrains
.idea .idea
# Extra test images installed from pillow-depends/test_images # Extra test images installed from python-pillow/test-images
Tests/images/README.md Tests/images/README.md
Tests/images/crash_1.tif Tests/images/crash_1.tif
Tests/images/crash_2.tif Tests/images/crash_2.tif

View File

@ -1,9 +1,9 @@
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 23.1.0 rev: 23.3.0
hooks: hooks:
- id: black - id: black
args: [--target-version=py37] args: [--target-version=py38]
# 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: []
@ -14,7 +14,7 @@ repos:
- id: isort - id: isort
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/PyCQA/bandit
rev: 1.7.4 rev: 1.7.5
hooks: hooks:
- id: bandit - id: bandit
args: [--severity-level=high] args: [--severity-level=high]
@ -26,10 +26,10 @@ repos:
- id: yesqa - id: yesqa
- repo: https://github.com/Lucas-C/pre-commit-hooks - repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.4.2 rev: v1.5.1
hooks: hooks:
- id: remove-tabs - id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$) exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 6.0.0 rev: 6.0.0
@ -57,7 +57,7 @@ repos:
- id: sphinx-lint - id: sphinx-lint
- repo: https://github.com/tox-dev/tox-ini-fmt - repo: https://github.com/tox-dev/tox-ini-fmt
rev: 0.6.1 rev: 1.0.0
hooks: hooks:
- id: tox-ini-fmt - id: tox-ini-fmt

View File

@ -1,5 +1,10 @@
version: 2 version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
python: python:
install: install:
- method: pip - method: pip

View File

@ -2,9 +2,99 @@
Changelog (Pillow) Changelog (Pillow)
================== ==================
9.5.0 (unreleased) 10.0.0 (unreleased)
-------------------
- Fixed type handling for include and lib directories #7069
[adisbladis, radarhere]
- Remove deprecations for Pillow 10.0.0 #7059, #7080
[hugovk, radarhere]
- Drop support for soon-EOL Python 3.7 #7058
[hugovk, radarhere]
9.5.0 (2023-04-01)
------------------ ------------------
- Added ImageSourceData to TAGS_V2 #7053
[radarhere]
- Clear PPM half token after use #7052
[radarhere]
- Removed absolute path to ldconfig #7044
[radarhere]
- Support custom comments and PLT markers when saving JPEG2000 images #6903
[joshware, radarhere, hugovk]
- Load before getting size in __array_interface__ #7034
[radarhere]
- Support creating BGR;15, BGR;16 and BGR;24 images, but drop support for BGR;32 #7010
[radarhere]
- Consider transparency when applying APNG blend mask #7018
[radarhere]
- Round duration when saving animated WebP images #6996
[radarhere]
- Added reading of JPEG2000 comments #6909
[radarhere]
- Decrement reference count #7003
[radarhere, nulano]
- Allow libtiff_support_custom_tags to be missing #7020
[radarhere]
- Improved I;16N support #6834
[radarhere]
- Added QOI reading #6852
[radarhere, hugovk]
- Added saving RGBA images as PDFs #6925
[radarhere]
- Do not raise an error if os.environ does not contain PATH #6935
[radarhere, hugovk]
- Close OleFileIO instance when closing or exiting FPX or MIC #7005
[radarhere]
- Added __int__ to IFDRational for Python >= 3.11 #6998
[radarhere]
- Added memoryview support to Dib.frombytes() #6988
[radarhere, nulano]
- Close file pointer copy in the libtiff encoder if still open #6986
[fcarron, radarhere]
- Raise an error if ImageDraw co-ordinates are incorrectly ordered #6978
[radarhere]
- Added "corners" argument to ImageDraw rounded_rectangle() #6954
[radarhere]
- Added memoryview support to frombytes() #6974
[radarhere]
- Allow comments in FITS images #6973
[radarhere]
- Support saving PDF with different X and Y resolutions #6961
[jvanderneutstulen, radarhere, hugovk]
- Fixed writing int as UNDEFINED tag #6950
[radarhere]
- Raise an error if EXIF data is too long when saving JPEG #6939
[radarhere]
- Handle more than one directory returned by pkg-config #6896 - Handle more than one directory returned by pkg-config #6896
[sebastic, radarhere] [sebastic, radarhere]

View File

@ -13,8 +13,8 @@ By obtaining, using, and/or copying this software and/or its associated
documentation, you agree that you have read, understood, and will comply documentation, you agree that you have read, understood, and will comply
with the following terms and conditions: with the following terms and conditions:
Permission to use, copy, modify, and distribute this software and its Permission to use, copy, modify and distribute this software and its
associated documentation for any purpose and without fee is hereby granted, documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appears in all copies, and that provided that the above copyright notice appears in all copies, and that
both that copyright notice and this permission notice appear in supporting both that copyright notice and this permission notice appear in supporting
documentation, and that the name of Secret Labs AB or the author not be documentation, and that the name of Secret Labs AB or the author not be

View File

@ -16,10 +16,16 @@ coverage:
python3 -m coverage report python3 -m coverage report
.PHONY: doc .PHONY: doc
doc: .PHONY: html
doc html:
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install . python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
$(MAKE) -C docs html $(MAKE) -C docs html
.PHONY: htmlview
htmlview:
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
$(MAKE) -C docs htmlview
.PHONY: doccheck .PHONY: doccheck
doccheck: doccheck:
$(MAKE) doc $(MAKE) doc
@ -38,7 +44,8 @@ help:
@echo " coverage run coverage test (in progress)" @echo " coverage run coverage test (in progress)"
@echo " doc make HTML docs" @echo " doc make HTML docs"
@echo " docserve run an HTTP server on the docs directory" @echo " docserve run an HTTP server on the docs directory"
@echo " html to make standalone HTML files" @echo " html make HTML docs"
@echo " htmlview open the index page built by the html target in your browser"
@echo " inplace make inplace extension" @echo " inplace make inplace extension"
@echo " install make and install" @echo " install make and install"
@echo " install-coverage make and install with C coverage" @echo " install-coverage make and install with C coverage"
@ -71,6 +78,7 @@ debug:
.PHONY: release-test .PHONY: release-test
release-test: release-test:
python3 Tests/check_release_notes.py
python3 -m pip install -e .[tests] python3 -m pip install -e .[tests]
python3 selftest.py python3 selftest.py
python3 -m pytest Tests python3 -m pytest Tests
@ -116,5 +124,5 @@ lint:
lint-fix: lint-fix:
python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black
python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort
python3 -m black --target-version py37 . python3 -m black --target-version py38 .
python3 -m isort . python3 -m isort .

View File

@ -11,14 +11,13 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
* [ ] Develop and prepare release in `main` branch. * [ ] Develop and prepare release in `main` branch.
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch. * [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI and GitHub Actions. * [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI and GitHub Actions.
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py` * [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
* [ ] Update `CHANGES.rst`. * [ ] Update `CHANGES.rst`.
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo. * [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
* [ ] Create branch and tag for release e.g.: * [ ] Create branch and tag for release e.g.:
```bash ```bash
git branch 5.2.x git branch 5.2.x
git tag 5.2.0 git tag 5.2.0
git push --all
git push --tags git push --tags
``` ```
* [ ] Create and check source distribution: * [ ] Create and check source distribution:
@ -32,8 +31,11 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
python3 -m twine upload dist/Pillow-5.2.0* python3 -m twine upload dist/Pillow-5.2.0*
``` ```
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py` * [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
```bash
git push --all
```
## Point Release ## Point Release
Released as needed for security, installation or critical bug fixes. Released as needed for security, installation or critical bug fixes.
@ -45,16 +47,12 @@ Released as needed for security, installation or critical bug fixes.
git checkout -t remotes/origin/5.2.x git checkout -t remotes/origin/5.2.x
``` ```
* [ ] Cherry pick individual commits from `main` branch to release branch e.g. `5.2.x`, then `git push`. * [ ] Cherry pick individual commits from `main` branch to release branch e.g. `5.2.x`, then `git push`.
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in release branch e.g. `5.2.x`. * [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in release branch e.g. `5.2.x`.
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py` * [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
* [ ] Run pre-release check via `make release-test`. * [ ] Run pre-release check via `make release-test`.
* [ ] Create tag for release e.g.: * [ ] Create tag for release e.g.:
```bash ```bash
git tag 5.2.1 git tag 5.2.1
git push
git push --tags git push --tags
``` ```
* [ ] Create and check source distribution: * [ ] Create and check source distribution:
@ -67,7 +65,10 @@ Released as needed for security, installation or critical bug fixes.
python3 -m twine check --strict dist/* python3 -m twine check --strict dist/*
python3 -m twine upload dist/Pillow-5.2.1* python3 -m twine upload dist/Pillow-5.2.1*
``` ```
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
```bash
git push
```
## Embargoed Release ## Embargoed Release
@ -83,7 +84,6 @@ Released as needed privately to individual vendors for critical security-related
```bash ```bash
git checkout 2.5.x git checkout 2.5.x
git tag 2.5.3 git tag 2.5.3
git push origin 2.5.x
git push origin --tags git push origin --tags
``` ```
* [ ] Create and check source distribution: * [ ] Create and check source distribution:
@ -91,15 +91,14 @@ Released as needed privately to individual vendors for critical security-related
make sdist make sdist
``` ```
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions)
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then:
```bash
git push origin 2.5.x
```
## Binary Distributions ## Binary Distributions
### Windows ### macOS and Linux
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
and copy into `dist/`
### Mac and Linux
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels): * [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
```bash ```bash
git clone https://github.com/python-pillow/pillow-wheels git clone https://github.com/python-pillow/pillow-wheels
@ -107,7 +106,18 @@ Released as needed privately to individual vendors for critical security-related
./update-pillow-tag.sh [[release tag]] ./update-pillow-tag.sh [[release tag]]
``` ```
* [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases) * [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases)
and copy into `dist/` and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli) from the main repo:
```bash
gh release download --dir dist --pattern "*.whl" --repo python-pillow/pillow-wheels
```
### Windows
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
```bash
gh run download --dir dist
# select dist-x.y.z
```
## Publicize Release ## Publicize Release

View File

@ -0,0 +1,6 @@
import sys
from pathlib import Path
for rst in Path("docs/releasenotes").glob("[1-9]*.rst"):
if "TODO" in open(rst).read():
sys.exit(f"Error: remove TODO from {rst}")

View File

@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
HAS_UPLOADER = False HAS_UPLOADER = False
if os.environ.get("SHOW_ERRORS", None): if os.environ.get("SHOW_ERRORS"):
# local img.show for errors. # local img.show for errors.
HAS_UPLOADER = True HAS_UPLOADER = True
@ -271,7 +271,7 @@ def netpbm_available():
def magick_command(): def magick_command():
if sys.platform == "win32": if sys.platform == "win32":
magickhome = os.environ.get("MAGICK_HOME", "") magickhome = os.environ.get("MAGICK_HOME")
if magickhome: if magickhome:
imagemagick = [os.path.join(magickhome, "convert.exe")] imagemagick = [os.path.join(magickhome, "convert.exe")]
graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"] graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"]

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

BIN
Tests/images/comment.jp2 Normal file

Binary file not shown.

BIN
Tests/images/hopper.qoi Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

BIN
Tests/images/pil123rgba.qoi Normal file

Binary file not shown.

View File

@ -177,13 +177,14 @@ class TestEnvVars:
Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"}) Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"})
assert Image.core.get_block_size() == 2 * 1024 * 1024 assert Image.core.get_block_size() == 2 * 1024 * 1024
def test_warnings(self): @pytest.mark.parametrize(
pytest.warns( "var",
UserWarning, Image._apply_env_variables, {"PILLOW_ALIGNMENT": "15"} (
) {"PILLOW_ALIGNMENT": "15"},
pytest.warns( {"PILLOW_BLOCK_SIZE": "1024"},
UserWarning, Image._apply_env_variables, {"PILLOW_BLOCK_SIZE": "1024"} {"PILLOW_BLOCKS_MAX": "wat"},
) ),
pytest.warns( )
UserWarning, Image._apply_env_variables, {"PILLOW_BLOCKS_MAX": "wat"} def test_warnings(self, var):
) with pytest.warns(UserWarning):
Image._apply_env_variables(var)

View File

@ -36,12 +36,10 @@ class TestDecompressionBomb:
Image.MAX_IMAGE_PIXELS = 128 * 128 - 1 Image.MAX_IMAGE_PIXELS = 128 * 128 - 1
assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1 assert Image.MAX_IMAGE_PIXELS == 128 * 128 - 1
def open(): with pytest.warns(Image.DecompressionBombWarning):
with Image.open(TEST_FILE): with Image.open(TEST_FILE):
pass pass
pytest.warns(Image.DecompressionBombWarning, open)
def test_exception(self): def test_exception(self):
# Set limit to trigger exception on the test file # Set limit to trigger exception on the test file
Image.MAX_IMAGE_PIXELS = 64 * 128 - 1 Image.MAX_IMAGE_PIXELS = 64 * 128 - 1
@ -87,7 +85,8 @@ class TestDecompressionCrop:
# same decompression bomb warnings on them. # same decompression bomb warnings on them.
with hopper() as src: with hopper() as src:
box = (0, 0, src.width * 2, src.height * 2) box = (0, 0, src.width * 2, src.height * 2)
pytest.warns(Image.DecompressionBombWarning, src.crop, box) with pytest.warns(Image.DecompressionBombWarning):
src.crop(box)
def test_crop_decompression_checks(self): def test_crop_decompression_checks(self):
im = Image.new("RGB", (100, 100)) im = Image.new("RGB", (100, 100))
@ -95,7 +94,8 @@ class TestDecompressionCrop:
for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)): for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)):
assert im.crop(value).size == (9, 9) assert im.crop(value).size == (9, 9)
pytest.warns(Image.DecompressionBombWarning, im.crop, (-160, -160, 99, 99)) with pytest.warns(Image.DecompressionBombWarning):
im.crop((-160, -160, 99, 99))
with pytest.raises(Image.DecompressionBombError): with pytest.raises(Image.DecompressionBombError):
im.crop((-99909, -99990, 99999, 99999)) im.crop((-99909, -99990, 99999, 99999))

View File

@ -6,11 +6,6 @@ from PIL import _deprecate
@pytest.mark.parametrize( @pytest.mark.parametrize(
"version, expected", "version, expected",
[ [
(
10,
"Old thing is deprecated and will be removed in Pillow 10 "
r"\(2023-07-01\)\. Use new thing instead\.",
),
( (
11, 11,
"Old thing is deprecated and will be removed in Pillow 11 " "Old thing is deprecated and will be removed in Pillow 11 "
@ -29,7 +24,7 @@ def test_version(version, expected):
def test_unknown_version(): def test_unknown_version():
expected = r"Unknown removal version, update PIL\._deprecate\?" expected = r"Unknown removal version: 12345. Update PIL\._deprecate\?"
with pytest.raises(ValueError, match=expected): with pytest.raises(ValueError, match=expected):
_deprecate.deprecate("Old thing", 12345, "new thing") _deprecate.deprecate("Old thing", 12345, "new thing")
@ -57,18 +52,18 @@ def test_old_version(deprecated, plural, expected):
def test_plural(): def test_plural():
expected = ( expected = (
r"Old things are deprecated and will be removed in Pillow 10 \(2023-07-01\)\. " r"Old things are deprecated and will be removed in Pillow 11 \(2024-10-15\)\. "
r"Use new thing instead\." r"Use new thing instead\."
) )
with pytest.warns(DeprecationWarning, match=expected): with pytest.warns(DeprecationWarning, match=expected):
_deprecate.deprecate("Old things", 10, "new thing", plural=True) _deprecate.deprecate("Old things", 11, "new thing", plural=True)
def test_replacement_and_action(): def test_replacement_and_action():
expected = "Use only one of 'replacement' and 'action'" expected = "Use only one of 'replacement' and 'action'"
with pytest.raises(ValueError, match=expected): with pytest.raises(ValueError, match=expected):
_deprecate.deprecate( _deprecate.deprecate(
"Old thing", 10, replacement="new thing", action="Upgrade to new thing" "Old thing", 11, replacement="new thing", action="Upgrade to new thing"
) )
@ -81,16 +76,16 @@ def test_replacement_and_action():
) )
def test_action(action): def test_action(action):
expected = ( expected = (
r"Old thing is deprecated and will be removed in Pillow 10 \(2023-07-01\)\. " r"Old thing is deprecated and will be removed in Pillow 11 \(2024-10-15\)\. "
r"Upgrade to new thing\." r"Upgrade to new thing\."
) )
with pytest.warns(DeprecationWarning, match=expected): with pytest.warns(DeprecationWarning, match=expected):
_deprecate.deprecate("Old thing", 10, action=action) _deprecate.deprecate("Old thing", 11, action=action)
def test_no_replacement_or_action(): def test_no_replacement_or_action():
expected = ( expected = (
r"Old thing is deprecated and will be removed in Pillow 10 \(2023-07-01\)" r"Old thing is deprecated and will be removed in Pillow 11 \(2024-10-15\)"
) )
with pytest.warns(DeprecationWarning, match=expected): with pytest.warns(DeprecationWarning, match=expected):
_deprecate.deprecate("Old thing", 10) _deprecate.deprecate("Old thing", 11)

View File

@ -1,18 +0,0 @@
import warnings
with warnings.catch_warnings(record=True) as w:
# Arrange: cause all warnings to always be triggered
warnings.simplefilter("always")
# Act: trigger a warning with Qt5
from PIL import ImageQt
def test_deprecated():
# Assert
if ImageQt.qt_version in ("5", "side2"):
assert len(w) == 1
assert issubclass(w[0].category, DeprecationWarning)
assert "deprecated" in str(w[0].message)
else:
assert len(w) == 0

View File

@ -163,6 +163,12 @@ def test_apng_blend():
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
def test_apng_blend_transparency():
with Image.open("Tests/images/blend_transparency.png") as im:
im.seek(1)
assert im.getpixel((0, 0)) == (255, 0, 0)
def test_apng_chunk_order(): def test_apng_chunk_order():
with Image.open("Tests/images/apng/fctl_actl.png") as im: with Image.open("Tests/images/apng/fctl_actl.png") as im:
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
@ -263,13 +269,11 @@ def test_apng_chunk_errors():
with Image.open("Tests/images/apng/chunk_no_actl.png") as im: with Image.open("Tests/images/apng/chunk_no_actl.png") as im:
assert not im.is_animated assert not im.is_animated
def open(): with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/chunk_multi_actl.png") as im: with Image.open("Tests/images/apng/chunk_multi_actl.png") as im:
im.load() im.load()
assert not im.is_animated assert not im.is_animated
pytest.warns(UserWarning, open)
with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im: with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im:
assert not im.is_animated assert not im.is_animated
@ -287,21 +291,17 @@ def test_apng_chunk_errors():
def test_apng_syntax_errors(): def test_apng_syntax_errors():
def open_frames_zero(): with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im: with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im:
assert not im.is_animated assert not im.is_animated
with pytest.raises(OSError): with pytest.raises(OSError):
im.load() im.load()
pytest.warns(UserWarning, open_frames_zero) with pytest.warns(UserWarning):
def open_frames_zero_default():
with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im: with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im:
assert not im.is_animated assert not im.is_animated
im.load() im.load()
pytest.warns(UserWarning, open_frames_zero_default)
# we can handle this case gracefully # we can handle this case gracefully
exception = None exception = None
with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im: with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im:
@ -316,13 +316,11 @@ def test_apng_syntax_errors():
im.seek(im.n_frames - 1) im.seek(im.n_frames - 1)
im.load() im.load()
def open(): with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im: with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im:
assert not im.is_animated assert not im.is_animated
im.load() im.load()
pytest.warns(UserWarning, open)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"test_file", "test_file",
@ -657,13 +655,3 @@ def test_different_modes_in_later_frames(mode, tmp_path):
im.save(test_file, save_all=True, append_images=[Image.new(mode, (1, 1))]) im.save(test_file, save_all=True, append_images=[Image.new(mode, (1, 1))])
with Image.open(test_file) as reloaded: with Image.open(test_file) as reloaded:
assert reloaded.mode == mode assert reloaded.mode == mode
def test_constants_deprecation():
for enum, prefix in {
PngImagePlugin.Disposal: "APNG_DISPOSE_",
PngImagePlugin.Blend: "APNG_BLEND_",
}.items():
for name in enum.__members__:
with pytest.warns(DeprecationWarning):
assert getattr(PngImagePlugin, prefix + name) == enum[name]

View File

@ -1,6 +1,6 @@
import pytest import pytest
from PIL import BlpImagePlugin, Image from PIL import Image
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
@ -72,14 +72,3 @@ def test_crashes(test_file):
with Image.open(f) as im: with Image.open(f) as im:
with pytest.raises(OSError): with pytest.raises(OSError):
im.load() im.load()
def test_constants_deprecation():
for enum, prefix in {
BlpImagePlugin.Format: "BLP_FORMAT_",
BlpImagePlugin.Encoding: "BLP_ENCODING_",
BlpImagePlugin.AlphaEncoding: "BLP_ALPHA_ENCODING_",
}.items():
for name in enum.__members__:
with pytest.warns(DeprecationWarning):
assert getattr(BlpImagePlugin, prefix + name) == enum[name]

View File

@ -56,6 +56,7 @@ def test_handler(tmp_path):
def load(self, im): def load(self, im):
self.loaded = True self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1)) return Image.new("RGB", (1, 1))
def save(self, im, fp, filename): def save(self, im, fp, filename):

View File

@ -28,7 +28,8 @@ def test_unclosed_file():
im = Image.open(TEST_FILE) im = Image.open(TEST_FILE)
im.load() im.load()
pytest.warns(ResourceWarning, open) with pytest.warns(ResourceWarning):
open()
def test_closed_file(): def test_closed_file():

View File

@ -28,34 +28,65 @@ FILE2_COMPARE_SCALE2 = "Tests/images/non_zero_bb_scale2.png"
# EPS test files with binary preview # EPS test files with binary preview
FILE3 = "Tests/images/binary_preview_map.eps" FILE3 = "Tests/images/binary_preview_map.eps"
# Three unsigned 32bit little-endian values:
# 0xC6D3D0C5 magic number
# byte position of start of postscript section (12)
# byte length of postscript section (0)
# this byte length isn't valid, but we don't read it
simple_binary_header = b"\xc5\xd0\xd3\xc6\x0c\x00\x00\x00\x00\x00\x00\x00"
# taken from page 8 of the specification
# https://web.archive.org/web/20220120164601/https://www.adobe.com/content/dam/acom/en/devnet/actionscript/articles/5002.EPSF_Spec.pdf
simple_eps_file = (
b"%!PS-Adobe-3.0 EPSF-3.0",
b"%%BoundingBox: 5 5 105 105",
b"10 setlinewidth",
b"10 10 moveto",
b"0 90 rlineto 90 0 rlineto 0 -90 rlineto closepath",
b"stroke",
)
simple_eps_file_with_comments = (
simple_eps_file[:1]
+ (
b"%%Comment1: Some Value",
b"%%SecondComment: Another Value",
)
+ simple_eps_file[1:]
)
simple_eps_file_without_version = simple_eps_file[1:]
simple_eps_file_without_boundingbox = simple_eps_file[:1] + simple_eps_file[2:]
simple_eps_file_with_invalid_boundingbox = (
simple_eps_file[:1] + (b"%%BoundingBox: a b c d",) + simple_eps_file[2:]
)
simple_eps_file_with_invalid_boundingbox_valid_imagedata = (
simple_eps_file_with_invalid_boundingbox + (b"%ImageData: 100 100 8 3",)
)
simple_eps_file_with_long_ascii_comment = (
simple_eps_file[:2] + (b"%%Comment: " + b"X" * 300,) + simple_eps_file[2:]
)
simple_eps_file_with_long_binary_data = (
simple_eps_file[:2]
+ (
b"%%BeginBinary: 300",
b"\0" * 300,
b"%%EndBinary",
)
+ simple_eps_file[2:]
)
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_sanity(): @pytest.mark.parametrize(
# Regular scale ("filename", "size"), ((FILE1, (460, 352)), (FILE2, (360, 252)))
with Image.open(FILE1) as image1: )
image1.load() @pytest.mark.parametrize("scale", (1, 2))
assert image1.mode == "RGB" def test_sanity(filename, size, scale):
assert image1.size == (460, 352) expected_size = tuple(s * scale for s in size)
assert image1.format == "EPS" with Image.open(filename) as image:
image.load(scale=scale)
with Image.open(FILE2) as image2: assert image.mode == "RGB"
image2.load() assert image.size == expected_size
assert image2.mode == "RGB" assert image.format == "EPS"
assert image2.size == (360, 252)
assert image2.format == "EPS"
# Double scale
with Image.open(FILE1) as image1_scale2:
image1_scale2.load(scale=2)
assert image1_scale2.mode == "RGB"
assert image1_scale2.size == (920, 704)
assert image1_scale2.format == "EPS"
with Image.open(FILE2) as image2_scale2:
image2_scale2.load(scale=2)
assert image2_scale2.mode == "RGB"
assert image2_scale2.size == (720, 504)
assert image2_scale2.format == "EPS"
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available") @pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@ -69,11 +100,72 @@ def test_load():
def test_invalid_file(): def test_invalid_file():
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
EpsImagePlugin.EpsImageFile(invalid_file) EpsImagePlugin.EpsImageFile(invalid_file)
def test_binary_header_only():
data = io.BytesIO(simple_binary_header)
with pytest.raises(SyntaxError, match='EPS header missing "%!PS-Adobe" comment'):
EpsImagePlugin.EpsImageFile(data)
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_missing_version_comment(prefix):
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_version))
with pytest.raises(SyntaxError):
EpsImagePlugin.EpsImageFile(data)
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_missing_boundingbox_comment(prefix):
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_without_boundingbox))
with pytest.raises(SyntaxError, match='EPS header missing "%%BoundingBox" comment'):
EpsImagePlugin.EpsImageFile(data)
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_invalid_boundingbox_comment(prefix):
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox))
with pytest.raises(OSError, match="cannot determine EPS bounding box"):
EpsImagePlugin.EpsImageFile(data)
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_invalid_boundingbox_comment_valid_imagedata_comment(prefix):
data = io.BytesIO(
prefix + b"\n".join(simple_eps_file_with_invalid_boundingbox_valid_imagedata)
)
with Image.open(data) as img:
assert img.mode == "RGB"
assert img.size == (100, 100)
assert img.format == "EPS"
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_ascii_comment_too_long(prefix):
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_ascii_comment))
with pytest.raises(SyntaxError, match="not an EPS file"):
EpsImagePlugin.EpsImageFile(data)
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_long_binary_data(prefix):
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_binary_data))
EpsImagePlugin.EpsImageFile(data)
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
def test_load_long_binary_data(prefix):
data = io.BytesIO(prefix + b"\n".join(simple_eps_file_with_long_binary_data))
with Image.open(data) as img:
img.load()
assert img.mode == "RGB"
assert img.size == (100, 100)
assert img.format == "EPS"
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )
@ -100,7 +192,7 @@ def test_showpage():
with Image.open("Tests/images/reqd_showpage.png") as target: with Image.open("Tests/images/reqd_showpage.png") as target:
# should not crash/hang # should not crash/hang
plot_image.load() plot_image.load()
# fonts could be slightly different # fonts could be slightly different
assert_image_similar(plot_image, target, 6) assert_image_similar(plot_image, target, 6)
@ -111,7 +203,7 @@ def test_transparency():
assert plot_image.mode == "RGBA" assert plot_image.mode == "RGBA"
with Image.open("Tests/images/reqd_showpage_transparency.png") as target: with Image.open("Tests/images/reqd_showpage_transparency.png") as target:
# fonts could be slightly different # fonts could be slightly different
assert_image_similar(plot_image, target, 6) assert_image_similar(plot_image, target, 6)
@ -206,7 +298,6 @@ def test_resize(filename):
@pytest.mark.parametrize("filename", (FILE1, FILE2)) @pytest.mark.parametrize("filename", (FILE1, FILE2))
def test_thumbnail(filename): def test_thumbnail(filename):
# Issue #619 # Issue #619
# Arrange
with Image.open(filename) as im: with Image.open(filename) as im:
new_size = (100, 100) new_size = (100, 100)
im.thumbnail(new_size) im.thumbnail(new_size)
@ -220,7 +311,7 @@ def test_read_binary_preview():
pass pass
def test_readline(tmp_path): def test_readline_psfile(tmp_path):
# check all the freaking line endings possible from the spec # check all the freaking line endings possible from the spec
# test_string = u'something\r\nelse\n\rbaz\rbif\n' # test_string = u'something\r\nelse\n\rbaz\rbif\n'
line_endings = ["\r\n", "\n", "\n\r", "\r"] line_endings = ["\r\n", "\n", "\n\r", "\r"]
@ -237,7 +328,8 @@ def test_readline(tmp_path):
def _test_readline_io_psfile(test_string, ending): def _test_readline_io_psfile(test_string, ending):
f = io.BytesIO(test_string.encode("latin-1")) f = io.BytesIO(test_string.encode("latin-1"))
t = EpsImagePlugin.PSFile(f) with pytest.warns(DeprecationWarning):
t = EpsImagePlugin.PSFile(f)
_test_readline(t, ending) _test_readline(t, ending)
def _test_readline_file_psfile(test_string, ending): def _test_readline_file_psfile(test_string, ending):
@ -246,7 +338,8 @@ def test_readline(tmp_path):
w.write(test_string.encode("latin-1")) w.write(test_string.encode("latin-1"))
with open(f, "rb") as r: with open(f, "rb") as r:
t = EpsImagePlugin.PSFile(r) with pytest.warns(DeprecationWarning):
t = EpsImagePlugin.PSFile(r)
_test_readline(t, ending) _test_readline(t, ending)
for ending in line_endings: for ending in line_endings:
@ -255,6 +348,25 @@ def test_readline(tmp_path):
_test_readline_file_psfile(s, ending) _test_readline_file_psfile(s, ending)
def test_psfile_deprecation():
with pytest.warns(DeprecationWarning):
EpsImagePlugin.PSFile(None)
@pytest.mark.parametrize("prefix", (b"", simple_binary_header))
@pytest.mark.parametrize(
"line_ending",
(b"\r\n", b"\n", b"\n\r", b"\r"),
)
def test_readline(prefix, line_ending):
simple_file = prefix + line_ending.join(simple_eps_file_with_comments)
data = io.BytesIO(simple_file)
test_file = EpsImagePlugin.EpsImageFile(data)
assert test_file.info["Comment1"] == "Some Value"
assert test_file.info["SecondComment"] == "Another Value"
assert test_file.size == (100, 100)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"filename", "filename",
( (

View File

@ -2,7 +2,7 @@ from io import BytesIO
import pytest import pytest
from PIL import FitsImagePlugin, FitsStubImagePlugin, Image from PIL import FitsImagePlugin, Image
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper
@ -44,36 +44,7 @@ def test_naxis_zero():
pass pass
def test_stub_deprecated(): def test_comment():
class Handler: image_data = b"SIMPLE = T / comment string"
opened = False with pytest.raises(OSError):
loaded = False FitsImagePlugin.FitsImageFile(BytesIO(image_data))
def open(self, im):
self.opened = True
def load(self, im):
self.loaded = True
return Image.new("RGB", (1, 1))
handler = Handler()
with pytest.warns(DeprecationWarning):
FitsStubImagePlugin.register_handler(handler)
with Image.open(TEST_FILE) as im:
assert im.format == "FITS"
assert im.size == (128, 128)
assert im.mode == "L"
assert handler.opened
assert not handler.loaded
im.load()
assert handler.loaded
FitsStubImagePlugin._handler = None
Image.register_open(
FitsImagePlugin.FitsImageFile.format,
FitsImagePlugin.FitsImageFile,
FitsImagePlugin._accept,
)

View File

@ -36,7 +36,8 @@ def test_unclosed_file():
im = Image.open(static_test_file) im = Image.open(static_test_file)
im.load() im.load()
pytest.warns(ResourceWarning, open) with pytest.warns(ResourceWarning):
open()
def test_closed_file(): def test_closed_file():

View File

@ -18,6 +18,16 @@ def test_sanity():
assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png") assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png")
def test_close():
with Image.open("Tests/images/input_bw_one_band.fpx") as im:
pass
assert im.ole.fp.closed
im = Image.open("Tests/images/input_bw_one_band.fpx")
im.close()
assert im.ole.fp.closed
def test_invalid_file(): def test_invalid_file():
# Test an invalid OLE file # Test an invalid OLE file
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"

View File

@ -21,12 +21,3 @@ def test_invalid_file():
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
FtexImagePlugin.FtexImageFile(invalid_file) FtexImagePlugin.FtexImageFile(invalid_file)
def test_constants_deprecation():
for enum, prefix in {
FtexImagePlugin.Format: "FORMAT_",
}.items():
for name in enum.__members__:
with pytest.warns(DeprecationWarning):
assert getattr(FtexImagePlugin, prefix + name) == enum[name]

View File

@ -36,7 +36,8 @@ def test_unclosed_file():
im = Image.open(TEST_GIF) im = Image.open(TEST_GIF)
im.load() im.load()
pytest.warns(ResourceWarning, open) with pytest.warns(ResourceWarning):
open()
def test_closed_file(): def test_closed_file():
@ -1087,7 +1088,8 @@ def test_rgb_transparency(tmp_path):
im = Image.new("RGB", (1, 1)) im = Image.new("RGB", (1, 1))
im.info["transparency"] = b"" im.info["transparency"] = b""
ims = [Image.new("RGB", (1, 1))] ims = [Image.new("RGB", (1, 1))]
pytest.warns(UserWarning, im.save, out, save_all=True, append_images=ims) with pytest.warns(UserWarning):
im.save(out, save_all=True, append_images=ims)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert "transparency" not in reloaded.info assert "transparency" not in reloaded.info

View File

@ -56,6 +56,7 @@ def test_handler(tmp_path):
def load(self, im): def load(self, im):
self.loaded = True self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1)) return Image.new("RGB", (1, 1))
def save(self, im, fp, filename): def save(self, im, fp, filename):

View File

@ -57,6 +57,7 @@ def test_handler(tmp_path):
def load(self, im): def load(self, im):
self.loaded = True self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1)) return Image.new("RGB", (1, 1))
def save(self, im, fp, filename): def save(self, im, fp, filename):

View File

@ -212,12 +212,10 @@ def test_save_append_images(tmp_path):
def test_unexpected_size(): def test_unexpected_size():
# This image has been manually hexedited to state that it is 16x32 # This image has been manually hexedited to state that it is 16x32
# while the image within is still 16x16 # while the image within is still 16x16
def open(): with pytest.warns(UserWarning):
with Image.open("Tests/images/hopper_unexpected.ico") as im: with Image.open("Tests/images/hopper_unexpected.ico") as im:
assert im.size == (16, 16) assert im.size == (16, 16)
pytest.warns(UserWarning, open)
def test_draw_reloaded(tmp_path): def test_draw_reloaded(tmp_path):
with Image.open(TEST_ICO_FILE) as im: with Image.open(TEST_ICO_FILE) as im:

View File

@ -32,7 +32,8 @@ def test_unclosed_file():
im = Image.open(TEST_IM) im = Image.open(TEST_IM)
im.load() im.load()
pytest.warns(ResourceWarning, open) with pytest.warns(ResourceWarning):
open()
def test_closed_file(): def test_closed_file():

View File

@ -270,7 +270,10 @@ class TestFileJpeg:
# https://github.com/python-pillow/Pillow/issues/148 # https://github.com/python-pillow/Pillow/issues/148
f = str(tmp_path / "temp.jpg") f = str(tmp_path / "temp.jpg")
im = hopper() im = hopper()
im.save(f, "JPEG", quality=90, exif=b"1" * 65532) im.save(f, "JPEG", quality=90, exif=b"1" * 65533)
with pytest.raises(ValueError):
im.save(f, "JPEG", quality=90, exif=b"1" * 65534)
def test_exif_typeerror(self): def test_exif_typeerror(self):
with Image.open("Tests/images/exif_typeerror.jpg") as im: with Image.open("Tests/images/exif_typeerror.jpg") as im:
@ -445,7 +448,7 @@ class TestFileJpeg:
ims = im.get_child_images() ims = im.get_child_images()
assert len(ims) == 1 assert len(ims) == 1
assert_image_equal_tofile(ims[0], "Tests/images/flower_thumbnail.png") assert_image_similar_tofile(ims[0], "Tests/images/flower_thumbnail.png", 2.1)
def test_mp(self): def test_mp(self):
with Image.open("Tests/images/pil_sample_rgb.jpg") as im: with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
@ -633,12 +636,6 @@ class TestFileJpeg:
assert max(im2.quantization[0]) <= 255 assert max(im2.quantization[0]) <= 255
assert max(im2.quantization[1]) <= 255 assert max(im2.quantization[1]) <= 255
def test_convert_dict_qtables_deprecation(self):
with pytest.warns(DeprecationWarning):
qtable = {0: [1, 2, 3, 4]}
qtable2 = JpegImagePlugin.convert_dict_qtables(qtable)
assert qtable == qtable2
@pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available")
def test_load_djpeg(self): def test_load_djpeg(self):
with Image.open(TEST_FILE) as img: with Image.open(TEST_FILE) as img:

View File

@ -4,13 +4,21 @@ from io import BytesIO
import pytest import pytest
from PIL import Image, ImageFile, Jpeg2KImagePlugin, UnidentifiedImageError, features from PIL import (
Image,
ImageFile,
Jpeg2KImagePlugin,
UnidentifiedImageError,
_binary,
features,
)
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
assert_image_similar, assert_image_similar,
assert_image_similar_tofile, assert_image_similar_tofile,
skip_unless_feature, skip_unless_feature,
skip_unless_feature_version,
) )
EXTRA_DIR = "Tests/images/jpeg2000" EXTRA_DIR = "Tests/images/jpeg2000"
@ -353,6 +361,35 @@ def test_subsampling_decode(name):
assert_image_similar(im, expected, epsilon) assert_image_similar(im, expected, epsilon)
def test_comment():
with Image.open("Tests/images/comment.jp2") as im:
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
# Test an image that is truncated partway through a codestream
with open("Tests/images/comment.jp2", "rb") as fp:
b = BytesIO(fp.read(130))
with Image.open(b) as im:
pass
def test_save_comment():
for comment in ("Created by Pillow", b"Created by Pillow"):
out = BytesIO()
test_card.save(out, "JPEG2000", comment=comment)
with Image.open(out) as im:
assert im.info["comment"] == b"Created by Pillow"
out = BytesIO()
long_comment = b" " * 65531
test_card.save(out, "JPEG2000", comment=long_comment)
with Image.open(out) as im:
assert im.info["comment"] == long_comment
with pytest.raises(ValueError):
test_card.save(out, "JPEG2000", comment=long_comment + b" ")
@pytest.mark.parametrize( @pytest.mark.parametrize(
"test_file", "test_file",
[ [
@ -370,3 +407,29 @@ def test_crashes(test_file):
im.load() im.load()
except OSError: except OSError:
pass pass
@skip_unless_feature_version("jpg_2000", "2.4.0")
def test_plt_marker():
# Search the start of the codesteam for PLT
out = BytesIO()
test_card.save(out, "JPEG2000", no_jp2=True, plt=True)
out.seek(0)
while True:
marker = out.read(2)
if not marker:
assert False, "End of stream without PLT"
jp2_boxid = _binary.i16be(marker)
if jp2_boxid == 0xFF4F:
# SOC has no length
continue
elif jp2_boxid == 0xFF58:
# PLT
return
elif jp2_boxid == 0xFF93:
assert False, "SOD without finding PLT first"
hdr = out.read(2)
length = _binary.i16be(hdr)
out.seek(length - 2, os.SEEK_CUR)

View File

@ -668,6 +668,16 @@ class TestFileLibTiff(LibTiffTestCase):
assert reloaded.tag_v2[530] == (1, 1) assert reloaded.tag_v2[530] == (1, 1)
assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255) assert reloaded.tag_v2[532] == (0, 255, 128, 255, 128, 255)
def test_exif_ifd(self, tmp_path):
outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/tiff_adobe_deflate.tif") as im:
assert im.tag_v2[34665] == 125456
im.save(outfile)
with Image.open(outfile) as reloaded:
if Image.core.libtiff_support_custom_tags:
assert reloaded.tag_v2[34665] == 125456
def test_crashing_metadata(self, tmp_path): def test_crashing_metadata(self, tmp_path):
# issue 1597 # issue 1597
with Image.open("Tests/images/rdf.tif") as im: with Image.open("Tests/images/rdf.tif") as im:
@ -984,6 +994,36 @@ class TestFileLibTiff(LibTiffTestCase):
) as im: ) as im:
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png") assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
@pytest.mark.parametrize(
"file_name, mode, size, tile",
[
(
"tiff_wrong_bits_per_sample.tiff",
"RGBA",
(52, 53),
[("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
),
(
"tiff_wrong_bits_per_sample_2.tiff",
"RGB",
(16, 16),
[("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
),
(
"tiff_wrong_bits_per_sample_3.tiff",
"RGBA",
(512, 256),
[("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
),
],
)
def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
with Image.open("Tests/images/" + file_name) as im:
assert im.mode == mode
assert im.size == size
assert im.tile == tile
im.load()
def test_no_rows_per_strip(self): def test_no_rows_per_strip(self):
# This image does not have a RowsPerStrip TIFF tag # This image does not have a RowsPerStrip TIFF tag
infile = "Tests/images/no_rows_per_strip.tif" infile = "Tests/images/no_rows_per_strip.tif"
@ -1065,3 +1105,27 @@ class TestFileLibTiff(LibTiffTestCase):
out = str(tmp_path / "temp.tif") out = str(tmp_path / "temp.tif")
with pytest.raises(SystemError): with pytest.raises(SystemError):
im.save(out, compression=compression) im.save(out, compression=compression)
def test_save_many_compressed(self, tmp_path):
im = hopper()
out = str(tmp_path / "temp.tif")
for _ in range(10000):
im.save(out, compression="jpeg")
@pytest.mark.parametrize(
"path, sizes",
(
("Tests/images/hopper.tif", ()),
("Tests/images/child_ifd.tiff", (16, 8)),
("Tests/images/child_ifd_jpeg.tiff", (20,)),
),
)
def test_get_child_images(self, path, sizes):
with Image.open(path) as im:
ims = im.get_child_images()
assert len(ims) == len(sizes)
for i, im in enumerate(ims):
w = sizes[i]
expected = Image.new("RGB", (w, w), "#f00")
assert_image_similar(im, expected, 1)

View File

@ -51,6 +51,16 @@ def test_seek():
assert im.tell() == 0 assert im.tell() == 0
def test_close():
with Image.open(TEST_FILE) as im:
pass
assert im.ole.fp.closed
im = Image.open(TEST_FILE)
im.close()
assert im.ole.fp.closed
def test_invalid_file(): def test_invalid_file():
# Test an invalid OLE file # Test an invalid OLE file
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"

View File

@ -42,7 +42,8 @@ def test_unclosed_file():
im = Image.open(test_files[0]) im = Image.open(test_files[0])
im.load() im.load()
pytest.warns(ResourceWarning, open) with pytest.warns(ResourceWarning):
open()
def test_closed_file(): def test_closed_file():

View File

@ -8,7 +8,7 @@ import pytest
from PIL import Image, PdfParser, features from PIL import Image, PdfParser, features
from .helper import hopper, mark_if_feature_version from .helper import hopper, mark_if_feature_version, skip_unless_feature
def helper_save_as_pdf(tmp_path, mode, **kwargs): def helper_save_as_pdf(tmp_path, mode, **kwargs):
@ -42,6 +42,11 @@ def test_save(tmp_path, mode):
helper_save_as_pdf(tmp_path, mode) helper_save_as_pdf(tmp_path, mode)
@skip_unless_feature("jpg_2000")
def test_save_rgba(tmp_path):
helper_save_as_pdf(tmp_path, "RGBA")
def test_monochrome(tmp_path): def test_monochrome(tmp_path):
# Arrange # Arrange
mode = "1" mode = "1"
@ -80,6 +85,34 @@ def test_resolution(tmp_path):
assert size == (61.44, 61.44) assert size == (61.44, 61.44)
@pytest.mark.parametrize(
"params",
(
{"dpi": (75, 150)},
{"dpi": (75, 150), "resolution": 200},
),
)
def test_dpi(params, tmp_path):
im = hopper()
outfile = str(tmp_path / "temp.pdf")
im.save(outfile, **params)
with open(outfile, "rb") as fp:
contents = fp.read()
size = tuple(
float(d)
for d in contents.split(b"stream\nq ")[1].split(b" 0 0 cm")[0].split(b" 0 0 ")
)
assert size == (122.88, 61.44)
size = tuple(
float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split()
)
assert size == (122.88, 61.44)
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
) )

View File

@ -256,6 +256,16 @@ def test_truncated_file(tmp_path):
im.load() im.load()
def test_not_enough_image_data(tmp_path):
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(b"P2 1 2 255 255")
with Image.open(path) as im:
with pytest.raises(ValueError):
im.load()
@pytest.mark.parametrize("maxval", (b"0", b"65536")) @pytest.mark.parametrize("maxval", (b"0", b"65536"))
def test_invalid_maxval(maxval, tmp_path): def test_invalid_maxval(maxval, tmp_path):
path = str(tmp_path / "temp.ppm") path = str(tmp_path / "temp.ppm")

View File

@ -27,7 +27,8 @@ def test_unclosed_file():
im = Image.open(test_file) im = Image.open(test_file)
im.load() im.load()
pytest.warns(ResourceWarning, open) with pytest.warns(ResourceWarning):
open()
def test_closed_file(): def test_closed_file():

28
Tests/test_file_qoi.py Normal file
View File

@ -0,0 +1,28 @@
import pytest
from PIL import Image, QoiImagePlugin
from .helper import assert_image_equal_tofile, assert_image_similar_tofile
def test_sanity():
with Image.open("Tests/images/hopper.qoi") as im:
assert im.mode == "RGB"
assert im.size == (128, 128)
assert im.format == "QOI"
assert_image_equal_tofile(im, "Tests/images/hopper.png")
with Image.open("Tests/images/pil123rgba.qoi") as im:
assert im.mode == "RGBA"
assert im.size == (162, 150)
assert im.format == "QOI"
assert_image_similar_tofile(im, "Tests/images/pil123rgba.png", 0.03)
def test_invalid_file():
invalid_file = "Tests/images/flower.jpg"
with pytest.raises(SyntaxError):
QoiImagePlugin.QoiImageFile(invalid_file)

View File

@ -25,7 +25,8 @@ def test_unclosed_file():
im = Image.open(TEST_FILE) im = Image.open(TEST_FILE)
im.load() im.load()
pytest.warns(ResourceWarning, open) with pytest.warns(ResourceWarning):
open()
def test_closed_file(): def test_closed_file():

View File

@ -29,11 +29,9 @@ def test_sanity(codec, test_path, format):
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file(): def test_unclosed_file():
def open(): with pytest.warns(ResourceWarning):
TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
pytest.warns(ResourceWarning, open)
def test_close(): def test_close():
with warnings.catch_warnings(): with warnings.catch_warnings():

View File

@ -163,7 +163,9 @@ def test_save_id_section(tmp_path):
# Save with custom id section greater than 255 characters # Save with custom id section greater than 255 characters
id_section = b"Test content" * 25 id_section = b"Test content" * 25
pytest.warns(UserWarning, lambda: im.save(out, id_section=id_section)) with pytest.warns(UserWarning):
im.save(out, id_section=id_section)
with Image.open(out) as test_im: with Image.open(out) as test_im:
assert test_im.info["id_section"] == id_section[:255] assert test_im.info["id_section"] == id_section[:255]

View File

@ -61,7 +61,8 @@ class TestFileTiff:
im = Image.open("Tests/images/multipage.tiff") im = Image.open("Tests/images/multipage.tiff")
im.load() im.load()
pytest.warns(ResourceWarning, open) with pytest.warns(ResourceWarning):
open()
def test_closed_file(self): def test_closed_file(self):
with warnings.catch_warnings(): with warnings.catch_warnings():
@ -83,24 +84,6 @@ class TestFileTiff:
with Image.open("Tests/images/multipage.tiff") as im: with Image.open("Tests/images/multipage.tiff") as im:
im.load() im.load()
@pytest.mark.parametrize(
"path, sizes",
(
("Tests/images/hopper.tif", ()),
("Tests/images/child_ifd.tiff", (16, 8)),
("Tests/images/child_ifd_jpeg.tiff", (20,)),
),
)
def test_get_child_images(self, path, sizes):
with Image.open(path) as im:
ims = im.get_child_images()
assert len(ims) == len(sizes)
for i, im in enumerate(ims):
w = sizes[i]
expected = Image.new("RGB", (w, w), "#f00")
assert_image_similar(im, expected, 1)
def test_mac_tiff(self): def test_mac_tiff(self):
# Read RGBa images from macOS [@PIL136] # Read RGBa images from macOS [@PIL136]
@ -117,36 +100,6 @@ class TestFileTiff:
with Image.open("Tests/images/hopper_bigtiff.tif") as im: with Image.open("Tests/images/hopper_bigtiff.tif") as im:
assert_image_equal_tofile(im, "Tests/images/hopper.tif") assert_image_equal_tofile(im, "Tests/images/hopper.tif")
@pytest.mark.parametrize(
"file_name,mode,size,tile",
[
(
"tiff_wrong_bits_per_sample.tiff",
"RGBA",
(52, 53),
[("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
),
(
"tiff_wrong_bits_per_sample_2.tiff",
"RGB",
(16, 16),
[("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
),
(
"tiff_wrong_bits_per_sample_3.tiff",
"RGBA",
(512, 256),
[("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
),
],
)
def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
with Image.open("Tests/images/" + file_name) as im:
assert im.mode == mode
assert im.size == size
assert im.tile == tile
im.load()
def test_set_legacy_api(self): def test_set_legacy_api(self):
ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd = TiffImagePlugin.ImageFileDirectory_v2()
with pytest.raises(Exception) as e: with pytest.raises(Exception) as e:
@ -231,7 +184,8 @@ class TestFileTiff:
def test_bad_exif(self): def test_bad_exif(self):
with Image.open("Tests/images/hopper_bad_exif.jpg") as i: with Image.open("Tests/images/hopper_bad_exif.jpg") as i:
# Should not raise struct.error. # Should not raise struct.error.
pytest.warns(UserWarning, i._getexif) with pytest.warns(UserWarning):
i._getexif()
def test_save_rgba(self, tmp_path): def test_save_rgba(self, tmp_path):
im = hopper("RGBA") im = hopper("RGBA")

View File

@ -216,6 +216,22 @@ def test_writing_other_types_to_bytes(value, tmp_path):
assert reloaded.tag_v2[700] == b"\x01" assert reloaded.tag_v2[700] == b"\x01"
def test_writing_other_types_to_undefined(tmp_path):
im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2()
tag = TiffTags.TAGS_V2[33723]
assert tag.type == TiffTags.UNDEFINED
info[33723] = 1
out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info)
with Image.open(out) as reloaded:
assert reloaded.tag_v2[33723] == b"1"
def test_undefined_zero(tmp_path): def test_undefined_zero(tmp_path):
# Check that the tag has not been changed since this test was created # Check that the tag has not been changed since this test was created
tag = TiffTags.TAGS_V2[45059] tag = TiffTags.TAGS_V2[45059]
@ -236,7 +252,8 @@ def test_empty_metadata():
head = f.read(8) head = f.read(8)
info = TiffImagePlugin.ImageFileDirectory(head) info = TiffImagePlugin.ImageFileDirectory(head)
# Should not raise struct.error. # Should not raise struct.error.
pytest.warns(UserWarning, info.load, f) with pytest.warns(UserWarning):
info.load(f)
def test_iccprofile(tmp_path): def test_iccprofile(tmp_path):
@ -402,11 +419,12 @@ def test_too_many_entries():
ifd = TiffImagePlugin.ImageFileDirectory_v2() ifd = TiffImagePlugin.ImageFileDirectory_v2()
# 277: ("SamplesPerPixel", SHORT, 1), # 277: ("SamplesPerPixel", SHORT, 1),
ifd._tagdata[277] = struct.pack("hh", 4, 4) ifd._tagdata[277] = struct.pack("<hh", 4, 4)
ifd.tagtype[277] = TiffTags.SHORT ifd.tagtype[277] = TiffTags.SHORT
# Should not raise ValueError. # Should not raise ValueError.
pytest.warns(UserWarning, lambda: ifd[277]) with pytest.warns(UserWarning):
assert ifd[277] == 4
def test_tag_group_data(): def test_tag_group_data():

View File

@ -29,7 +29,10 @@ class TestUnsupportedWebp:
WebPImagePlugin.SUPPORTED = False WebPImagePlugin.SUPPORTED = False
file_path = "Tests/images/hopper.webp" file_path = "Tests/images/hopper.webp"
pytest.warns(UserWarning, lambda: pytest.raises(OSError, Image.open, file_path)) with pytest.warns(UserWarning):
with pytest.raises(OSError):
with Image.open(file_path):
pass
if HAVE_WEBP: if HAVE_WEBP:
WebPImagePlugin.SUPPORTED = True WebPImagePlugin.SUPPORTED = True

View File

@ -134,6 +134,18 @@ def test_timestamp_and_duration(tmp_path):
ts += durations[frame] ts += durations[frame]
def test_float_duration(tmp_path):
temp_file = str(tmp_path / "temp.webp")
with Image.open("Tests/images/iss634.apng") as im:
assert im.info["duration"] == 70.0
im.save(temp_file, save_all=True)
with Image.open(temp_file) as reloaded:
reloaded.load()
assert reloaded.info["duration"] == 70
def test_seeking(tmp_path): def test_seeking(tmp_path):
""" """
Create an animated WebP file, and then try seeking through frames in reverse-order, Create an animated WebP file, and then try seeking through frames in reverse-order,

View File

@ -82,9 +82,6 @@ def test_textsize(request, tmp_path):
assert dy == 20 assert dy == 20
assert dx in (0, 10) assert dx in (0, 10)
assert font.getlength(chr(i)) == dx assert font.getlength(chr(i)) == dx
with pytest.warns(DeprecationWarning) as log:
assert font.getsize(chr(i)) == (dx, dy)
assert len(log) == 1
for i in range(len(message)): for i in range(len(message)):
msg = message[: i + 1] msg = message[: i + 1]
assert font.getlength(msg) == len(msg) * 10 assert font.getlength(msg) == len(msg) * 10

View File

@ -48,6 +48,9 @@ class TestImage:
"RGBX", "RGBX",
"RGBA", "RGBA",
"RGBa", "RGBa",
"BGR;15",
"BGR;16",
"BGR;24",
"CMYK", "CMYK",
"YCbCr", "YCbCr",
"LAB", "LAB",
@ -57,9 +60,7 @@ class TestImage:
def test_image_modes_success(self, mode): def test_image_modes_success(self, mode):
Image.new(mode, (1, 1)) Image.new(mode, (1, 1))
@pytest.mark.parametrize( @pytest.mark.parametrize("mode", ("", "bad", "very very long"))
"mode", ("", "bad", "very very long", "BGR;15", "BGR;16", "BGR;24", "BGR;32")
)
def test_image_modes_fail(self, mode): def test_image_modes_fail(self, mode):
with pytest.raises(ValueError) as e: with pytest.raises(ValueError) as e:
Image.new(mode, (1, 1)) Image.new(mode, (1, 1))
@ -928,25 +929,7 @@ class TestImage:
im.apply_transparency() im.apply_transparency()
assert im.palette.colors[(27, 35, 6, 214)] == 24 assert im.palette.colors[(27, 35, 6, 214)] == 24
def test_categories_deprecation(self):
with pytest.warns(DeprecationWarning):
assert hopper().category == 0
with pytest.warns(DeprecationWarning):
assert Image.NORMAL == 0
with pytest.warns(DeprecationWarning):
assert Image.SEQUENCE == 1
with pytest.warns(DeprecationWarning):
assert Image.CONTAINER == 2
def test_constants(self): def test_constants(self):
with pytest.warns(DeprecationWarning):
assert Image.LINEAR == Image.Resampling.BILINEAR
with pytest.warns(DeprecationWarning):
assert Image.CUBIC == Image.Resampling.BICUBIC
with pytest.warns(DeprecationWarning):
assert Image.ANTIALIAS == Image.Resampling.LANCZOS
for enum in ( for enum in (
Image.Transpose, Image.Transpose,
Image.Transform, Image.Transform,

View File

@ -132,22 +132,26 @@ class TestImageGetPixel(AccessTest):
return 1 return 1
return tuple(range(1, bands + 1)) return tuple(range(1, bands + 1))
def check(self, mode, c=None): def check(self, mode, expected_color=None):
if not c: if not expected_color:
c = self.color(mode) expected_color = self.color(mode)
# check putpixel # check putpixel
im = Image.new(mode, (1, 1), None) im = Image.new(mode, (1, 1), None)
im.putpixel((0, 0), c) im.putpixel((0, 0), expected_color)
assert ( actual_color = im.getpixel((0, 0))
im.getpixel((0, 0)) == c assert actual_color == expected_color, (
), f"put/getpixel roundtrip failed for mode {mode}, color {c}" f"put/getpixel roundtrip failed for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# check putpixel negative index # check putpixel negative index
im.putpixel((-1, -1), c) im.putpixel((-1, -1), expected_color)
assert ( actual_color = im.getpixel((-1, -1))
im.getpixel((-1, -1)) == c assert actual_color == expected_color, (
), f"put/getpixel roundtrip negative index failed for mode {mode}, color {c}" f"put/getpixel roundtrip negative index failed for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# Check 0 # Check 0
im = Image.new(mode, (0, 0), None) im = Image.new(mode, (0, 0), None)
@ -155,27 +159,32 @@ class TestImageGetPixel(AccessTest):
error = ValueError if self._need_cffi_access else IndexError error = ValueError if self._need_cffi_access else IndexError
with pytest.raises(error): with pytest.raises(error):
im.putpixel((0, 0), c) im.putpixel((0, 0), expected_color)
with pytest.raises(error): with pytest.raises(error):
im.getpixel((0, 0)) im.getpixel((0, 0))
# Check 0 negative index # Check 0 negative index
with pytest.raises(error): with pytest.raises(error):
im.putpixel((-1, -1), c) im.putpixel((-1, -1), expected_color)
with pytest.raises(error): with pytest.raises(error):
im.getpixel((-1, -1)) im.getpixel((-1, -1))
# check initial color # check initial color
im = Image.new(mode, (1, 1), c) im = Image.new(mode, (1, 1), expected_color)
assert ( actual_color = im.getpixel((0, 0))
im.getpixel((0, 0)) == c assert actual_color == expected_color, (
), f"initial color failed for mode {mode}, color {c} " f"initial color failed for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# check initial color negative index # check initial color negative index
assert ( actual_color = im.getpixel((-1, -1))
im.getpixel((-1, -1)) == c assert actual_color == expected_color, (
), f"initial color failed with negative index for mode {mode}, color {c} " f"initial color failed with negative index for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# Check 0 # Check 0
im = Image.new(mode, (0, 0), c) im = Image.new(mode, (0, 0), expected_color)
with pytest.raises(error): with pytest.raises(error):
im.getpixel((0, 0)) im.getpixel((0, 0))
# Check 0 negative index # Check 0 negative index
@ -205,13 +214,13 @@ class TestImageGetPixel(AccessTest):
self.check(mode) self.check(mode)
@pytest.mark.parametrize("mode", ("I;16", "I;16B")) @pytest.mark.parametrize("mode", ("I;16", "I;16B"))
def test_signedness(self, mode): @pytest.mark.parametrize(
"expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)
)
def test_signedness(self, mode, expected_color):
# see https://github.com/python-pillow/Pillow/issues/452 # see https://github.com/python-pillow/Pillow/issues/452
# pixelaccess is using signed int* instead of uint* # pixelaccess is using signed int* instead of uint*
self.check(mode, 2**15 - 1) self.check(mode, expected_color)
self.check(mode, 2**15)
self.check(mode, 2**15 + 1)
self.check(mode, 2**16 - 1)
@pytest.mark.parametrize("mode", ("P", "PA")) @pytest.mark.parametrize("mode", ("P", "PA"))
@pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255))) @pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))
@ -266,15 +275,10 @@ class TestCffi(AccessTest):
# self._test_get_access(hopper('PA')) # PA -- how do I make a PA image? # self._test_get_access(hopper('PA')) # PA -- how do I make a PA image?
self._test_get_access(hopper("F")) self._test_get_access(hopper("F"))
im = Image.new("I;16", (10, 10), 40000) for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
self._test_get_access(im) im = Image.new(mode, (10, 10), 40000)
im = Image.new("I;16L", (10, 10), 40000) self._test_get_access(im)
self._test_get_access(im)
im = Image.new("I;16B", (10, 10), 40000)
self._test_get_access(im)
im = Image.new("I", (10, 10), 40000)
self._test_get_access(im)
# These don't actually appear to be modes that I can actually make, # These don't actually appear to be modes that I can actually make,
# as unpack sets them directly into the I mode. # as unpack sets them directly into the I mode.
# im = Image.new('I;32L', (10, 10), -2**10) # im = Image.new('I;32L', (10, 10), -2**10)
@ -313,15 +317,10 @@ class TestCffi(AccessTest):
# self._test_set_access(i, (128, 128)) #PA -- undone how to make # self._test_set_access(i, (128, 128)) #PA -- undone how to make
self._test_set_access(hopper("F"), 1024.0) self._test_set_access(hopper("F"), 1024.0)
im = Image.new("I;16", (10, 10), 40000) for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
self._test_set_access(im, 45000) im = Image.new(mode, (10, 10), 40000)
im = Image.new("I;16L", (10, 10), 40000) self._test_set_access(im, 45000)
self._test_set_access(im, 45000)
im = Image.new("I;16B", (10, 10), 40000)
self._test_set_access(im, 45000)
im = Image.new("I", (10, 10), 40000)
self._test_set_access(im, 45000)
# im = Image.new('I;32L', (10, 10), -(2**10)) # im = Image.new('I;32L', (10, 10), -(2**10))
# self._test_set_access(im, -(2**13)+1) # self._test_set_access(im, -(2**13)+1)
# im = Image.new('I;32B', (10, 10), 2**10) # im = Image.new('I;32B', (10, 10), 2**10)
@ -354,8 +353,8 @@ class TestCffi(AccessTest):
class TestImagePutPixelError(AccessTest): class TestImagePutPixelError(AccessTest):
IMAGE_MODES1 = ["L", "LA", "RGB", "RGBA"] IMAGE_MODES1 = ["LA", "RGB", "RGBA", "BGR;15"]
IMAGE_MODES2 = ["I", "I;16", "BGR;15"] IMAGE_MODES2 = ["L", "I", "I;16"]
INVALID_TYPES = ["foo", 1.0, None] INVALID_TYPES = ["foo", 1.0, None]
@pytest.mark.parametrize("mode", IMAGE_MODES1) @pytest.mark.parametrize("mode", IMAGE_MODES1)
@ -370,6 +369,11 @@ class TestImagePutPixelError(AccessTest):
( (
("L", (0, 2), "color must be int or single-element tuple"), ("L", (0, 2), "color must be int or single-element tuple"),
("LA", (0, 3), "color must be int, or tuple of one or two elements"), ("LA", (0, 3), "color must be int, or tuple of one or two elements"),
(
"BGR;15",
(0, 2),
"color must be int, or tuple of one or three elements",
),
( (
"RGB", "RGB",
(0, 2, 5), (0, 2, 5),
@ -398,11 +402,6 @@ class TestImagePutPixelError(AccessTest):
with pytest.raises(OverflowError): with pytest.raises(OverflowError):
im.putpixel((0, 0), 2**80) 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.xfail(reason="failing test") @pytest.mark.xfail(reason="failing test")

View File

@ -254,17 +254,6 @@ def test_p2pa_palette():
assert im_pa.getpalette() == im.getpalette() assert im_pa.getpalette() == im.getpalette()
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
def test_rgb_lab(mode):
im = Image.new(mode, (1, 1))
converted_im = im.convert("LAB")
assert converted_im.getpixel((0, 0)) == (0, 128, 128)
im = Image.new("LAB", (1, 1), (255, 0, 0))
converted_im = im.convert(mode)
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)
def test_matrix_illegal_conversion(): def test_matrix_illegal_conversion():
# Arrange # Arrange
im = hopper("CMYK") im = hopper("CMYK")

View File

@ -1,10 +1,17 @@
import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper
def test_sanity(): @pytest.mark.parametrize("data_type", ("bytes", "memoryview"))
def test_sanity(data_type):
im1 = hopper() im1 = hopper()
im2 = Image.frombytes(im1.mode, im1.size, im1.tobytes())
data = im1.tobytes()
if data_type == "memoryview":
data = memoryview(data)
im2 = Image.frombytes(im1.mode, im1.size, data)
assert_image_equal(im1, im2) assert_image_equal(im1, im2)

View File

@ -1,7 +1,5 @@
import pytest import pytest
from PIL import Image
from .helper import assert_image_equal, hopper from .helper import assert_image_equal, hopper
@ -62,8 +60,3 @@ def test_f_mode():
im = hopper("F") im = hopper("F")
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.point(None) im.point(None)
def test_coerce_e_deprecation():
with pytest.warns(DeprecationWarning):
assert Image.coerce_e(2).data == 2

View File

@ -617,11 +617,12 @@ def test_auxiliary_channels_isolated():
assert_image_equal(test_image.convert(dst_format[2]), reference_image) assert_image_equal(test_image.convert(dst_format[2]), reference_image)
def test_constants_deprecation(): @pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
for enum, prefix in { def test_rgb_lab(mode):
ImageCms.Intent: "INTENT_", im = Image.new(mode, (1, 1))
ImageCms.Direction: "DIRECTION_", converted_im = im.convert("LAB")
}.items(): assert converted_im.getpixel((0, 0)) == (0, 128, 128)
for name in enum.__members__:
with pytest.warns(DeprecationWarning): im = Image.new("LAB", (1, 1), (255, 0, 0))
assert getattr(ImageCms, prefix + name) == enum[name] converted_im = im.convert(mode)
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)

View File

@ -374,7 +374,13 @@ def ellipse_various_sizes_helper(filled):
for w in ellipse_sizes: for w in ellipse_sizes:
y = 1 y = 1
for h in ellipse_sizes: for h in ellipse_sizes:
border = [x, y, x + w - 1, y + h - 1] x1 = x + w
if w:
x1 -= 1
y1 = y + h
if h:
y1 -= 1
border = [x, y, x1, y1]
if filled: if filled:
draw.ellipse(border, fill="white") draw.ellipse(border, fill="white")
else: else:
@ -763,6 +769,36 @@ def test_rounded_rectangle(xy):
assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png") assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png")
@pytest.mark.parametrize("top_left", (True, False))
@pytest.mark.parametrize("top_right", (True, False))
@pytest.mark.parametrize("bottom_right", (True, False))
@pytest.mark.parametrize("bottom_left", (True, False))
def test_rounded_rectangle_corners(top_left, top_right, bottom_right, bottom_left):
corners = (top_left, top_right, bottom_right, bottom_left)
# Arrange
im = Image.new("RGB", (200, 200))
draw = ImageDraw.Draw(im)
# Act
draw.rounded_rectangle(
(10, 20, 190, 180), 30, fill="red", outline="green", width=5, corners=corners
)
# Assert
suffix = "".join(
(
("y" if top_left else "n"),
("y" if top_right else "n"),
("y" if bottom_right else "n"),
("y" if bottom_left else "n"),
)
)
assert_image_equal_tofile(
im, "Tests/images/imagedraw_rounded_rectangle_corners_" + suffix + ".png"
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"xy, radius, type", "xy, radius, type",
[ [
@ -934,9 +970,6 @@ def test_square():
img, draw = create_base_image_draw((10, 10)) img, draw = create_base_image_draw((10, 10))
draw.rectangle((2, 2, 7, 7), BLACK) draw.rectangle((2, 2, 7, 7), BLACK)
assert_image_equal_tofile(img, expected, "square as normal rectangle failed") assert_image_equal_tofile(img, expected, "square as normal rectangle failed")
img, draw = create_base_image_draw((10, 10))
draw.rectangle((7, 7, 2, 2), BLACK)
assert_image_equal_tofile(img, expected, "square as inverted rectangle failed")
def test_triangle_right(): def test_triangle_right():
@ -1223,21 +1256,6 @@ def test_textbbox_stroke():
assert draw.textbbox((2, 2), "ABC\nAaaa", font, stroke_width=4) == (-2, 2, 54, 50) assert draw.textbbox((2, 2), "ABC\nAaaa", font, stroke_width=4) == (-2, 2, 54, 50)
def test_textsize_deprecation():
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
with pytest.warns(DeprecationWarning) as log:
draw.textsize("Hello")
assert len(log) == 1
with pytest.warns(DeprecationWarning) as log:
draw.textsize("Hello\nWorld")
assert len(log) == 1
with pytest.warns(DeprecationWarning) as log:
draw.multiline_textsize("Hello\nWorld")
assert len(log) == 1
@skip_unless_feature("freetype2") @skip_unless_feature("freetype2")
def test_stroke(): def test_stroke():
for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items(): for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items():
@ -1502,3 +1520,21 @@ def test_polygon2():
draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red") draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red")
expected = "Tests/images/imagedraw_outline_polygon_RGB.png" expected = "Tests/images/imagedraw_outline_polygon_RGB.png"
assert_image_similar_tofile(im, expected, 1) assert_image_similar_tofile(im, expected, 1)
@pytest.mark.parametrize("xy", ((1, 1, 0, 1), (1, 1, 1, 0)))
def test_incorrectly_ordered_coordinates(xy):
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
with pytest.raises(ValueError):
draw.arc(xy, 10, 260)
with pytest.raises(ValueError):
draw.chord(xy, 10, 260)
with pytest.raises(ValueError):
draw.ellipse(xy)
with pytest.raises(ValueError):
draw.pieslice(xy, 10, 260)
with pytest.raises(ValueError):
draw.rectangle(xy)
with pytest.raises(ValueError):
draw.rounded_rectangle(xy)

View File

@ -2,7 +2,7 @@ import os.path
import pytest import pytest
from PIL import Image, ImageDraw, ImageDraw2 from PIL import Image, ImageDraw, ImageDraw2, features
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
@ -173,19 +173,18 @@ def test_text():
@skip_unless_feature("freetype2") @skip_unless_feature("freetype2")
def test_textsize(): def test_textbbox():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw2.Draw(im) draw = ImageDraw2.Draw(im)
font = ImageDraw2.Font("white", FONT_PATH) font = ImageDraw2.Font("white", FONT_PATH)
# Act # Act
with pytest.warns(DeprecationWarning) as log: bbox = draw.textbbox((0, 0), "ImageDraw2", font)
size = draw.textsize("ImageDraw2", font)
assert len(log) == 1
# Assert # Assert
assert size[1] == 12 right = 72 if features.check_feature("raqm") else 70
assert bbox == (0, 2, right, 12)
@skip_unless_feature("freetype2") @skip_unless_feature("freetype2")

View File

@ -251,27 +251,6 @@ def test_draw_align(font):
draw.text((100, 40), line, (0, 0, 0), font=font, align="left") draw.text((100, 40), line, (0, 0, 0), font=font, align="left")
def test_multiline_size(font):
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
with pytest.warns(DeprecationWarning) as log:
# Test that textsize() correctly connects to multiline_textsize()
assert draw.textsize(TEST_TEXT, font=font) == draw.multiline_textsize(
TEST_TEXT, font=font
)
# Test that multiline_textsize corresponds to ImageFont.textsize()
# for single line text
assert font.getsize("A") == draw.multiline_textsize("A", font=font)
# Test that textsize() can pass on additional arguments
# to multiline_textsize()
draw.textsize(TEST_TEXT, font=font, spacing=4)
draw.textsize(TEST_TEXT, font, 4)
assert len(log) == 6
def test_multiline_bbox(font): def test_multiline_bbox(font):
im = Image.new(mode="RGB", size=(300, 100)) im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -298,12 +277,6 @@ def test_multiline_width(font):
draw.textbbox((0, 0), "longest line", font=font)[2] draw.textbbox((0, 0), "longest line", font=font)[2]
== draw.multiline_textbbox((0, 0), "longest line\nline", font=font)[2] == draw.multiline_textbbox((0, 0), "longest line\nline", font=font)[2]
) )
with pytest.warns(DeprecationWarning) as log:
assert (
draw.textsize("longest line", font=font)[0]
== draw.multiline_textsize("longest line\nline", font=font)[0]
)
assert len(log) == 2
def test_multiline_spacing(font): def test_multiline_spacing(font):
@ -326,32 +299,27 @@ def test_rotated_transposed_font(font, orientation):
# Original font # Original font
draw.font = font draw.font = font
with pytest.warns(DeprecationWarning) as log:
box_size_a = draw.textsize(word)
assert box_size_a == font.getsize(word)
assert len(log) == 2
bbox_a = draw.textbbox((10, 10), word) bbox_a = draw.textbbox((10, 10), word)
# Rotated font # Rotated font
draw.font = transposed_font draw.font = transposed_font
with pytest.warns(DeprecationWarning) as log:
box_size_b = draw.textsize(word)
assert box_size_b == transposed_font.getsize(word)
assert len(log) == 2
bbox_b = draw.textbbox((20, 20), word) bbox_b = draw.textbbox((20, 20), word)
# Check (w,h) of box a is (h,w) of box b # Check (w, h) of box a is (h, w) of box b
assert box_size_a[0] == box_size_b[1] assert (
assert box_size_a[1] == box_size_b[0] bbox_a[2] - bbox_a[0],
bbox_a[3] - bbox_a[1],
) == (
bbox_b[3] - bbox_b[1],
bbox_b[2] - bbox_b[0],
)
# Check bbox b is (20, 20, 20 + h, 20 + w) # Check top left co-ordinates are correct
assert bbox_b[0] == 20 assert bbox_b[:2] == (20, 20)
assert bbox_b[1] == 20
assert bbox_b[2] == 20 + bbox_a[3] - bbox_a[1]
assert bbox_b[3] == 20 + bbox_a[2] - bbox_a[0]
# text length is undefined for vertical text # text length is undefined for vertical text
pytest.raises(ValueError, draw.textlength, word) with pytest.raises(ValueError):
draw.textlength(word)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -372,28 +340,25 @@ def test_unrotated_transposed_font(font, orientation):
# Original font # Original font
draw.font = font draw.font = font
with pytest.warns(DeprecationWarning) as log:
box_size_a = draw.textsize(word)
assert len(log) == 1
bbox_a = draw.textbbox((10, 10), word) bbox_a = draw.textbbox((10, 10), word)
length_a = draw.textlength(word) length_a = draw.textlength(word)
# Rotated font # Rotated font
draw.font = transposed_font draw.font = transposed_font
with pytest.warns(DeprecationWarning) as log:
box_size_b = draw.textsize(word)
assert len(log) == 1
bbox_b = draw.textbbox((20, 20), word) bbox_b = draw.textbbox((20, 20), word)
length_b = draw.textlength(word) length_b = draw.textlength(word)
# Check boxes a and b are same size # Check boxes a and b are same size
assert box_size_a == box_size_b assert (
bbox_a[2] - bbox_a[0],
bbox_a[3] - bbox_a[1],
) == (
bbox_b[2] - bbox_b[0],
bbox_b[3] - bbox_b[1],
)
# Check bbox b is (20, 20, 20 + w, 20 + h) # Check top left co-ordinates are correct
assert bbox_b[0] == 20 assert bbox_b[:2] == (20, 20)
assert bbox_b[1] == 20
assert bbox_b[2] == 20 + bbox_a[2] - bbox_a[0]
assert bbox_b[3] == 20 + bbox_a[3] - bbox_a[1]
assert length_a == length_b assert length_a == length_b
@ -446,19 +411,6 @@ def test_free_type_font_get_metrics(font):
assert (ascent, descent) == (16, 4) assert (ascent, descent) == (16, 4)
def test_free_type_font_get_offset(font):
# Arrange
text = "offset this"
# Act
with pytest.warns(DeprecationWarning) as log:
offset = font.getoffset(text)
# Assert
assert len(log) == 1
assert offset == (0, 3)
def test_free_type_font_get_mask(font): def test_free_type_font_get_mask(font):
# Arrange # Arrange
text = "mask this" text = "mask this"
@ -617,19 +569,6 @@ def test_imagefont_getters(font):
assert font.getlength("M") == 12 assert font.getlength("M") == 12
assert font.getlength("y") == 12 assert font.getlength("y") == 12
assert font.getlength("a") == 12 assert font.getlength("a") == 12
with pytest.warns(DeprecationWarning) as log:
assert font.getsize("A") == (12, 16)
assert font.getsize("AB") == (24, 16)
assert font.getsize("M") == (12, 16)
assert font.getsize("y") == (12, 20)
assert font.getsize("a") == (12, 16)
assert font.getsize_multiline("A") == (12, 16)
assert font.getsize_multiline("AB") == (24, 16)
assert font.getsize_multiline("a") == (12, 16)
assert font.getsize_multiline("ABC\n") == (36, 36)
assert font.getsize_multiline("ABC\nA") == (36, 36)
assert font.getsize_multiline("ABC\nAaaa") == (48, 36)
assert len(log) == 11
@pytest.mark.parametrize("stroke_width", (0, 2)) @pytest.mark.parametrize("stroke_width", (0, 2))
@ -640,16 +579,6 @@ def test_getsize_stroke(font, stroke_width):
12 + stroke_width, 12 + stroke_width,
16 + stroke_width, 16 + stroke_width,
) )
with pytest.warns(DeprecationWarning) as log:
assert font.getsize("A", stroke_width=stroke_width) == (
12 + stroke_width * 2,
16 + stroke_width * 2,
)
assert font.getsize_multiline("ABC\nAaaa", stroke_width=stroke_width) == (
48 + stroke_width * 2,
36 + stroke_width * 4,
)
assert len(log) == 2
def test_complex_font_settings(): def test_complex_font_settings():
@ -780,11 +709,8 @@ def test_textbbox_non_freetypefont():
im = Image.new("RGB", (200, 200)) im = Image.new("RGB", (200, 200))
d = ImageDraw.Draw(im) d = ImageDraw.Draw(im)
default_font = ImageFont.load_default() default_font = ImageFont.load_default()
with pytest.warns(DeprecationWarning) as log: assert d.textlength("test", font=default_font) == 24
width, height = d.textsize("test", font=default_font) assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, 24, 11)
assert len(log) == 1
assert d.textlength("test", font=default_font) == width
assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, width, height)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -872,25 +798,23 @@ def test_anchor_invalid(font):
d.font = font d.font = font
for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]: for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]:
pytest.raises(ValueError, lambda: font.getmask2("hello", anchor=anchor)) with pytest.raises(ValueError):
pytest.raises(ValueError, lambda: font.getbbox("hello", anchor=anchor)) font.getmask2("hello", anchor=anchor)
pytest.raises(ValueError, lambda: d.text((0, 0), "hello", anchor=anchor)) with pytest.raises(ValueError):
pytest.raises(ValueError, lambda: d.textbbox((0, 0), "hello", anchor=anchor)) font.getbbox("hello", anchor=anchor)
pytest.raises( with pytest.raises(ValueError):
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor) d.text((0, 0), "hello", anchor=anchor)
) with pytest.raises(ValueError):
pytest.raises( d.textbbox((0, 0), "hello", anchor=anchor)
ValueError, with pytest.raises(ValueError):
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor), d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
) with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor)
for anchor in ["lt", "lb"]: for anchor in ["lt", "lb"]:
pytest.raises( with pytest.raises(ValueError):
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor) d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
) with pytest.raises(ValueError):
pytest.raises( d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor)
ValueError,
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
)
@pytest.mark.parametrize("bpp", (1, 2, 4, 8)) @pytest.mark.parametrize("bpp", (1, 2, 4, 8))
@ -1084,14 +1008,6 @@ def test_woff2(layout_engine):
assert_image_similar_tofile(im, "Tests/images/test_woff2.png", 5) assert_image_similar_tofile(im, "Tests/images/test_woff2.png", 5)
def test_fill_deprecation(font):
with pytest.warns(DeprecationWarning):
font.getmask2("Hello world", fill=Image.core.fill)
with pytest.warns(DeprecationWarning):
with pytest.raises(TypeError):
font.getmask2("Hello world", fill=None)
def test_render_mono_size(): def test_render_mono_size():
# issue 4177 # issue 4177
@ -1131,12 +1047,3 @@ def test_raqm_missing_warning(monkeypatch):
"Raqm layout was requested, but Raqm is not available. " "Raqm layout was requested, but Raqm is not available. "
"Falling back to basic layout." "Falling back to basic layout."
) )
def test_constants_deprecation():
for enum, prefix in {
ImageFont.Layout: "LAYOUT_",
}.items():
for name in enum.__members__:
with pytest.warns(DeprecationWarning):
assert getattr(ImageFont, prefix + name) == enum[name]

View File

@ -360,37 +360,20 @@ def test_anchor_invalid_ttb():
d.font = font d.font = font
for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]: for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]:
pytest.raises( with pytest.raises(ValueError):
ValueError, lambda: font.getmask2("hello", anchor=anchor, direction="ttb") font.getmask2("hello", anchor=anchor, direction="ttb")
) with pytest.raises(ValueError):
pytest.raises( font.getbbox("hello", anchor=anchor, direction="ttb")
ValueError, lambda: font.getbbox("hello", anchor=anchor, direction="ttb") with pytest.raises(ValueError):
) d.text((0, 0), "hello", anchor=anchor, direction="ttb")
pytest.raises( with pytest.raises(ValueError):
ValueError, lambda: d.text((0, 0), "hello", anchor=anchor, direction="ttb") d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb")
) with pytest.raises(ValueError):
pytest.raises( d.multiline_text((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
ValueError, with pytest.raises(ValueError):
lambda: d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb"), d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
)
pytest.raises(
ValueError,
lambda: d.multiline_text(
(0, 0), "foo\nbar", anchor=anchor, direction="ttb"
),
)
pytest.raises(
ValueError,
lambda: d.multiline_textbbox(
(0, 0), "foo\nbar", anchor=anchor, direction="ttb"
),
)
# ttb multiline text does not support anchors at all # ttb multiline text does not support anchors at all
pytest.raises( with pytest.raises(ValueError):
ValueError, d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb")
lambda: d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb"), with pytest.raises(ValueError):
) d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb")
pytest.raises(
ValueError,
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb"),
)

View File

@ -9,10 +9,6 @@ def test_sanity():
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
assert len(palette.colors) == 256 assert len(palette.colors) == 256
with pytest.warns(DeprecationWarning):
with pytest.raises(ValueError):
ImagePalette.ImagePalette("RGB", list(range(256)) * 3, 10)
def test_reload(): def test_reload():
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:

View File

@ -2,13 +2,10 @@ import warnings
import pytest import pytest
from PIL import ImageQt
from .helper import assert_image_similar, hopper from .helper import assert_image_similar, hopper
with warnings.catch_warnings() as w:
warnings.simplefilter("ignore", category=DeprecationWarning)
from PIL import ImageQt
pytestmark = pytest.mark.skipif( pytestmark = pytest.mark.skipif(
not ImageQt.qt_is_installed, reason="Qt bindings are not installed" not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
) )
@ -26,10 +23,6 @@ def test_rgb():
from PyQt6.QtGui import qRgb from PyQt6.QtGui import qRgb
elif ImageQt.qt_version == "side6": elif ImageQt.qt_version == "side6":
from PySide6.QtGui import qRgb from PySide6.QtGui import qRgb
elif ImageQt.qt_version == "5":
from PyQt5.QtGui import qRgb
elif ImageQt.qt_version == "side2":
from PySide2.QtGui import qRgb
assert qRgb(0, 0, 0) == qRgba(0, 0, 0, 255) assert qRgb(0, 0, 0) == qRgba(0, 0, 0, 255)

View File

@ -55,8 +55,8 @@ def test_show_without_viewers():
viewers = ImageShow._viewers viewers = ImageShow._viewers
ImageShow._viewers = [] ImageShow._viewers = []
im = hopper() with hopper() as im:
assert not ImageShow.show(im) assert not ImageShow.show(im)
ImageShow._viewers = viewers ImageShow._viewers = viewers
@ -89,20 +89,3 @@ def test_ipythonviewer():
im = hopper() im = hopper()
assert test_viewer.show(im) == 1 assert test_viewer.show(im) == 1
@pytest.mark.skipif(
not on_ci() or is_win32(),
reason="Only run on CIs; hangs on Windows CIs",
)
@pytest.mark.parametrize("viewer", ImageShow._viewers)
def test_file_deprecated(tmp_path, viewer):
f = str(tmp_path / "temp.jpg")
hopper().save(f)
with pytest.warns(DeprecationWarning):
try:
viewer.show_file(file=f)
except NotImplementedError:
pass
with pytest.raises(TypeError):
viewer.show_file()

View File

@ -89,13 +89,6 @@ def test_photoimage_blank(mode):
assert_image_equal(reloaded.convert(mode), im) assert_image_equal(reloaded.convert(mode), im)
def test_box_deprecation():
im = hopper()
im_tk = ImageTk.PhotoImage(im)
with pytest.warns(DeprecationWarning):
im_tk.paste(im, (0, 0, 128, 128))
def test_bitmapimage(): def test_bitmapimage():
im = hopper("1") im = hopper("1")

View File

@ -100,8 +100,11 @@ class TestImageWinDib:
# Act # Act
# Make one the same as the using tobytes()/frombytes() # Make one the same as the using tobytes()/frombytes()
test_buffer = dib1.tobytes() test_buffer = dib1.tobytes()
dib2.frombytes(test_buffer) for datatype in ("bytes", "memoryview"):
if datatype == "memoryview":
test_buffer = memoryview(test_buffer)
dib2.frombytes(test_buffer)
# Assert # Assert
# Confirm they're the same # Confirm they're the same
assert dib1.tobytes() == dib2.tobytes() assert dib1.tobytes() == dib2.tobytes()

View File

@ -207,6 +207,9 @@ class TestLibPack:
0x01000083, 0x01000083,
) )
def test_I16(self):
self.assert_pack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605)
def test_F_float(self): def test_F_float(self):
self.assert_pack("F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34) self.assert_pack("F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34)
@ -761,10 +764,12 @@ class TestLibUnpack:
self.assert_unpack("I;16", "I;16N", 2, 0x0201, 0x0403, 0x0605) self.assert_unpack("I;16", "I;16N", 2, 0x0201, 0x0403, 0x0605)
self.assert_unpack("I;16B", "I;16N", 2, 0x0201, 0x0403, 0x0605) self.assert_unpack("I;16B", "I;16N", 2, 0x0201, 0x0403, 0x0605)
self.assert_unpack("I;16L", "I;16N", 2, 0x0201, 0x0403, 0x0605) self.assert_unpack("I;16L", "I;16N", 2, 0x0201, 0x0403, 0x0605)
self.assert_unpack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605)
else: else:
self.assert_unpack("I;16", "I;16N", 2, 0x0102, 0x0304, 0x0506) self.assert_unpack("I;16", "I;16N", 2, 0x0102, 0x0304, 0x0506)
self.assert_unpack("I;16B", "I;16N", 2, 0x0102, 0x0304, 0x0506) self.assert_unpack("I;16B", "I;16N", 2, 0x0102, 0x0304, 0x0506)
self.assert_unpack("I;16L", "I;16N", 2, 0x0102, 0x0304, 0x0506) self.assert_unpack("I;16L", "I;16N", 2, 0x0102, 0x0304, 0x0506)
self.assert_unpack("I;16N", "I;16N", 2, 0x0102, 0x0304, 0x0506)
def test_CMYK16(self): def test_CMYK16(self):
self.assert_unpack("CMYK", "CMYK;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16)) self.assert_unpack("CMYK", "CMYK;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16))

View File

@ -88,10 +88,7 @@ def test_tobytes():
def test_convert(): def test_convert():
im = original.copy() im = original.copy()
verify(im.convert("I;16")) for mode in ("I;16", "I;16B", "I;16N"):
verify(im.convert("I;16").convert("L")) verify(im.convert(mode))
verify(im.convert("I;16").convert("I")) verify(im.convert(mode).convert("L"))
verify(im.convert(mode).convert("I"))
verify(im.convert("I;16B"))
verify(im.convert("I;16B").convert("L"))
verify(im.convert("I;16B").convert("I"))

View File

@ -4,7 +4,7 @@ import pytest
from PIL import Image from PIL import Image
from .helper import assert_deep_equal, assert_image, hopper from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature
numpy = pytest.importorskip("numpy", reason="NumPy not installed") numpy = pytest.importorskip("numpy", reason="NumPy not installed")
@ -219,6 +219,13 @@ def test_zero_size():
assert im.size == (0, 0) assert im.size == (0, 0)
@skip_unless_feature("libtiff")
def test_load_first():
with Image.open("Tests/images/g4_orientation_5.tif") as im:
a = numpy.array(im)
assert a.shape == (88, 590)
def test_bool(): def test_bool():
# https://github.com/python-pillow/Pillow/issues/2044 # https://github.com/python-pillow/Pillow/issues/2044
a = numpy.zeros((10, 2), dtype=bool) a = numpy.zeros((10, 2), dtype=bool)

View File

@ -1,10 +1,6 @@
import warnings
import pytest import pytest
with warnings.catch_warnings(): from PIL import ImageQt
warnings.simplefilter("ignore", category=DeprecationWarning)
from PIL import ImageQt
from .helper import assert_image_equal_tofile, assert_image_similar, hopper from .helper import assert_image_equal_tofile, assert_image_similar, hopper
@ -19,14 +15,6 @@ if ImageQt.qt_is_installed:
from PySide6.QtCore import QPoint from PySide6.QtCore import QPoint
from PySide6.QtGui import QImage, QPainter, QRegion from PySide6.QtGui import QImage, QPainter, QRegion
from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
elif ImageQt.qt_version == "5":
from PyQt5.QtCore import QPoint
from PyQt5.QtGui import QImage, QPainter, QRegion
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
elif ImageQt.qt_version == "side2":
from PySide2.QtCore import QPoint
from PySide2.QtGui import QImage, QPainter, QRegion
from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
class Example(QWidget): class Example(QWidget):
def __init__(self): def __init__(self):
@ -48,7 +36,7 @@ if ImageQt.qt_is_installed:
def roundtrip(expected): def roundtrip(expected):
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected)) result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
# Qt saves all pixmaps as rgb # Qt saves all pixmaps as rgb
assert_image_similar(result, expected.convert("RGB"), 0.3) assert_image_similar(result, expected.convert("RGB"), 1)
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed") @pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")

View File

@ -1,10 +1,6 @@
import warnings
import pytest import pytest
with warnings.catch_warnings(): from PIL import ImageQt
warnings.simplefilter("ignore", category=DeprecationWarning)
from PIL import ImageQt
from .helper import assert_image_equal, assert_image_equal_tofile, hopper from .helper import assert_image_equal, assert_image_equal_tofile, hopper
@ -32,7 +28,7 @@ def test_sanity(mode, tmp_path):
assert_image_equal(rt, src) assert_image_equal(rt, src)
if mode == "1": if mode == "1":
# BW appears to not save correctly on QT5 # BW appears to not save correctly on Qt
# kicks out errors on console: # kicks out errors on console:
# libpng warning: Invalid color type/bit depth combination # libpng warning: Invalid color type/bit depth combination
# in IHDR # in IHDR

View File

@ -8,5 +8,5 @@ if [ ! -f $archive.tar.gz ]; then
wget -O $archive.tar.gz $url wget -O $archive.tar.gz $url
fi fi
rm -r $archive rmdir $archive
tar -xvzf $archive.tar.gz tar -xvzf $archive.tar.gz

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