Merge branch 'main' into convert_mode
|
@ -25,8 +25,8 @@ install:
|
||||||
- mv c:\pillow-depends-main c:\pillow-depends
|
- mv c:\pillow-depends-main c:\pillow-depends
|
||||||
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images
|
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images
|
||||||
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
|
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
|
||||||
- ..\pillow-depends\gs9561w32.exe /S
|
- ..\pillow-depends\gs1000w32.exe /S
|
||||||
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs9.56.1\bin;%PATH%
|
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH%
|
||||||
- cd c:\pillow\winbuild\
|
- cd c:\pillow\winbuild\
|
||||||
- ps: |
|
- ps: |
|
||||||
c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
|
c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
|
||||||
|
|
|
@ -35,15 +35,13 @@ python3 -m pip install -U pytest
|
||||||
python3 -m pip install -U pytest-cov
|
python3 -m pip install -U pytest-cov
|
||||||
python3 -m pip install -U pytest-timeout
|
python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
python3 -m pip install test-image-results
|
|
||||||
|
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
if [[ $(uname) != CYGWIN* ]]; then
|
||||||
# TODO Remove condition when NumPy supports 3.11
|
python3 -m pip install numpy
|
||||||
if ! [ "$GHA_PYTHON_VERSION" == "3.11-dev" ]; then python3 -m pip install numpy ; fi
|
|
||||||
|
|
||||||
# PyQt6 doesn't support PyPy3
|
# PyQt6 doesn't support PyPy3
|
||||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||||
sudo apt-get -qq install libegl1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxkbcommon-x11-0
|
sudo apt-get -qq install libegl1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
||||||
python3 -m pip install pyqt6
|
python3 -m pip install pyqt6
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
17
.github/renovate.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:base"
|
||||||
|
],
|
||||||
|
"labels": [
|
||||||
|
"Dependency"
|
||||||
|
],
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"groupName": "github-actions",
|
||||||
|
"matchManagers": ["github-actions"],
|
||||||
|
"separateMajorMinor": "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"schedule": ["on the 3rd day of the month"]
|
||||||
|
}
|
7
.github/workflows/cifuzz.yml
vendored
|
@ -11,6 +11,13 @@ on:
|
||||||
- "**.h"
|
- "**.h"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Fuzzing:
|
Fuzzing:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
11
.github/workflows/lint.yml
vendored
|
@ -2,6 +2,13 @@ name: Lint
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
|
@ -13,7 +20,7 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: pre-commit cache
|
- name: pre-commit cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pre-commit
|
path: ~/.cache/pre-commit
|
||||||
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
|
||||||
|
@ -21,7 +28,7 @@ jobs:
|
||||||
lint-pre-commit-
|
lint-pre-commit-
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v3
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|
4
.github/workflows/macos-install.sh
vendored
|
@ -12,11 +12,9 @@ python3 -m pip install -U pytest
|
||||||
python3 -m pip install -U pytest-cov
|
python3 -m pip install -U pytest-cov
|
||||||
python3 -m pip install -U pytest-timeout
|
python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
python3 -m pip install test-image-results
|
|
||||||
|
|
||||||
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
|
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
|
||||||
# TODO Remove condition when NumPy supports 3.11
|
python3 -m pip install numpy
|
||||||
if ! [ "$GHA_PYTHON_VERSION" == "3.11-dev" ]; then python3 -m pip install numpy ; fi
|
|
||||||
|
|
||||||
# extra test images
|
# extra test images
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
10
.github/workflows/release-drafter.yml
vendored
|
@ -7,8 +7,18 @@ on:
|
||||||
- main
|
- main
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update_release_draft:
|
update_release_draft:
|
||||||
|
permissions:
|
||||||
|
contents: write # for release-drafter/release-drafter to create a github release
|
||||||
|
pull-requests: write # for release-drafter/release-drafter to add label to PR
|
||||||
if: github.repository == 'python-pillow/Pillow'
|
if: github.repository == 'python-pillow/Pillow'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
6
.github/workflows/stale.yml
vendored
|
@ -8,6 +8,10 @@ on:
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
stale:
|
stale:
|
||||||
if: github.repository_owner == 'python-pillow'
|
if: github.repository_owner == 'python-pillow'
|
||||||
|
@ -16,7 +20,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Check issues"
|
- name: "Check issues"
|
||||||
uses: actions/stale@v5
|
uses: actions/stale@v6
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
only-labels: "Awaiting OP Action"
|
only-labels: "Awaiting OP Action"
|
||||||
|
|
11
.github/workflows/test-cygwin.yml
vendored
|
@ -2,6 +2,13 @@ name: Test Cygwin
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
@ -41,7 +48,7 @@ jobs:
|
||||||
qt5-devel-tools subversion xorg-server-extra zlib-devel
|
qt5-devel-tools subversion xorg-server-extra zlib-devel
|
||||||
|
|
||||||
- name: Add Lapack to PATH
|
- name: Add Lapack to PATH
|
||||||
uses: egor-tensin/cleanup-path@v1
|
uses: egor-tensin/cleanup-path@v2
|
||||||
with:
|
with:
|
||||||
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
|
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
|
||||||
|
|
||||||
|
@ -99,6 +106,8 @@ jobs:
|
||||||
name: Cygwin Python 3.${{ matrix.python-minor-version }}
|
name: Cygwin Python 3.${{ matrix.python-minor-version }}
|
||||||
|
|
||||||
success:
|
success:
|
||||||
|
permissions:
|
||||||
|
contents: none
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Cygwin Test Successful
|
name: Cygwin Test Successful
|
||||||
|
|
11
.github/workflows/test-docker.yml
vendored
|
@ -2,6 +2,13 @@ name: Test Docker
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
|
@ -76,12 +83,14 @@ jobs:
|
||||||
MATRIX_DOCKER: ${{ matrix.docker }}
|
MATRIX_DOCKER: ${{ matrix.docker }}
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
flags: GHA_Docker
|
flags: GHA_Docker
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
success:
|
success:
|
||||||
|
permissions:
|
||||||
|
contents: none
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Docker Test Successful
|
name: Docker Test Successful
|
||||||
|
|
19
.github/workflows/test-mingw.yml
vendored
|
@ -2,6 +2,13 @@ name: Test MinGW
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
@ -70,13 +77,15 @@ jobs:
|
||||||
python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
|
python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
run: |
|
uses: codecov/codecov-action@v3
|
||||||
python3 -m pip install codecov
|
with:
|
||||||
bash <(curl -s https://codecov.io/bash) -F GHA_Windows
|
file: ./coverage.xml
|
||||||
env:
|
flags: GHA_Windows
|
||||||
CODECOV_NAME: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
|
|
||||||
success:
|
success:
|
||||||
|
permissions:
|
||||||
|
contents: none
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: MinGW Test Successful
|
name: MinGW Test Successful
|
||||||
|
|
9
.github/workflows/test-valgrind.yml
vendored
|
@ -13,6 +13,13 @@ on:
|
||||||
- "**.h"
|
- "**.h"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
|
@ -21,7 +28,7 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
docker: [
|
docker: [
|
||||||
ubuntu-20.04-focal-amd64-valgrind,
|
ubuntu-22.04-jammy-amd64-valgrind,
|
||||||
]
|
]
|
||||||
dockerTag: [main]
|
dockerTag: [main]
|
||||||
|
|
||||||
|
|
19
.github/workflows/test-windows.yml
vendored
|
@ -2,6 +2,13 @@ name: Test Windows
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
@ -33,7 +40,7 @@ jobs:
|
||||||
|
|
||||||
# sets env: pythonLocation
|
# sets env: pythonLocation
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v3
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
architecture: ${{ matrix.architecture }}
|
architecture: ${{ matrix.architecture }}
|
||||||
|
@ -52,8 +59,8 @@ jobs:
|
||||||
7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
||||||
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH
|
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
winbuild\depends\gs9561w32.exe /S
|
winbuild\depends\gs1000w32.exe /S
|
||||||
echo "C:\Program Files (x86)\gs\gs9.56.1\bin" >> $env:GITHUB_PATH
|
echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
xcopy /S /Y winbuild\depends\test_images\* Tests\images\
|
xcopy /S /Y winbuild\depends\test_images\* Tests\images\
|
||||||
|
|
||||||
|
@ -63,7 +70,7 @@ jobs:
|
||||||
|
|
||||||
- name: Cache build
|
- name: Cache build
|
||||||
id: build-cache
|
id: build-cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: winbuild\build
|
path: winbuild\build
|
||||||
key:
|
key:
|
||||||
|
@ -168,7 +175,7 @@ jobs:
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
file: ./coverage.xml
|
file: ./coverage.xml
|
||||||
flags: GHA_Windows
|
flags: GHA_Windows
|
||||||
|
@ -189,6 +196,8 @@ jobs:
|
||||||
path: dist\*.whl
|
path: dist\*.whl
|
||||||
|
|
||||||
success:
|
success:
|
||||||
|
permissions:
|
||||||
|
contents: none
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Windows Test Successful
|
name: Windows Test Successful
|
||||||
|
|
25
.github/workflows/test.yml
vendored
|
@ -2,6 +2,13 @@ name: Test
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
|
@ -27,11 +34,6 @@ jobs:
|
||||||
REVERSE: "--reverse"
|
REVERSE: "--reverse"
|
||||||
- python-version: "3.8"
|
- python-version: "3.8"
|
||||||
PYTHONOPTIMIZE: 2
|
PYTHONOPTIMIZE: 2
|
||||||
# Include new variables for Codecov
|
|
||||||
- os: ubuntu-latest
|
|
||||||
codecov-flag: GHA_Ubuntu
|
|
||||||
- os: macos-latest
|
|
||||||
codecov-flag: GHA_macOS
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||||
|
@ -40,7 +42,7 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v3
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
cache: pip
|
cache: pip
|
||||||
|
@ -96,7 +98,6 @@ jobs:
|
||||||
- name: Docs
|
- name: Docs
|
||||||
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.10
|
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.10
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install furo sphinx-copybutton sphinx-issues sphinx-removed-in sphinxext-opengraph
|
|
||||||
make doccheck
|
make doccheck
|
||||||
|
|
||||||
- name: After success
|
- name: After success
|
||||||
|
@ -104,11 +105,15 @@ jobs:
|
||||||
.ci/after_success.sh
|
.ci/after_success.sh
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
run: bash <(curl -s https://codecov.io/bash) -F ${{ matrix.codecov-flag }}
|
uses: codecov/codecov-action@v3
|
||||||
env:
|
with:
|
||||||
CODECOV_NAME: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
file: ./coverage.xml
|
||||||
|
flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
|
||||||
|
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||||
|
|
||||||
success:
|
success:
|
||||||
|
permissions:
|
||||||
|
contents: none
|
||||||
needs: build
|
needs: build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Test Successful
|
name: Test Successful
|
||||||
|
|
8
.github/workflows/tidelift.yml
vendored
|
@ -1,4 +1,5 @@
|
||||||
name: Tidelift Align
|
name: Tidelift Align
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "30 2 * * *" # daily at 02:30 UTC
|
- cron: "30 2 * * *" # daily at 02:30 UTC
|
||||||
|
@ -12,6 +13,13 @@ on:
|
||||||
- ".github/workflows/tidelift.yml"
|
- ".github/workflows/tidelift.yml"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.repository_owner == 'python-pillow'
|
if: github.repository_owner == 'python-pillow'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 22.3.0
|
rev: 22.8.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: ["--target-version", "py37"]
|
args: ["--target-version", "py37"]
|
||||||
|
@ -14,18 +14,18 @@ repos:
|
||||||
- id: isort
|
- id: isort
|
||||||
|
|
||||||
- repo: https://github.com/asottile/yesqa
|
- repo: https://github.com/asottile/yesqa
|
||||||
rev: v1.3.0
|
rev: v1.4.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: yesqa
|
- id: yesqa
|
||||||
|
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||||
rev: v1.2.0
|
rev: v1.3.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: remove-tabs
|
- id: remove-tabs
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 4.0.1
|
rev: 5.0.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
|
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
|
||||||
|
@ -37,13 +37,14 @@ repos:
|
||||||
- id: rst-backticks
|
- id: rst-backticks
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.2.0
|
rev: v4.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
|
- id: check-json
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
|
|
||||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||||
rev: v0.6
|
rev: v0.6.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: sphinx-lint
|
- id: sphinx-lint
|
||||||
|
|
||||||
|
|
128
CHANGES.rst
|
@ -2,9 +2,135 @@
|
||||||
Changelog (Pillow)
|
Changelog (Pillow)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
9.2.0 (unreleased)
|
9.3.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Added reading of TIFF child images #6569
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Improved ImageOps palette handling #6596
|
||||||
|
[PososikTeam, radarhere]
|
||||||
|
|
||||||
|
- Defer parsing of palette into colors #6567
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Apply transparency to P images in ImageTk.PhotoImage #6559
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use rounding in ImageOps contain() and pad() #6522
|
||||||
|
[bibinhashley, radarhere]
|
||||||
|
|
||||||
|
- Fixed GIF remapping to palette with duplicate entries #6548
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow remap_palette() to return an image with less than 256 palette entries #6543
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Corrected BMP and TGA palette size when saving #6500
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not call load() before draft() in Image.thumbnail #6539
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Copy palette when converting from P to PA #6497
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow RGB and RGBA values for PA image putpixel #6504
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Removed support for tkinter in PyPy before Python 3.6 #6551
|
||||||
|
[nulano]
|
||||||
|
|
||||||
|
- Do not use CCITTFaxDecode filter if libtiff is not available #6518
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fallback to not using mmap if buffer is not large enough #6510
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed writing bytes as ASCII tag #6493
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Open 1 bit EPS in mode 1 #6499
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Removed support for tkinter before Python 1.5.2 #6549
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow default ImageDraw font to be set #6484
|
||||||
|
[radarhere, hugovk]
|
||||||
|
|
||||||
|
- Save 1 mode PDF using CCITTFaxDecode filter #6470
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added support for RGBA PSD images #6481
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Parse orientation from XMP tag contents #6463
|
||||||
|
[bigcat88, radarhere]
|
||||||
|
|
||||||
|
- Added support for reading ATI1/ATI2 (BC4/BC5) DDS images #6457
|
||||||
|
[REDxEYE, radarhere]
|
||||||
|
|
||||||
|
- Do not clear GIF tile when checking number of frames #6455
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Support saving multiple MPO frames #6444
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Do not double quote Pillow version for setuptools >= 60 #6450
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added ABGR BMP mask mode #6436
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed PSDraw rectangle #6429
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Raise ValueError if PNG sRGB chunk is truncated #6431
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Handle missing Python executable in ImageShow on macOS #6416
|
||||||
|
[bryant1410, radarhere]
|
||||||
|
|
||||||
|
9.2.0 (2022-07-01)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Deprecate ImageFont.getsize and related functions #6381
|
||||||
|
[nulano, radarhere]
|
||||||
|
|
||||||
|
- Fixed null check for fribidi_version_info in FriBiDi shim #6376
|
||||||
|
[nulano]
|
||||||
|
|
||||||
|
- Added GIF decompression bomb check #6402
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Handle PCF fonts files with less than 256 characters #6386
|
||||||
|
[dawidcrivelli, radarhere]
|
||||||
|
|
||||||
|
- Improved GIF optimize condition #6378
|
||||||
|
[raygard, radarhere]
|
||||||
|
|
||||||
|
- Reverted to __array_interface__ with the release of NumPy 1.23 #6394
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Pad PCX palette to 768 bytes when saving #6391
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed bug with rounding pixels to palette colors #6377
|
||||||
|
[btrekkie, radarhere]
|
||||||
|
|
||||||
|
- Use gnome-screenshot on Linux if available #6361
|
||||||
|
[radarhere, nulano]
|
||||||
|
|
||||||
|
- Fixed loading L mode BMP RLE8 images #6384
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed incorrect operator in ImageCms error #6370
|
||||||
|
[LostBenjamin, hugovk, radarhere]
|
||||||
|
|
||||||
|
- Limit FPX tile size to avoid extending outside image #6368
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Added support for decoding plain PPM formats #5242
|
- Added support for decoding plain PPM formats #5242
|
||||||
[Piolie, radarhere]
|
[Piolie, radarhere]
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ exclude .coveragerc
|
||||||
exclude .editorconfig
|
exclude .editorconfig
|
||||||
exclude .readthedocs.yml
|
exclude .readthedocs.yml
|
||||||
exclude codecov.yml
|
exclude codecov.yml
|
||||||
|
exclude renovate.json
|
||||||
global-exclude .git*
|
global-exclude .git*
|
||||||
global-exclude *.pyc
|
global-exclude *.pyc
|
||||||
global-exclude *.so
|
global-exclude *.so
|
||||||
|
|
3
Makefile
|
@ -17,11 +17,12 @@ coverage:
|
||||||
|
|
||||||
.PHONY: doc
|
.PHONY: doc
|
||||||
doc:
|
doc:
|
||||||
|
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
|
||||||
$(MAKE) -C docs html
|
$(MAKE) -C docs html
|
||||||
|
|
||||||
.PHONY: doccheck
|
.PHONY: doccheck
|
||||||
doccheck:
|
doccheck:
|
||||||
$(MAKE) -C docs html
|
$(MAKE) doc
|
||||||
# Don't make our tests rely on the links in the docs being up every single build.
|
# Don't make our tests rely on the links in the docs being up every single build.
|
||||||
# We don't control them. But do check, and update them to the target of their redirects.
|
# We don't control them. But do check, and update them to the target of their redirects.
|
||||||
$(MAKE) -C docs linkcheck || true
|
$(MAKE) -C docs linkcheck || true
|
||||||
|
|
|
@ -74,6 +74,9 @@ As of 2019, Pillow development is
|
||||||
<a href="https://pypi.org/project/Pillow/"><img
|
<a href="https://pypi.org/project/Pillow/"><img
|
||||||
alt="Number of PyPI downloads"
|
alt="Number of PyPI downloads"
|
||||||
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
||||||
|
<a href="https://bestpractices.coreinfrastructure.org/projects/6331"><img
|
||||||
|
alt="OpenSSF Best Practices"
|
||||||
|
src="https://bestpractices.coreinfrastructure.org/projects/6331/badge"></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -96,8 +96,8 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
## Binary Distributions
|
## Binary Distributions
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
* [ ] Contact `@cgohlke` for Windows binaries via release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174.
|
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
|
||||||
* [ ] Download and extract tarball from `@cgohlke` and copy into `dist/`
|
and copy into `dist/`
|
||||||
|
|
||||||
### Mac and Linux
|
### Mac and Linux
|
||||||
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
||||||
|
|
BIN
Tests/fonts/10x20-ISO8859-1-fewer-characters.pcf
Normal file
BIN
Tests/images/1.eps
Normal file
BIN
Tests/images/ati1.dds
Normal file
BIN
Tests/images/ati1.png
Normal file
After Width: | Height: | Size: 969 B |
BIN
Tests/images/ati2.dds
Normal file
BIN
Tests/images/child_ifd.tiff
Normal file
BIN
Tests/images/child_ifd_jpeg.tiff
Normal file
BIN
Tests/images/comment_after_only_frame.gif
Normal file
After Width: | Height: | Size: 54 B |
BIN
Tests/images/decompression_bomb_extents.gif
Normal file
After Width: | Height: | Size: 368 B |
BIN
Tests/images/hopper_rle8_greyscale.bmp
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
Tests/images/input_bw_one_band.fpx
Normal file
BIN
Tests/images/input_bw_one_band.png
Normal file
After Width: | Height: | Size: 477 B |
BIN
Tests/images/mmap_error.bmp
Normal file
After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
Tests/images/rgb32bf-abgr.bmp
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
Tests/images/rgba.psd
Normal file
BIN
Tests/images/xmp_tags_orientation_exiftool.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
|
@ -33,9 +33,9 @@ def fuzz_font(data):
|
||||||
# different font objects.
|
# different font objects.
|
||||||
return
|
return
|
||||||
|
|
||||||
font.getsize_multiline("ABC\nAaaa")
|
font.getbbox("ABC")
|
||||||
font.getmask("test text")
|
font.getmask("test text")
|
||||||
with Image.new(mode="RGBA", size=(200, 200)) as im:
|
with Image.new(mode="RGBA", size=(200, 200)) as im:
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2)
|
draw.multiline_textbbox((10, 10), "ABC\nAaaa", font, stroke_width=2)
|
||||||
draw.text((10, 10), "Test Text", font=font, fill="#000")
|
draw.text((10, 10), "Test Text", font=font, fill="#000")
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
import PIL
|
from PIL import Image
|
||||||
import PIL.Image
|
|
||||||
|
|
||||||
|
|
||||||
def test_sanity():
|
def test_sanity():
|
||||||
# Make sure we have the binary extension
|
# Make sure we have the binary extension
|
||||||
PIL.Image.core.new("L", (100, 100))
|
Image.core.new("L", (100, 100))
|
||||||
|
|
||||||
# Create an image and do stuff with it.
|
# Create an image and do stuff with it.
|
||||||
im = PIL.Image.new("1", (100, 100))
|
im = Image.new("1", (100, 100))
|
||||||
assert (im.mode, im.size) == ("1", (100, 100))
|
assert (im.mode, im.size) == ("1", (100, 100))
|
||||||
assert len(im.tobytes()) == 1300
|
assert len(im.tobytes()) == 1300
|
||||||
|
|
||||||
# Create images in all remaining major modes.
|
# Create images in all remaining major modes.
|
||||||
PIL.Image.new("L", (100, 100))
|
Image.new("L", (100, 100))
|
||||||
PIL.Image.new("P", (100, 100))
|
Image.new("P", (100, 100))
|
||||||
PIL.Image.new("RGB", (100, 100))
|
Image.new("RGB", (100, 100))
|
||||||
PIL.Image.new("I", (100, 100))
|
Image.new("I", (100, 100))
|
||||||
PIL.Image.new("F", (100, 100))
|
Image.new("F", (100, 100))
|
||||||
|
|
|
@ -61,6 +61,11 @@ class TestDecompressionBomb:
|
||||||
with Image.open("Tests/images/decompression_bomb.gif"):
|
with Image.open("Tests/images/decompression_bomb.gif"):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_exception_gif_extents(self):
|
||||||
|
with Image.open("Tests/images/decompression_bomb_extents.gif") as im:
|
||||||
|
with pytest.raises(Image.DecompressionBombError):
|
||||||
|
im.seek(1)
|
||||||
|
|
||||||
def test_exception_bmp(self):
|
def test_exception_bmp(self):
|
||||||
with pytest.raises(Image.DecompressionBombError):
|
with pytest.raises(Image.DecompressionBombError):
|
||||||
with Image.open("Tests/images/bmp/b/reallybig.bmp"):
|
with Image.open("Tests/images/bmp/b/reallybig.bmp"):
|
||||||
|
|
|
@ -70,13 +70,13 @@ def test_libimagequant_version():
|
||||||
assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant"))
|
assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant"))
|
||||||
|
|
||||||
|
|
||||||
def test_check_modules():
|
@pytest.mark.parametrize("feature", features.modules)
|
||||||
for feature in features.modules:
|
def test_check_modules(feature):
|
||||||
assert features.check_module(feature) in [True, False]
|
assert features.check_module(feature) in [True, False]
|
||||||
|
|
||||||
|
|
||||||
def test_check_codecs():
|
@pytest.mark.parametrize("feature", features.codecs)
|
||||||
for feature in features.codecs:
|
def test_check_codecs(feature):
|
||||||
assert features.check_codec(feature) in [True, False]
|
assert features.check_codec(feature) in [True, False]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -39,13 +39,12 @@ def test_apng_basic():
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
def test_apng_fdat():
|
@pytest.mark.parametrize(
|
||||||
with Image.open("Tests/images/apng/split_fdat.png") as im:
|
"filename",
|
||||||
im.seek(im.n_frames - 1)
|
("Tests/images/apng/split_fdat.png", "Tests/images/apng/split_fdat_zero_chunk.png"),
|
||||||
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
)
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
def test_apng_fdat(filename):
|
||||||
|
with Image.open(filename) as im:
|
||||||
with Image.open("Tests/images/apng/split_fdat_zero_chunk.png") as im:
|
|
||||||
im.seek(im.n_frames - 1)
|
im.seek(im.n_frames - 1)
|
||||||
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
@ -325,8 +324,9 @@ def test_apng_syntax_errors():
|
||||||
pytest.warns(UserWarning, open)
|
pytest.warns(UserWarning, open)
|
||||||
|
|
||||||
|
|
||||||
def test_apng_sequence_errors():
|
@pytest.mark.parametrize(
|
||||||
test_files = [
|
"test_file",
|
||||||
|
(
|
||||||
"sequence_start.png",
|
"sequence_start.png",
|
||||||
"sequence_gap.png",
|
"sequence_gap.png",
|
||||||
"sequence_repeat.png",
|
"sequence_repeat.png",
|
||||||
|
@ -334,10 +334,11 @@ def test_apng_sequence_errors():
|
||||||
"sequence_reorder.png",
|
"sequence_reorder.png",
|
||||||
"sequence_reorder_chunk.png",
|
"sequence_reorder_chunk.png",
|
||||||
"sequence_fdat_fctl.png",
|
"sequence_fdat_fctl.png",
|
||||||
]
|
),
|
||||||
for f in test_files:
|
)
|
||||||
|
def test_apng_sequence_errors(test_file):
|
||||||
with pytest.raises(SyntaxError):
|
with pytest.raises(SyntaxError):
|
||||||
with Image.open(f"Tests/images/apng/{f}") as im:
|
with Image.open(f"Tests/images/apng/{test_file}") as im:
|
||||||
im.seek(im.n_frames - 1)
|
im.seek(im.n_frames - 1)
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,13 @@ def test_invalid_file():
|
||||||
BmpImagePlugin.BmpImageFile(fp)
|
BmpImagePlugin.BmpImageFile(fp)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fallback_if_mmap_errors():
|
||||||
|
# This image has been truncated,
|
||||||
|
# so that the buffer is not large enough when using mmap
|
||||||
|
with Image.open("Tests/images/mmap_error.bmp") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/pal8_offset.bmp")
|
||||||
|
|
||||||
|
|
||||||
def test_save_to_bytes():
|
def test_save_to_bytes():
|
||||||
output = io.BytesIO()
|
output = io.BytesIO()
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
@ -51,6 +58,18 @@ def test_save_to_bytes():
|
||||||
assert reloaded.format == "BMP"
|
assert reloaded.format == "BMP"
|
||||||
|
|
||||||
|
|
||||||
|
def test_small_palette(tmp_path):
|
||||||
|
im = Image.new("P", (1, 1))
|
||||||
|
colors = [0, 0, 0, 125, 125, 125, 255, 255, 255]
|
||||||
|
im.putpalette(colors)
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.bmp")
|
||||||
|
im.save(out)
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.getpalette() == colors
|
||||||
|
|
||||||
|
|
||||||
def test_save_too_large(tmp_path):
|
def test_save_too_large(tmp_path):
|
||||||
outfile = str(tmp_path / "temp.bmp")
|
outfile = str(tmp_path / "temp.bmp")
|
||||||
with Image.new("RGB", (1, 1)) as im:
|
with Image.new("RGB", (1, 1)) as im:
|
||||||
|
@ -129,11 +148,21 @@ def test_rgba_bitfields():
|
||||||
|
|
||||||
assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp")
|
assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp")
|
||||||
|
|
||||||
|
# This test image has been manually hexedited
|
||||||
|
# to change the bitfield compression in the header from XBGR to ABGR
|
||||||
|
with Image.open("Tests/images/rgb32bf-abgr.bmp") as im:
|
||||||
|
assert_image_equal_tofile(
|
||||||
|
im.convert("RGB"), "Tests/images/bmp/q/rgb32bf-xbgr.bmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_rle8():
|
def test_rle8():
|
||||||
with Image.open("Tests/images/hopper_rle8.bmp") as im:
|
with Image.open("Tests/images/hopper_rle8.bmp") as im:
|
||||||
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
assert_image_similar_tofile(im.convert("RGB"), "Tests/images/hopper.bmp", 12)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/hopper_rle8_greyscale.bmp") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
|
||||||
|
|
||||||
# This test image has been manually hexedited
|
# This test image has been manually hexedited
|
||||||
# to have rows with too much data
|
# to have rows with too much data
|
||||||
with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im:
|
with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im:
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import ContainerIO, Image
|
from PIL import ContainerIO, Image
|
||||||
|
|
||||||
from .helper import hopper
|
from .helper import hopper
|
||||||
|
@ -59,9 +61,9 @@ def test_seek_mode_2():
|
||||||
assert container.tell() == 100
|
assert container.tell() == 100
|
||||||
|
|
||||||
|
|
||||||
def test_read_n0():
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_read_n0(bytesmode):
|
||||||
# Arrange
|
# Arrange
|
||||||
for bytesmode in (True, False):
|
|
||||||
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
|
@ -75,9 +77,9 @@ def test_read_n0():
|
||||||
assert data == "7\nThis is line 8\n"
|
assert data == "7\nThis is line 8\n"
|
||||||
|
|
||||||
|
|
||||||
def test_read_n():
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_read_n(bytesmode):
|
||||||
# Arrange
|
# Arrange
|
||||||
for bytesmode in (True, False):
|
|
||||||
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
|
@ -91,9 +93,9 @@ def test_read_n():
|
||||||
assert data == "7\nT"
|
assert data == "7\nT"
|
||||||
|
|
||||||
|
|
||||||
def test_read_eof():
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_read_eof(bytesmode):
|
||||||
# Arrange
|
# Arrange
|
||||||
for bytesmode in (True, False):
|
|
||||||
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
|
@ -107,9 +109,9 @@ def test_read_eof():
|
||||||
assert data == ""
|
assert data == ""
|
||||||
|
|
||||||
|
|
||||||
def test_readline():
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_readline(bytesmode):
|
||||||
# Arrange
|
# Arrange
|
||||||
for bytesmode in (True, False):
|
|
||||||
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
container = ContainerIO.ContainerIO(fh, 0, 120)
|
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||||
|
|
||||||
|
@ -122,9 +124,9 @@ def test_readline():
|
||||||
assert data == "This is line 1\n"
|
assert data == "This is line 1\n"
|
||||||
|
|
||||||
|
|
||||||
def test_readlines():
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_readlines(bytesmode):
|
||||||
# Arrange
|
# Arrange
|
||||||
for bytesmode in (True, False):
|
|
||||||
expected = [
|
expected = [
|
||||||
"This is line 1\n",
|
"This is line 1\n",
|
||||||
"This is line 2\n",
|
"This is line 2\n",
|
||||||
|
|
|
@ -10,6 +10,8 @@ from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
||||||
TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
|
TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
|
||||||
TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
|
TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
|
||||||
TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds"
|
TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds"
|
||||||
|
TEST_FILE_ATI1 = "Tests/images/ati1.dds"
|
||||||
|
TEST_FILE_ATI2 = "Tests/images/ati2.dds"
|
||||||
TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds"
|
TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds"
|
||||||
TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
|
TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
|
||||||
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
|
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
|
||||||
|
@ -62,6 +64,32 @@ def test_sanity_dxt5():
|
||||||
assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png"))
|
assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanity_ati1():
|
||||||
|
"""Check ATI1 images can be opened"""
|
||||||
|
|
||||||
|
with Image.open(TEST_FILE_ATI1) as im:
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
assert im.format == "DDS"
|
||||||
|
assert im.mode == "L"
|
||||||
|
assert im.size == (64, 64)
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanity_ati2():
|
||||||
|
"""Check ATI2 images can be opened"""
|
||||||
|
|
||||||
|
with Image.open(TEST_FILE_ATI2) as im:
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
assert im.format == "DDS"
|
||||||
|
assert im.mode == "RGB"
|
||||||
|
assert im.size == (256, 256)
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, TEST_FILE_DX10_BC5_UNORM.replace(".dds", ".png"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("image_path", "expected_path"),
|
("image_path", "expected_path"),
|
||||||
(
|
(
|
||||||
|
|
|
@ -124,14 +124,6 @@ def test_file_object(tmp_path):
|
||||||
image1.save(fh, "EPS")
|
image1.save(fh, "EPS")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
|
|
||||||
def test_iobase_object(tmp_path):
|
|
||||||
# issue 479
|
|
||||||
with Image.open(FILE1) as image1:
|
|
||||||
with open(str(tmp_path / "temp_iobase.eps"), "wb") as fh:
|
|
||||||
image1.save(fh, "EPS")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
|
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
|
||||||
def test_bytesio_object():
|
def test_bytesio_object():
|
||||||
with open(FILE1, "rb") as f:
|
with open(FILE1, "rb") as f:
|
||||||
|
@ -146,6 +138,11 @@ def test_bytesio_object():
|
||||||
assert_image_similar(img, image1_scale1_compare, 5)
|
assert_image_similar(img, image1_scale1_compare, 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_1_mode():
|
||||||
|
with Image.open("Tests/images/1.eps") as im:
|
||||||
|
assert im.mode == "1"
|
||||||
|
|
||||||
|
|
||||||
def test_image_mode_not_supported(tmp_path):
|
def test_image_mode_not_supported(tmp_path):
|
||||||
im = hopper("RGBA")
|
im = hopper("RGBA")
|
||||||
tmpfile = str(tmp_path / "temp.eps")
|
tmpfile = str(tmp_path / "temp.eps")
|
||||||
|
@ -198,22 +195,20 @@ def test_render_scale2():
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
|
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
|
||||||
def test_resize():
|
@pytest.mark.parametrize("filename", (FILE1, FILE2, "Tests/images/illu10_preview.eps"))
|
||||||
files = [FILE1, FILE2, "Tests/images/illu10_preview.eps"]
|
def test_resize(filename):
|
||||||
for fn in files:
|
with Image.open(filename) as im:
|
||||||
with Image.open(fn) as im:
|
|
||||||
new_size = (100, 100)
|
new_size = (100, 100)
|
||||||
im = im.resize(new_size)
|
im = im.resize(new_size)
|
||||||
assert im.size == new_size
|
assert im.size == new_size
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
|
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
|
||||||
def test_thumbnail():
|
@pytest.mark.parametrize("filename", (FILE1, FILE2))
|
||||||
|
def test_thumbnail(filename):
|
||||||
# Issue #619
|
# Issue #619
|
||||||
# Arrange
|
# Arrange
|
||||||
files = [FILE1, FILE2]
|
with Image.open(filename) as im:
|
||||||
for fn in files:
|
|
||||||
with Image.open(FILE1) as im:
|
|
||||||
new_size = (100, 100)
|
new_size = (100, 100)
|
||||||
im.thumbnail(new_size)
|
im.thumbnail(new_size)
|
||||||
assert max(im.size) == max(new_size)
|
assert max(im.size) == max(new_size)
|
||||||
|
@ -261,18 +256,17 @@ def test_readline(tmp_path):
|
||||||
_test_readline_file_psfile(s, ending)
|
_test_readline_file_psfile(s, ending)
|
||||||
|
|
||||||
|
|
||||||
def test_open_eps():
|
@pytest.mark.parametrize(
|
||||||
# https://github.com/python-pillow/Pillow/issues/1104
|
"filename",
|
||||||
# Arrange
|
(
|
||||||
FILES = [
|
|
||||||
"Tests/images/illu10_no_preview.eps",
|
"Tests/images/illu10_no_preview.eps",
|
||||||
"Tests/images/illu10_preview.eps",
|
"Tests/images/illu10_preview.eps",
|
||||||
"Tests/images/illuCS6_no_preview.eps",
|
"Tests/images/illuCS6_no_preview.eps",
|
||||||
"Tests/images/illuCS6_preview.eps",
|
"Tests/images/illuCS6_preview.eps",
|
||||||
]
|
),
|
||||||
|
)
|
||||||
# Act / Assert
|
def test_open_eps(filename):
|
||||||
for filename in FILES:
|
# https://github.com/python-pillow/Pillow/issues/1104
|
||||||
with Image.open(filename) as img:
|
with Image.open(filename) as img:
|
||||||
assert img.mode == "RGB"
|
assert img.mode == "RGB"
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,22 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
from .helper import assert_image_equal_tofile
|
||||||
|
|
||||||
FpxImagePlugin = pytest.importorskip(
|
FpxImagePlugin = pytest.importorskip(
|
||||||
"PIL.FpxImagePlugin", reason="olefile not installed"
|
"PIL.FpxImagePlugin", reason="olefile not installed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanity():
|
||||||
|
with Image.open("Tests/images/input_bw_one_band.fpx") as im:
|
||||||
|
assert im.mode == "L"
|
||||||
|
assert im.size == (70, 46)
|
||||||
|
assert im.format == "FPX"
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/input_bw_one_band.png")
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file():
|
||||||
# Test an invalid OLE file
|
# Test an invalid OLE file
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
|
@ -158,6 +158,9 @@ def test_optimize_correctness():
|
||||||
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
||||||
|
|
||||||
# These do optimize the palette
|
# These do optimize the palette
|
||||||
|
check(256, 511, 256)
|
||||||
|
check(255, 511, 255)
|
||||||
|
check(129, 511, 129)
|
||||||
check(128, 511, 128)
|
check(128, 511, 128)
|
||||||
check(64, 511, 64)
|
check(64, 511, 64)
|
||||||
check(4, 511, 4)
|
check(4, 511, 4)
|
||||||
|
@ -167,11 +170,6 @@ def test_optimize_correctness():
|
||||||
check(64, 513, 256)
|
check(64, 513, 256)
|
||||||
check(4, 513, 256)
|
check(4, 513, 256)
|
||||||
|
|
||||||
# Other limits that don't optimize the palette
|
|
||||||
check(129, 511, 256)
|
|
||||||
check(255, 511, 256)
|
|
||||||
check(256, 511, 256)
|
|
||||||
|
|
||||||
|
|
||||||
def test_optimize_full_l():
|
def test_optimize_full_l():
|
||||||
im = Image.frombytes("L", (16, 16), bytes(range(256)))
|
im = Image.frombytes("L", (16, 16), bytes(range(256)))
|
||||||
|
@ -180,6 +178,19 @@ def test_optimize_full_l():
|
||||||
assert im.mode == "L"
|
assert im.mode == "L"
|
||||||
|
|
||||||
|
|
||||||
|
def test_optimize_if_palette_can_be_reduced_by_half():
|
||||||
|
with Image.open("Tests/images/test.colors.gif") as im:
|
||||||
|
# Reduce dimensions because original is too big for _get_optimize()
|
||||||
|
im = im.resize((591, 443))
|
||||||
|
im_rgb = im.convert("RGB")
|
||||||
|
|
||||||
|
for (optimize, colors) in ((False, 256), (True, 8)):
|
||||||
|
out = BytesIO()
|
||||||
|
im_rgb.save(out, "GIF", optimize=optimize)
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert len(reloaded.palette.palette) // 3 == colors
|
||||||
|
|
||||||
|
|
||||||
def test_roundtrip(tmp_path):
|
def test_roundtrip(tmp_path):
|
||||||
out = str(tmp_path / "temp.gif")
|
out = str(tmp_path / "temp.gif")
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
@ -388,6 +399,11 @@ def test_no_change():
|
||||||
assert im.is_animated
|
assert im.is_animated
|
||||||
assert_image_equal(im, expected)
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/comment_after_only_frame.gif") as im:
|
||||||
|
expected = Image.new("P", (1, 1))
|
||||||
|
assert not im.is_animated
|
||||||
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_eoferror():
|
def test_eoferror():
|
||||||
with Image.open(TEST_GIF) as im:
|
with Image.open(TEST_GIF) as im:
|
||||||
|
@ -777,8 +793,10 @@ def test_identical_frames(tmp_path):
|
||||||
assert reread.info["duration"] == 4500
|
assert reread.info["duration"] == 4500
|
||||||
|
|
||||||
|
|
||||||
def test_identical_frames_to_single_frame(tmp_path):
|
@pytest.mark.parametrize(
|
||||||
for duration in ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500):
|
"duration", ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500)
|
||||||
|
)
|
||||||
|
def test_identical_frames_to_single_frame(duration, tmp_path):
|
||||||
out = str(tmp_path / "temp.gif")
|
out = str(tmp_path / "temp.gif")
|
||||||
im_list = [
|
im_list = [
|
||||||
Image.new("L", (100, 100), "#000"),
|
Image.new("L", (100, 100), "#000"),
|
||||||
|
@ -786,9 +804,7 @@ def test_identical_frames_to_single_frame(tmp_path):
|
||||||
Image.new("L", (100, 100), "#000"),
|
Image.new("L", (100, 100), "#000"),
|
||||||
]
|
]
|
||||||
|
|
||||||
im_list[0].save(
|
im_list[0].save(out, save_all=True, append_images=im_list[1:], duration=duration)
|
||||||
out, save_all=True, append_images=im_list[1:], duration=duration
|
|
||||||
)
|
|
||||||
with Image.open(out) as reread:
|
with Image.open(out) as reread:
|
||||||
# Assert that all frames were combined
|
# Assert that all frames were combined
|
||||||
assert reread.n_frames == 1
|
assert reread.n_frames == 1
|
||||||
|
@ -982,8 +998,8 @@ def test_append_images(tmp_path):
|
||||||
def test_transparent_optimize(tmp_path):
|
def test_transparent_optimize(tmp_path):
|
||||||
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
|
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
|
||||||
# transparency.
|
# transparency.
|
||||||
# Need a palette that isn't using the 0 color, and one that's > 128 items where the
|
# Need a palette that isn't using the 0 color,
|
||||||
# transparent color is actually the top palette entry to trigger the bug.
|
# where the transparent color is actually the top palette entry to trigger the bug.
|
||||||
|
|
||||||
data = bytes(range(1, 254))
|
data = bytes(range(1, 254))
|
||||||
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
|
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
|
||||||
|
@ -993,10 +1009,10 @@ def test_transparent_optimize(tmp_path):
|
||||||
im.putpalette(palette)
|
im.putpalette(palette)
|
||||||
|
|
||||||
out = str(tmp_path / "temp.gif")
|
out = str(tmp_path / "temp.gif")
|
||||||
im.save(out, transparency=253)
|
im.save(out, transparency=im.getpixel((252, 0)))
|
||||||
with Image.open(out) as reloaded:
|
|
||||||
|
|
||||||
assert reloaded.info["transparency"] == 253
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.info["transparency"] == reloaded.getpixel((252, 0))
|
||||||
|
|
||||||
|
|
||||||
def test_rgb_transparency(tmp_path):
|
def test_rgb_transparency(tmp_path):
|
||||||
|
@ -1071,6 +1087,19 @@ def test_palette_save_P(tmp_path):
|
||||||
assert_image_equal(reloaded, im)
|
assert_image_equal(reloaded, im)
|
||||||
|
|
||||||
|
|
||||||
|
def test_palette_save_duplicate_entries(tmp_path):
|
||||||
|
im = Image.new("P", (1, 2))
|
||||||
|
im.putpixel((0, 1), 1)
|
||||||
|
|
||||||
|
im.putpalette((0, 0, 0, 0, 0, 0))
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.gif")
|
||||||
|
im.save(out, palette=[0, 0, 0, 0, 0, 0, 1, 1, 1])
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.convert("RGB").getpixel((0, 1)) == (0, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
def test_palette_save_all_P(tmp_path):
|
def test_palette_save_all_P(tmp_path):
|
||||||
frames = []
|
frames = []
|
||||||
colors = ((255, 0, 0), (0, 255, 0))
|
colors = ((255, 0, 0), (0, 255, 0))
|
||||||
|
|
|
@ -78,16 +78,13 @@ def test_eoferror():
|
||||||
im.seek(n_frames - 1)
|
im.seek(n_frames - 1)
|
||||||
|
|
||||||
|
|
||||||
def test_roundtrip(tmp_path):
|
@pytest.mark.parametrize("mode", ("RGB", "P", "PA"))
|
||||||
def roundtrip(mode):
|
def test_roundtrip(mode, tmp_path):
|
||||||
out = str(tmp_path / "temp.im")
|
out = str(tmp_path / "temp.im")
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
im.save(out)
|
im.save(out)
|
||||||
assert_image_equal_tofile(im, out)
|
assert_image_equal_tofile(im, out)
|
||||||
|
|
||||||
for mode in ["RGB", "P", "PA"]:
|
|
||||||
roundtrip(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_save_unsupported_mode(tmp_path):
|
def test_save_unsupported_mode(tmp_path):
|
||||||
out = str(tmp_path / "temp.im")
|
out = str(tmp_path / "temp.im")
|
||||||
|
|
|
@ -150,9 +150,23 @@ class TestFileJpeg:
|
||||||
assert not im1.info.get("icc_profile")
|
assert not im1.info.get("icc_profile")
|
||||||
assert im2.info.get("icc_profile")
|
assert im2.info.get("icc_profile")
|
||||||
|
|
||||||
def test_icc_big(self):
|
@pytest.mark.parametrize(
|
||||||
|
"n",
|
||||||
|
(
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
65533 - 14, # full JPEG marker block
|
||||||
|
65533 - 14 + 1, # full block plus one byte
|
||||||
|
ImageFile.MAXBLOCK, # full buffer block
|
||||||
|
ImageFile.MAXBLOCK + 1, # full buffer block plus one byte
|
||||||
|
ImageFile.MAXBLOCK * 4 + 3, # large block
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_icc_big(self, n):
|
||||||
# Make sure that the "extra" support handles large blocks
|
# Make sure that the "extra" support handles large blocks
|
||||||
def test(n):
|
|
||||||
# The ICC APP marker can store 65519 bytes per marker, so
|
# The ICC APP marker can store 65519 bytes per marker, so
|
||||||
# using a 4-byte test code should allow us to detect out of
|
# using a 4-byte test code should allow us to detect out of
|
||||||
# order issues.
|
# order issues.
|
||||||
|
@ -161,17 +175,6 @@ class TestFileJpeg:
|
||||||
im1 = self.roundtrip(hopper(), icc_profile=icc_profile)
|
im1 = self.roundtrip(hopper(), icc_profile=icc_profile)
|
||||||
assert im1.info.get("icc_profile") == (icc_profile or None)
|
assert im1.info.get("icc_profile") == (icc_profile or None)
|
||||||
|
|
||||||
test(0)
|
|
||||||
test(1)
|
|
||||||
test(3)
|
|
||||||
test(4)
|
|
||||||
test(5)
|
|
||||||
test(65533 - 14) # full JPEG marker block
|
|
||||||
test(65533 - 14 + 1) # full block plus one byte
|
|
||||||
test(ImageFile.MAXBLOCK) # full buffer block
|
|
||||||
test(ImageFile.MAXBLOCK + 1) # full buffer block plus one byte
|
|
||||||
test(ImageFile.MAXBLOCK * 4 + 3) # large block
|
|
||||||
|
|
||||||
@mark_if_feature_version(
|
@mark_if_feature_version(
|
||||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||||
)
|
)
|
||||||
|
@ -649,9 +652,9 @@ class TestFileJpeg:
|
||||||
# Assert
|
# Assert
|
||||||
assert im.format == "JPEG"
|
assert im.format == "JPEG"
|
||||||
|
|
||||||
def test_save_correct_modes(self):
|
@pytest.mark.parametrize("mode", ("1", "L", "RGB", "RGBX", "CMYK", "YCbCr"))
|
||||||
|
def test_save_correct_modes(self, mode):
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
for mode in ["1", "L", "RGB", "RGBX", "CMYK", "YCbCr"]:
|
|
||||||
img = Image.new(mode, (20, 20))
|
img = Image.new(mode, (20, 20))
|
||||||
img.save(out, "JPEG")
|
img.save(out, "JPEG")
|
||||||
|
|
||||||
|
|
|
@ -126,8 +126,8 @@ def test_prog_res_rt():
|
||||||
assert_image_equal(im, test_card)
|
assert_image_equal(im, test_card)
|
||||||
|
|
||||||
|
|
||||||
def test_default_num_resolutions():
|
@pytest.mark.parametrize("num_resolutions", range(2, 6))
|
||||||
for num_resolutions in range(2, 6):
|
def test_default_num_resolutions(num_resolutions):
|
||||||
d = 1 << (num_resolutions - 1)
|
d = 1 << (num_resolutions - 1)
|
||||||
im = test_card.resize((d - 1, d - 1))
|
im = test_card.resize((d - 1, d - 1))
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
|
@ -266,14 +266,11 @@ def test_rgba():
|
||||||
assert jp2.mode == "RGBA"
|
assert jp2.mode == "RGBA"
|
||||||
|
|
||||||
|
|
||||||
def test_16bit_monochrome_has_correct_mode():
|
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
|
||||||
with Image.open("Tests/images/16bit.cropped.j2k") as j2k:
|
def test_16bit_monochrome_has_correct_mode(ext):
|
||||||
j2k.load()
|
with Image.open("Tests/images/16bit.cropped" + ext) as im:
|
||||||
assert j2k.mode == "I;16"
|
im.load()
|
||||||
|
assert im.mode == "I;16"
|
||||||
with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
|
|
||||||
jp2.load()
|
|
||||||
assert jp2.mode == "I;16"
|
|
||||||
|
|
||||||
|
|
||||||
def test_16bit_monochrome_jp2_like_tiff():
|
def test_16bit_monochrome_jp2_like_tiff():
|
||||||
|
|
|
@ -135,9 +135,9 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
||||||
|
|
||||||
def test_write_metadata(self, tmp_path):
|
@pytest.mark.parametrize("legacy_api", (False, True))
|
||||||
|
def test_write_metadata(self, legacy_api, tmp_path):
|
||||||
"""Test metadata writing through libtiff"""
|
"""Test metadata writing through libtiff"""
|
||||||
for legacy_api in [False, True]:
|
|
||||||
f = str(tmp_path / "temp.tiff")
|
f = str(tmp_path / "temp.tiff")
|
||||||
with Image.open("Tests/images/hopper_g4.tif") as img:
|
with Image.open("Tests/images/hopper_g4.tif") as img:
|
||||||
img.save(f, tiffinfo=img.tag)
|
img.save(f, tiffinfo=img.tag)
|
||||||
|
@ -509,20 +509,13 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
# colormap/palette tag
|
# colormap/palette tag
|
||||||
assert len(reloaded.tag_v2[320]) == 768
|
assert len(reloaded.tag_v2[320]) == 768
|
||||||
|
|
||||||
def xtest_bw_compression_w_rgb(self, tmp_path):
|
@pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4"))
|
||||||
"""This test passes, but when running all tests causes a failure due
|
def test_bw_compression_w_rgb(self, compression, tmp_path):
|
||||||
to output on stderr from the error thrown by libtiff. We need to
|
|
||||||
capture that but not now"""
|
|
||||||
|
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
out = str(tmp_path / "temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
im.save(out, compression="tiff_ccitt")
|
im.save(out, compression=compression)
|
||||||
with pytest.raises(OSError):
|
|
||||||
im.save(out, compression="group3")
|
|
||||||
with pytest.raises(OSError):
|
|
||||||
im.save(out, compression="group4")
|
|
||||||
|
|
||||||
def test_fp_leak(self):
|
def test_fp_leak(self):
|
||||||
im = Image.open("Tests/images/hopper_g4_500.tif")
|
im = Image.open("Tests/images/hopper_g4_500.tif")
|
||||||
|
@ -856,7 +849,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
def test_strip_ycbcr_jpeg_2x2_sampling(self):
|
def test_strip_ycbcr_jpeg_2x2_sampling(self):
|
||||||
infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
|
infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
|
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.2)
|
||||||
|
|
||||||
@mark_if_feature_version(
|
@mark_if_feature_version(
|
||||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||||
|
@ -864,7 +857,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
def test_strip_ycbcr_jpeg_1x1_sampling(self):
|
def test_strip_ycbcr_jpeg_1x1_sampling(self):
|
||||||
infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
|
infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/flower2.jpg")
|
assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01)
|
||||||
|
|
||||||
def test_tiled_cmyk_jpeg(self):
|
def test_tiled_cmyk_jpeg(self):
|
||||||
infile = "Tests/images/tiff_tiled_cmyk_jpeg.tif"
|
infile = "Tests/images/tiff_tiled_cmyk_jpeg.tif"
|
||||||
|
@ -877,7 +870,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
def test_tiled_ycbcr_jpeg_1x1_sampling(self):
|
def test_tiled_ycbcr_jpeg_1x1_sampling(self):
|
||||||
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
|
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/flower2.jpg")
|
assert_image_similar_tofile(im, "Tests/images/flower2.jpg", 0.01)
|
||||||
|
|
||||||
@mark_if_feature_version(
|
@mark_if_feature_version(
|
||||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||||
|
@ -885,7 +878,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
def test_tiled_ycbcr_jpeg_2x2_sampling(self):
|
def test_tiled_ycbcr_jpeg_2x2_sampling(self):
|
||||||
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
|
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
|
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 1.5)
|
||||||
|
|
||||||
def test_strip_planar_rgb(self):
|
def test_strip_planar_rgb(self):
|
||||||
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
|
# gdal_translate -co TILED=no -co INTERLEAVE=BAND -co COMPRESS=LZW \
|
||||||
|
@ -1011,14 +1004,18 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
# Assert that there are multiple strips
|
# Assert that there are multiple strips
|
||||||
assert len(im.tag_v2[STRIPOFFSETS]) > 1
|
assert len(im.tag_v2[STRIPOFFSETS]) > 1
|
||||||
|
|
||||||
def test_save_single_strip(self, tmp_path):
|
@pytest.mark.parametrize("argument", (True, False))
|
||||||
|
def test_save_single_strip(self, argument, tmp_path):
|
||||||
im = hopper("RGB").resize((256, 256))
|
im = hopper("RGB").resize((256, 256))
|
||||||
out = str(tmp_path / "temp.tif")
|
out = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
|
if not argument:
|
||||||
TiffImagePlugin.STRIP_SIZE = 2**18
|
TiffImagePlugin.STRIP_SIZE = 2**18
|
||||||
try:
|
try:
|
||||||
|
arguments = {"compression": "tiff_adobe_deflate"}
|
||||||
im.save(out, compression="tiff_adobe_deflate")
|
if argument:
|
||||||
|
arguments["strip_size"] = 2**18
|
||||||
|
im.save(out, **arguments)
|
||||||
|
|
||||||
with Image.open(out) as im:
|
with Image.open(out) as im:
|
||||||
assert len(im.tag_v2[STRIPOFFSETS]) == 1
|
assert len(im.tag_v2[STRIPOFFSETS]) == 1
|
||||||
|
|
|
@ -5,15 +5,19 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import assert_image_similar, is_pypy, skip_unless_feature
|
from .helper import (
|
||||||
|
assert_image_equal,
|
||||||
|
assert_image_similar,
|
||||||
|
is_pypy,
|
||||||
|
skip_unless_feature,
|
||||||
|
)
|
||||||
|
|
||||||
test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"]
|
test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"]
|
||||||
|
|
||||||
pytestmark = skip_unless_feature("jpg")
|
pytestmark = skip_unless_feature("jpg")
|
||||||
|
|
||||||
|
|
||||||
def frame_roundtrip(im, **options):
|
def roundtrip(im, **options):
|
||||||
# Note that for now, there is no MPO saving functionality
|
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
im.save(out, "MPO", **options)
|
im.save(out, "MPO", **options)
|
||||||
test_bytes = out.tell()
|
test_bytes = out.tell()
|
||||||
|
@ -23,8 +27,8 @@ def frame_roundtrip(im, **options):
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
def test_sanity():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_sanity(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im.load()
|
im.load()
|
||||||
assert im.mode == "RGB"
|
assert im.mode == "RGB"
|
||||||
|
@ -62,21 +66,20 @@ def test_context_manager():
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
def test_app():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_app(test_file):
|
||||||
# Test APP/COM reader (@PIL135)
|
# Test APP/COM reader (@PIL135)
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert im.applist[0][0] == "APP1"
|
assert im.applist[0][0] == "APP1"
|
||||||
assert im.applist[1][0] == "APP2"
|
assert im.applist[1][0] == "APP2"
|
||||||
assert (
|
assert (
|
||||||
im.applist[1][1][:16]
|
im.applist[1][1][:16] == b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00"
|
||||||
== b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00"
|
|
||||||
)
|
)
|
||||||
assert len(im.applist) == 2
|
assert len(im.applist) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_exif():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_exif(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
info = im._getexif()
|
info = im._getexif()
|
||||||
assert info[272] == "Nintendo 3DS"
|
assert info[272] == "Nintendo 3DS"
|
||||||
|
@ -133,8 +136,8 @@ def test_reload_exif_after_seek():
|
||||||
assert 296 in exif
|
assert 296 in exif
|
||||||
|
|
||||||
|
|
||||||
def test_mp():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_mp(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
mpinfo = im._getmp()
|
mpinfo = im._getmp()
|
||||||
assert mpinfo[45056] == b"0100"
|
assert mpinfo[45056] == b"0100"
|
||||||
|
@ -158,8 +161,8 @@ def test_mp_no_data():
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
|
|
||||||
|
|
||||||
def test_mp_attribute():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_mp_attribute(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
mpinfo = im._getmp()
|
mpinfo = im._getmp()
|
||||||
frame_number = 0
|
frame_number = 0
|
||||||
|
@ -177,8 +180,8 @@ def test_mp_attribute():
|
||||||
frame_number += 1
|
frame_number += 1
|
||||||
|
|
||||||
|
|
||||||
def test_seek():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_seek(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert im.tell() == 0
|
assert im.tell() == 0
|
||||||
# prior to first image raises an error, both blatant and borderline
|
# prior to first image raises an error, both blatant and borderline
|
||||||
|
@ -221,8 +224,8 @@ def test_eoferror():
|
||||||
im.seek(n_frames - 1)
|
im.seek(n_frames - 1)
|
||||||
|
|
||||||
|
|
||||||
def test_image_grab():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_image_grab(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert im.tell() == 0
|
assert im.tell() == 0
|
||||||
im0 = im.tobytes()
|
im0 = im.tobytes()
|
||||||
|
@ -236,14 +239,39 @@ def test_image_grab():
|
||||||
assert im0 != im1
|
assert im0 != im1
|
||||||
|
|
||||||
|
|
||||||
def test_save():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
# Note that only individual frames can be saved at present
|
def test_save(test_file):
|
||||||
for test_file in test_files:
|
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert im.tell() == 0
|
assert im.tell() == 0
|
||||||
jpg0 = frame_roundtrip(im)
|
jpg0 = roundtrip(im)
|
||||||
assert_image_similar(im, jpg0, 30)
|
assert_image_similar(im, jpg0, 30)
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.tell() == 1
|
assert im.tell() == 1
|
||||||
jpg1 = frame_roundtrip(im)
|
jpg1 = roundtrip(im)
|
||||||
assert_image_similar(im, jpg1, 30)
|
assert_image_similar(im, jpg1, 30)
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_all():
|
||||||
|
for test_file in test_files:
|
||||||
|
with Image.open(test_file) as im:
|
||||||
|
im_reloaded = roundtrip(im, save_all=True)
|
||||||
|
|
||||||
|
im.seek(0)
|
||||||
|
assert_image_similar(im, im_reloaded, 30)
|
||||||
|
|
||||||
|
im.seek(1)
|
||||||
|
im_reloaded.seek(1)
|
||||||
|
assert_image_similar(im, im_reloaded, 30)
|
||||||
|
|
||||||
|
im = Image.new("RGB", (1, 1))
|
||||||
|
im2 = Image.new("RGB", (1, 1), "#f00")
|
||||||
|
im_reloaded = roundtrip(im, save_all=True, append_images=[im2])
|
||||||
|
|
||||||
|
assert_image_equal(im, im_reloaded)
|
||||||
|
|
||||||
|
im_reloaded.seek(1)
|
||||||
|
assert_image_similar(im2, im_reloaded, 1)
|
||||||
|
|
||||||
|
# Test that a single frame image will not be saved as an MPO
|
||||||
|
jpg = roundtrip(im, save_all=True)
|
||||||
|
assert "mp" not in jpg.info
|
||||||
|
|
|
@ -63,19 +63,7 @@ def test_p_mode(tmp_path):
|
||||||
roundtrip(tmp_path, mode)
|
roundtrip(tmp_path, mode)
|
||||||
|
|
||||||
|
|
||||||
def test_l_oserror(tmp_path):
|
@pytest.mark.parametrize("mode", ("L", "RGB"))
|
||||||
# Arrange
|
def test_oserror(tmp_path, mode):
|
||||||
mode = "L"
|
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
with pytest.raises(OSError):
|
|
||||||
helper_save_as_palm(tmp_path, mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_rgb_oserror(tmp_path):
|
|
||||||
# Arrange
|
|
||||||
mode = "RGB"
|
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
helper_save_as_palm(tmp_path, mode)
|
helper_save_as_palm(tmp_path, mode)
|
||||||
|
|
|
@ -20,6 +20,11 @@ def test_sanity(tmp_path):
|
||||||
for mode in ("1", "L", "P", "RGB"):
|
for mode in ("1", "L", "P", "RGB"):
|
||||||
_roundtrip(tmp_path, hopper(mode))
|
_roundtrip(tmp_path, hopper(mode))
|
||||||
|
|
||||||
|
# Test a palette with less than 256 colors
|
||||||
|
im = Image.new("P", (1, 1))
|
||||||
|
im.putpalette((255, 0, 0))
|
||||||
|
_roundtrip(tmp_path, im)
|
||||||
|
|
||||||
# Test an unsupported mode
|
# Test an unsupported mode
|
||||||
f = str(tmp_path / "temp.pcx")
|
f = str(tmp_path / "temp.pcx")
|
||||||
im = hopper("RGBA")
|
im = hopper("RGBA")
|
||||||
|
@ -34,11 +39,11 @@ def test_invalid_file():
|
||||||
PcxImagePlugin.PcxImageFile(invalid_file)
|
PcxImagePlugin.PcxImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
def test_odd(tmp_path):
|
@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB"))
|
||||||
|
def test_odd(tmp_path, mode):
|
||||||
# See issue #523, odd sized images should have a stride that's even.
|
# See issue #523, odd sized images should have a stride that's even.
|
||||||
# Not that ImageMagick or GIMP write PCX that way.
|
# Not that ImageMagick or GIMP write PCX that way.
|
||||||
# We were not handling properly.
|
# We were not handling properly.
|
||||||
for mode in ("1", "L", "P", "RGB"):
|
|
||||||
# larger, odd sized images are better here to ensure that
|
# larger, odd sized images are better here to ensure that
|
||||||
# we handle interrupted scan lines properly.
|
# we handle interrupted scan lines properly.
|
||||||
_roundtrip(tmp_path, hopper(mode).resize((511, 511)))
|
_roundtrip(tmp_path, hopper(mode).resize((511, 511)))
|
||||||
|
|
|
@ -6,7 +6,7 @@ import time
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, PdfParser
|
from PIL import Image, PdfParser, features
|
||||||
|
|
||||||
from .helper import hopper, mark_if_feature_version
|
from .helper import hopper, mark_if_feature_version
|
||||||
|
|
||||||
|
@ -37,45 +37,19 @@ def helper_save_as_pdf(tmp_path, mode, **kwargs):
|
||||||
return outfile
|
return outfile
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", ("L", "P", "RGB", "CMYK"))
|
||||||
|
def test_save(tmp_path, mode):
|
||||||
|
helper_save_as_pdf(tmp_path, mode)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.valgrind_known_error(reason="Temporary skip")
|
||||||
def test_monochrome(tmp_path):
|
def test_monochrome(tmp_path):
|
||||||
# Arrange
|
# Arrange
|
||||||
mode = "1"
|
mode = "1"
|
||||||
|
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
outfile = helper_save_as_pdf(tmp_path, mode)
|
outfile = helper_save_as_pdf(tmp_path, mode)
|
||||||
assert os.path.getsize(outfile) < 15000
|
assert os.path.getsize(outfile) < (5000 if features.check("libtiff") else 15000)
|
||||||
|
|
||||||
|
|
||||||
def test_greyscale(tmp_path):
|
|
||||||
# Arrange
|
|
||||||
mode = "L"
|
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
helper_save_as_pdf(tmp_path, mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_rgb(tmp_path):
|
|
||||||
# Arrange
|
|
||||||
mode = "RGB"
|
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
helper_save_as_pdf(tmp_path, mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_p_mode(tmp_path):
|
|
||||||
# Arrange
|
|
||||||
mode = "P"
|
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
helper_save_as_pdf(tmp_path, mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_cmyk_mode(tmp_path):
|
|
||||||
# Arrange
|
|
||||||
mode = "CMYK"
|
|
||||||
|
|
||||||
# Act / Assert
|
|
||||||
helper_save_as_pdf(tmp_path, mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_unsupported_mode(tmp_path):
|
def test_unsupported_mode(tmp_path):
|
||||||
|
|
|
@ -643,7 +643,9 @@ class TestFilePng:
|
||||||
|
|
||||||
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
|
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
|
||||||
|
|
||||||
@pytest.mark.parametrize("cid", (b"IHDR", b"pHYs", b"acTL", b"fcTL", b"fdAT"))
|
@pytest.mark.parametrize(
|
||||||
|
"cid", (b"IHDR", b"sRGB", b"pHYs", b"acTL", b"fcTL", b"fdAT")
|
||||||
|
)
|
||||||
def test_truncated_chunks(self, cid):
|
def test_truncated_chunks(self, cid):
|
||||||
fp = BytesIO()
|
fp = BytesIO()
|
||||||
with PngImagePlugin.PngStream(fp) as png:
|
with PngImagePlugin.PngStream(fp) as png:
|
||||||
|
|
|
@ -4,7 +4,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, PsdImagePlugin
|
from PIL import Image, PsdImagePlugin
|
||||||
|
|
||||||
from .helper import assert_image_similar, hopper, is_pypy
|
from .helper import assert_image_equal_tofile, assert_image_similar, hopper, is_pypy
|
||||||
|
|
||||||
test_file = "Tests/images/hopper.psd"
|
test_file = "Tests/images/hopper.psd"
|
||||||
|
|
||||||
|
@ -107,6 +107,11 @@ def test_open_after_exclusive_load():
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
|
def test_rgba():
|
||||||
|
with Image.open("Tests/images/rgba.psd") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_square.png")
|
||||||
|
|
||||||
|
|
||||||
def test_icc_profile():
|
def test_icc_profile():
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert "icc_profile" in im.info
|
assert "icc_profile" in im.info
|
||||||
|
|
|
@ -18,18 +18,15 @@ _ORIGINS = ("tl", "bl")
|
||||||
_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
|
_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
|
||||||
|
|
||||||
|
|
||||||
def test_sanity(tmp_path):
|
@pytest.mark.parametrize("mode", _MODES)
|
||||||
for mode in _MODES:
|
def test_sanity(mode, tmp_path):
|
||||||
|
|
||||||
def roundtrip(original_im):
|
def roundtrip(original_im):
|
||||||
out = str(tmp_path / "temp.tga")
|
out = str(tmp_path / "temp.tga")
|
||||||
|
|
||||||
original_im.save(out, rle=rle)
|
original_im.save(out, rle=rle)
|
||||||
with Image.open(out) as saved_im:
|
with Image.open(out) as saved_im:
|
||||||
if rle:
|
if rle:
|
||||||
assert (
|
assert saved_im.info["compression"] == original_im.info["compression"]
|
||||||
saved_im.info["compression"] == original_im.info["compression"]
|
|
||||||
)
|
|
||||||
assert saved_im.info["orientation"] == original_im.info["orientation"]
|
assert saved_im.info["orientation"] == original_im.info["orientation"]
|
||||||
if mode == "P":
|
if mode == "P":
|
||||||
assert saved_im.getpalette() == original_im.getpalette()
|
assert saved_im.getpalette() == original_im.getpalette()
|
||||||
|
@ -123,6 +120,18 @@ def test_save(tmp_path):
|
||||||
assert test_im.size == (100, 100)
|
assert test_im.size == (100, 100)
|
||||||
|
|
||||||
|
|
||||||
|
def test_small_palette(tmp_path):
|
||||||
|
im = Image.new("P", (1, 1))
|
||||||
|
colors = [0, 0, 0]
|
||||||
|
im.putpalette(colors)
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.tga")
|
||||||
|
im.save(out)
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.getpalette() == colors
|
||||||
|
|
||||||
|
|
||||||
def test_save_wrong_mode(tmp_path):
|
def test_save_wrong_mode(tmp_path):
|
||||||
im = hopper("PA")
|
im = hopper("PA")
|
||||||
out = str(tmp_path / "temp.tga")
|
out = str(tmp_path / "temp.tga")
|
||||||
|
|
|
@ -84,6 +84,24 @@ class TestFileTiff:
|
||||||
with Image.open("Tests/images/multipage.tiff") as im:
|
with Image.open("Tests/images/multipage.tiff") as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path, sizes",
|
||||||
|
(
|
||||||
|
("Tests/images/hopper.tif", ()),
|
||||||
|
("Tests/images/child_ifd.tiff", (16, 8)),
|
||||||
|
("Tests/images/child_ifd_jpeg.tiff", (20,)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_get_child_images(self, path, sizes):
|
||||||
|
with Image.open(path) as im:
|
||||||
|
ims = im.get_child_images()
|
||||||
|
|
||||||
|
assert len(ims) == len(sizes)
|
||||||
|
for i, im in enumerate(ims):
|
||||||
|
w = sizes[i]
|
||||||
|
expected = Image.new("RGB", (w, w), "#f00")
|
||||||
|
assert_image_similar(im, expected, 1)
|
||||||
|
|
||||||
def test_mac_tiff(self):
|
def test_mac_tiff(self):
|
||||||
# Read RGBa images from macOS [@PIL136]
|
# Read RGBa images from macOS [@PIL136]
|
||||||
|
|
||||||
|
@ -293,11 +311,14 @@ class TestFileTiff:
|
||||||
with Image.open("Tests/images/hopper_unknown_pixel_mode.tif"):
|
with Image.open("Tests/images/hopper_unknown_pixel_mode.tif"):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_n_frames(self):
|
@pytest.mark.parametrize(
|
||||||
for path, n_frames in [
|
"path, n_frames",
|
||||||
["Tests/images/multipage-lastframe.tif", 1],
|
(
|
||||||
["Tests/images/multipage.tiff", 3],
|
("Tests/images/multipage-lastframe.tif", 1),
|
||||||
]:
|
("Tests/images/multipage.tiff", 3),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_n_frames(self, path, n_frames):
|
||||||
with Image.open(path) as im:
|
with Image.open(path) as im:
|
||||||
assert im.n_frames == n_frames
|
assert im.n_frames == n_frames
|
||||||
assert im.is_animated == (n_frames != 1)
|
assert im.is_animated == (n_frames != 1)
|
||||||
|
@ -416,8 +437,8 @@ class TestFileTiff:
|
||||||
len_after = len(dict(im.ifd))
|
len_after = len(dict(im.ifd))
|
||||||
assert len_before == len_after + 1
|
assert len_before == len_after + 1
|
||||||
|
|
||||||
def test_load_byte(self):
|
@pytest.mark.parametrize("legacy_api", (False, True))
|
||||||
for legacy_api in [False, True]:
|
def test_load_byte(self, legacy_api):
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
data = b"abc"
|
data = b"abc"
|
||||||
ret = ifd.load_byte(data, legacy_api)
|
ret = ifd.load_byte(data, legacy_api)
|
||||||
|
@ -667,8 +688,8 @@ class TestFileTiff:
|
||||||
with Image.open(outfile) as reloaded:
|
with Image.open(outfile) as reloaded:
|
||||||
assert_image_equal_tofile(reloaded, infile)
|
assert_image_equal_tofile(reloaded, infile)
|
||||||
|
|
||||||
def test_palette(self, tmp_path):
|
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||||
def roundtrip(mode):
|
def test_palette(self, mode, tmp_path):
|
||||||
outfile = str(tmp_path / "temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
|
@ -677,9 +698,6 @@ class TestFileTiff:
|
||||||
with Image.open(outfile) as reloaded:
|
with Image.open(outfile) as reloaded:
|
||||||
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
|
||||||
|
|
||||||
for mode in ["P", "PA"]:
|
|
||||||
roundtrip(mode)
|
|
||||||
|
|
||||||
def test_tiff_save_all(self):
|
def test_tiff_save_all(self):
|
||||||
mp = BytesIO()
|
mp = BytesIO()
|
||||||
with Image.open("Tests/images/multipage.tiff") as im:
|
with Image.open("Tests/images/multipage.tiff") as im:
|
||||||
|
|
|
@ -185,6 +185,22 @@ def test_iptc(tmp_path):
|
||||||
im.save(out)
|
im.save(out)
|
||||||
|
|
||||||
|
|
||||||
|
def test_writing_bytes_to_ascii(tmp_path):
|
||||||
|
im = hopper()
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
|
|
||||||
|
tag = TiffTags.TAGS_V2[271]
|
||||||
|
assert tag.type == TiffTags.ASCII
|
||||||
|
|
||||||
|
info[271] = b"test"
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.tiff")
|
||||||
|
im.save(out, tiffinfo=info)
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.tag_v2[271] == "test"
|
||||||
|
|
||||||
|
|
||||||
def test_undefined_zero(tmp_path):
|
def test_undefined_zero(tmp_path):
|
||||||
# Check that the tag has not been changed since this test was created
|
# Check that the tag has not been changed since this test was created
|
||||||
tag = TiffTags.TAGS_V2[45059]
|
tag = TiffTags.TAGS_V2[45059]
|
||||||
|
|
|
@ -66,10 +66,10 @@ def test_load_set_dpi():
|
||||||
assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1)
|
assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1)
|
||||||
|
|
||||||
|
|
||||||
def test_save(tmp_path):
|
@pytest.mark.parametrize("ext", (".wmf", ".emf"))
|
||||||
|
def test_save(ext, tmp_path):
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
|
||||||
for ext in [".wmf", ".emf"]:
|
|
||||||
tmpfile = str(tmp_path / ("temp" + ext))
|
tmpfile = str(tmp_path / ("temp" + ext))
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
im.save(tmpfile)
|
im.save(tmpfile)
|
||||||
|
|
|
@ -49,6 +49,14 @@ def test_sanity(request, tmp_path):
|
||||||
save_font(request, tmp_path)
|
save_font(request, tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_less_than_256_characters():
|
||||||
|
with open("Tests/fonts/10x20-ISO8859-1-fewer-characters.pcf", "rb") as test_file:
|
||||||
|
font = PcfFontFile.PcfFontFile(test_file)
|
||||||
|
assert isinstance(font, FontFile.FontFile)
|
||||||
|
# check the number of characters in the font
|
||||||
|
assert len([_f for _f in font.glyph if _f]) == 127
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file():
|
def test_invalid_file():
|
||||||
with open("Tests/images/flower.jpg", "rb") as fp:
|
with open("Tests/images/flower.jpg", "rb") as fp:
|
||||||
with pytest.raises(SyntaxError):
|
with pytest.raises(SyntaxError):
|
||||||
|
@ -68,12 +76,19 @@ def test_textsize(request, tmp_path):
|
||||||
tempname = save_font(request, tmp_path)
|
tempname = save_font(request, tmp_path)
|
||||||
font = ImageFont.load(tempname)
|
font = ImageFont.load(tempname)
|
||||||
for i in range(255):
|
for i in range(255):
|
||||||
(dx, dy) = font.getsize(chr(i))
|
(ox, oy, dx, dy) = font.getbbox(chr(i))
|
||||||
|
assert ox == 0
|
||||||
|
assert oy == 0
|
||||||
assert dy == 20
|
assert dy == 20
|
||||||
assert dx in (0, 10)
|
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)):
|
for i in range(len(message)):
|
||||||
msg = message[: i + 1]
|
msg = message[: i + 1]
|
||||||
assert font.getsize(msg) == (len(msg) * 10, 20)
|
assert font.getlength(msg) == len(msg) * 10
|
||||||
|
assert font.getbbox(msg) == (0, 0, len(msg) * 10, 20)
|
||||||
|
|
||||||
|
|
||||||
def _test_high_characters(request, tmp_path, message):
|
def _test_high_characters(request, tmp_path, message):
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile
|
from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
|
@ -59,23 +61,13 @@ def save_font(request, tmp_path, encoding):
|
||||||
return tempname
|
return tempname
|
||||||
|
|
||||||
|
|
||||||
def _test_sanity(request, tmp_path, encoding):
|
@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250"))
|
||||||
|
def test_sanity(request, tmp_path, encoding):
|
||||||
save_font(request, tmp_path, encoding)
|
save_font(request, tmp_path, encoding)
|
||||||
|
|
||||||
|
|
||||||
def test_sanity_iso8859_1(request, tmp_path):
|
@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250"))
|
||||||
_test_sanity(request, tmp_path, "iso8859-1")
|
def test_draw(request, tmp_path, encoding):
|
||||||
|
|
||||||
|
|
||||||
def test_sanity_iso8859_2(request, tmp_path):
|
|
||||||
_test_sanity(request, tmp_path, "iso8859-2")
|
|
||||||
|
|
||||||
|
|
||||||
def test_sanity_cp1250(request, tmp_path):
|
|
||||||
_test_sanity(request, tmp_path, "cp1250")
|
|
||||||
|
|
||||||
|
|
||||||
def _test_draw(request, tmp_path, encoding):
|
|
||||||
tempname = save_font(request, tmp_path, encoding)
|
tempname = save_font(request, tmp_path, encoding)
|
||||||
font = ImageFont.load(tempname)
|
font = ImageFont.load(tempname)
|
||||||
im = Image.new("L", (150, 30), "white")
|
im = Image.new("L", (150, 30), "white")
|
||||||
|
@ -85,38 +77,19 @@ def _test_draw(request, tmp_path, encoding):
|
||||||
assert_image_similar_tofile(im, charsets[encoding]["image1"], 0)
|
assert_image_similar_tofile(im, charsets[encoding]["image1"], 0)
|
||||||
|
|
||||||
|
|
||||||
def test_draw_iso8859_1(request, tmp_path):
|
@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250"))
|
||||||
_test_draw(request, tmp_path, "iso8859-1")
|
def test_textsize(request, tmp_path, encoding):
|
||||||
|
|
||||||
|
|
||||||
def test_draw_iso8859_2(request, tmp_path):
|
|
||||||
_test_draw(request, tmp_path, "iso8859-2")
|
|
||||||
|
|
||||||
|
|
||||||
def test_draw_cp1250(request, tmp_path):
|
|
||||||
_test_draw(request, tmp_path, "cp1250")
|
|
||||||
|
|
||||||
|
|
||||||
def _test_textsize(request, tmp_path, encoding):
|
|
||||||
tempname = save_font(request, tmp_path, encoding)
|
tempname = save_font(request, tmp_path, encoding)
|
||||||
font = ImageFont.load(tempname)
|
font = ImageFont.load(tempname)
|
||||||
for i in range(255):
|
for i in range(255):
|
||||||
(dx, dy) = font.getsize(bytearray([i]))
|
(ox, oy, dx, dy) = font.getbbox(bytearray([i]))
|
||||||
|
assert ox == 0
|
||||||
|
assert oy == 0
|
||||||
assert dy == 20
|
assert dy == 20
|
||||||
assert dx in (0, 10)
|
assert dx in (0, 10)
|
||||||
|
assert font.getlength(bytearray([i])) == dx
|
||||||
message = charsets[encoding]["message"].encode(encoding)
|
message = charsets[encoding]["message"].encode(encoding)
|
||||||
for i in range(len(message)):
|
for i in range(len(message)):
|
||||||
msg = message[: i + 1]
|
msg = message[: i + 1]
|
||||||
assert font.getsize(msg) == (len(msg) * 10, 20)
|
assert font.getlength(msg) == len(msg) * 10
|
||||||
|
assert font.getbbox(msg) == (0, 0, len(msg) * 10, 20)
|
||||||
|
|
||||||
def test_textsize_iso8859_1(request, tmp_path):
|
|
||||||
_test_textsize(request, tmp_path, "iso8859-1")
|
|
||||||
|
|
||||||
|
|
||||||
def test_textsize_iso8859_2(request, tmp_path):
|
|
||||||
_test_textsize(request, tmp_path, "iso8859-2")
|
|
||||||
|
|
||||||
|
|
||||||
def test_textsize_cp1250(request, tmp_path):
|
|
||||||
_test_textsize(request, tmp_path, "cp1250")
|
|
||||||
|
|
|
@ -29,8 +29,9 @@ from .helper import (
|
||||||
|
|
||||||
|
|
||||||
class TestImage:
|
class TestImage:
|
||||||
def test_image_modes_success(self):
|
@pytest.mark.parametrize(
|
||||||
for mode in [
|
"mode",
|
||||||
|
(
|
||||||
"1",
|
"1",
|
||||||
"P",
|
"P",
|
||||||
"PA",
|
"PA",
|
||||||
|
@ -51,19 +52,15 @@ class TestImage:
|
||||||
"YCbCr",
|
"YCbCr",
|
||||||
"LAB",
|
"LAB",
|
||||||
"HSV",
|
"HSV",
|
||||||
]:
|
),
|
||||||
|
)
|
||||||
|
def test_image_modes_success(self, mode):
|
||||||
Image.new(mode, (1, 1))
|
Image.new(mode, (1, 1))
|
||||||
|
|
||||||
def test_image_modes_fail(self):
|
@pytest.mark.parametrize(
|
||||||
for mode in [
|
"mode", ("", "bad", "very very long", "BGR;15", "BGR;16", "BGR;24", "BGR;32")
|
||||||
"",
|
)
|
||||||
"bad",
|
def test_image_modes_fail(self, mode):
|
||||||
"very very long",
|
|
||||||
"BGR;15",
|
|
||||||
"BGR;16",
|
|
||||||
"BGR;24",
|
|
||||||
"BGR;32",
|
|
||||||
]:
|
|
||||||
with pytest.raises(ValueError) as e:
|
with pytest.raises(ValueError) as e:
|
||||||
Image.new(mode, (1, 1))
|
Image.new(mode, (1, 1))
|
||||||
assert str(e.value) == "unrecognized image mode"
|
assert str(e.value) == "unrecognized image mode"
|
||||||
|
@ -605,11 +602,10 @@ class TestImage:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Image.linear_gradient(wrong_mode)
|
Image.linear_gradient(wrong_mode)
|
||||||
|
|
||||||
def test_linear_gradient(self):
|
@pytest.mark.parametrize("mode", ("L", "P", "I", "F"))
|
||||||
|
def test_linear_gradient(self, mode):
|
||||||
# Arrange
|
# Arrange
|
||||||
target_file = "Tests/images/linear_gradient.png"
|
target_file = "Tests/images/linear_gradient.png"
|
||||||
for mode in ["L", "P", "I", "F"]:
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
im = Image.linear_gradient(mode)
|
im = Image.linear_gradient(mode)
|
||||||
|
@ -631,11 +627,10 @@ class TestImage:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Image.radial_gradient(wrong_mode)
|
Image.radial_gradient(wrong_mode)
|
||||||
|
|
||||||
def test_radial_gradient(self):
|
@pytest.mark.parametrize("mode", ("L", "P", "I", "F"))
|
||||||
|
def test_radial_gradient(self, mode):
|
||||||
# Arrange
|
# Arrange
|
||||||
target_file = "Tests/images/radial_gradient.png"
|
target_file = "Tests/images/radial_gradient.png"
|
||||||
for mode in ["L", "P", "I", "F"]:
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
im = Image.radial_gradient(mode)
|
im = Image.radial_gradient(mode)
|
||||||
|
@ -691,6 +686,7 @@ class TestImage:
|
||||||
|
|
||||||
im_remapped = im.remap_palette([1, 0])
|
im_remapped = im.remap_palette([1, 0])
|
||||||
assert im_remapped.info["transparency"] == 1
|
assert im_remapped.info["transparency"] == 1
|
||||||
|
assert len(im_remapped.getpalette()) == 6
|
||||||
|
|
||||||
# Test unused transparency
|
# Test unused transparency
|
||||||
im.info["transparency"] = 2
|
im.info["transparency"] = 2
|
||||||
|
|
|
@ -184,8 +184,9 @@ class TestImageGetPixel(AccessTest):
|
||||||
with pytest.raises(error):
|
with pytest.raises(error):
|
||||||
im.getpixel((-1, -1))
|
im.getpixel((-1, -1))
|
||||||
|
|
||||||
def test_basic(self):
|
@pytest.mark.parametrize(
|
||||||
for mode in (
|
"mode",
|
||||||
|
(
|
||||||
"1",
|
"1",
|
||||||
"L",
|
"L",
|
||||||
"LA",
|
"LA",
|
||||||
|
@ -200,23 +201,28 @@ class TestImageGetPixel(AccessTest):
|
||||||
"RGBX",
|
"RGBX",
|
||||||
"CMYK",
|
"CMYK",
|
||||||
"YCbCr",
|
"YCbCr",
|
||||||
):
|
),
|
||||||
|
)
|
||||||
|
def test_basic(self, mode):
|
||||||
self.check(mode)
|
self.check(mode)
|
||||||
|
|
||||||
def test_signedness(self):
|
@pytest.mark.parametrize("mode", ("I;16", "I;16B"))
|
||||||
|
def test_signedness(self, mode):
|
||||||
# see https://github.com/python-pillow/Pillow/issues/452
|
# see https://github.com/python-pillow/Pillow/issues/452
|
||||||
# pixelaccess is using signed int* instead of uint*
|
# pixelaccess is using signed int* instead of uint*
|
||||||
for mode in ("I;16", "I;16B"):
|
|
||||||
self.check(mode, 2**15 - 1)
|
self.check(mode, 2**15 - 1)
|
||||||
self.check(mode, 2**15)
|
self.check(mode, 2**15)
|
||||||
self.check(mode, 2**15 + 1)
|
self.check(mode, 2**15 + 1)
|
||||||
self.check(mode, 2**16 - 1)
|
self.check(mode, 2**16 - 1)
|
||||||
|
|
||||||
def test_p_putpixel_rgb_rgba(self):
|
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||||
for color in [(255, 0, 0), (255, 0, 0, 255)]:
|
@pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))
|
||||||
im = Image.new("P", (1, 1), 0)
|
def test_p_putpixel_rgb_rgba(self, mode, color):
|
||||||
|
im = Image.new(mode, (1, 1))
|
||||||
im.putpixel((0, 0), color)
|
im.putpixel((0, 0), color)
|
||||||
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
|
|
||||||
|
alpha = color[3] if len(color) == 4 and mode == "PA" else 255
|
||||||
|
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||||
|
@ -337,12 +343,15 @@ class TestCffi(AccessTest):
|
||||||
# pixels can contain garbage if image is released
|
# pixels can contain garbage if image is released
|
||||||
assert px[i, 0] == 0
|
assert px[i, 0] == 0
|
||||||
|
|
||||||
def test_p_putpixel_rgb_rgba(self):
|
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||||
for color in [(255, 0, 0), (255, 0, 0, 255)]:
|
def test_p_putpixel_rgb_rgba(self, mode):
|
||||||
im = Image.new("P", (1, 1), 0)
|
for color in [(255, 0, 0), (255, 0, 0, 127)]:
|
||||||
|
im = Image.new(mode, (1, 1))
|
||||||
access = PyAccess.new(im, False)
|
access = PyAccess.new(im, False)
|
||||||
access.putpixel((0, 0), color)
|
access.putpixel((0, 0), color)
|
||||||
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
|
|
||||||
|
alpha = color[3] if len(color) == 4 and mode == "PA" else 255
|
||||||
|
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)
|
||||||
|
|
||||||
|
|
||||||
class TestImagePutPixelError(AccessTest):
|
class TestImagePutPixelError(AccessTest):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
from packaging.version import parse as parse_version
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ def test_toarray():
|
||||||
test_with_dtype(numpy.float64)
|
test_with_dtype(numpy.float64)
|
||||||
test_with_dtype(numpy.uint8)
|
test_with_dtype(numpy.uint8)
|
||||||
|
|
||||||
|
if parse_version(numpy.__version__) >= parse_version("1.23"):
|
||||||
with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated:
|
with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated:
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
numpy.array(im_truncated)
|
numpy.array(im_truncated)
|
||||||
|
|
|
@ -236,6 +236,12 @@ def test_p2pa_alpha():
|
||||||
assert im_a.getpixel((x, y)) == alpha
|
assert im_a.getpixel((x, y)) == alpha
|
||||||
|
|
||||||
|
|
||||||
|
def test_p2pa_palette():
|
||||||
|
with Image.open("Tests/images/tiny.png") as im:
|
||||||
|
im_pa = im.convert("PA")
|
||||||
|
assert im_pa.getpalette() == im.getpalette()
|
||||||
|
|
||||||
|
|
||||||
def test_matrix_illegal_conversion():
|
def test_matrix_illegal_conversion():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = hopper("CMYK")
|
im = hopper("CMYK")
|
||||||
|
@ -268,8 +274,8 @@ def test_matrix_wrong_mode():
|
||||||
im.convert(mode="L", matrix=matrix)
|
im.convert(mode="L", matrix=matrix)
|
||||||
|
|
||||||
|
|
||||||
def test_matrix_xyz():
|
@pytest.mark.parametrize("mode", ("RGB", "L"))
|
||||||
def matrix_convert(mode):
|
def test_matrix_xyz(mode):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
im.info["transparency"] = (255, 0, 0)
|
im.info["transparency"] = (255, 0, 0)
|
||||||
|
@ -296,9 +302,6 @@ def test_matrix_xyz():
|
||||||
assert_image_similar(converted_im, target.getchannel(0), 1)
|
assert_image_similar(converted_im, target.getchannel(0), 1)
|
||||||
assert converted_im.info["transparency"] == 105
|
assert converted_im.info["transparency"] == 105
|
||||||
|
|
||||||
matrix_convert("RGB")
|
|
||||||
matrix_convert("L")
|
|
||||||
|
|
||||||
|
|
||||||
def test_matrix_identity():
|
def test_matrix_identity():
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import hopper
|
from .helper import hopper
|
||||||
|
|
||||||
|
|
||||||
def test_copy():
|
@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
|
||||||
|
def test_copy(mode):
|
||||||
cropped_coordinates = (10, 10, 20, 20)
|
cropped_coordinates = (10, 10, 20, 20)
|
||||||
cropped_size = (10, 10)
|
cropped_size = (10, 10)
|
||||||
for mode in "1", "P", "L", "RGB", "I", "F":
|
|
||||||
# Internal copy method
|
# Internal copy method
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
out = im.copy()
|
out = im.copy()
|
||||||
|
|
|
@ -5,8 +5,8 @@ from PIL import Image
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
|
|
||||||
def test_crop():
|
@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
|
||||||
def crop(mode):
|
def test_crop(mode):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
assert_image_equal(im.crop(), im)
|
assert_image_equal(im.crop(), im)
|
||||||
|
|
||||||
|
@ -14,9 +14,6 @@ def test_crop():
|
||||||
assert cropped.mode == mode
|
assert cropped.mode == mode
|
||||||
assert cropped.size == (50, 50)
|
assert cropped.size == (50, 50)
|
||||||
|
|
||||||
for mode in "1", "P", "L", "RGB", "I", "F":
|
|
||||||
crop(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_wide_crop():
|
def test_wide_crop():
|
||||||
def crop(*bbox):
|
def crop(*bbox):
|
||||||
|
|
|
@ -9,7 +9,7 @@ def test_entropy():
|
||||||
assert round(abs(entropy("L") - 7.063008716585465), 7) == 0
|
assert round(abs(entropy("L") - 7.063008716585465), 7) == 0
|
||||||
assert round(abs(entropy("I") - 7.063008716585465), 7) == 0
|
assert round(abs(entropy("I") - 7.063008716585465), 7) == 0
|
||||||
assert round(abs(entropy("F") - 7.063008716585465), 7) == 0
|
assert round(abs(entropy("F") - 7.063008716585465), 7) == 0
|
||||||
assert round(abs(entropy("P") - 5.0530452472519745), 7) == 0
|
assert round(abs(entropy("P") - 5.082506854662517), 7) == 0
|
||||||
assert round(abs(entropy("RGB") - 8.821286587714319), 7) == 0
|
assert round(abs(entropy("RGB") - 8.821286587714319), 7) == 0
|
||||||
assert round(abs(entropy("RGBA") - 7.42724306524488), 7) == 0
|
assert round(abs(entropy("RGBA") - 7.42724306524488), 7) == 0
|
||||||
assert round(abs(entropy("CMYK") - 7.4272430652448795), 7) == 0
|
assert round(abs(entropy("CMYK") - 7.4272430652448795), 7) == 0
|
||||||
|
|
|
@ -5,53 +5,62 @@ from PIL import Image, ImageFilter
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
|
|
||||||
def test_sanity():
|
@pytest.mark.parametrize(
|
||||||
def apply_filter(filter_to_apply):
|
"filter_to_apply",
|
||||||
for mode in ["L", "RGB", "CMYK"]:
|
(
|
||||||
|
ImageFilter.BLUR,
|
||||||
|
ImageFilter.CONTOUR,
|
||||||
|
ImageFilter.DETAIL,
|
||||||
|
ImageFilter.EDGE_ENHANCE,
|
||||||
|
ImageFilter.EDGE_ENHANCE_MORE,
|
||||||
|
ImageFilter.EMBOSS,
|
||||||
|
ImageFilter.FIND_EDGES,
|
||||||
|
ImageFilter.SMOOTH,
|
||||||
|
ImageFilter.SMOOTH_MORE,
|
||||||
|
ImageFilter.SHARPEN,
|
||||||
|
ImageFilter.MaxFilter,
|
||||||
|
ImageFilter.MedianFilter,
|
||||||
|
ImageFilter.MinFilter,
|
||||||
|
ImageFilter.ModeFilter,
|
||||||
|
ImageFilter.GaussianBlur,
|
||||||
|
ImageFilter.GaussianBlur(5),
|
||||||
|
ImageFilter.BoxBlur(5),
|
||||||
|
ImageFilter.UnsharpMask,
|
||||||
|
ImageFilter.UnsharpMask(10),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK"))
|
||||||
|
def test_sanity(filter_to_apply, mode):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
out = im.filter(filter_to_apply)
|
out = im.filter(filter_to_apply)
|
||||||
assert out.mode == im.mode
|
assert out.mode == im.mode
|
||||||
assert out.size == im.size
|
assert out.size == im.size
|
||||||
|
|
||||||
apply_filter(ImageFilter.BLUR)
|
|
||||||
apply_filter(ImageFilter.CONTOUR)
|
|
||||||
apply_filter(ImageFilter.DETAIL)
|
|
||||||
apply_filter(ImageFilter.EDGE_ENHANCE)
|
|
||||||
apply_filter(ImageFilter.EDGE_ENHANCE_MORE)
|
|
||||||
apply_filter(ImageFilter.EMBOSS)
|
|
||||||
apply_filter(ImageFilter.FIND_EDGES)
|
|
||||||
apply_filter(ImageFilter.SMOOTH)
|
|
||||||
apply_filter(ImageFilter.SMOOTH_MORE)
|
|
||||||
apply_filter(ImageFilter.SHARPEN)
|
|
||||||
apply_filter(ImageFilter.MaxFilter)
|
|
||||||
apply_filter(ImageFilter.MedianFilter)
|
|
||||||
apply_filter(ImageFilter.MinFilter)
|
|
||||||
apply_filter(ImageFilter.ModeFilter)
|
|
||||||
apply_filter(ImageFilter.GaussianBlur)
|
|
||||||
apply_filter(ImageFilter.GaussianBlur(5))
|
|
||||||
apply_filter(ImageFilter.BoxBlur(5))
|
|
||||||
apply_filter(ImageFilter.UnsharpMask)
|
|
||||||
apply_filter(ImageFilter.UnsharpMask(10))
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", ("L", "RGB", "CMYK"))
|
||||||
|
def test_sanity_error(mode):
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
apply_filter("hello")
|
im = hopper(mode)
|
||||||
|
im.filter("hello")
|
||||||
|
|
||||||
|
|
||||||
def test_crash():
|
|
||||||
|
|
||||||
# crashes on small images
|
# crashes on small images
|
||||||
im = Image.new("RGB", (1, 1))
|
@pytest.mark.parametrize("size", ((1, 1), (2, 2), (3, 3)))
|
||||||
im.filter(ImageFilter.SMOOTH)
|
def test_crash(size):
|
||||||
|
im = Image.new("RGB", size)
|
||||||
im = Image.new("RGB", (2, 2))
|
|
||||||
im.filter(ImageFilter.SMOOTH)
|
|
||||||
|
|
||||||
im = Image.new("RGB", (3, 3))
|
|
||||||
im.filter(ImageFilter.SMOOTH)
|
im.filter(ImageFilter.SMOOTH)
|
||||||
|
|
||||||
|
|
||||||
def test_modefilter():
|
@pytest.mark.parametrize(
|
||||||
def modefilter(mode):
|
"mode, expected",
|
||||||
|
(
|
||||||
|
("1", (4, 0)),
|
||||||
|
("L", (4, 0)),
|
||||||
|
("P", (4, 0)),
|
||||||
|
("RGB", ((4, 0, 0), (0, 0, 0))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_modefilter(mode, expected):
|
||||||
im = Image.new(mode, (3, 3), None)
|
im = Image.new(mode, (3, 3), None)
|
||||||
im.putdata(list(range(9)))
|
im.putdata(list(range(9)))
|
||||||
# image is:
|
# image is:
|
||||||
|
@ -61,16 +70,20 @@ def test_modefilter():
|
||||||
mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1))
|
mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1))
|
||||||
im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0
|
im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0
|
||||||
mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1))
|
mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1))
|
||||||
return mod, mod2
|
assert (mod, mod2) == expected
|
||||||
|
|
||||||
assert modefilter("1") == (4, 0)
|
|
||||||
assert modefilter("L") == (4, 0)
|
|
||||||
assert modefilter("P") == (4, 0)
|
|
||||||
assert modefilter("RGB") == ((4, 0, 0), (0, 0, 0))
|
|
||||||
|
|
||||||
|
|
||||||
def test_rankfilter():
|
@pytest.mark.parametrize(
|
||||||
def rankfilter(mode):
|
"mode, expected",
|
||||||
|
(
|
||||||
|
("1", (0, 4, 8)),
|
||||||
|
("L", (0, 4, 8)),
|
||||||
|
("RGB", ((0, 0, 0), (4, 0, 0), (8, 0, 0))),
|
||||||
|
("I", (0, 4, 8)),
|
||||||
|
("F", (0.0, 4.0, 8.0)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_rankfilter(mode, expected):
|
||||||
im = Image.new(mode, (3, 3), None)
|
im = Image.new(mode, (3, 3), None)
|
||||||
im.putdata(list(range(9)))
|
im.putdata(list(range(9)))
|
||||||
# image is:
|
# image is:
|
||||||
|
@ -80,15 +93,21 @@ def test_rankfilter():
|
||||||
minimum = im.filter(ImageFilter.MinFilter).getpixel((1, 1))
|
minimum = im.filter(ImageFilter.MinFilter).getpixel((1, 1))
|
||||||
med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1))
|
med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1))
|
||||||
maximum = im.filter(ImageFilter.MaxFilter).getpixel((1, 1))
|
maximum = im.filter(ImageFilter.MaxFilter).getpixel((1, 1))
|
||||||
return minimum, med, maximum
|
assert (minimum, med, maximum) == expected
|
||||||
|
|
||||||
assert rankfilter("1") == (0, 4, 8)
|
|
||||||
assert rankfilter("L") == (0, 4, 8)
|
@pytest.mark.parametrize(
|
||||||
|
"filter", (ImageFilter.MinFilter, ImageFilter.MedianFilter, ImageFilter.MaxFilter)
|
||||||
|
)
|
||||||
|
def test_rankfilter_error(filter):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
rankfilter("P")
|
im = Image.new("P", (3, 3), None)
|
||||||
assert rankfilter("RGB") == ((0, 0, 0), (4, 0, 0), (8, 0, 0))
|
im.putdata(list(range(9)))
|
||||||
assert rankfilter("I") == (0, 4, 8)
|
# image is:
|
||||||
assert rankfilter("F") == (0.0, 4.0, 8.0)
|
# 0 1 2
|
||||||
|
# 3 4 5
|
||||||
|
# 6 7 8
|
||||||
|
im.filter(filter).getpixel((1, 1))
|
||||||
|
|
||||||
|
|
||||||
def test_rankfilter_properties():
|
def test_rankfilter_properties():
|
||||||
|
@ -110,7 +129,8 @@ def test_kernel_not_enough_coefficients():
|
||||||
ImageFilter.Kernel((3, 3), (0, 0))
|
ImageFilter.Kernel((3, 3), (0, 0))
|
||||||
|
|
||||||
|
|
||||||
def test_consistency_3x3():
|
@pytest.mark.parametrize("mode", ("L", "LA", "RGB", "CMYK"))
|
||||||
|
def test_consistency_3x3(mode):
|
||||||
with Image.open("Tests/images/hopper.bmp") as source:
|
with Image.open("Tests/images/hopper.bmp") as source:
|
||||||
with Image.open("Tests/images/hopper_emboss.bmp") as reference:
|
with Image.open("Tests/images/hopper_emboss.bmp") as reference:
|
||||||
kernel = ImageFilter.Kernel(
|
kernel = ImageFilter.Kernel(
|
||||||
|
@ -125,14 +145,14 @@ def test_consistency_3x3():
|
||||||
source = source.split() * 2
|
source = source.split() * 2
|
||||||
reference = reference.split() * 2
|
reference = reference.split() * 2
|
||||||
|
|
||||||
for mode in ["L", "LA", "RGB", "CMYK"]:
|
|
||||||
assert_image_equal(
|
assert_image_equal(
|
||||||
Image.merge(mode, source[: len(mode)]).filter(kernel),
|
Image.merge(mode, source[: len(mode)]).filter(kernel),
|
||||||
Image.merge(mode, reference[: len(mode)]),
|
Image.merge(mode, reference[: len(mode)]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_consistency_5x5():
|
@pytest.mark.parametrize("mode", ("L", "LA", "RGB", "CMYK"))
|
||||||
|
def test_consistency_5x5(mode):
|
||||||
with Image.open("Tests/images/hopper.bmp") as source:
|
with Image.open("Tests/images/hopper.bmp") as source:
|
||||||
with Image.open("Tests/images/hopper_emboss_more.bmp") as reference:
|
with Image.open("Tests/images/hopper_emboss_more.bmp") as reference:
|
||||||
kernel = ImageFilter.Kernel(
|
kernel = ImageFilter.Kernel(
|
||||||
|
@ -149,7 +169,6 @@ def test_consistency_5x5():
|
||||||
source = source.split() * 2
|
source = source.split() * 2
|
||||||
reference = reference.split() * 2
|
reference = reference.split() * 2
|
||||||
|
|
||||||
for mode in ["L", "LA", "RGB", "CMYK"]:
|
|
||||||
assert_image_equal(
|
assert_image_equal(
|
||||||
Image.merge(mode, source[: len(mode)]).filter(kernel),
|
Image.merge(mode, source[: len(mode)]).filter(kernel),
|
||||||
Image.merge(mode, reference[: len(mode)]),
|
Image.merge(mode, reference[: len(mode)]),
|
||||||
|
|
|
@ -16,7 +16,7 @@ def test_getcolors():
|
||||||
assert getcolors("L") == 255
|
assert getcolors("L") == 255
|
||||||
assert getcolors("I") == 255
|
assert getcolors("I") == 255
|
||||||
assert getcolors("F") == 255
|
assert getcolors("F") == 255
|
||||||
assert getcolors("P") == 90 # fixed palette
|
assert getcolors("P") == 96 # fixed palette
|
||||||
assert getcolors("RGB") is None
|
assert getcolors("RGB") is None
|
||||||
assert getcolors("RGBA") is None
|
assert getcolors("RGBA") is None
|
||||||
assert getcolors("CMYK") is None
|
assert getcolors("CMYK") is None
|
||||||
|
|
|
@ -10,7 +10,7 @@ def test_histogram():
|
||||||
assert histogram("L") == (256, 0, 662)
|
assert histogram("L") == (256, 0, 662)
|
||||||
assert histogram("I") == (256, 0, 662)
|
assert histogram("I") == (256, 0, 662)
|
||||||
assert histogram("F") == (256, 0, 662)
|
assert histogram("F") == (256, 0, 662)
|
||||||
assert histogram("P") == (256, 0, 1871)
|
assert histogram("P") == (256, 0, 1551)
|
||||||
assert histogram("RGB") == (768, 4, 675)
|
assert histogram("RGB") == (768, 4, 675)
|
||||||
assert histogram("RGBA") == (1024, 0, 16384)
|
assert histogram("RGBA") == (1024, 0, 16384)
|
||||||
assert histogram("CMYK") == (1024, 0, 16384)
|
assert histogram("CMYK") == (1024, 0, 16384)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import CachedProperty, assert_image_equal
|
from .helper import CachedProperty, assert_image_equal
|
||||||
|
@ -101,8 +103,8 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_image_solid(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_image_solid(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "red")
|
im = Image.new(mode, (200, 200), "red")
|
||||||
im2 = getattr(self, "gradient_" + mode)
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
|
@ -111,8 +113,8 @@ class TestImagingPaste:
|
||||||
im = im.crop((12, 23, im2.width + 12, im2.height + 23))
|
im = im.crop((12, 23, im2.width + 12, im2.height + 23))
|
||||||
assert_image_equal(im, im2)
|
assert_image_equal(im, im2)
|
||||||
|
|
||||||
def test_image_mask_1(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_image_mask_1(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "white")
|
im = Image.new(mode, (200, 200), "white")
|
||||||
im2 = getattr(self, "gradient_" + mode)
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
|
@ -133,8 +135,8 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_image_mask_L(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_image_mask_L(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "white")
|
im = Image.new(mode, (200, 200), "white")
|
||||||
im2 = getattr(self, "gradient_" + mode)
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
|
@ -155,8 +157,8 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_image_mask_LA(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_image_mask_LA(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "white")
|
im = Image.new(mode, (200, 200), "white")
|
||||||
im2 = getattr(self, "gradient_" + mode)
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
|
@ -177,8 +179,8 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_image_mask_RGBA(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_image_mask_RGBA(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "white")
|
im = Image.new(mode, (200, 200), "white")
|
||||||
im2 = getattr(self, "gradient_" + mode)
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
|
@ -199,8 +201,8 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_image_mask_RGBa(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_image_mask_RGBa(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "white")
|
im = Image.new(mode, (200, 200), "white")
|
||||||
im2 = getattr(self, "gradient_" + mode)
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
|
@ -221,8 +223,8 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_color_solid(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_color_solid(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "black")
|
im = Image.new(mode, (200, 200), "black")
|
||||||
|
|
||||||
rect = (12, 23, 128 + 12, 128 + 23)
|
rect = (12, 23, 128 + 12, 128 + 23)
|
||||||
|
@ -234,8 +236,8 @@ class TestImagingPaste:
|
||||||
assert head[255] == 128 * 128
|
assert head[255] == 128 * 128
|
||||||
assert sum(head[:255]) == 0
|
assert sum(head[:255]) == 0
|
||||||
|
|
||||||
def test_color_mask_1(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_color_mask_1(self, mode):
|
||||||
im = Image.new(mode, (200, 200), (50, 60, 70, 80)[: len(mode)])
|
im = Image.new(mode, (200, 200), (50, 60, 70, 80)[: len(mode)])
|
||||||
color = (10, 20, 30, 40)[: len(mode)]
|
color = (10, 20, 30, 40)[: len(mode)]
|
||||||
|
|
||||||
|
@ -256,8 +258,8 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_color_mask_L(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_color_mask_L(self, mode):
|
||||||
im = getattr(self, "gradient_" + mode).copy()
|
im = getattr(self, "gradient_" + mode).copy()
|
||||||
color = "white"
|
color = "white"
|
||||||
|
|
||||||
|
@ -278,8 +280,8 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_color_mask_RGBA(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_color_mask_RGBA(self, mode):
|
||||||
im = getattr(self, "gradient_" + mode).copy()
|
im = getattr(self, "gradient_" + mode).copy()
|
||||||
color = "white"
|
color = "white"
|
||||||
|
|
||||||
|
@ -300,8 +302,8 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_color_mask_RGBa(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_color_mask_RGBa(self, mode):
|
||||||
im = getattr(self, "gradient_" + mode).copy()
|
im = getattr(self, "gradient_" + mode).copy()
|
||||||
color = "white"
|
color = "white"
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,22 @@ def test_quantize_no_dither():
|
||||||
assert converted.palette.palette == palette.palette.palette
|
assert converted.palette.palette == palette.palette.palette
|
||||||
|
|
||||||
|
|
||||||
|
def test_quantize_no_dither2():
|
||||||
|
im = Image.new("RGB", (9, 1))
|
||||||
|
im.putdata(list((p,) * 3 for p in range(0, 36, 4)))
|
||||||
|
|
||||||
|
palette = Image.new("P", (1, 1))
|
||||||
|
data = (0, 0, 0, 32, 32, 32)
|
||||||
|
palette.putpalette(data)
|
||||||
|
quantized = im.quantize(dither=Image.Dither.NONE, palette=palette)
|
||||||
|
|
||||||
|
assert tuple(quantized.palette.palette) == data
|
||||||
|
|
||||||
|
px = quantized.load()
|
||||||
|
for x in range(9):
|
||||||
|
assert px[x, 0] == (0 if x < 5 else 1)
|
||||||
|
|
||||||
|
|
||||||
def test_quantize_dither_diff():
|
def test_quantize_dither_diff():
|
||||||
image = hopper()
|
image = hopper()
|
||||||
with Image.open("Tests/images/caption_6_33_22.png") as palette:
|
with Image.open("Tests/images/caption_6_33_22.png") as palette:
|
||||||
|
|
|
@ -38,58 +38,64 @@ gradients_image = Image.open("Tests/images/radial_gradients.png")
|
||||||
gradients_image.load()
|
gradients_image.load()
|
||||||
|
|
||||||
|
|
||||||
def test_args_factor():
|
@pytest.mark.parametrize(
|
||||||
|
"size, expected",
|
||||||
|
(
|
||||||
|
(3, (4, 4)),
|
||||||
|
((3, 1), (4, 10)),
|
||||||
|
((1, 3), (10, 4)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_args_factor(size, expected):
|
||||||
im = Image.new("L", (10, 10))
|
im = Image.new("L", (10, 10))
|
||||||
|
assert expected == im.reduce(size).size
|
||||||
assert (4, 4) == im.reduce(3).size
|
|
||||||
assert (4, 10) == im.reduce((3, 1)).size
|
|
||||||
assert (10, 4) == im.reduce((1, 3)).size
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.reduce(0)
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
im.reduce(2.0)
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.reduce((0, 10))
|
|
||||||
|
|
||||||
|
|
||||||
def test_args_box():
|
@pytest.mark.parametrize(
|
||||||
|
"size, expected_error", ((0, ValueError), (2.0, TypeError), ((0, 10), ValueError))
|
||||||
|
)
|
||||||
|
def test_args_factor_error(size, expected_error):
|
||||||
im = Image.new("L", (10, 10))
|
im = Image.new("L", (10, 10))
|
||||||
|
with pytest.raises(expected_error):
|
||||||
assert (5, 5) == im.reduce(2, (0, 0, 10, 10)).size
|
im.reduce(size)
|
||||||
assert (1, 1) == im.reduce(2, (5, 5, 6, 6)).size
|
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
im.reduce(2, "stri")
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
im.reduce(2, 2)
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.reduce(2, (0, 0, 11, 10))
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.reduce(2, (0, 0, 10, 11))
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.reduce(2, (-1, 0, 10, 10))
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.reduce(2, (0, -1, 10, 10))
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.reduce(2, (0, 5, 10, 5))
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.reduce(2, (5, 0, 5, 10))
|
|
||||||
|
|
||||||
|
|
||||||
def test_unsupported_modes():
|
@pytest.mark.parametrize(
|
||||||
|
"size, expected",
|
||||||
|
(
|
||||||
|
((0, 0, 10, 10), (5, 5)),
|
||||||
|
((5, 5, 6, 6), (1, 1)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_args_box(size, expected):
|
||||||
|
im = Image.new("L", (10, 10))
|
||||||
|
assert expected == im.reduce(2, size).size
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"size, expected_error",
|
||||||
|
(
|
||||||
|
("stri", TypeError),
|
||||||
|
((0, 0, 11, 10), ValueError),
|
||||||
|
((0, 0, 10, 11), ValueError),
|
||||||
|
((-1, 0, 10, 10), ValueError),
|
||||||
|
((0, -1, 10, 10), ValueError),
|
||||||
|
((0, 5, 10, 5), ValueError),
|
||||||
|
((5, 0, 5, 10), ValueError),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_args_box_error(size, expected_error):
|
||||||
|
im = Image.new("L", (10, 10))
|
||||||
|
with pytest.raises(expected_error):
|
||||||
|
im.reduce(2, size).size
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", ("P", "1", "I;16"))
|
||||||
|
def test_unsupported_modes(mode):
|
||||||
im = Image.new("P", (10, 10))
|
im = Image.new("P", (10, 10))
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.reduce(3)
|
im.reduce(3)
|
||||||
|
|
||||||
im = Image.new("1", (10, 10))
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.reduce(3)
|
|
||||||
|
|
||||||
im = Image.new("I;16", (10, 10))
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
im.reduce(3)
|
|
||||||
|
|
||||||
|
|
||||||
def get_image(mode):
|
def get_image(mode):
|
||||||
mode_info = ImageMode.getmode(mode)
|
mode_info = ImageMode.getmode(mode)
|
||||||
|
@ -190,68 +196,74 @@ def assert_compare_images(a, b, max_average_diff, max_diff=255):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_mode_L():
|
@pytest.mark.parametrize("factor", remarkable_factors)
|
||||||
|
def test_mode_L(factor):
|
||||||
im = get_image("L")
|
im = get_image("L")
|
||||||
for factor in remarkable_factors:
|
|
||||||
compare_reduce_with_reference(im, factor)
|
compare_reduce_with_reference(im, factor)
|
||||||
compare_reduce_with_box(im, factor)
|
compare_reduce_with_box(im, factor)
|
||||||
|
|
||||||
|
|
||||||
def test_mode_LA():
|
@pytest.mark.parametrize("factor", remarkable_factors)
|
||||||
|
def test_mode_LA(factor):
|
||||||
im = get_image("LA")
|
im = get_image("LA")
|
||||||
for factor in remarkable_factors:
|
|
||||||
compare_reduce_with_reference(im, factor, 0.8, 5)
|
compare_reduce_with_reference(im, factor, 0.8, 5)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("factor", remarkable_factors)
|
||||||
|
def test_mode_LA_opaque(factor):
|
||||||
|
im = get_image("LA")
|
||||||
# With opaque alpha, an error should be way smaller.
|
# With opaque alpha, an error should be way smaller.
|
||||||
im.putalpha(Image.new("L", im.size, 255))
|
im.putalpha(Image.new("L", im.size, 255))
|
||||||
for factor in remarkable_factors:
|
|
||||||
compare_reduce_with_reference(im, factor)
|
compare_reduce_with_reference(im, factor)
|
||||||
compare_reduce_with_box(im, factor)
|
compare_reduce_with_box(im, factor)
|
||||||
|
|
||||||
|
|
||||||
def test_mode_La():
|
@pytest.mark.parametrize("factor", remarkable_factors)
|
||||||
|
def test_mode_La(factor):
|
||||||
im = get_image("La")
|
im = get_image("La")
|
||||||
for factor in remarkable_factors:
|
|
||||||
compare_reduce_with_reference(im, factor)
|
compare_reduce_with_reference(im, factor)
|
||||||
compare_reduce_with_box(im, factor)
|
compare_reduce_with_box(im, factor)
|
||||||
|
|
||||||
|
|
||||||
def test_mode_RGB():
|
@pytest.mark.parametrize("factor", remarkable_factors)
|
||||||
|
def test_mode_RGB(factor):
|
||||||
im = get_image("RGB")
|
im = get_image("RGB")
|
||||||
for factor in remarkable_factors:
|
|
||||||
compare_reduce_with_reference(im, factor)
|
compare_reduce_with_reference(im, factor)
|
||||||
compare_reduce_with_box(im, factor)
|
compare_reduce_with_box(im, factor)
|
||||||
|
|
||||||
|
|
||||||
def test_mode_RGBA():
|
@pytest.mark.parametrize("factor", remarkable_factors)
|
||||||
|
def test_mode_RGBA(factor):
|
||||||
im = get_image("RGBA")
|
im = get_image("RGBA")
|
||||||
for factor in remarkable_factors:
|
|
||||||
compare_reduce_with_reference(im, factor, 0.8, 5)
|
compare_reduce_with_reference(im, factor, 0.8, 5)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("factor", remarkable_factors)
|
||||||
|
def test_mode_RGBA_opaque(factor):
|
||||||
|
im = get_image("RGBA")
|
||||||
# With opaque alpha, an error should be way smaller.
|
# With opaque alpha, an error should be way smaller.
|
||||||
im.putalpha(Image.new("L", im.size, 255))
|
im.putalpha(Image.new("L", im.size, 255))
|
||||||
for factor in remarkable_factors:
|
|
||||||
compare_reduce_with_reference(im, factor)
|
compare_reduce_with_reference(im, factor)
|
||||||
compare_reduce_with_box(im, factor)
|
compare_reduce_with_box(im, factor)
|
||||||
|
|
||||||
|
|
||||||
def test_mode_RGBa():
|
@pytest.mark.parametrize("factor", remarkable_factors)
|
||||||
|
def test_mode_RGBa(factor):
|
||||||
im = get_image("RGBa")
|
im = get_image("RGBa")
|
||||||
for factor in remarkable_factors:
|
|
||||||
compare_reduce_with_reference(im, factor)
|
compare_reduce_with_reference(im, factor)
|
||||||
compare_reduce_with_box(im, factor)
|
compare_reduce_with_box(im, factor)
|
||||||
|
|
||||||
|
|
||||||
def test_mode_I():
|
@pytest.mark.parametrize("factor", remarkable_factors)
|
||||||
|
def test_mode_I(factor):
|
||||||
im = get_image("I")
|
im = get_image("I")
|
||||||
for factor in remarkable_factors:
|
|
||||||
compare_reduce_with_reference(im, factor)
|
compare_reduce_with_reference(im, factor)
|
||||||
compare_reduce_with_box(im, factor)
|
compare_reduce_with_box(im, factor)
|
||||||
|
|
||||||
|
|
||||||
def test_mode_F():
|
@pytest.mark.parametrize("factor", remarkable_factors)
|
||||||
|
def test_mode_F(factor):
|
||||||
im = get_image("F")
|
im = get_image("F")
|
||||||
for factor in remarkable_factors:
|
|
||||||
compare_reduce_with_reference(im, factor, 0, 0)
|
compare_reduce_with_reference(im, factor, 0, 0)
|
||||||
compare_reduce_with_box(im, factor)
|
compare_reduce_with_box(im, factor)
|
||||||
|
|
||||||
|
|
|
@ -100,8 +100,8 @@ class TestImagingCoreResampleAccuracy:
|
||||||
for y in range(image.size[1])
|
for y in range(image.size[1])
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_reduce_box(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_reduce_box(self, mode):
|
||||||
case = self.make_case(mode, (8, 8), 0xE1)
|
case = self.make_case(mode, (8, 8), 0xE1)
|
||||||
case = case.resize((4, 4), Image.Resampling.BOX)
|
case = case.resize((4, 4), Image.Resampling.BOX)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -111,8 +111,8 @@ class TestImagingCoreResampleAccuracy:
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (4, 4)))
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
||||||
|
|
||||||
def test_reduce_bilinear(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_reduce_bilinear(self, mode):
|
||||||
case = self.make_case(mode, (8, 8), 0xE1)
|
case = self.make_case(mode, (8, 8), 0xE1)
|
||||||
case = case.resize((4, 4), Image.Resampling.BILINEAR)
|
case = case.resize((4, 4), Image.Resampling.BILINEAR)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -122,8 +122,8 @@ class TestImagingCoreResampleAccuracy:
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (4, 4)))
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
||||||
|
|
||||||
def test_reduce_hamming(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_reduce_hamming(self, mode):
|
||||||
case = self.make_case(mode, (8, 8), 0xE1)
|
case = self.make_case(mode, (8, 8), 0xE1)
|
||||||
case = case.resize((4, 4), Image.Resampling.HAMMING)
|
case = case.resize((4, 4), Image.Resampling.HAMMING)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -133,7 +133,8 @@ class TestImagingCoreResampleAccuracy:
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (4, 4)))
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
||||||
|
|
||||||
def test_reduce_bicubic(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
|
def test_reduce_bicubic(self, mode):
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
for mode in ["RGBX", "RGB", "La", "L"]:
|
||||||
case = self.make_case(mode, (12, 12), 0xE1)
|
case = self.make_case(mode, (12, 12), 0xE1)
|
||||||
case = case.resize((6, 6), Image.Resampling.BICUBIC)
|
case = case.resize((6, 6), Image.Resampling.BICUBIC)
|
||||||
|
@ -145,8 +146,8 @@ class TestImagingCoreResampleAccuracy:
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (6, 6)))
|
self.check_case(channel, self.make_sample(data, (6, 6)))
|
||||||
|
|
||||||
def test_reduce_lanczos(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_reduce_lanczos(self, mode):
|
||||||
case = self.make_case(mode, (16, 16), 0xE1)
|
case = self.make_case(mode, (16, 16), 0xE1)
|
||||||
case = case.resize((8, 8), Image.Resampling.LANCZOS)
|
case = case.resize((8, 8), Image.Resampling.LANCZOS)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -158,8 +159,8 @@ class TestImagingCoreResampleAccuracy:
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (8, 8)))
|
self.check_case(channel, self.make_sample(data, (8, 8)))
|
||||||
|
|
||||||
def test_enlarge_box(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_enlarge_box(self, mode):
|
||||||
case = self.make_case(mode, (2, 2), 0xE1)
|
case = self.make_case(mode, (2, 2), 0xE1)
|
||||||
case = case.resize((4, 4), Image.Resampling.BOX)
|
case = case.resize((4, 4), Image.Resampling.BOX)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -169,8 +170,8 @@ class TestImagingCoreResampleAccuracy:
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (4, 4)))
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
||||||
|
|
||||||
def test_enlarge_bilinear(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_enlarge_bilinear(self, mode):
|
||||||
case = self.make_case(mode, (2, 2), 0xE1)
|
case = self.make_case(mode, (2, 2), 0xE1)
|
||||||
case = case.resize((4, 4), Image.Resampling.BILINEAR)
|
case = case.resize((4, 4), Image.Resampling.BILINEAR)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -180,8 +181,8 @@ class TestImagingCoreResampleAccuracy:
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (4, 4)))
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
||||||
|
|
||||||
def test_enlarge_hamming(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_enlarge_hamming(self, mode):
|
||||||
case = self.make_case(mode, (2, 2), 0xE1)
|
case = self.make_case(mode, (2, 2), 0xE1)
|
||||||
case = case.resize((4, 4), Image.Resampling.HAMMING)
|
case = case.resize((4, 4), Image.Resampling.HAMMING)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -191,8 +192,8 @@ class TestImagingCoreResampleAccuracy:
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (4, 4)))
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
||||||
|
|
||||||
def test_enlarge_bicubic(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_enlarge_bicubic(self, mode):
|
||||||
case = self.make_case(mode, (4, 4), 0xE1)
|
case = self.make_case(mode, (4, 4), 0xE1)
|
||||||
case = case.resize((8, 8), Image.Resampling.BICUBIC)
|
case = case.resize((8, 8), Image.Resampling.BICUBIC)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -204,8 +205,8 @@ class TestImagingCoreResampleAccuracy:
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (8, 8)))
|
self.check_case(channel, self.make_sample(data, (8, 8)))
|
||||||
|
|
||||||
def test_enlarge_lanczos(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_enlarge_lanczos(self, mode):
|
||||||
case = self.make_case(mode, (6, 6), 0xE1)
|
case = self.make_case(mode, (6, 6), 0xE1)
|
||||||
case = case.resize((12, 12), Image.Resampling.LANCZOS)
|
case = case.resize((12, 12), Image.Resampling.LANCZOS)
|
||||||
data = (
|
data = (
|
||||||
|
@ -419,16 +420,19 @@ class TestCoreResampleCoefficients:
|
||||||
|
|
||||||
|
|
||||||
class TestCoreResampleBox:
|
class TestCoreResampleBox:
|
||||||
def test_wrong_arguments(self):
|
@pytest.mark.parametrize(
|
||||||
im = hopper()
|
"resample",
|
||||||
for resample in (
|
(
|
||||||
Image.Resampling.NEAREST,
|
Image.Resampling.NEAREST,
|
||||||
Image.Resampling.BOX,
|
Image.Resampling.BOX,
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
Image.Resampling.HAMMING,
|
Image.Resampling.HAMMING,
|
||||||
Image.Resampling.BICUBIC,
|
Image.Resampling.BICUBIC,
|
||||||
Image.Resampling.LANCZOS,
|
Image.Resampling.LANCZOS,
|
||||||
):
|
),
|
||||||
|
)
|
||||||
|
def test_wrong_arguments(self, resample):
|
||||||
|
im = hopper()
|
||||||
im.resize((32, 32), resample, (0, 0, im.width, im.height))
|
im.resize((32, 32), resample, (0, 0, im.width, im.height))
|
||||||
im.resize((32, 32), resample, (20, 20, im.width, im.height))
|
im.resize((32, 32), resample, (20, 20, im.width, im.height))
|
||||||
im.resize((32, 32), resample, (20, 20, 20, 100))
|
im.resize((32, 32), resample, (20, 20, 20, 100))
|
||||||
|
@ -509,9 +513,11 @@ class TestCoreResampleBox:
|
||||||
with pytest.raises(AssertionError, match=r"difference 29\."):
|
with pytest.raises(AssertionError, match=r"difference 29\."):
|
||||||
assert_image_similar(reference, without_box, 5)
|
assert_image_similar(reference, without_box, 5)
|
||||||
|
|
||||||
def test_formats(self):
|
@pytest.mark.parametrize("mode", ("RGB", "L", "RGBA", "LA", "I", ""))
|
||||||
for resample in [Image.Resampling.NEAREST, Image.Resampling.BILINEAR]:
|
@pytest.mark.parametrize(
|
||||||
for mode in ["RGB", "L", "RGBA", "LA", "I", ""]:
|
"resample", (Image.Resampling.NEAREST, Image.Resampling.BILINEAR)
|
||||||
|
)
|
||||||
|
def test_formats(self, mode, resample):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
box = (20, 20, im.size[0] - 20, im.size[1] - 20)
|
box = (20, 20, im.size[0] - 20, im.size[1] - 20)
|
||||||
with_box = im.resize((32, 32), resample, box)
|
with_box = im.resize((32, 32), resample, box)
|
||||||
|
@ -548,11 +554,13 @@ class TestCoreResampleBox:
|
||||||
# check that the difference at least that much
|
# check that the difference at least that much
|
||||||
assert_image_similar(res, im.crop(box), 20, f">>> {size} {box}")
|
assert_image_similar(res, im.crop(box), 20, f">>> {size} {box}")
|
||||||
|
|
||||||
def test_skip_horizontal(self):
|
@pytest.mark.parametrize(
|
||||||
|
"flt", (Image.Resampling.NEAREST, Image.Resampling.BICUBIC)
|
||||||
|
)
|
||||||
|
def test_skip_horizontal(self, flt):
|
||||||
# Can skip resize for one dimension
|
# Can skip resize for one dimension
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
|
||||||
for flt in [Image.Resampling.NEAREST, Image.Resampling.BICUBIC]:
|
|
||||||
for size, box in [
|
for size, box in [
|
||||||
((40, 50), (0, 0, 40, 90)),
|
((40, 50), (0, 0, 40, 90)),
|
||||||
((40, 50), (0, 20, 40, 90)),
|
((40, 50), (0, 20, 40, 90)),
|
||||||
|
@ -569,11 +577,13 @@ class TestCoreResampleBox:
|
||||||
f">>> {size} {box} {flt}",
|
f">>> {size} {box} {flt}",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_skip_vertical(self):
|
@pytest.mark.parametrize(
|
||||||
|
"flt", (Image.Resampling.NEAREST, Image.Resampling.BICUBIC)
|
||||||
|
)
|
||||||
|
def test_skip_vertical(self, flt):
|
||||||
# Can skip resize for one dimension
|
# Can skip resize for one dimension
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
|
||||||
for flt in [Image.Resampling.NEAREST, Image.Resampling.BICUBIC]:
|
|
||||||
for size, box in [
|
for size, box in [
|
||||||
((40, 50), (0, 0, 90, 50)),
|
((40, 50), (0, 0, 90, 50)),
|
||||||
((40, 50), (20, 0, 90, 50)),
|
((40, 50), (20, 0, 90, 50)),
|
||||||
|
|
|
@ -22,19 +22,10 @@ class TestImagingCoreResize:
|
||||||
im.load()
|
im.load()
|
||||||
return im._new(im.im.resize(size, f))
|
return im._new(im.im.resize(size, f))
|
||||||
|
|
||||||
def test_nearest_mode(self):
|
@pytest.mark.parametrize(
|
||||||
for mode in [
|
"mode", ("1", "P", "L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr", "I;16")
|
||||||
"1",
|
)
|
||||||
"P",
|
def test_nearest_mode(self, mode):
|
||||||
"L",
|
|
||||||
"I",
|
|
||||||
"F",
|
|
||||||
"RGB",
|
|
||||||
"RGBA",
|
|
||||||
"CMYK",
|
|
||||||
"YCbCr",
|
|
||||||
"I;16",
|
|
||||||
]: # exotic mode
|
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
r = self.resize(im, (15, 12), Image.Resampling.NEAREST)
|
r = self.resize(im, (15, 12), Image.Resampling.NEAREST)
|
||||||
assert r.mode == mode
|
assert r.mode == mode
|
||||||
|
@ -55,33 +46,58 @@ class TestImagingCoreResize:
|
||||||
assert r.size == (15, 12)
|
assert r.size == (15, 12)
|
||||||
assert r.im.bands == im.im.bands
|
assert r.im.bands == im.im.bands
|
||||||
|
|
||||||
def test_reduce_filters(self):
|
@pytest.mark.parametrize(
|
||||||
for f in [
|
"resample",
|
||||||
|
(
|
||||||
Image.Resampling.NEAREST,
|
Image.Resampling.NEAREST,
|
||||||
Image.Resampling.BOX,
|
Image.Resampling.BOX,
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
Image.Resampling.HAMMING,
|
Image.Resampling.HAMMING,
|
||||||
Image.Resampling.BICUBIC,
|
Image.Resampling.BICUBIC,
|
||||||
Image.Resampling.LANCZOS,
|
Image.Resampling.LANCZOS,
|
||||||
]:
|
),
|
||||||
r = self.resize(hopper("RGB"), (15, 12), f)
|
)
|
||||||
|
def test_reduce_filters(self, resample):
|
||||||
|
r = self.resize(hopper("RGB"), (15, 12), resample)
|
||||||
assert r.mode == "RGB"
|
assert r.mode == "RGB"
|
||||||
assert r.size == (15, 12)
|
assert r.size == (15, 12)
|
||||||
|
|
||||||
def test_enlarge_filters(self):
|
@pytest.mark.parametrize(
|
||||||
for f in [
|
"resample",
|
||||||
|
(
|
||||||
Image.Resampling.NEAREST,
|
Image.Resampling.NEAREST,
|
||||||
Image.Resampling.BOX,
|
Image.Resampling.BOX,
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
Image.Resampling.HAMMING,
|
Image.Resampling.HAMMING,
|
||||||
Image.Resampling.BICUBIC,
|
Image.Resampling.BICUBIC,
|
||||||
Image.Resampling.LANCZOS,
|
Image.Resampling.LANCZOS,
|
||||||
]:
|
),
|
||||||
r = self.resize(hopper("RGB"), (212, 195), f)
|
)
|
||||||
|
def test_enlarge_filters(self, resample):
|
||||||
|
r = self.resize(hopper("RGB"), (212, 195), resample)
|
||||||
assert r.mode == "RGB"
|
assert r.mode == "RGB"
|
||||||
assert r.size == (212, 195)
|
assert r.size == (212, 195)
|
||||||
|
|
||||||
def test_endianness(self):
|
@pytest.mark.parametrize(
|
||||||
|
"resample",
|
||||||
|
(
|
||||||
|
Image.Resampling.NEAREST,
|
||||||
|
Image.Resampling.BOX,
|
||||||
|
Image.Resampling.BILINEAR,
|
||||||
|
Image.Resampling.HAMMING,
|
||||||
|
Image.Resampling.BICUBIC,
|
||||||
|
Image.Resampling.LANCZOS,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"mode, channels_set",
|
||||||
|
(
|
||||||
|
("RGB", ("blank", "filled", "dirty")),
|
||||||
|
("RGBA", ("blank", "blank", "filled", "dirty")),
|
||||||
|
("LA", ("filled", "dirty")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_endianness(self, resample, mode, channels_set):
|
||||||
# Make an image with one colored pixel, in one channel.
|
# Make an image with one colored pixel, in one channel.
|
||||||
# When resized, that channel should be the same as a GS image.
|
# When resized, that channel should be the same as a GS image.
|
||||||
# Other channels should be unaffected.
|
# Other channels should be unaffected.
|
||||||
|
@ -95,44 +111,34 @@ class TestImagingCoreResize:
|
||||||
}
|
}
|
||||||
samples["dirty"].putpixel((1, 1), 128)
|
samples["dirty"].putpixel((1, 1), 128)
|
||||||
|
|
||||||
for f in [
|
|
||||||
Image.Resampling.NEAREST,
|
|
||||||
Image.Resampling.BOX,
|
|
||||||
Image.Resampling.BILINEAR,
|
|
||||||
Image.Resampling.HAMMING,
|
|
||||||
Image.Resampling.BICUBIC,
|
|
||||||
Image.Resampling.LANCZOS,
|
|
||||||
]:
|
|
||||||
# samples resized with current filter
|
# samples resized with current filter
|
||||||
references = {
|
references = {
|
||||||
name: self.resize(ch, (4, 4), f) for name, ch in samples.items()
|
name: self.resize(ch, (4, 4), resample) for name, ch in samples.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
for mode, channels_set in [
|
|
||||||
("RGB", ("blank", "filled", "dirty")),
|
|
||||||
("RGBA", ("blank", "blank", "filled", "dirty")),
|
|
||||||
("LA", ("filled", "dirty")),
|
|
||||||
]:
|
|
||||||
for channels in set(permutations(channels_set)):
|
for channels in set(permutations(channels_set)):
|
||||||
# compile image from different channels permutations
|
# compile image from different channels permutations
|
||||||
im = Image.merge(mode, [samples[ch] for ch in channels])
|
im = Image.merge(mode, [samples[ch] for ch in channels])
|
||||||
resized = self.resize(im, (4, 4), f)
|
resized = self.resize(im, (4, 4), resample)
|
||||||
|
|
||||||
for i, ch in enumerate(resized.split()):
|
for i, ch in enumerate(resized.split()):
|
||||||
# check what resized channel in image is the same
|
# check what resized channel in image is the same
|
||||||
# as separately resized channel
|
# as separately resized channel
|
||||||
assert_image_equal(ch, references[channels[i]])
|
assert_image_equal(ch, references[channels[i]])
|
||||||
|
|
||||||
def test_enlarge_zero(self):
|
@pytest.mark.parametrize(
|
||||||
for f in [
|
"resample",
|
||||||
|
(
|
||||||
Image.Resampling.NEAREST,
|
Image.Resampling.NEAREST,
|
||||||
Image.Resampling.BOX,
|
Image.Resampling.BOX,
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
Image.Resampling.HAMMING,
|
Image.Resampling.HAMMING,
|
||||||
Image.Resampling.BICUBIC,
|
Image.Resampling.BICUBIC,
|
||||||
Image.Resampling.LANCZOS,
|
Image.Resampling.LANCZOS,
|
||||||
]:
|
),
|
||||||
r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), f)
|
)
|
||||||
|
def test_enlarge_zero(self, resample):
|
||||||
|
r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), resample)
|
||||||
assert r.mode == "RGB"
|
assert r.mode == "RGB"
|
||||||
assert r.size == (212, 195)
|
assert r.size == (212, 195)
|
||||||
assert r.getdata()[0] == (0, 0, 0)
|
assert r.getdata()[0] == (0, 0, 0)
|
||||||
|
@ -179,12 +185,11 @@ class TestReducingGapResize:
|
||||||
(52, 34), Image.Resampling.BICUBIC, reducing_gap=0.99
|
(52, 34), Image.Resampling.BICUBIC, reducing_gap=0.99
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_reducing_gap_1(self, gradients_image):
|
@pytest.mark.parametrize(
|
||||||
for box, epsilon in [
|
"box, epsilon",
|
||||||
(None, 4),
|
((None, 4), ((1.1, 2.2, 510.8, 510.9), 4), ((3, 10, 410, 256), 10)),
|
||||||
((1.1, 2.2, 510.8, 510.9), 4),
|
)
|
||||||
((3, 10, 410, 256), 10),
|
def test_reducing_gap_1(self, gradients_image, box, epsilon):
|
||||||
]:
|
|
||||||
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
|
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
|
||||||
im = gradients_image.resize(
|
im = gradients_image.resize(
|
||||||
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
|
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
|
||||||
|
@ -195,12 +200,11 @@ class TestReducingGapResize:
|
||||||
|
|
||||||
assert_image_similar(ref, im, epsilon)
|
assert_image_similar(ref, im, epsilon)
|
||||||
|
|
||||||
def test_reducing_gap_2(self, gradients_image):
|
@pytest.mark.parametrize(
|
||||||
for box, epsilon in [
|
"box, epsilon",
|
||||||
(None, 1.5),
|
((None, 1.5), ((1.1, 2.2, 510.8, 510.9), 1.5), ((3, 10, 410, 256), 1)),
|
||||||
((1.1, 2.2, 510.8, 510.9), 1.5),
|
)
|
||||||
((3, 10, 410, 256), 1),
|
def test_reducing_gap_2(self, gradients_image, box, epsilon):
|
||||||
]:
|
|
||||||
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
|
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
|
||||||
im = gradients_image.resize(
|
im = gradients_image.resize(
|
||||||
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0
|
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0
|
||||||
|
@ -211,12 +215,11 @@ class TestReducingGapResize:
|
||||||
|
|
||||||
assert_image_similar(ref, im, epsilon)
|
assert_image_similar(ref, im, epsilon)
|
||||||
|
|
||||||
def test_reducing_gap_3(self, gradients_image):
|
@pytest.mark.parametrize(
|
||||||
for box, epsilon in [
|
"box, epsilon",
|
||||||
(None, 1),
|
((None, 1), ((1.1, 2.2, 510.8, 510.9), 1), ((3, 10, 410, 256), 0.5)),
|
||||||
((1.1, 2.2, 510.8, 510.9), 1),
|
)
|
||||||
((3, 10, 410, 256), 0.5),
|
def test_reducing_gap_3(self, gradients_image, box, epsilon):
|
||||||
]:
|
|
||||||
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
|
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
|
||||||
im = gradients_image.resize(
|
im = gradients_image.resize(
|
||||||
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0
|
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0
|
||||||
|
@ -227,8 +230,8 @@ class TestReducingGapResize:
|
||||||
|
|
||||||
assert_image_similar(ref, im, epsilon)
|
assert_image_similar(ref, im, epsilon)
|
||||||
|
|
||||||
def test_reducing_gap_8(self, gradients_image):
|
@pytest.mark.parametrize("box", (None, (1.1, 2.2, 510.8, 510.9), (3, 10, 410, 256)))
|
||||||
for box in [None, (1.1, 2.2, 510.8, 510.9), (3, 10, 410, 256)]:
|
def test_reducing_gap_8(self, gradients_image, box):
|
||||||
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
|
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
|
||||||
im = gradients_image.resize(
|
im = gradients_image.resize(
|
||||||
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=8.0
|
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=8.0
|
||||||
|
@ -236,11 +239,11 @@ class TestReducingGapResize:
|
||||||
|
|
||||||
assert_image_equal(ref, im)
|
assert_image_equal(ref, im)
|
||||||
|
|
||||||
def test_box_filter(self, gradients_image):
|
@pytest.mark.parametrize(
|
||||||
for box, epsilon in [
|
"box, epsilon",
|
||||||
((0, 0, 512, 512), 5.5),
|
(((0, 0, 512, 512), 5.5), ((0.9, 1.7, 128, 128), 9.5)),
|
||||||
((0.9, 1.7, 128, 128), 9.5),
|
)
|
||||||
]:
|
def test_box_filter(self, gradients_image, box, epsilon):
|
||||||
ref = gradients_image.resize((52, 34), Image.Resampling.BOX, box=box)
|
ref = gradients_image.resize((52, 34), Image.Resampling.BOX, box=box)
|
||||||
im = gradients_image.resize(
|
im = gradients_image.resize(
|
||||||
(52, 34), Image.Resampling.BOX, box=box, reducing_gap=1.0
|
(52, 34), Image.Resampling.BOX, box=box, reducing_gap=1.0
|
||||||
|
@ -273,15 +276,14 @@ class TestImageResize:
|
||||||
im = im.resize((64, 64))
|
im = im.resize((64, 64))
|
||||||
assert im.size == (64, 64)
|
assert im.size == (64, 64)
|
||||||
|
|
||||||
def test_default_filter(self):
|
@pytest.mark.parametrize("mode", ("L", "RGB", "I", "F"))
|
||||||
for mode in "L", "RGB", "I", "F":
|
def test_default_filter_bicubic(self, mode):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
|
assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
|
||||||
|
|
||||||
for mode in "1", "P":
|
@pytest.mark.parametrize(
|
||||||
im = hopper(mode)
|
"mode", ("1", "P", "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16")
|
||||||
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
|
)
|
||||||
|
def test_default_filter_nearest(self, mode):
|
||||||
for mode in "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16":
|
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
|
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
|
@ -22,14 +24,14 @@ def rotate(im, mode, angle, center=None, translate=None):
|
||||||
assert out.size != im.size
|
assert out.size != im.size
|
||||||
|
|
||||||
|
|
||||||
def test_mode():
|
@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
|
||||||
for mode in ("1", "P", "L", "RGB", "I", "F"):
|
def test_mode(mode):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
rotate(im, mode, 45)
|
rotate(im, mode, 45)
|
||||||
|
|
||||||
|
|
||||||
def test_angle():
|
@pytest.mark.parametrize("angle", (0, 90, 180, 270))
|
||||||
for angle in (0, 90, 180, 270):
|
def test_angle(angle):
|
||||||
with Image.open("Tests/images/test-card.png") as im:
|
with Image.open("Tests/images/test-card.png") as im:
|
||||||
rotate(im, im.mode, angle)
|
rotate(im, im.mode, angle)
|
||||||
|
|
||||||
|
@ -37,8 +39,8 @@ def test_angle():
|
||||||
assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1))
|
assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1))
|
||||||
|
|
||||||
|
|
||||||
def test_zero():
|
@pytest.mark.parametrize("angle", (0, 45, 90, 180, 270))
|
||||||
for angle in (0, 45, 90, 180, 270):
|
def test_zero(angle):
|
||||||
im = Image.new("RGB", (0, 0))
|
im = Image.new("RGB", (0, 0))
|
||||||
rotate(im, im.mode, angle)
|
rotate(im, im.mode, angle)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, features
|
from PIL import Image, features
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
@ -29,19 +31,12 @@ def test_split():
|
||||||
assert split("YCbCr") == [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)]
|
assert split("YCbCr") == [("L", 128, 128), ("L", 128, 128), ("L", 128, 128)]
|
||||||
|
|
||||||
|
|
||||||
def test_split_merge():
|
@pytest.mark.parametrize(
|
||||||
def split_merge(mode):
|
"mode", ("1", "L", "I", "F", "P", "RGB", "RGBA", "CMYK", "YCbCr")
|
||||||
return Image.merge(mode, hopper(mode).split())
|
)
|
||||||
|
def test_split_merge(mode):
|
||||||
assert_image_equal(hopper("1"), split_merge("1"))
|
expected = Image.merge(mode, hopper(mode).split())
|
||||||
assert_image_equal(hopper("L"), split_merge("L"))
|
assert_image_equal(hopper(mode), expected)
|
||||||
assert_image_equal(hopper("I"), split_merge("I"))
|
|
||||||
assert_image_equal(hopper("F"), split_merge("F"))
|
|
||||||
assert_image_equal(hopper("P"), split_merge("P"))
|
|
||||||
assert_image_equal(hopper("RGB"), split_merge("RGB"))
|
|
||||||
assert_image_equal(hopper("RGBA"), split_merge("RGBA"))
|
|
||||||
assert_image_equal(hopper("CMYK"), split_merge("CMYK"))
|
|
||||||
assert_image_equal(hopper("YCbCr"), split_merge("YCbCr"))
|
|
||||||
|
|
||||||
|
|
||||||
def test_split_open(tmp_path):
|
def test_split_open(tmp_path):
|
||||||
|
|
|
@ -97,6 +97,28 @@ def test_load_first():
|
||||||
im.thumbnail((64, 64))
|
im.thumbnail((64, 64))
|
||||||
assert im.size == (64, 10)
|
assert im.size == (64, 10)
|
||||||
|
|
||||||
|
# Test thumbnail(), without draft(),
|
||||||
|
# on an image that is large enough once load() has changed the size
|
||||||
|
with Image.open("Tests/images/g4_orientation_5.tif") as im:
|
||||||
|
im.thumbnail((590, 88), reducing_gap=None)
|
||||||
|
assert im.size == (590, 88)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_first_unless_jpeg():
|
||||||
|
# Test that thumbnail() still uses draft() for JPEG
|
||||||
|
with Image.open("Tests/images/hopper.jpg") as im:
|
||||||
|
draft = im.draft
|
||||||
|
|
||||||
|
def im_draft(mode, size):
|
||||||
|
result = draft(mode, size)
|
||||||
|
assert result is not None
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
im.draft = im_draft
|
||||||
|
|
||||||
|
im.thumbnail((64, 64))
|
||||||
|
|
||||||
|
|
||||||
# valgrind test is failing with memory allocated in libjpeg
|
# valgrind test is failing with memory allocated in libjpeg
|
||||||
@pytest.mark.valgrind_known_error(reason="Known Failing")
|
@pytest.mark.valgrind_known_error(reason="Known Failing")
|
||||||
|
|
|
@ -75,12 +75,15 @@ class TestImageTransform:
|
||||||
|
|
||||||
assert_image_equal(transformed, scaled)
|
assert_image_equal(transformed, scaled)
|
||||||
|
|
||||||
def test_fill(self):
|
@pytest.mark.parametrize(
|
||||||
for mode, pixel in [
|
"mode, expected_pixel",
|
||||||
["RGB", (255, 0, 0)],
|
(
|
||||||
["RGBA", (255, 0, 0, 255)],
|
("RGB", (255, 0, 0)),
|
||||||
["LA", (76, 0)],
|
("RGBA", (255, 0, 0, 255)),
|
||||||
]:
|
("LA", (76, 0)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_fill(self, mode, expected_pixel):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
(w, h) = im.size
|
(w, h) = im.size
|
||||||
transformed = im.transform(
|
transformed = im.transform(
|
||||||
|
@ -90,8 +93,7 @@ class TestImageTransform:
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
fillcolor="red",
|
fillcolor="red",
|
||||||
)
|
)
|
||||||
|
assert transformed.getpixel((w - 1, h - 1)) == expected_pixel
|
||||||
assert transformed.getpixel((w - 1, h - 1)) == pixel
|
|
||||||
|
|
||||||
def test_mesh(self):
|
def test_mesh(self):
|
||||||
# this should be a checkerboard of halfsized hoppers in ul, lr
|
# this should be a checkerboard of halfsized hoppers in ul, lr
|
||||||
|
@ -222,14 +224,12 @@ class TestImageTransform:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.transform((100, 100), None)
|
im.transform((100, 100), None)
|
||||||
|
|
||||||
def test_unknown_resampling_filter(self):
|
@pytest.mark.parametrize("resample", (Image.Resampling.BOX, "unknown"))
|
||||||
|
def test_unknown_resampling_filter(self, resample):
|
||||||
with hopper() as im:
|
with hopper() as im:
|
||||||
(w, h) = im.size
|
(w, h) = im.size
|
||||||
for resample in (Image.Resampling.BOX, "unknown"):
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.transform(
|
im.transform((100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample)
|
||||||
(100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestImageTransformAffine:
|
class TestImageTransformAffine:
|
||||||
|
@ -239,7 +239,16 @@ class TestImageTransformAffine:
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
return im.crop((10, 20, im.width - 10, im.height - 20))
|
return im.crop((10, 20, im.width - 10, im.height - 20))
|
||||||
|
|
||||||
def _test_rotate(self, deg, transpose):
|
@pytest.mark.parametrize(
|
||||||
|
"deg, transpose",
|
||||||
|
(
|
||||||
|
(0, None),
|
||||||
|
(90, Image.Transpose.ROTATE_90),
|
||||||
|
(180, Image.Transpose.ROTATE_180),
|
||||||
|
(270, Image.Transpose.ROTATE_270),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_rotate(self, deg, transpose):
|
||||||
im = self._test_image()
|
im = self._test_image()
|
||||||
|
|
||||||
angle = -math.radians(deg)
|
angle = -math.radians(deg)
|
||||||
|
@ -271,77 +280,65 @@ class TestImageTransformAffine:
|
||||||
)
|
)
|
||||||
assert_image_equal(transposed, transformed)
|
assert_image_equal(transposed, transformed)
|
||||||
|
|
||||||
def test_rotate_0_deg(self):
|
@pytest.mark.parametrize(
|
||||||
self._test_rotate(0, None)
|
"scale, epsilon_scale",
|
||||||
|
(
|
||||||
def test_rotate_90_deg(self):
|
(1.1, 6.9),
|
||||||
self._test_rotate(90, Image.Transpose.ROTATE_90)
|
(1.5, 5.5),
|
||||||
|
(2.0, 5.5),
|
||||||
def test_rotate_180_deg(self):
|
(2.3, 3.7),
|
||||||
self._test_rotate(180, Image.Transpose.ROTATE_180)
|
(2.5, 3.7),
|
||||||
|
),
|
||||||
def test_rotate_270_deg(self):
|
)
|
||||||
self._test_rotate(270, Image.Transpose.ROTATE_270)
|
@pytest.mark.parametrize(
|
||||||
|
"resample,epsilon",
|
||||||
def _test_resize(self, scale, epsilonscale):
|
(
|
||||||
|
(Image.Resampling.NEAREST, 0),
|
||||||
|
(Image.Resampling.BILINEAR, 2),
|
||||||
|
(Image.Resampling.BICUBIC, 1),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_resize(self, scale, epsilon_scale, resample, epsilon):
|
||||||
im = self._test_image()
|
im = self._test_image()
|
||||||
|
|
||||||
size_up = int(round(im.width * scale)), int(round(im.height * scale))
|
size_up = int(round(im.width * scale)), int(round(im.height * scale))
|
||||||
matrix_up = [1 / scale, 0, 0, 0, 1 / scale, 0, 0, 0]
|
matrix_up = [1 / scale, 0, 0, 0, 1 / scale, 0, 0, 0]
|
||||||
matrix_down = [scale, 0, 0, 0, scale, 0, 0, 0]
|
matrix_down = [scale, 0, 0, 0, scale, 0, 0, 0]
|
||||||
|
|
||||||
for resample, epsilon in [
|
|
||||||
(Image.Resampling.NEAREST, 0),
|
|
||||||
(Image.Resampling.BILINEAR, 2),
|
|
||||||
(Image.Resampling.BICUBIC, 1),
|
|
||||||
]:
|
|
||||||
transformed = im.transform(size_up, self.transform, matrix_up, resample)
|
transformed = im.transform(size_up, self.transform, matrix_up, resample)
|
||||||
transformed = transformed.transform(
|
transformed = transformed.transform(
|
||||||
im.size, self.transform, matrix_down, resample
|
im.size, self.transform, matrix_down, resample
|
||||||
)
|
)
|
||||||
assert_image_similar(transformed, im, epsilon * epsilonscale)
|
assert_image_similar(transformed, im, epsilon * epsilon_scale)
|
||||||
|
|
||||||
def test_resize_1_1x(self):
|
@pytest.mark.parametrize(
|
||||||
self._test_resize(1.1, 6.9)
|
"x, y, epsilon_scale",
|
||||||
|
(
|
||||||
def test_resize_1_5x(self):
|
(0.1, 0, 3.7),
|
||||||
self._test_resize(1.5, 5.5)
|
(0.6, 0, 9.1),
|
||||||
|
(50, 50, 0),
|
||||||
def test_resize_2_0x(self):
|
),
|
||||||
self._test_resize(2.0, 5.5)
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
def test_resize_2_3x(self):
|
"resample, epsilon",
|
||||||
self._test_resize(2.3, 3.7)
|
(
|
||||||
|
(Image.Resampling.NEAREST, 0),
|
||||||
def test_resize_2_5x(self):
|
(Image.Resampling.BILINEAR, 1.5),
|
||||||
self._test_resize(2.5, 3.7)
|
(Image.Resampling.BICUBIC, 1),
|
||||||
|
),
|
||||||
def _test_translate(self, x, y, epsilonscale):
|
)
|
||||||
|
def test_translate(self, x, y, epsilon_scale, resample, epsilon):
|
||||||
im = self._test_image()
|
im = self._test_image()
|
||||||
|
|
||||||
size_up = int(round(im.width + x)), int(round(im.height + y))
|
size_up = int(round(im.width + x)), int(round(im.height + y))
|
||||||
matrix_up = [1, 0, -x, 0, 1, -y, 0, 0]
|
matrix_up = [1, 0, -x, 0, 1, -y, 0, 0]
|
||||||
matrix_down = [1, 0, x, 0, 1, y, 0, 0]
|
matrix_down = [1, 0, x, 0, 1, y, 0, 0]
|
||||||
|
|
||||||
for resample, epsilon in [
|
|
||||||
(Image.Resampling.NEAREST, 0),
|
|
||||||
(Image.Resampling.BILINEAR, 1.5),
|
|
||||||
(Image.Resampling.BICUBIC, 1),
|
|
||||||
]:
|
|
||||||
transformed = im.transform(size_up, self.transform, matrix_up, resample)
|
transformed = im.transform(size_up, self.transform, matrix_up, resample)
|
||||||
transformed = transformed.transform(
|
transformed = transformed.transform(
|
||||||
im.size, self.transform, matrix_down, resample
|
im.size, self.transform, matrix_down, resample
|
||||||
)
|
)
|
||||||
assert_image_similar(transformed, im, epsilon * epsilonscale)
|
assert_image_similar(transformed, im, epsilon * epsilon_scale)
|
||||||
|
|
||||||
def test_translate_0_1(self):
|
|
||||||
self._test_translate(0.1, 0, 3.7)
|
|
||||||
|
|
||||||
def test_translate_0_6(self):
|
|
||||||
self._test_translate(0.6, 0, 9.1)
|
|
||||||
|
|
||||||
def test_translate_50(self):
|
|
||||||
self._test_translate(50, 50, 0)
|
|
||||||
|
|
||||||
|
|
||||||
class TestImageTransformPerspective(TestImageTransformAffine):
|
class TestImageTransformPerspective(TestImageTransformAffine):
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL.Image import Transpose
|
from PIL.Image import Transpose
|
||||||
|
|
||||||
from . import helper
|
from . import helper
|
||||||
|
@ -9,8 +11,8 @@ HOPPER = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_flip_left_right():
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def transpose(mode):
|
def test_flip_left_right(mode):
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.FLIP_LEFT_RIGHT)
|
out = im.transpose(Transpose.FLIP_LEFT_RIGHT)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
|
@ -22,12 +24,9 @@ def test_flip_left_right():
|
||||||
assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, y - 2))
|
assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, y - 2))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, y - 2))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, y - 2))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def test_flip_top_bottom():
|
def test_flip_top_bottom(mode):
|
||||||
def transpose(mode):
|
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.FLIP_TOP_BOTTOM)
|
out = im.transpose(Transpose.FLIP_TOP_BOTTOM)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
|
@ -39,12 +38,9 @@ def test_flip_top_bottom():
|
||||||
assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
|
assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((x - 2, 1))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((x - 2, 1))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def test_rotate_90():
|
def test_rotate_90(mode):
|
||||||
def transpose(mode):
|
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.ROTATE_90)
|
out = im.transpose(Transpose.ROTATE_90)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
|
@ -56,12 +52,9 @@ def test_rotate_90():
|
||||||
assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, x - 2))
|
assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, x - 2))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, 1))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, 1))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def test_rotate_180():
|
def test_rotate_180(mode):
|
||||||
def transpose(mode):
|
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.ROTATE_180)
|
out = im.transpose(Transpose.ROTATE_180)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
|
@ -73,12 +66,9 @@ def test_rotate_180():
|
||||||
assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, 1))
|
assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, 1))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def test_rotate_270():
|
def test_rotate_270(mode):
|
||||||
def transpose(mode):
|
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.ROTATE_270)
|
out = im.transpose(Transpose.ROTATE_270)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
|
@ -90,12 +80,9 @@ def test_rotate_270():
|
||||||
assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
|
assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, x - 2))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, x - 2))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def test_transpose():
|
def test_transpose(mode):
|
||||||
def transpose(mode):
|
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.TRANSPOSE)
|
out = im.transpose(Transpose.TRANSPOSE)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
|
@ -107,12 +94,9 @@ def test_transpose():
|
||||||
assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, 1))
|
assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, 1))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, x - 2))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, x - 2))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def test_tranverse():
|
def test_tranverse(mode):
|
||||||
def transpose(mode):
|
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.TRANSVERSE)
|
out = im.transpose(Transpose.TRANSVERSE)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
|
@ -124,12 +108,9 @@ def test_tranverse():
|
||||||
assert im.getpixel((1, y - 2)) == out.getpixel((1, x - 2))
|
assert im.getpixel((1, y - 2)) == out.getpixel((1, x - 2))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def test_roundtrip():
|
def test_roundtrip(mode):
|
||||||
for mode in HOPPER:
|
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
|
|
||||||
def transpose(first, second):
|
def transpose(first, second):
|
||||||
|
|
|
@ -174,19 +174,24 @@ def test_exceptions():
|
||||||
psRGB = ImageCms.createProfile("sRGB")
|
psRGB = ImageCms.createProfile("sRGB")
|
||||||
pLab = ImageCms.createProfile("LAB")
|
pLab = ImageCms.createProfile("LAB")
|
||||||
t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB")
|
t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB")
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="mode mismatch"):
|
||||||
t.apply_in_place(hopper("RGBA"))
|
t.apply_in_place(hopper("RGBA"))
|
||||||
|
|
||||||
# the procedural pyCMS API uses PyCMSError for all sorts of errors
|
# the procedural pyCMS API uses PyCMSError for all sorts of errors
|
||||||
with hopper() as im:
|
with hopper() as im:
|
||||||
with pytest.raises(ImageCms.PyCMSError):
|
with pytest.raises(ImageCms.PyCMSError, match="cannot open profile file"):
|
||||||
ImageCms.profileToProfile(im, "foo", "bar")
|
ImageCms.profileToProfile(im, "foo", "bar")
|
||||||
with pytest.raises(ImageCms.PyCMSError):
|
|
||||||
|
with pytest.raises(ImageCms.PyCMSError, match="cannot open profile file"):
|
||||||
ImageCms.buildTransform("foo", "bar", "RGB", "RGB")
|
ImageCms.buildTransform("foo", "bar", "RGB", "RGB")
|
||||||
with pytest.raises(ImageCms.PyCMSError):
|
|
||||||
|
with pytest.raises(ImageCms.PyCMSError, match="Invalid type for Profile"):
|
||||||
ImageCms.getProfileName(None)
|
ImageCms.getProfileName(None)
|
||||||
skip_missing()
|
skip_missing()
|
||||||
with pytest.raises(ImageCms.PyCMSError):
|
|
||||||
|
# Python <= 3.9: "an integer is required (got type NoneType)"
|
||||||
|
# Python > 3.9: "'NoneType' object cannot be interpreted as an integer"
|
||||||
|
with pytest.raises(ImageCms.PyCMSError, match="integer"):
|
||||||
ImageCms.isIntentSupported(SRGB, None, None)
|
ImageCms.isIntentSupported(SRGB, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
@ -201,15 +206,32 @@ def test_lab_color_profile():
|
||||||
|
|
||||||
|
|
||||||
def test_unsupported_color_space():
|
def test_unsupported_color_space():
|
||||||
with pytest.raises(ImageCms.PyCMSError):
|
with pytest.raises(
|
||||||
|
ImageCms.PyCMSError,
|
||||||
|
match=re.escape(
|
||||||
|
"Color space not supported for on-the-fly profile creation (unsupported)"
|
||||||
|
),
|
||||||
|
):
|
||||||
ImageCms.createProfile("unsupported")
|
ImageCms.createProfile("unsupported")
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_color_temperature():
|
def test_invalid_color_temperature():
|
||||||
with pytest.raises(ImageCms.PyCMSError):
|
with pytest.raises(
|
||||||
|
ImageCms.PyCMSError,
|
||||||
|
match='Color temperature must be numeric, "invalid" not valid',
|
||||||
|
):
|
||||||
ImageCms.createProfile("LAB", "invalid")
|
ImageCms.createProfile("LAB", "invalid")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("flag", ("my string", -1))
|
||||||
|
def test_invalid_flag(flag):
|
||||||
|
with hopper() as im:
|
||||||
|
with pytest.raises(
|
||||||
|
ImageCms.PyCMSError, match="flags must be an integer between 0 and "
|
||||||
|
):
|
||||||
|
ImageCms.profileToProfile(im, "foo", "bar", flags=flag)
|
||||||
|
|
||||||
|
|
||||||
def test_simple_lab():
|
def test_simple_lab():
|
||||||
i = Image.new("RGB", (10, 10), (128, 128, 128))
|
i = Image.new("RGB", (10, 10), (128, 128, 128))
|
||||||
|
|
||||||
|
@ -461,9 +483,9 @@ def test_profile_typesafety():
|
||||||
prepatch, these would segfault, postpatch they should emit a typeerror
|
prepatch, these would segfault, postpatch they should emit a typeerror
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError, match="Invalid type for Profile"):
|
||||||
ImageCms.ImageCmsProfile(0).tobytes()
|
ImageCms.ImageCmsProfile(0).tobytes()
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError, match="Invalid type for Profile"):
|
||||||
ImageCms.ImageCmsProfile(1).tobytes()
|
ImageCms.ImageCmsProfile(1).tobytes()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,9 @@ def test_mode_mismatch():
|
||||||
ImageDraw.ImageDraw(im, mode="L")
|
ImageDraw.ImageDraw(im, mode="L")
|
||||||
|
|
||||||
|
|
||||||
def helper_arc(bbox, start, end):
|
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
||||||
|
@pytest.mark.parametrize("start, end", ((0, 180), (0.5, 180.4)))
|
||||||
|
def test_arc(bbox, start, end):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
@ -76,16 +78,6 @@ def helper_arc(bbox, start, end):
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_arc.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_arc.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_arc1():
|
|
||||||
helper_arc(BBOX1, 0, 180)
|
|
||||||
helper_arc(BBOX1, 0.5, 180.4)
|
|
||||||
|
|
||||||
|
|
||||||
def test_arc2():
|
|
||||||
helper_arc(BBOX2, 0, 180)
|
|
||||||
helper_arc(BBOX2, 0.5, 180.4)
|
|
||||||
|
|
||||||
|
|
||||||
def test_arc_end_le_start():
|
def test_arc_end_le_start():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -192,29 +184,21 @@ def test_bitmap():
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_bitmap.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_bitmap.png")
|
||||||
|
|
||||||
|
|
||||||
def helper_chord(mode, bbox, start, end):
|
@pytest.mark.parametrize("mode", ("RGB", "L"))
|
||||||
|
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
||||||
|
def test_chord(mode, bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new(mode, (W, H))
|
im = Image.new(mode, (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
expected = f"Tests/images/imagedraw_chord_{mode}.png"
|
expected = f"Tests/images/imagedraw_chord_{mode}.png"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.chord(bbox, start, end, fill="red", outline="yellow")
|
draw.chord(bbox, 0, 180, fill="red", outline="yellow")
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, expected, 1)
|
assert_image_similar_tofile(im, expected, 1)
|
||||||
|
|
||||||
|
|
||||||
def test_chord1():
|
|
||||||
for mode in ["RGB", "L"]:
|
|
||||||
helper_chord(mode, BBOX1, 0, 180)
|
|
||||||
|
|
||||||
|
|
||||||
def test_chord2():
|
|
||||||
for mode in ["RGB", "L"]:
|
|
||||||
helper_chord(mode, BBOX2, 0, 180)
|
|
||||||
|
|
||||||
|
|
||||||
def test_chord_width():
|
def test_chord_width():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -263,7 +247,9 @@ def test_chord_too_fat():
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_chord_too_fat.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_chord_too_fat.png")
|
||||||
|
|
||||||
|
|
||||||
def helper_ellipse(mode, bbox):
|
@pytest.mark.parametrize("mode", ("RGB", "L"))
|
||||||
|
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
||||||
|
def test_ellipse(mode, bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new(mode, (W, H))
|
im = Image.new(mode, (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
@ -276,16 +262,6 @@ def helper_ellipse(mode, bbox):
|
||||||
assert_image_similar_tofile(im, expected, 1)
|
assert_image_similar_tofile(im, expected, 1)
|
||||||
|
|
||||||
|
|
||||||
def test_ellipse1():
|
|
||||||
for mode in ["RGB", "L"]:
|
|
||||||
helper_ellipse(mode, BBOX1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_ellipse2():
|
|
||||||
for mode in ["RGB", "L"]:
|
|
||||||
helper_ellipse(mode, BBOX2)
|
|
||||||
|
|
||||||
|
|
||||||
def test_ellipse_translucent():
|
def test_ellipse_translucent():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -405,7 +381,8 @@ def test_ellipse_various_sizes_filled():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def helper_line(points):
|
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
|
||||||
|
def test_line(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
@ -417,14 +394,6 @@ def helper_line(points):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png")
|
||||||
|
|
||||||
|
|
||||||
def test_line1():
|
|
||||||
helper_line(POINTS1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_line2():
|
|
||||||
helper_line(POINTS2)
|
|
||||||
|
|
||||||
|
|
||||||
def test_shape1():
|
def test_shape1():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (100, 100), "white")
|
im = Image.new("RGB", (100, 100), "white")
|
||||||
|
@ -484,7 +453,9 @@ def test_transform():
|
||||||
assert_image_equal(im, expected)
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def helper_pieslice(bbox, start, end):
|
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
||||||
|
@pytest.mark.parametrize("start, end", ((-92, 46), (-92.2, 46.2)))
|
||||||
|
def test_pieslice(bbox, start, end):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
@ -496,16 +467,6 @@ def helper_pieslice(bbox, start, end):
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_pieslice.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_pieslice1():
|
|
||||||
helper_pieslice(BBOX1, -92, 46)
|
|
||||||
helper_pieslice(BBOX1, -92.2, 46.2)
|
|
||||||
|
|
||||||
|
|
||||||
def test_pieslice2():
|
|
||||||
helper_pieslice(BBOX2, -92, 46)
|
|
||||||
helper_pieslice(BBOX2, -92.2, 46.2)
|
|
||||||
|
|
||||||
|
|
||||||
def test_pieslice_width():
|
def test_pieslice_width():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -585,7 +546,8 @@ def test_pieslice_no_spikes():
|
||||||
assert_image_equal(im, im_pre_erase)
|
assert_image_equal(im, im_pre_erase)
|
||||||
|
|
||||||
|
|
||||||
def helper_point(points):
|
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
|
||||||
|
def test_point(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
@ -597,15 +559,8 @@ def helper_point(points):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png")
|
||||||
|
|
||||||
|
|
||||||
def test_point1():
|
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
|
||||||
helper_point(POINTS1)
|
def test_polygon(points):
|
||||||
|
|
||||||
|
|
||||||
def test_point2():
|
|
||||||
helper_point(POINTS2)
|
|
||||||
|
|
||||||
|
|
||||||
def helper_polygon(points):
|
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
@ -617,18 +572,10 @@ def helper_polygon(points):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png")
|
||||||
|
|
||||||
|
|
||||||
def test_polygon1():
|
@pytest.mark.parametrize("mode", ("RGB", "L"))
|
||||||
helper_polygon(POINTS1)
|
def test_polygon_kite(mode):
|
||||||
|
|
||||||
|
|
||||||
def test_polygon2():
|
|
||||||
helper_polygon(POINTS2)
|
|
||||||
|
|
||||||
|
|
||||||
def test_polygon_kite():
|
|
||||||
# Test drawing lines of different gradients (dx>dy, dy>dx) and
|
# Test drawing lines of different gradients (dx>dy, dy>dx) and
|
||||||
# vertical (dx==0) and horizontal (dy==0) lines
|
# vertical (dx==0) and horizontal (dy==0) lines
|
||||||
for mode in ["RGB", "L"]:
|
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new(mode, (W, H))
|
im = Image.new(mode, (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
@ -682,7 +629,8 @@ def test_polygon_translucent():
|
||||||
assert_image_equal_tofile(im, expected)
|
assert_image_equal_tofile(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def helper_rectangle(bbox):
|
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
||||||
|
def test_rectangle(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
@ -694,14 +642,6 @@ def helper_rectangle(bbox):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png")
|
||||||
|
|
||||||
|
|
||||||
def test_rectangle1():
|
|
||||||
helper_rectangle(BBOX1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_rectangle2():
|
|
||||||
helper_rectangle(BBOX2)
|
|
||||||
|
|
||||||
|
|
||||||
def test_big_rectangle():
|
def test_big_rectangle():
|
||||||
# Test drawing a rectangle bigger than the image
|
# Test drawing a rectangle bigger than the image
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -1232,21 +1172,39 @@ def test_textsize_empty_string():
|
||||||
# Act
|
# Act
|
||||||
# Should not cause 'SystemError: <built-in method getsize of
|
# Should not cause 'SystemError: <built-in method getsize of
|
||||||
# ImagingFont object at 0x...> returned NULL without setting an error'
|
# ImagingFont object at 0x...> returned NULL without setting an error'
|
||||||
draw.textsize("")
|
draw.textbbox((0, 0), "")
|
||||||
draw.textsize("\n")
|
draw.textbbox((0, 0), "\n")
|
||||||
draw.textsize("test\n")
|
draw.textbbox((0, 0), "test\n")
|
||||||
|
draw.textlength("")
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("freetype2")
|
@skip_unless_feature("freetype2")
|
||||||
def test_textsize_stroke():
|
def test_textbbox_stroke():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
|
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
|
||||||
|
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
assert draw.textsize("A", font, stroke_width=2) == (16, 20)
|
assert draw.textbbox((2, 2), "A", font, stroke_width=2) == (0, 4, 16, 20)
|
||||||
assert draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2) == (52, 44)
|
assert draw.textbbox((2, 2), "A", font, stroke_width=4) == (-2, 2, 18, 22)
|
||||||
|
assert draw.textbbox((2, 2), "ABC\nAaaa", font, stroke_width=2) == (0, 4, 52, 44)
|
||||||
|
assert draw.textbbox((2, 2), "ABC\nAaaa", font, stroke_width=4) == (-2, 2, 54, 50)
|
||||||
|
|
||||||
|
|
||||||
|
def test_textsize_deprecation():
|
||||||
|
im = Image.new("RGB", (W, H))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
|
with pytest.warns(DeprecationWarning) as log:
|
||||||
|
draw.textsize("Hello")
|
||||||
|
assert len(log) == 1
|
||||||
|
with pytest.warns(DeprecationWarning) as log:
|
||||||
|
draw.textsize("Hello\nWorld")
|
||||||
|
assert len(log) == 1
|
||||||
|
with pytest.warns(DeprecationWarning) as log:
|
||||||
|
draw.multiline_textsize("Hello\nWorld")
|
||||||
|
assert len(log) == 1
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("freetype2")
|
@skip_unless_feature("freetype2")
|
||||||
|
@ -1296,6 +1254,23 @@ def test_stroke_multiline():
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_setting_default_font():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (100, 250))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
ImageDraw.ImageDraw.font = font
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
try:
|
||||||
|
assert draw.getfont() == font
|
||||||
|
finally:
|
||||||
|
ImageDraw.ImageDraw.font = None
|
||||||
|
assert isinstance(draw.getfont(), ImageFont.ImageFont)
|
||||||
|
|
||||||
|
|
||||||
def test_same_color_outline():
|
def test_same_color_outline():
|
||||||
# Prepare shape
|
# Prepare shape
|
||||||
x0, y0 = 5, 5
|
x0, y0 = 5, 5
|
||||||
|
@ -1468,7 +1443,7 @@ def test_discontiguous_corners_polygon():
|
||||||
assert_image_similar_tofile(img, expected, 1)
|
assert_image_similar_tofile(img, expected, 1)
|
||||||
|
|
||||||
|
|
||||||
def test_polygon():
|
def test_polygon2():
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red")
|
draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red")
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageDraw2
|
from PIL import Image, ImageDraw, ImageDraw2
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
|
@ -50,27 +52,19 @@ def test_sanity():
|
||||||
draw.line(list(range(10)), pen)
|
draw.line(list(range(10)), pen)
|
||||||
|
|
||||||
|
|
||||||
def helper_ellipse(mode, bbox):
|
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
||||||
|
def test_ellipse(bbox):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw2.Draw(im)
|
draw = ImageDraw2.Draw(im)
|
||||||
pen = ImageDraw2.Pen("blue", width=2)
|
pen = ImageDraw2.Pen("blue", width=2)
|
||||||
brush = ImageDraw2.Brush("green")
|
brush = ImageDraw2.Brush("green")
|
||||||
expected = f"Tests/images/imagedraw_ellipse_{mode}.png"
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.ellipse(bbox, pen, brush)
|
draw.ellipse(bbox, pen, brush)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_similar_tofile(im, expected, 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_RGB.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_ellipse1():
|
|
||||||
helper_ellipse("RGB", BBOX1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_ellipse2():
|
|
||||||
helper_ellipse("RGB", BBOX2)
|
|
||||||
|
|
||||||
|
|
||||||
def test_ellipse_edge():
|
def test_ellipse_edge():
|
||||||
|
@ -86,7 +80,8 @@ def test_ellipse_edge():
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_edge.png", 1)
|
||||||
|
|
||||||
|
|
||||||
def helper_line(points):
|
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
|
||||||
|
def test_line(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw2.Draw(im)
|
draw = ImageDraw2.Draw(im)
|
||||||
|
@ -99,14 +94,6 @@ def helper_line(points):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png")
|
||||||
|
|
||||||
|
|
||||||
def test_line1_pen():
|
|
||||||
helper_line(POINTS1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_line2_pen():
|
|
||||||
helper_line(POINTS2)
|
|
||||||
|
|
||||||
|
|
||||||
def test_line_pen_as_brush():
|
def test_line_pen_as_brush():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
|
@ -122,7 +109,8 @@ def test_line_pen_as_brush():
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_line.png")
|
||||||
|
|
||||||
|
|
||||||
def helper_polygon(points):
|
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
|
||||||
|
def test_polygon(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw2.Draw(im)
|
draw = ImageDraw2.Draw(im)
|
||||||
|
@ -136,15 +124,8 @@ def helper_polygon(points):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png")
|
||||||
|
|
||||||
|
|
||||||
def test_polygon1():
|
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
|
||||||
helper_polygon(POINTS1)
|
def test_rectangle(bbox):
|
||||||
|
|
||||||
|
|
||||||
def test_polygon2():
|
|
||||||
helper_polygon(POINTS2)
|
|
||||||
|
|
||||||
|
|
||||||
def helper_rectangle(bbox):
|
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (W, H))
|
im = Image.new("RGB", (W, H))
|
||||||
draw = ImageDraw2.Draw(im)
|
draw = ImageDraw2.Draw(im)
|
||||||
|
@ -158,14 +139,6 @@ def helper_rectangle(bbox):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle.png")
|
||||||
|
|
||||||
|
|
||||||
def test_rectangle1():
|
|
||||||
helper_rectangle(BBOX1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_rectangle2():
|
|
||||||
helper_rectangle(BBOX2)
|
|
||||||
|
|
||||||
|
|
||||||
def test_big_rectangle():
|
def test_big_rectangle():
|
||||||
# Test drawing a rectangle bigger than the image
|
# Test drawing a rectangle bigger than the image
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -205,7 +178,9 @@ def test_textsize():
|
||||||
font = ImageDraw2.Font("white", FONT_PATH)
|
font = ImageDraw2.Font("white", FONT_PATH)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
|
with pytest.warns(DeprecationWarning) as log:
|
||||||
size = draw.textsize("ImageDraw2", font)
|
size = draw.textsize("ImageDraw2", font)
|
||||||
|
assert len(log) == 1
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert size[1] == 12
|
assert size[1] == 12
|
||||||
|
@ -221,9 +196,10 @@ def test_textsize_empty_string():
|
||||||
# Act
|
# Act
|
||||||
# Should not cause 'SystemError: <built-in method getsize of
|
# Should not cause 'SystemError: <built-in method getsize of
|
||||||
# ImagingFont object at 0x...> returned NULL without setting an error'
|
# ImagingFont object at 0x...> returned NULL without setting an error'
|
||||||
draw.textsize("", font)
|
draw.textbbox((0, 0), "", font)
|
||||||
draw.textsize("\n", font)
|
draw.textbbox((0, 0), "\n", font)
|
||||||
draw.textsize("test\n", font)
|
draw.textbbox((0, 0), "test\n", font)
|
||||||
|
draw.textlength("", font)
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("freetype2")
|
@skip_unless_feature("freetype2")
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageEnhance
|
from PIL import Image, ImageEnhance
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
@ -39,13 +41,13 @@ def _check_alpha(im, original, op, amount):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_alpha():
|
@pytest.mark.parametrize("op", ("Color", "Brightness", "Contrast", "Sharpness"))
|
||||||
|
def test_alpha(op):
|
||||||
# Issue https://github.com/python-pillow/Pillow/issues/899
|
# Issue https://github.com/python-pillow/Pillow/issues/899
|
||||||
# Is alpha preserved through image enhancement?
|
# Is alpha preserved through image enhancement?
|
||||||
|
|
||||||
original = _half_transparent_image()
|
original = _half_transparent_image()
|
||||||
|
|
||||||
for op in ["Color", "Brightness", "Contrast", "Sharpness"]:
|
|
||||||
for amount in [0, 0.5, 1.0]:
|
for amount in [0, 0.5, 1.0]:
|
||||||
_check_alpha(
|
_check_alpha(
|
||||||
getattr(ImageEnhance, op)(original).enhance(amount),
|
getattr(ImageEnhance, op)(original).enhance(amount),
|
||||||
|
|
|
@ -140,8 +140,8 @@ def test_ligature_features():
|
||||||
target = "Tests/images/test_ligature_features.png"
|
target = "Tests/images/test_ligature_features.png"
|
||||||
assert_image_similar_tofile(im, target, 0.5)
|
assert_image_similar_tofile(im, target, 0.5)
|
||||||
|
|
||||||
liga_size = ttf.getsize("fi", features=["-liga"])
|
liga_bbox = ttf.getbbox("fi", features=["-liga"])
|
||||||
assert liga_size == (13, 19)
|
assert liga_bbox == (0, 4, 13, 19)
|
||||||
|
|
||||||
|
|
||||||
def test_kerning_features():
|
def test_kerning_features():
|
||||||
|
|
|
@ -65,8 +65,10 @@ def create_lut():
|
||||||
|
|
||||||
|
|
||||||
# create_lut()
|
# create_lut()
|
||||||
def test_lut():
|
@pytest.mark.parametrize(
|
||||||
for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"):
|
"op", ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge")
|
||||||
|
)
|
||||||
|
def test_lut(op):
|
||||||
lb = ImageMorph.LutBuilder(op_name=op)
|
lb = ImageMorph.LutBuilder(op_name=op)
|
||||||
assert lb.get_lut() is None
|
assert lb.get_lut() is None
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,16 @@ def test_contain(new_size):
|
||||||
assert new_im.size == (256, 256)
|
assert new_im.size == (256, 256)
|
||||||
|
|
||||||
|
|
||||||
|
def test_contain_round():
|
||||||
|
im = Image.new("1", (43, 63), 1)
|
||||||
|
new_im = ImageOps.contain(im, (5, 7))
|
||||||
|
assert new_im.width == 5
|
||||||
|
|
||||||
|
im = Image.new("1", (63, 43), 1)
|
||||||
|
new_im = ImageOps.contain(im, (7, 5))
|
||||||
|
assert new_im.height == 5
|
||||||
|
|
||||||
|
|
||||||
def test_pad():
|
def test_pad():
|
||||||
# Same ratio
|
# Same ratio
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
@ -130,6 +140,30 @@ def test_pad():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pad_round():
|
||||||
|
im = Image.new("1", (1, 1), 1)
|
||||||
|
new_im = ImageOps.pad(im, (4, 1))
|
||||||
|
assert new_im.load()[2, 0] == 1
|
||||||
|
|
||||||
|
new_im = ImageOps.pad(im, (1, 4))
|
||||||
|
assert new_im.load()[0, 2] == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||||
|
def test_palette(mode):
|
||||||
|
im = hopper(mode)
|
||||||
|
|
||||||
|
# Expand
|
||||||
|
expanded_im = ImageOps.expand(im)
|
||||||
|
assert_image_equal(im.convert("RGB"), expanded_im.convert("RGB"))
|
||||||
|
|
||||||
|
# Pad
|
||||||
|
padded_im = ImageOps.pad(im, (256, 128), centering=(0, 0))
|
||||||
|
assert_image_equal(
|
||||||
|
im.convert("RGB"), padded_im.convert("RGB").crop((0, 0, 128, 128))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_pil163():
|
def test_pil163():
|
||||||
# Division by zero in equalize if < 255 pixels in image (@PIL163)
|
# Division by zero in equalize if < 255 pixels in image (@PIL163)
|
||||||
|
|
||||||
|
@ -345,12 +379,16 @@ def test_exif_transpose():
|
||||||
check(orientation_im)
|
check(orientation_im)
|
||||||
|
|
||||||
# Orientation from "XML:com.adobe.xmp" info key
|
# Orientation from "XML:com.adobe.xmp" info key
|
||||||
with Image.open("Tests/images/xmp_tags_orientation.png") as im:
|
for suffix in ("", "_exiftool"):
|
||||||
|
with Image.open("Tests/images/xmp_tags_orientation" + suffix + ".png") as im:
|
||||||
assert im.getexif()[0x0112] == 3
|
assert im.getexif()[0x0112] == 3
|
||||||
|
|
||||||
transposed_im = ImageOps.exif_transpose(im)
|
transposed_im = ImageOps.exif_transpose(im)
|
||||||
assert 0x0112 not in transposed_im.getexif()
|
assert 0x0112 not in transposed_im.getexif()
|
||||||
|
|
||||||
|
transposed_im._reload_exif()
|
||||||
|
assert 0x0112 not in transposed_im.getexif()
|
||||||
|
|
||||||
# Orientation from "Raw profile type exif" info key
|
# Orientation from "Raw profile type exif" info key
|
||||||
# This test image has been manually hexedited from exif_imagemagick.png
|
# This test image has been manually hexedited from exif_imagemagick.png
|
||||||
# to have a different orientation
|
# to have a different orientation
|
||||||
|
|
|
@ -45,8 +45,8 @@ def test_viewer_show(order):
|
||||||
not on_ci() or is_win32(),
|
not on_ci() or is_win32(),
|
||||||
reason="Only run on CIs; hangs on Windows CIs",
|
reason="Only run on CIs; hangs on Windows CIs",
|
||||||
)
|
)
|
||||||
def test_show():
|
@pytest.mark.parametrize("mode", ("1", "I;16", "LA", "RGB", "RGBA"))
|
||||||
for mode in ("1", "I;16", "LA", "RGB", "RGBA"):
|
def test_show(mode):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
assert ImageShow.show(im)
|
assert ImageShow.show(im)
|
||||||
|
|
||||||
|
@ -70,8 +70,8 @@ def test_viewer():
|
||||||
viewer.get_command(None)
|
viewer.get_command(None)
|
||||||
|
|
||||||
|
|
||||||
def test_viewers():
|
@pytest.mark.parametrize("viewer", ImageShow._viewers)
|
||||||
for viewer in ImageShow._viewers:
|
def test_viewers(viewer):
|
||||||
try:
|
try:
|
||||||
viewer.get_command("test.jpg")
|
viewer.get_command("test.jpg")
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
|
@ -95,9 +95,9 @@ def test_ipythonviewer():
|
||||||
not on_ci() or is_win32(),
|
not on_ci() or is_win32(),
|
||||||
reason="Only run on CIs; hangs on Windows CIs",
|
reason="Only run on CIs; hangs on Windows CIs",
|
||||||
)
|
)
|
||||||
def test_file_deprecated(tmp_path):
|
@pytest.mark.parametrize("viewer", ImageShow._viewers)
|
||||||
|
def test_file_deprecated(tmp_path, viewer):
|
||||||
f = str(tmp_path / "temp.jpg")
|
f = str(tmp_path / "temp.jpg")
|
||||||
for viewer in ImageShow._viewers:
|
|
||||||
hopper().save(f)
|
hopper().save(f)
|
||||||
with pytest.warns(DeprecationWarning):
|
with pytest.warns(DeprecationWarning):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -54,8 +54,8 @@ def test_kw():
|
||||||
assert im is None
|
assert im is None
|
||||||
|
|
||||||
|
|
||||||
def test_photoimage():
|
@pytest.mark.parametrize("mode", TK_MODES)
|
||||||
for mode in TK_MODES:
|
def test_photoimage(mode):
|
||||||
# test as image:
|
# test as image:
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
|
|
||||||
|
@ -69,9 +69,16 @@ def test_photoimage():
|
||||||
assert_image_equal(reloaded, im.convert("RGBA"))
|
assert_image_equal(reloaded, im.convert("RGBA"))
|
||||||
|
|
||||||
|
|
||||||
def test_photoimage_blank():
|
def test_photoimage_apply_transparency():
|
||||||
|
with Image.open("Tests/images/pil123p.png") as im:
|
||||||
|
im_tk = ImageTk.PhotoImage(im)
|
||||||
|
reloaded = ImageTk.getimage(im_tk)
|
||||||
|
assert_image_equal(reloaded, im.convert("RGBA"))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mode", TK_MODES)
|
||||||
|
def test_photoimage_blank(mode):
|
||||||
# test a image using mode/size:
|
# test a image using mode/size:
|
||||||
for mode in TK_MODES:
|
|
||||||
im_tk = ImageTk.PhotoImage(mode, (100, 100))
|
im_tk = ImageTk.PhotoImage(mode, (100, 100))
|
||||||
|
|
||||||
assert im_tk.width() == 100
|
assert im_tk.width() == 100
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import hopper
|
from .helper import hopper
|
||||||
|
@ -20,12 +22,11 @@ def verify(im1):
|
||||||
), f"got {repr(p1)} from mode {im1.mode} at {xy}, expected {repr(p2)}"
|
), f"got {repr(p1)} from mode {im1.mode} at {xy}, expected {repr(p2)}"
|
||||||
|
|
||||||
|
|
||||||
def test_basic(tmp_path):
|
@pytest.mark.parametrize("mode", ("L", "I;16", "I;16B", "I;16L", "I"))
|
||||||
|
def test_basic(tmp_path, mode):
|
||||||
# PIL 1.1 has limited support for 16-bit image data. Check that
|
# PIL 1.1 has limited support for 16-bit image data. Check that
|
||||||
# create/copy/transform and save works as expected.
|
# create/copy/transform and save works as expected.
|
||||||
|
|
||||||
def basic(mode):
|
|
||||||
|
|
||||||
im_in = original.convert(mode)
|
im_in = original.convert(mode)
|
||||||
verify(im_in)
|
verify(im_in)
|
||||||
|
|
||||||
|
@ -72,14 +73,6 @@ def test_basic(tmp_path):
|
||||||
im_in.putpixel((0, 0), 512)
|
im_in.putpixel((0, 0), 512)
|
||||||
assert im_in.getpixel((0, 0)) == min(512, maximum)
|
assert im_in.getpixel((0, 0)) == min(512, maximum)
|
||||||
|
|
||||||
basic("L")
|
|
||||||
|
|
||||||
basic("I;16")
|
|
||||||
basic("I;16B")
|
|
||||||
basic("I;16L")
|
|
||||||
|
|
||||||
basic("I")
|
|
||||||
|
|
||||||
|
|
||||||
def test_tobytes():
|
def test_tobytes():
|
||||||
def tobytes(mode):
|
def tobytes(mode):
|
||||||
|
|
|
@ -137,19 +137,9 @@ def test_save_tiff_uint16():
|
||||||
assert img_px[0, 0] == pixel_value
|
assert img_px[0, 0] == pixel_value
|
||||||
|
|
||||||
|
|
||||||
def test_to_array():
|
@pytest.mark.parametrize(
|
||||||
def _to_array(mode, dtype):
|
"mode, dtype",
|
||||||
img = hopper(mode)
|
(
|
||||||
|
|
||||||
# Resize to non-square
|
|
||||||
img = img.crop((3, 0, 124, 127))
|
|
||||||
assert img.size == (121, 127)
|
|
||||||
|
|
||||||
np_img = numpy.array(img)
|
|
||||||
_test_img_equals_nparray(img, np_img)
|
|
||||||
assert np_img.dtype == dtype
|
|
||||||
|
|
||||||
modes = [
|
|
||||||
("L", numpy.uint8),
|
("L", numpy.uint8),
|
||||||
("I", numpy.int32),
|
("I", numpy.int32),
|
||||||
("F", numpy.float32),
|
("F", numpy.float32),
|
||||||
|
@ -163,10 +153,18 @@ def test_to_array():
|
||||||
("I;16B", ">u2"),
|
("I;16B", ">u2"),
|
||||||
("I;16L", "<u2"),
|
("I;16L", "<u2"),
|
||||||
("HSV", numpy.uint8),
|
("HSV", numpy.uint8),
|
||||||
]
|
),
|
||||||
|
)
|
||||||
|
def test_to_array(mode, dtype):
|
||||||
|
img = hopper(mode)
|
||||||
|
|
||||||
for mode in modes:
|
# Resize to non-square
|
||||||
_to_array(*mode)
|
img = img.crop((3, 0, 124, 127))
|
||||||
|
assert img.size == (121, 127)
|
||||||
|
|
||||||
|
np_img = numpy.array(img)
|
||||||
|
_test_img_equals_nparray(img, np_img)
|
||||||
|
assert np_img.dtype == dtype
|
||||||
|
|
||||||
|
|
||||||
def test_point_lut():
|
def test_point_lut():
|
||||||
|
|