Merge branch 'python-pillow:main' into test_lists_and_tuples
|
@ -13,7 +13,7 @@ environment:
|
|||
- PYTHON: C:/Python311
|
||||
ARCHITECTURE: x86
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||
- PYTHON: C:/Python37-x64
|
||||
- PYTHON: C:/Python38-x64
|
||||
ARCHITECTURE: x64
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
|
||||
|
@ -21,15 +21,17 @@ environment:
|
|||
install:
|
||||
- '%PYTHON%\%EXECUTABLE% --version'
|
||||
- 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-test-images.zip -oc:\
|
||||
- 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:\
|
||||
- ..\pillow-depends\gs1000w32.exe /S
|
||||
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH%
|
||||
- choco install ghostscript --version=10.0.0.20230317
|
||||
- path c:\nasm-2.15.05;C:\Program Files\gs\gs10.00.0\bin;%PATH%
|
||||
- cd c:\pillow\winbuild\
|
||||
- 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
|
||||
$host.SetShouldExit(0)
|
||||
- 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?
|
||||
|
||||
after_test:
|
||||
- python -m pip install codecov
|
||||
- codecov --file coverage.xml --name %PYTHON% --flags AppVeyor
|
||||
- curl -Os https://uploader.codecov.io/latest/windows/codecov.exe
|
||||
- .\codecov.exe --file coverage.xml --name %PYTHON% --flags AppVeyor
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
# gather the coverage data
|
||||
python3 -m pip install codecov
|
||||
python3 -m pip install coverage
|
||||
if [[ $MATRIX_DOCKER ]]; then
|
||||
python3 -m coverage xml --ignore-errors
|
||||
else
|
||||
|
|
|
@ -37,11 +37,12 @@ python3 -m pip install -U pytest-timeout
|
|||
python3 -m pip install pyroma
|
||||
|
||||
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
|
||||
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
|
||||
fi
|
||||
|
||||
|
|
4
.github/workflows/cifuzz.yml
vendored
|
@ -3,10 +3,12 @@ name: CIFuzz
|
|||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/cifuzz.yml"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/cifuzz.yml"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
workflow_dispatch:
|
||||
|
@ -14,7 +16,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
|
55
.github/workflows/docs.yml
vendored
Normal 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
|
3
.github/workflows/macos-install.sh
vendored
|
@ -13,7 +13,8 @@ python3 -m pip install -U pytest-cov
|
|||
python3 -m pip install -U pytest-timeout
|
||||
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
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
|
2
.github/workflows/stale.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: "Check issues"
|
||||
uses: actions/stale@v7
|
||||
uses: actions/stale@v8
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
only-labels: "Awaiting OP Action"
|
||||
|
|
14
.github/workflows/test-cygwin.yml
vendored
|
@ -1,6 +1,15 @@
|
|||
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:
|
||||
contents: read
|
||||
|
@ -58,8 +67,7 @@ jobs:
|
|||
python3${{ matrix.python-minor-version }}-numpy
|
||||
python3${{ matrix.python-minor-version }}-sip
|
||||
python3${{ matrix.python-minor-version }}-tkinter
|
||||
qt5-devel-tools
|
||||
subversion
|
||||
wget
|
||||
xorg-server-extra
|
||||
zlib-devel
|
||||
|
||||
|
|
14
.github/workflows/test-docker.yml
vendored
|
@ -1,6 +1,15 @@
|
|||
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:
|
||||
contents: read
|
||||
|
@ -24,11 +33,11 @@ jobs:
|
|||
# Then run the remainder
|
||||
alpine,
|
||||
amazon-2-amd64,
|
||||
amazon-2023-amd64,
|
||||
arch,
|
||||
centos-7-amd64,
|
||||
centos-stream-8-amd64,
|
||||
centos-stream-9-amd64,
|
||||
debian-10-buster-x86,
|
||||
debian-11-bullseye-x86,
|
||||
fedora-36-amd64,
|
||||
fedora-37-amd64,
|
||||
|
@ -87,6 +96,7 @@ jobs:
|
|||
with:
|
||||
flags: GHA_Docker
|
||||
name: ${{ matrix.docker }}
|
||||
gcov: true
|
||||
|
||||
success:
|
||||
permissions:
|
||||
|
|
14
.github/workflows/test-mingw.yml
vendored
|
@ -1,6 +1,15 @@
|
|||
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:
|
||||
contents: read
|
||||
|
@ -59,8 +68,7 @@ jobs:
|
|||
${{ matrix.package }}-python3-numpy \
|
||||
${{ matrix.package }}-python3-olefile \
|
||||
${{ matrix.package }}-python3-pip \
|
||||
${{ matrix.package }}-python3-setuptools \
|
||||
subversion
|
||||
${{ matrix.package }}-python3-setuptools
|
||||
|
||||
if [ ${{ matrix.package }} == "mingw-w64-x86_64" ]; then
|
||||
pacman -S --noconfirm \
|
||||
|
|
4
.github/workflows/test-valgrind.yml
vendored
|
@ -5,10 +5,12 @@ name: Test Valgrind
|
|||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/test-valgrind.yml"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/test-valgrind.yml"
|
||||
- "**.c"
|
||||
- "**.h"
|
||||
workflow_dispatch:
|
||||
|
@ -16,7 +18,7 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
|
28
.github/workflows/test-windows.yml
vendored
|
@ -1,6 +1,15 @@
|
|||
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:
|
||||
contents: read
|
||||
|
@ -15,7 +24,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
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"]
|
||||
include:
|
||||
# PyPy 7.3.4+ only ships 64-bit binaries for Windows
|
||||
|
@ -38,6 +47,12 @@ jobs:
|
|||
repository: python-pillow/pillow-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
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
|
@ -59,10 +74,11 @@ jobs:
|
|||
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
|
||||
|
||||
winbuild\depends\gs1000w32.exe /S
|
||||
echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH
|
||||
choco install ghostscript --version=10.0.0.20230317
|
||||
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
|
||||
& "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" `
|
||||
|
@ -81,7 +97,7 @@ jobs:
|
|||
- name: Prepare build
|
||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
& python.exe winbuild\build_prepare.py -v --python=$env:pythonLocation --srcdir
|
||||
& python.exe winbuild\build_prepare.py -v --python $env:pythonLocation
|
||||
shell: pwsh
|
||||
|
||||
- name: Build dependencies / libjpeg-turbo
|
||||
|
|
22
.github/workflows/test.yml
vendored
|
@ -1,6 +1,15 @@
|
|||
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:
|
||||
contents: read
|
||||
|
@ -22,14 +31,14 @@ jobs:
|
|||
python-version: [
|
||||
"pypy3.9",
|
||||
"pypy3.8",
|
||||
"3.12-dev",
|
||||
"3.11",
|
||||
"3.10",
|
||||
"3.9",
|
||||
"3.8",
|
||||
"3.7",
|
||||
]
|
||||
include:
|
||||
- python-version: "3.7"
|
||||
- python-version: "3.9"
|
||||
PYTHONOPTIMIZE: 1
|
||||
REVERSE: "--reverse"
|
||||
- python-version: "3.8"
|
||||
|
@ -95,11 +104,6 @@ jobs:
|
|||
name: errors
|
||||
path: Tests/errors
|
||||
|
||||
- name: Docs
|
||||
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.11
|
||||
run: |
|
||||
make doccheck
|
||||
|
||||
- name: After success
|
||||
run: |
|
||||
.ci/after_success.sh
|
||||
|
@ -107,9 +111,9 @@ jobs:
|
|||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
|
||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
gcov: true
|
||||
|
||||
success:
|
||||
permissions:
|
||||
|
|
2
.gitignore
vendored
|
@ -79,7 +79,7 @@ docs/_build/
|
|||
# JetBrains
|
||||
.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/crash_1.tif
|
||||
Tests/images/crash_2.tif
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--target-version=py37]
|
||||
args: [--target-version=py38]
|
||||
# Only .py files, until https://github.com/psf/black/issues/402 resolved
|
||||
files: \.py$
|
||||
types: []
|
||||
|
@ -14,7 +14,7 @@ repos:
|
|||
- id: isort
|
||||
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: 1.7.4
|
||||
rev: 1.7.5
|
||||
hooks:
|
||||
- id: bandit
|
||||
args: [--severity-level=high]
|
||||
|
@ -26,10 +26,10 @@ repos:
|
|||
- id: yesqa
|
||||
|
||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||
rev: v1.4.2
|
||||
rev: v1.5.1
|
||||
hooks:
|
||||
- id: remove-tabs
|
||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
|
||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
|
@ -57,7 +57,7 @@ repos:
|
|||
- id: sphinx-lint
|
||||
|
||||
- repo: https://github.com/tox-dev/tox-ini-fmt
|
||||
rev: 0.6.1
|
||||
rev: 1.0.0
|
||||
hooks:
|
||||
- id: tox-ini-fmt
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
version: 2
|
||||
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
|
||||
python:
|
||||
install:
|
||||
- method: pip
|
||||
|
|
92
CHANGES.rst
|
@ -2,9 +2,99 @@
|
|||
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
|
||||
[sebastic, radarhere]
|
||||
|
||||
|
|
4
LICENSE
|
@ -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
|
||||
with the following terms and conditions:
|
||||
|
||||
Permission to use, copy, modify, and distribute this software and its
|
||||
associated documentation for any purpose and without fee is hereby granted,
|
||||
Permission to use, copy, modify and distribute this software and its
|
||||
documentation for any purpose and without fee is hereby granted,
|
||||
provided that the above copyright notice appears in all copies, and that
|
||||
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
|
||||
|
|
14
Makefile
|
@ -16,10 +16,16 @@ coverage:
|
|||
python3 -m coverage report
|
||||
|
||||
.PHONY: doc
|
||||
doc:
|
||||
.PHONY: html
|
||||
doc html:
|
||||
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
|
||||
$(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
|
||||
doccheck:
|
||||
$(MAKE) doc
|
||||
|
@ -38,7 +44,8 @@ help:
|
|||
@echo " coverage run coverage test (in progress)"
|
||||
@echo " doc make HTML docs"
|
||||
@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 " install make and install"
|
||||
@echo " install-coverage make and install with C coverage"
|
||||
|
@ -71,6 +78,7 @@ debug:
|
|||
|
||||
.PHONY: release-test
|
||||
release-test:
|
||||
python3 Tests/check_release_notes.py
|
||||
python3 -m pip install -e .[tests]
|
||||
python3 selftest.py
|
||||
python3 -m pytest Tests
|
||||
|
@ -116,5 +124,5 @@ lint:
|
|||
lint-fix:
|
||||
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 -m black --target-version py37 .
|
||||
python3 -m black --target-version py38 .
|
||||
python3 -m isort .
|
||||
|
|
46
RELEASING.md
|
@ -11,14 +11,13 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
|||
* [ ] 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 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`.
|
||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||
* [ ] Create branch and tag for release e.g.:
|
||||
```bash
|
||||
git branch 5.2.x
|
||||
git tag 5.2.0
|
||||
git push --all
|
||||
git push --tags
|
||||
```
|
||||
* [ ] 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*
|
||||
```
|
||||
* [ ] 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
|
||||
|
||||
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
|
||||
```
|
||||
* [ ] 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`.
|
||||
* [ ] 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`.
|
||||
* [ ] Create tag for release e.g.:
|
||||
```bash
|
||||
git tag 5.2.1
|
||||
git push
|
||||
git push --tags
|
||||
```
|
||||
* [ ] 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 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
|
||||
|
||||
|
@ -83,7 +84,6 @@ Released as needed privately to individual vendors for critical security-related
|
|||
```bash
|
||||
git checkout 2.5.x
|
||||
git tag 2.5.3
|
||||
git push origin 2.5.x
|
||||
git push origin --tags
|
||||
```
|
||||
* [ ] Create and check source distribution:
|
||||
|
@ -91,15 +91,14 @@ Released as needed privately to individual vendors for critical security-related
|
|||
make sdist
|
||||
```
|
||||
* [ ] 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
|
||||
|
||||
### 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/`
|
||||
|
||||
### Mac and Linux
|
||||
### macOS and Linux
|
||||
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
||||
```bash
|
||||
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]]
|
||||
```
|
||||
* [ ] 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
|
||||
|
||||
|
|
6
Tests/check_release_notes.py
Normal 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}")
|
|
@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
HAS_UPLOADER = False
|
||||
|
||||
if os.environ.get("SHOW_ERRORS", None):
|
||||
if os.environ.get("SHOW_ERRORS"):
|
||||
# local img.show for errors.
|
||||
HAS_UPLOADER = True
|
||||
|
||||
|
@ -271,7 +271,7 @@ def netpbm_available():
|
|||
|
||||
def magick_command():
|
||||
if sys.platform == "win32":
|
||||
magickhome = os.environ.get("MAGICK_HOME", "")
|
||||
magickhome = os.environ.get("MAGICK_HOME")
|
||||
if magickhome:
|
||||
imagemagick = [os.path.join(magickhome, "convert.exe")]
|
||||
graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"]
|
||||
|
|
BIN
Tests/images/blend_transparency.png
Normal file
After Width: | Height: | Size: 211 B |
BIN
Tests/images/comment.jp2
Normal file
BIN
Tests/images/hopper.qoi
Normal file
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnnn.png
Normal file
After Width: | Height: | Size: 544 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnny.png
Normal file
After Width: | Height: | Size: 685 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnyn.png
Normal file
After Width: | Height: | Size: 649 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnyy.png
Normal file
After Width: | Height: | Size: 755 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nynn.png
Normal file
After Width: | Height: | Size: 643 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nyny.png
Normal file
After Width: | Height: | Size: 775 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nyyn.png
Normal file
After Width: | Height: | Size: 741 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nyyy.png
Normal file
After Width: | Height: | Size: 844 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynnn.png
Normal file
After Width: | Height: | Size: 656 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynny.png
Normal file
After Width: | Height: | Size: 785 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynyn.png
Normal file
After Width: | Height: | Size: 752 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynyy.png
Normal file
After Width: | Height: | Size: 856 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yynn.png
Normal file
After Width: | Height: | Size: 737 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yyny.png
Normal file
After Width: | Height: | Size: 870 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yyyn.png
Normal file
After Width: | Height: | Size: 835 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yyyy.png
Normal file
After Width: | Height: | Size: 934 B |
BIN
Tests/images/pil123rgba.qoi
Normal file
|
@ -177,13 +177,14 @@ class TestEnvVars:
|
|||
Image._apply_env_variables({"PILLOW_BLOCK_SIZE": "2m"})
|
||||
assert Image.core.get_block_size() == 2 * 1024 * 1024
|
||||
|
||||
def test_warnings(self):
|
||||
pytest.warns(
|
||||
UserWarning, Image._apply_env_variables, {"PILLOW_ALIGNMENT": "15"}
|
||||
)
|
||||
pytest.warns(
|
||||
UserWarning, Image._apply_env_variables, {"PILLOW_BLOCK_SIZE": "1024"}
|
||||
)
|
||||
pytest.warns(
|
||||
UserWarning, Image._apply_env_variables, {"PILLOW_BLOCKS_MAX": "wat"}
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"var",
|
||||
(
|
||||
{"PILLOW_ALIGNMENT": "15"},
|
||||
{"PILLOW_BLOCK_SIZE": "1024"},
|
||||
{"PILLOW_BLOCKS_MAX": "wat"},
|
||||
),
|
||||
)
|
||||
def test_warnings(self, var):
|
||||
with pytest.warns(UserWarning):
|
||||
Image._apply_env_variables(var)
|
||||
|
|
|
@ -36,12 +36,10 @@ class TestDecompressionBomb:
|
|||
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):
|
||||
pass
|
||||
|
||||
pytest.warns(Image.DecompressionBombWarning, open)
|
||||
|
||||
def test_exception(self):
|
||||
# Set limit to trigger exception on the test file
|
||||
Image.MAX_IMAGE_PIXELS = 64 * 128 - 1
|
||||
|
@ -87,7 +85,8 @@ class TestDecompressionCrop:
|
|||
# same decompression bomb warnings on them.
|
||||
with hopper() as src:
|
||||
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):
|
||||
im = Image.new("RGB", (100, 100))
|
||||
|
@ -95,7 +94,8 @@ class TestDecompressionCrop:
|
|||
for value in ((-9999, -9999, -9990, -9990), (-999, -999, -990, -990)):
|
||||
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):
|
||||
im.crop((-99909, -99990, 99999, 99999))
|
||||
|
|
|
@ -6,11 +6,6 @@ from PIL import _deprecate
|
|||
@pytest.mark.parametrize(
|
||||
"version, expected",
|
||||
[
|
||||
(
|
||||
10,
|
||||
"Old thing is deprecated and will be removed in Pillow 10 "
|
||||
r"\(2023-07-01\)\. Use new thing instead\.",
|
||||
),
|
||||
(
|
||||
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():
|
||||
expected = r"Unknown removal version, update PIL\._deprecate\?"
|
||||
expected = r"Unknown removal version: 12345. Update PIL\._deprecate\?"
|
||||
with pytest.raises(ValueError, match=expected):
|
||||
_deprecate.deprecate("Old thing", 12345, "new thing")
|
||||
|
||||
|
@ -57,18 +52,18 @@ def test_old_version(deprecated, plural, expected):
|
|||
|
||||
def test_plural():
|
||||
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\."
|
||||
)
|
||||
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():
|
||||
expected = "Use only one of 'replacement' and 'action'"
|
||||
with pytest.raises(ValueError, match=expected):
|
||||
_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):
|
||||
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\."
|
||||
)
|
||||
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():
|
||||
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):
|
||||
_deprecate.deprecate("Old thing", 10)
|
||||
_deprecate.deprecate("Old thing", 11)
|
||||
|
|
|
@ -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
|
|
@ -163,6 +163,12 @@ def test_apng_blend():
|
|||
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():
|
||||
with Image.open("Tests/images/apng/fctl_actl.png") as im:
|
||||
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:
|
||||
assert not im.is_animated
|
||||
|
||||
def open():
|
||||
with pytest.warns(UserWarning):
|
||||
with Image.open("Tests/images/apng/chunk_multi_actl.png") as im:
|
||||
im.load()
|
||||
assert not im.is_animated
|
||||
|
||||
pytest.warns(UserWarning, open)
|
||||
|
||||
with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im:
|
||||
assert not im.is_animated
|
||||
|
||||
|
@ -287,21 +291,17 @@ def test_apng_chunk_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:
|
||||
assert not im.is_animated
|
||||
with pytest.raises(OSError):
|
||||
im.load()
|
||||
|
||||
pytest.warns(UserWarning, open_frames_zero)
|
||||
|
||||
def open_frames_zero_default():
|
||||
with pytest.warns(UserWarning):
|
||||
with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im:
|
||||
assert not im.is_animated
|
||||
im.load()
|
||||
|
||||
pytest.warns(UserWarning, open_frames_zero_default)
|
||||
|
||||
# we can handle this case gracefully
|
||||
exception = None
|
||||
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.load()
|
||||
|
||||
def open():
|
||||
with pytest.warns(UserWarning):
|
||||
with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im:
|
||||
assert not im.is_animated
|
||||
im.load()
|
||||
|
||||
pytest.warns(UserWarning, open)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"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))])
|
||||
with Image.open(test_file) as reloaded:
|
||||
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]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import pytest
|
||||
|
||||
from PIL import BlpImagePlugin, Image
|
||||
from PIL import Image
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
|
@ -72,14 +72,3 @@ def test_crashes(test_file):
|
|||
with Image.open(f) as im:
|
||||
with pytest.raises(OSError):
|
||||
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]
|
||||
|
|
|
@ -56,6 +56,7 @@ def test_handler(tmp_path):
|
|||
|
||||
def load(self, im):
|
||||
self.loaded = True
|
||||
im.fp.close()
|
||||
return Image.new("RGB", (1, 1))
|
||||
|
||||
def save(self, im, fp, filename):
|
||||
|
|
|
@ -28,7 +28,8 @@ def test_unclosed_file():
|
|||
im = Image.open(TEST_FILE)
|
||||
im.load()
|
||||
|
||||
pytest.warns(ResourceWarning, open)
|
||||
with pytest.warns(ResourceWarning):
|
||||
open()
|
||||
|
||||
|
||||
def test_closed_file():
|
||||
|
|
|
@ -28,34 +28,65 @@ FILE2_COMPARE_SCALE2 = "Tests/images/non_zero_bb_scale2.png"
|
|||
# EPS test files with binary preview
|
||||
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")
|
||||
def test_sanity():
|
||||
# Regular scale
|
||||
with Image.open(FILE1) as image1:
|
||||
image1.load()
|
||||
assert image1.mode == "RGB"
|
||||
assert image1.size == (460, 352)
|
||||
assert image1.format == "EPS"
|
||||
|
||||
with Image.open(FILE2) as image2:
|
||||
image2.load()
|
||||
assert image2.mode == "RGB"
|
||||
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.parametrize(
|
||||
("filename", "size"), ((FILE1, (460, 352)), (FILE2, (360, 252)))
|
||||
)
|
||||
@pytest.mark.parametrize("scale", (1, 2))
|
||||
def test_sanity(filename, size, scale):
|
||||
expected_size = tuple(s * scale for s in size)
|
||||
with Image.open(filename) as image:
|
||||
image.load(scale=scale)
|
||||
assert image.mode == "RGB"
|
||||
assert image.size == expected_size
|
||||
assert image.format == "EPS"
|
||||
|
||||
|
||||
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
|
||||
|
@ -69,11 +100,72 @@ def test_load():
|
|||
|
||||
def test_invalid_file():
|
||||
invalid_file = "Tests/images/flower.jpg"
|
||||
|
||||
with pytest.raises(SyntaxError):
|
||||
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(
|
||||
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:
|
||||
# should not crash/hang
|
||||
plot_image.load()
|
||||
# fonts could be slightly different
|
||||
# fonts could be slightly different
|
||||
assert_image_similar(plot_image, target, 6)
|
||||
|
||||
|
||||
|
@ -111,7 +203,7 @@ def test_transparency():
|
|||
assert plot_image.mode == "RGBA"
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
@ -206,7 +298,6 @@ def test_resize(filename):
|
|||
@pytest.mark.parametrize("filename", (FILE1, FILE2))
|
||||
def test_thumbnail(filename):
|
||||
# Issue #619
|
||||
# Arrange
|
||||
with Image.open(filename) as im:
|
||||
new_size = (100, 100)
|
||||
im.thumbnail(new_size)
|
||||
|
@ -220,7 +311,7 @@ def test_read_binary_preview():
|
|||
pass
|
||||
|
||||
|
||||
def test_readline(tmp_path):
|
||||
def test_readline_psfile(tmp_path):
|
||||
# check all the freaking line endings possible from the spec
|
||||
# test_string = u'something\r\nelse\n\rbaz\rbif\n'
|
||||
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):
|
||||
f = io.BytesIO(test_string.encode("latin-1"))
|
||||
t = EpsImagePlugin.PSFile(f)
|
||||
with pytest.warns(DeprecationWarning):
|
||||
t = EpsImagePlugin.PSFile(f)
|
||||
_test_readline(t, 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"))
|
||||
|
||||
with open(f, "rb") as r:
|
||||
t = EpsImagePlugin.PSFile(r)
|
||||
with pytest.warns(DeprecationWarning):
|
||||
t = EpsImagePlugin.PSFile(r)
|
||||
_test_readline(t, ending)
|
||||
|
||||
for ending in line_endings:
|
||||
|
@ -255,6 +348,25 @@ def test_readline(tmp_path):
|
|||
_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(
|
||||
"filename",
|
||||
(
|
||||
|
|
|
@ -2,7 +2,7 @@ from io import BytesIO
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import FitsImagePlugin, FitsStubImagePlugin, Image
|
||||
from PIL import FitsImagePlugin, Image
|
||||
|
||||
from .helper import assert_image_equal, hopper
|
||||
|
||||
|
@ -44,36 +44,7 @@ def test_naxis_zero():
|
|||
pass
|
||||
|
||||
|
||||
def test_stub_deprecated():
|
||||
class Handler:
|
||||
opened = False
|
||||
loaded = False
|
||||
|
||||
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,
|
||||
)
|
||||
def test_comment():
|
||||
image_data = b"SIMPLE = T / comment string"
|
||||
with pytest.raises(OSError):
|
||||
FitsImagePlugin.FitsImageFile(BytesIO(image_data))
|
||||
|
|
|
@ -36,7 +36,8 @@ def test_unclosed_file():
|
|||
im = Image.open(static_test_file)
|
||||
im.load()
|
||||
|
||||
pytest.warns(ResourceWarning, open)
|
||||
with pytest.warns(ResourceWarning):
|
||||
open()
|
||||
|
||||
|
||||
def test_closed_file():
|
||||
|
|
|
@ -18,6 +18,16 @@ def test_sanity():
|
|||
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():
|
||||
# Test an invalid OLE file
|
||||
invalid_file = "Tests/images/flower.jpg"
|
||||
|
|
|
@ -21,12 +21,3 @@ def test_invalid_file():
|
|||
|
||||
with pytest.raises(SyntaxError):
|
||||
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]
|
||||
|
|
|
@ -36,7 +36,8 @@ def test_unclosed_file():
|
|||
im = Image.open(TEST_GIF)
|
||||
im.load()
|
||||
|
||||
pytest.warns(ResourceWarning, open)
|
||||
with pytest.warns(ResourceWarning):
|
||||
open()
|
||||
|
||||
|
||||
def test_closed_file():
|
||||
|
@ -1087,7 +1088,8 @@ def test_rgb_transparency(tmp_path):
|
|||
im = Image.new("RGB", (1, 1))
|
||||
im.info["transparency"] = b""
|
||||
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:
|
||||
assert "transparency" not in reloaded.info
|
||||
|
|
|
@ -56,6 +56,7 @@ def test_handler(tmp_path):
|
|||
|
||||
def load(self, im):
|
||||
self.loaded = True
|
||||
im.fp.close()
|
||||
return Image.new("RGB", (1, 1))
|
||||
|
||||
def save(self, im, fp, filename):
|
||||
|
|
|
@ -57,6 +57,7 @@ def test_handler(tmp_path):
|
|||
|
||||
def load(self, im):
|
||||
self.loaded = True
|
||||
im.fp.close()
|
||||
return Image.new("RGB", (1, 1))
|
||||
|
||||
def save(self, im, fp, filename):
|
||||
|
|
|
@ -212,12 +212,10 @@ def test_save_append_images(tmp_path):
|
|||
def test_unexpected_size():
|
||||
# This image has been manually hexedited to state that it is 16x32
|
||||
# while the image within is still 16x16
|
||||
def open():
|
||||
with pytest.warns(UserWarning):
|
||||
with Image.open("Tests/images/hopper_unexpected.ico") as im:
|
||||
assert im.size == (16, 16)
|
||||
|
||||
pytest.warns(UserWarning, open)
|
||||
|
||||
|
||||
def test_draw_reloaded(tmp_path):
|
||||
with Image.open(TEST_ICO_FILE) as im:
|
||||
|
|
|
@ -32,7 +32,8 @@ def test_unclosed_file():
|
|||
im = Image.open(TEST_IM)
|
||||
im.load()
|
||||
|
||||
pytest.warns(ResourceWarning, open)
|
||||
with pytest.warns(ResourceWarning):
|
||||
open()
|
||||
|
||||
|
||||
def test_closed_file():
|
||||
|
|
|
@ -270,7 +270,10 @@ class TestFileJpeg:
|
|||
# https://github.com/python-pillow/Pillow/issues/148
|
||||
f = str(tmp_path / "temp.jpg")
|
||||
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):
|
||||
with Image.open("Tests/images/exif_typeerror.jpg") as im:
|
||||
|
@ -445,7 +448,7 @@ class TestFileJpeg:
|
|||
ims = im.get_child_images()
|
||||
|
||||
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):
|
||||
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[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")
|
||||
def test_load_djpeg(self):
|
||||
with Image.open(TEST_FILE) as img:
|
||||
|
|
|
@ -4,13 +4,21 @@ from io import BytesIO
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageFile, Jpeg2KImagePlugin, UnidentifiedImageError, features
|
||||
from PIL import (
|
||||
Image,
|
||||
ImageFile,
|
||||
Jpeg2KImagePlugin,
|
||||
UnidentifiedImageError,
|
||||
_binary,
|
||||
features,
|
||||
)
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
assert_image_similar,
|
||||
assert_image_similar_tofile,
|
||||
skip_unless_feature,
|
||||
skip_unless_feature_version,
|
||||
)
|
||||
|
||||
EXTRA_DIR = "Tests/images/jpeg2000"
|
||||
|
@ -353,6 +361,35 @@ def test_subsampling_decode(name):
|
|||
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(
|
||||
"test_file",
|
||||
[
|
||||
|
@ -370,3 +407,29 @@ def test_crashes(test_file):
|
|||
im.load()
|
||||
except OSError:
|
||||
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)
|
||||
|
|
|
@ -668,6 +668,16 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
assert reloaded.tag_v2[530] == (1, 1)
|
||||
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):
|
||||
# issue 1597
|
||||
with Image.open("Tests/images/rdf.tif") as im:
|
||||
|
@ -984,6 +994,36 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
) as im:
|
||||
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):
|
||||
# This image does not have a RowsPerStrip TIFF tag
|
||||
infile = "Tests/images/no_rows_per_strip.tif"
|
||||
|
@ -1065,3 +1105,27 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
out = str(tmp_path / "temp.tif")
|
||||
with pytest.raises(SystemError):
|
||||
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)
|
||||
|
|
|
@ -51,6 +51,16 @@ def test_seek():
|
|||
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():
|
||||
# Test an invalid OLE file
|
||||
invalid_file = "Tests/images/flower.jpg"
|
||||
|
|
|
@ -42,7 +42,8 @@ def test_unclosed_file():
|
|||
im = Image.open(test_files[0])
|
||||
im.load()
|
||||
|
||||
pytest.warns(ResourceWarning, open)
|
||||
with pytest.warns(ResourceWarning):
|
||||
open()
|
||||
|
||||
|
||||
def test_closed_file():
|
||||
|
|
|
@ -8,7 +8,7 @@ import pytest
|
|||
|
||||
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):
|
||||
|
@ -42,6 +42,11 @@ def test_save(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):
|
||||
# Arrange
|
||||
mode = "1"
|
||||
|
@ -80,6 +85,34 @@ def test_resolution(tmp_path):
|
|||
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(
|
||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||
)
|
||||
|
|
|
@ -256,6 +256,16 @@ def test_truncated_file(tmp_path):
|
|||
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"))
|
||||
def test_invalid_maxval(maxval, tmp_path):
|
||||
path = str(tmp_path / "temp.ppm")
|
||||
|
|
|
@ -27,7 +27,8 @@ def test_unclosed_file():
|
|||
im = Image.open(test_file)
|
||||
im.load()
|
||||
|
||||
pytest.warns(ResourceWarning, open)
|
||||
with pytest.warns(ResourceWarning):
|
||||
open()
|
||||
|
||||
|
||||
def test_closed_file():
|
||||
|
|
28
Tests/test_file_qoi.py
Normal 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)
|
|
@ -25,7 +25,8 @@ def test_unclosed_file():
|
|||
im = Image.open(TEST_FILE)
|
||||
im.load()
|
||||
|
||||
pytest.warns(ResourceWarning, open)
|
||||
with pytest.warns(ResourceWarning):
|
||||
open()
|
||||
|
||||
|
||||
def test_closed_file():
|
||||
|
|
|
@ -29,11 +29,9 @@ def test_sanity(codec, test_path, format):
|
|||
|
||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
|
||||
def test_unclosed_file():
|
||||
def open():
|
||||
with pytest.warns(ResourceWarning):
|
||||
TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
|
||||
|
||||
pytest.warns(ResourceWarning, open)
|
||||
|
||||
|
||||
def test_close():
|
||||
with warnings.catch_warnings():
|
||||
|
|
|
@ -163,7 +163,9 @@ def test_save_id_section(tmp_path):
|
|||
|
||||
# Save with custom id section greater than 255 characters
|
||||
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:
|
||||
assert test_im.info["id_section"] == id_section[:255]
|
||||
|
||||
|
|
|
@ -61,7 +61,8 @@ class TestFileTiff:
|
|||
im = Image.open("Tests/images/multipage.tiff")
|
||||
im.load()
|
||||
|
||||
pytest.warns(ResourceWarning, open)
|
||||
with pytest.warns(ResourceWarning):
|
||||
open()
|
||||
|
||||
def test_closed_file(self):
|
||||
with warnings.catch_warnings():
|
||||
|
@ -83,24 +84,6 @@ class TestFileTiff:
|
|||
with Image.open("Tests/images/multipage.tiff") as im:
|
||||
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):
|
||||
# Read RGBa images from macOS [@PIL136]
|
||||
|
||||
|
@ -117,36 +100,6 @@ class TestFileTiff:
|
|||
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
|
||||
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):
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
with pytest.raises(Exception) as e:
|
||||
|
@ -231,7 +184,8 @@ class TestFileTiff:
|
|||
def test_bad_exif(self):
|
||||
with Image.open("Tests/images/hopper_bad_exif.jpg") as i:
|
||||
# Should not raise struct.error.
|
||||
pytest.warns(UserWarning, i._getexif)
|
||||
with pytest.warns(UserWarning):
|
||||
i._getexif()
|
||||
|
||||
def test_save_rgba(self, tmp_path):
|
||||
im = hopper("RGBA")
|
||||
|
|
|
@ -216,6 +216,22 @@ def test_writing_other_types_to_bytes(value, tmp_path):
|
|||
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):
|
||||
# Check that the tag has not been changed since this test was created
|
||||
tag = TiffTags.TAGS_V2[45059]
|
||||
|
@ -236,7 +252,8 @@ def test_empty_metadata():
|
|||
head = f.read(8)
|
||||
info = TiffImagePlugin.ImageFileDirectory(head)
|
||||
# Should not raise struct.error.
|
||||
pytest.warns(UserWarning, info.load, f)
|
||||
with pytest.warns(UserWarning):
|
||||
info.load(f)
|
||||
|
||||
|
||||
def test_iccprofile(tmp_path):
|
||||
|
@ -402,11 +419,12 @@ def test_too_many_entries():
|
|||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
|
||||
# 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
|
||||
|
||||
# Should not raise ValueError.
|
||||
pytest.warns(UserWarning, lambda: ifd[277])
|
||||
with pytest.warns(UserWarning):
|
||||
assert ifd[277] == 4
|
||||
|
||||
|
||||
def test_tag_group_data():
|
||||
|
|
|
@ -29,7 +29,10 @@ class TestUnsupportedWebp:
|
|||
WebPImagePlugin.SUPPORTED = False
|
||||
|
||||
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:
|
||||
WebPImagePlugin.SUPPORTED = True
|
||||
|
|
|
@ -134,6 +134,18 @@ def test_timestamp_and_duration(tmp_path):
|
|||
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):
|
||||
"""
|
||||
Create an animated WebP file, and then try seeking through frames in reverse-order,
|
||||
|
|
|
@ -82,9 +82,6 @@ def test_textsize(request, tmp_path):
|
|||
assert dy == 20
|
||||
assert dx in (0, 10)
|
||||
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)):
|
||||
msg = message[: i + 1]
|
||||
assert font.getlength(msg) == len(msg) * 10
|
||||
|
|
|
@ -48,6 +48,9 @@ class TestImage:
|
|||
"RGBX",
|
||||
"RGBA",
|
||||
"RGBa",
|
||||
"BGR;15",
|
||||
"BGR;16",
|
||||
"BGR;24",
|
||||
"CMYK",
|
||||
"YCbCr",
|
||||
"LAB",
|
||||
|
@ -57,9 +60,7 @@ class TestImage:
|
|||
def test_image_modes_success(self, mode):
|
||||
Image.new(mode, (1, 1))
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mode", ("", "bad", "very very long", "BGR;15", "BGR;16", "BGR;24", "BGR;32")
|
||||
)
|
||||
@pytest.mark.parametrize("mode", ("", "bad", "very very long"))
|
||||
def test_image_modes_fail(self, mode):
|
||||
with pytest.raises(ValueError) as e:
|
||||
Image.new(mode, (1, 1))
|
||||
|
@ -928,25 +929,7 @@ class TestImage:
|
|||
im.apply_transparency()
|
||||
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):
|
||||
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 (
|
||||
Image.Transpose,
|
||||
Image.Transform,
|
||||
|
|
|
@ -132,22 +132,26 @@ class TestImageGetPixel(AccessTest):
|
|||
return 1
|
||||
return tuple(range(1, bands + 1))
|
||||
|
||||
def check(self, mode, c=None):
|
||||
if not c:
|
||||
c = self.color(mode)
|
||||
def check(self, mode, expected_color=None):
|
||||
if not expected_color:
|
||||
expected_color = self.color(mode)
|
||||
|
||||
# check putpixel
|
||||
im = Image.new(mode, (1, 1), None)
|
||||
im.putpixel((0, 0), c)
|
||||
assert (
|
||||
im.getpixel((0, 0)) == c
|
||||
), f"put/getpixel roundtrip failed for mode {mode}, color {c}"
|
||||
im.putpixel((0, 0), expected_color)
|
||||
actual_color = im.getpixel((0, 0))
|
||||
assert actual_color == expected_color, (
|
||||
f"put/getpixel roundtrip failed for mode {mode}, "
|
||||
f"expected {expected_color} got {actual_color}"
|
||||
)
|
||||
|
||||
# check putpixel negative index
|
||||
im.putpixel((-1, -1), c)
|
||||
assert (
|
||||
im.getpixel((-1, -1)) == c
|
||||
), f"put/getpixel roundtrip negative index failed for mode {mode}, color {c}"
|
||||
im.putpixel((-1, -1), expected_color)
|
||||
actual_color = im.getpixel((-1, -1))
|
||||
assert actual_color == expected_color, (
|
||||
f"put/getpixel roundtrip negative index failed for mode {mode}, "
|
||||
f"expected {expected_color} got {actual_color}"
|
||||
)
|
||||
|
||||
# Check 0
|
||||
im = Image.new(mode, (0, 0), None)
|
||||
|
@ -155,27 +159,32 @@ class TestImageGetPixel(AccessTest):
|
|||
|
||||
error = ValueError if self._need_cffi_access else IndexError
|
||||
with pytest.raises(error):
|
||||
im.putpixel((0, 0), c)
|
||||
im.putpixel((0, 0), expected_color)
|
||||
with pytest.raises(error):
|
||||
im.getpixel((0, 0))
|
||||
# Check 0 negative index
|
||||
with pytest.raises(error):
|
||||
im.putpixel((-1, -1), c)
|
||||
im.putpixel((-1, -1), expected_color)
|
||||
with pytest.raises(error):
|
||||
im.getpixel((-1, -1))
|
||||
|
||||
# check initial color
|
||||
im = Image.new(mode, (1, 1), c)
|
||||
assert (
|
||||
im.getpixel((0, 0)) == c
|
||||
), f"initial color failed for mode {mode}, color {c} "
|
||||
im = Image.new(mode, (1, 1), expected_color)
|
||||
actual_color = im.getpixel((0, 0))
|
||||
assert actual_color == expected_color, (
|
||||
f"initial color failed for mode {mode}, "
|
||||
f"expected {expected_color} got {actual_color}"
|
||||
)
|
||||
|
||||
# check initial color negative index
|
||||
assert (
|
||||
im.getpixel((-1, -1)) == c
|
||||
), f"initial color failed with negative index for mode {mode}, color {c} "
|
||||
actual_color = im.getpixel((-1, -1))
|
||||
assert actual_color == expected_color, (
|
||||
f"initial color failed with negative index for mode {mode}, "
|
||||
f"expected {expected_color} got {actual_color}"
|
||||
)
|
||||
|
||||
# Check 0
|
||||
im = Image.new(mode, (0, 0), c)
|
||||
im = Image.new(mode, (0, 0), expected_color)
|
||||
with pytest.raises(error):
|
||||
im.getpixel((0, 0))
|
||||
# Check 0 negative index
|
||||
|
@ -205,13 +214,13 @@ class TestImageGetPixel(AccessTest):
|
|||
self.check(mode)
|
||||
|
||||
@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
|
||||
# pixelaccess is using signed int* instead of uint*
|
||||
self.check(mode, 2**15 - 1)
|
||||
self.check(mode, 2**15)
|
||||
self.check(mode, 2**15 + 1)
|
||||
self.check(mode, 2**16 - 1)
|
||||
self.check(mode, expected_color)
|
||||
|
||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||
@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("F"))
|
||||
|
||||
im = Image.new("I;16", (10, 10), 40000)
|
||||
self._test_get_access(im)
|
||||
im = Image.new("I;16L", (10, 10), 40000)
|
||||
self._test_get_access(im)
|
||||
im = Image.new("I;16B", (10, 10), 40000)
|
||||
self._test_get_access(im)
|
||||
for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
|
||||
im = Image.new(mode, (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,
|
||||
# as unpack sets them directly into the I mode.
|
||||
# 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(hopper("F"), 1024.0)
|
||||
|
||||
im = Image.new("I;16", (10, 10), 40000)
|
||||
self._test_set_access(im, 45000)
|
||||
im = Image.new("I;16L", (10, 10), 40000)
|
||||
self._test_set_access(im, 45000)
|
||||
im = Image.new("I;16B", (10, 10), 40000)
|
||||
self._test_set_access(im, 45000)
|
||||
for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
|
||||
im = Image.new(mode, (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))
|
||||
# self._test_set_access(im, -(2**13)+1)
|
||||
# im = Image.new('I;32B', (10, 10), 2**10)
|
||||
|
@ -354,8 +353,8 @@ class TestCffi(AccessTest):
|
|||
|
||||
|
||||
class TestImagePutPixelError(AccessTest):
|
||||
IMAGE_MODES1 = ["L", "LA", "RGB", "RGBA"]
|
||||
IMAGE_MODES2 = ["I", "I;16", "BGR;15"]
|
||||
IMAGE_MODES1 = ["LA", "RGB", "RGBA", "BGR;15"]
|
||||
IMAGE_MODES2 = ["L", "I", "I;16"]
|
||||
INVALID_TYPES = ["foo", 1.0, None]
|
||||
|
||||
@pytest.mark.parametrize("mode", IMAGE_MODES1)
|
||||
|
@ -370,6 +369,11 @@ class TestImagePutPixelError(AccessTest):
|
|||
(
|
||||
("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"),
|
||||
(
|
||||
"BGR;15",
|
||||
(0, 2),
|
||||
"color must be int, or tuple of one or three elements",
|
||||
),
|
||||
(
|
||||
"RGB",
|
||||
(0, 2, 5),
|
||||
|
@ -398,11 +402,6 @@ class TestImagePutPixelError(AccessTest):
|
|||
with pytest.raises(OverflowError):
|
||||
im.putpixel((0, 0), 2**80)
|
||||
|
||||
def test_putpixel_unrecognized_mode(self):
|
||||
im = hopper("BGR;15")
|
||||
with pytest.raises(ValueError, match="unrecognized image mode"):
|
||||
im.putpixel((0, 0), 0)
|
||||
|
||||
|
||||
class TestEmbeddable:
|
||||
@pytest.mark.xfail(reason="failing test")
|
||||
|
|
|
@ -254,17 +254,6 @@ def test_p2pa_palette():
|
|||
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():
|
||||
# Arrange
|
||||
im = hopper("CMYK")
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from .helper import assert_image_equal, hopper
|
||||
|
||||
|
||||
def test_sanity():
|
||||
@pytest.mark.parametrize("data_type", ("bytes", "memoryview"))
|
||||
def test_sanity(data_type):
|
||||
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)
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from .helper import assert_image_equal, hopper
|
||||
|
||||
|
||||
|
@ -62,8 +60,3 @@ def test_f_mode():
|
|||
im = hopper("F")
|
||||
with pytest.raises(ValueError):
|
||||
im.point(None)
|
||||
|
||||
|
||||
def test_coerce_e_deprecation():
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert Image.coerce_e(2).data == 2
|
||||
|
|
|
@ -617,11 +617,12 @@ def test_auxiliary_channels_isolated():
|
|||
assert_image_equal(test_image.convert(dst_format[2]), reference_image)
|
||||
|
||||
|
||||
def test_constants_deprecation():
|
||||
for enum, prefix in {
|
||||
ImageCms.Intent: "INTENT_",
|
||||
ImageCms.Direction: "DIRECTION_",
|
||||
}.items():
|
||||
for name in enum.__members__:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert getattr(ImageCms, prefix + name) == enum[name]
|
||||
@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)
|
||||
|
|
|
@ -374,7 +374,13 @@ def ellipse_various_sizes_helper(filled):
|
|||
for w in ellipse_sizes:
|
||||
y = 1
|
||||
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:
|
||||
draw.ellipse(border, fill="white")
|
||||
else:
|
||||
|
@ -763,6 +769,36 @@ def test_rounded_rectangle(xy):
|
|||
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(
|
||||
"xy, radius, type",
|
||||
[
|
||||
|
@ -934,9 +970,6 @@ def test_square():
|
|||
img, draw = create_base_image_draw((10, 10))
|
||||
draw.rectangle((2, 2, 7, 7), BLACK)
|
||||
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():
|
||||
|
@ -1223,21 +1256,6 @@ def test_textbbox_stroke():
|
|||
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")
|
||||
def test_stroke():
|
||||
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")
|
||||
expected = "Tests/images/imagedraw_outline_polygon_RGB.png"
|
||||
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)
|
||||
|
|
|
@ -2,7 +2,7 @@ import os.path
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageDraw, ImageDraw2
|
||||
from PIL import Image, ImageDraw, ImageDraw2, features
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
|
@ -173,19 +173,18 @@ def test_text():
|
|||
|
||||
|
||||
@skip_unless_feature("freetype2")
|
||||
def test_textsize():
|
||||
def test_textbbox():
|
||||
# Arrange
|
||||
im = Image.new("RGB", (W, H))
|
||||
draw = ImageDraw2.Draw(im)
|
||||
font = ImageDraw2.Font("white", FONT_PATH)
|
||||
|
||||
# Act
|
||||
with pytest.warns(DeprecationWarning) as log:
|
||||
size = draw.textsize("ImageDraw2", font)
|
||||
assert len(log) == 1
|
||||
bbox = draw.textbbox((0, 0), "ImageDraw2", font)
|
||||
|
||||
# Assert
|
||||
assert size[1] == 12
|
||||
right = 72 if features.check_feature("raqm") else 70
|
||||
assert bbox == (0, 2, right, 12)
|
||||
|
||||
|
||||
@skip_unless_feature("freetype2")
|
||||
|
|
|
@ -251,27 +251,6 @@ def test_draw_align(font):
|
|||
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):
|
||||
im = Image.new(mode="RGB", size=(300, 100))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
@ -298,12 +277,6 @@ def test_multiline_width(font):
|
|||
draw.textbbox((0, 0), "longest line", 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):
|
||||
|
@ -326,32 +299,27 @@ def test_rotated_transposed_font(font, orientation):
|
|||
|
||||
# Original 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)
|
||||
|
||||
# Rotated 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)
|
||||
|
||||
# Check (w,h) of box a is (h,w) of box b
|
||||
assert box_size_a[0] == box_size_b[1]
|
||||
assert box_size_a[1] == box_size_b[0]
|
||||
# Check (w, h) of box a is (h, w) of box b
|
||||
assert (
|
||||
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)
|
||||
assert bbox_b[0] == 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]
|
||||
# Check top left co-ordinates are correct
|
||||
assert bbox_b[:2] == (20, 20)
|
||||
|
||||
# text length is undefined for vertical text
|
||||
pytest.raises(ValueError, draw.textlength, word)
|
||||
with pytest.raises(ValueError):
|
||||
draw.textlength(word)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -372,28 +340,25 @@ def test_unrotated_transposed_font(font, orientation):
|
|||
|
||||
# Original 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)
|
||||
length_a = draw.textlength(word)
|
||||
|
||||
# Rotated 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)
|
||||
length_b = draw.textlength(word)
|
||||
|
||||
# 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)
|
||||
assert bbox_b[0] == 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]
|
||||
# Check top left co-ordinates are correct
|
||||
assert bbox_b[:2] == (20, 20)
|
||||
|
||||
assert length_a == length_b
|
||||
|
||||
|
@ -446,19 +411,6 @@ def test_free_type_font_get_metrics(font):
|
|||
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):
|
||||
# Arrange
|
||||
text = "mask this"
|
||||
|
@ -617,19 +569,6 @@ def test_imagefont_getters(font):
|
|||
assert font.getlength("M") == 12
|
||||
assert font.getlength("y") == 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))
|
||||
|
@ -640,16 +579,6 @@ def test_getsize_stroke(font, stroke_width):
|
|||
12 + 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():
|
||||
|
@ -780,11 +709,8 @@ def test_textbbox_non_freetypefont():
|
|||
im = Image.new("RGB", (200, 200))
|
||||
d = ImageDraw.Draw(im)
|
||||
default_font = ImageFont.load_default()
|
||||
with pytest.warns(DeprecationWarning) as log:
|
||||
width, height = d.textsize("test", font=default_font)
|
||||
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)
|
||||
assert d.textlength("test", font=default_font) == 24
|
||||
assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, 24, 11)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -872,25 +798,23 @@ def test_anchor_invalid(font):
|
|||
d.font = font
|
||||
|
||||
for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]:
|
||||
pytest.raises(ValueError, lambda: font.getmask2("hello", anchor=anchor))
|
||||
pytest.raises(ValueError, lambda: font.getbbox("hello", anchor=anchor))
|
||||
pytest.raises(ValueError, lambda: d.text((0, 0), "hello", anchor=anchor))
|
||||
pytest.raises(ValueError, lambda: d.textbbox((0, 0), "hello", anchor=anchor))
|
||||
pytest.raises(
|
||||
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
|
||||
)
|
||||
pytest.raises(
|
||||
ValueError,
|
||||
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
font.getmask2("hello", anchor=anchor)
|
||||
with pytest.raises(ValueError):
|
||||
font.getbbox("hello", anchor=anchor)
|
||||
with pytest.raises(ValueError):
|
||||
d.text((0, 0), "hello", anchor=anchor)
|
||||
with pytest.raises(ValueError):
|
||||
d.textbbox((0, 0), "hello", anchor=anchor)
|
||||
with pytest.raises(ValueError):
|
||||
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"]:
|
||||
pytest.raises(
|
||||
ValueError, lambda: d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
|
||||
)
|
||||
pytest.raises(
|
||||
ValueError,
|
||||
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor),
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
d.multiline_text((0, 0), "foo\nbar", anchor=anchor)
|
||||
with pytest.raises(ValueError):
|
||||
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor)
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
|
||||
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():
|
||||
# issue 4177
|
||||
|
||||
|
@ -1131,12 +1047,3 @@ def test_raqm_missing_warning(monkeypatch):
|
|||
"Raqm layout was requested, but Raqm is not available. "
|
||||
"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]
|
||||
|
|
|
@ -360,37 +360,20 @@ def test_anchor_invalid_ttb():
|
|||
d.font = font
|
||||
|
||||
for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]:
|
||||
pytest.raises(
|
||||
ValueError, lambda: font.getmask2("hello", anchor=anchor, direction="ttb")
|
||||
)
|
||||
pytest.raises(
|
||||
ValueError, lambda: font.getbbox("hello", anchor=anchor, direction="ttb")
|
||||
)
|
||||
pytest.raises(
|
||||
ValueError, lambda: d.text((0, 0), "hello", anchor=anchor, direction="ttb")
|
||||
)
|
||||
pytest.raises(
|
||||
ValueError,
|
||||
lambda: d.textbbox((0, 0), "hello", 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"
|
||||
),
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
font.getmask2("hello", anchor=anchor, direction="ttb")
|
||||
with pytest.raises(ValueError):
|
||||
font.getbbox("hello", anchor=anchor, direction="ttb")
|
||||
with pytest.raises(ValueError):
|
||||
d.text((0, 0), "hello", anchor=anchor, direction="ttb")
|
||||
with pytest.raises(ValueError):
|
||||
d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb")
|
||||
with pytest.raises(ValueError):
|
||||
d.multiline_text((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
|
||||
with pytest.raises(ValueError):
|
||||
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor, direction="ttb")
|
||||
# ttb multiline text does not support anchors at all
|
||||
pytest.raises(
|
||||
ValueError,
|
||||
lambda: d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb"),
|
||||
)
|
||||
pytest.raises(
|
||||
ValueError,
|
||||
lambda: d.multiline_textbbox((0, 0), "foo\nbar", anchor="mm", direction="ttb"),
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
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")
|
||||
|
|
|
@ -9,10 +9,6 @@ def test_sanity():
|
|||
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
|
||||
assert len(palette.colors) == 256
|
||||
|
||||
with pytest.warns(DeprecationWarning):
|
||||
with pytest.raises(ValueError):
|
||||
ImagePalette.ImagePalette("RGB", list(range(256)) * 3, 10)
|
||||
|
||||
|
||||
def test_reload():
|
||||
with Image.open("Tests/images/hopper.gif") as im:
|
||||
|
|
|
@ -2,13 +2,10 @@ import warnings
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import ImageQt
|
||||
|
||||
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(
|
||||
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
|
||||
)
|
||||
|
@ -26,10 +23,6 @@ def test_rgb():
|
|||
from PyQt6.QtGui import qRgb
|
||||
elif ImageQt.qt_version == "side6":
|
||||
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)
|
||||
|
||||
|
|
|
@ -55,8 +55,8 @@ def test_show_without_viewers():
|
|||
viewers = ImageShow._viewers
|
||||
ImageShow._viewers = []
|
||||
|
||||
im = hopper()
|
||||
assert not ImageShow.show(im)
|
||||
with hopper() as im:
|
||||
assert not ImageShow.show(im)
|
||||
|
||||
ImageShow._viewers = viewers
|
||||
|
||||
|
@ -89,20 +89,3 @@ def test_ipythonviewer():
|
|||
|
||||
im = hopper()
|
||||
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()
|
||||
|
|
|
@ -89,13 +89,6 @@ def test_photoimage_blank(mode):
|
|||
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():
|
||||
im = hopper("1")
|
||||
|
||||
|
|
|
@ -100,8 +100,11 @@ class TestImageWinDib:
|
|||
# Act
|
||||
# Make one the same as the using tobytes()/frombytes()
|
||||
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
|
||||
# Confirm they're the same
|
||||
assert dib1.tobytes() == dib2.tobytes()
|
||||
# Assert
|
||||
# Confirm they're the same
|
||||
assert dib1.tobytes() == dib2.tobytes()
|
||||
|
|
|
@ -207,6 +207,9 @@ class TestLibPack:
|
|||
0x01000083,
|
||||
)
|
||||
|
||||
def test_I16(self):
|
||||
self.assert_pack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605)
|
||||
|
||||
def test_F_float(self):
|
||||
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;16B", "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:
|
||||
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;16L", "I;16N", 2, 0x0102, 0x0304, 0x0506)
|
||||
self.assert_unpack("I;16N", "I;16N", 2, 0x0102, 0x0304, 0x0506)
|
||||
|
||||
def test_CMYK16(self):
|
||||
self.assert_unpack("CMYK", "CMYK;16L", 8, (2, 4, 6, 8), (10, 12, 14, 16))
|
||||
|
|
|
@ -88,10 +88,7 @@ def test_tobytes():
|
|||
def test_convert():
|
||||
im = original.copy()
|
||||
|
||||
verify(im.convert("I;16"))
|
||||
verify(im.convert("I;16").convert("L"))
|
||||
verify(im.convert("I;16").convert("I"))
|
||||
|
||||
verify(im.convert("I;16B"))
|
||||
verify(im.convert("I;16B").convert("L"))
|
||||
verify(im.convert("I;16B").convert("I"))
|
||||
for mode in ("I;16", "I;16B", "I;16N"):
|
||||
verify(im.convert(mode))
|
||||
verify(im.convert(mode).convert("L"))
|
||||
verify(im.convert(mode).convert("I"))
|
||||
|
|
|
@ -4,7 +4,7 @@ import pytest
|
|||
|
||||
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")
|
||||
|
||||
|
@ -219,6 +219,13 @@ def test_zero_size():
|
|||
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():
|
||||
# https://github.com/python-pillow/Pillow/issues/2044
|
||||
a = numpy.zeros((10, 2), dtype=bool)
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", category=DeprecationWarning)
|
||||
from PIL import ImageQt
|
||||
from PIL import ImageQt
|
||||
|
||||
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.QtGui import QImage, QPainter, QRegion
|
||||
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):
|
||||
def __init__(self):
|
||||
|
@ -48,7 +36,7 @@ if ImageQt.qt_is_installed:
|
|||
def roundtrip(expected):
|
||||
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
|
||||
# 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")
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", category=DeprecationWarning)
|
||||
from PIL import ImageQt
|
||||
from PIL import ImageQt
|
||||
|
||||
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)
|
||||
|
||||
if mode == "1":
|
||||
# BW appears to not save correctly on QT5
|
||||
# BW appears to not save correctly on Qt
|
||||
# kicks out errors on console:
|
||||
# libpng warning: Invalid color type/bit depth combination
|
||||
# in IHDR
|
||||
|
|
|
@ -8,5 +8,5 @@ if [ ! -f $archive.tar.gz ]; then
|
|||
wget -O $archive.tar.gz $url
|
||||
fi
|
||||
|
||||
rm -r $archive
|
||||
rmdir $archive
|
||||
tar -xvzf $archive.tar.gz
|
||||
|
|