Merge branch 'main' into convert_mode

This commit is contained in:
Andrew Murray 2022-10-04 19:35:10 +11:00
commit 689fbb4329
164 changed files with 4221 additions and 3071 deletions

View File

@ -25,8 +25,8 @@ install:
- mv c:\pillow-depends-main c:\pillow-depends
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
- ..\pillow-depends\gs9561w32.exe /S
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs9.56.1\bin;%PATH%
- ..\pillow-depends\gs1000w32.exe /S
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH%
- cd c:\pillow\winbuild\
- ps: |
c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\

View File

@ -35,15 +35,13 @@ python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
python3 -m pip install test-image-results
if [[ $(uname) != CYGWIN* ]]; then
# TODO Remove condition when NumPy supports 3.11
if ! [ "$GHA_PYTHON_VERSION" == "3.11-dev" ]; then python3 -m pip install numpy ; fi
python3 -m pip install numpy
# PyQt6 doesn't support PyPy3
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
sudo apt-get -qq install libegl1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 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
fi

17
.github/renovate.json vendored Normal file
View 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"]
}

View File

@ -11,6 +11,13 @@ on:
- "**.h"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
Fuzzing:
runs-on: ubuntu-latest

View File

@ -2,6 +2,13 @@ name: Lint
on: [push, pull_request, workflow_dispatch]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
@ -13,7 +20,7 @@ jobs:
- uses: actions/checkout@v3
- name: pre-commit cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.cache/pre-commit
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
@ -21,7 +28,7 @@ jobs:
lint-pre-commit-
- name: Set up Python
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: "3.10"
cache: pip

View File

@ -12,11 +12,9 @@ python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
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
# TODO Remove condition when NumPy supports 3.11
if ! [ "$GHA_PYTHON_VERSION" == "3.11-dev" ]; then python3 -m pip install numpy ; fi
python3 -m pip install numpy
# extra test images
pushd depends && ./install_extra_test_images.sh && popd

View File

@ -7,8 +7,18 @@ on:
- main
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
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'
runs-on: ubuntu-latest
steps:

View File

@ -8,6 +8,10 @@ on:
permissions:
issues: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
stale:
if: github.repository_owner == 'python-pillow'
@ -16,7 +20,7 @@ jobs:
steps:
- name: "Check issues"
uses: actions/stale@v5
uses: actions/stale@v6
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
only-labels: "Awaiting OP Action"

View File

@ -2,6 +2,13 @@ name: Test Cygwin
on: [push, pull_request, workflow_dispatch]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: windows-latest
@ -41,7 +48,7 @@ jobs:
qt5-devel-tools subversion xorg-server-extra zlib-devel
- name: Add Lapack to PATH
uses: egor-tensin/cleanup-path@v1
uses: egor-tensin/cleanup-path@v2
with:
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
@ -99,6 +106,8 @@ jobs:
name: Cygwin Python 3.${{ matrix.python-minor-version }}
success:
permissions:
contents: none
needs: build
runs-on: ubuntu-latest
name: Cygwin Test Successful

View File

@ -2,6 +2,13 @@ name: Test Docker
on: [push, pull_request, workflow_dispatch]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
@ -76,12 +83,14 @@ jobs:
MATRIX_DOCKER: ${{ matrix.docker }}
- name: Upload coverage
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v3
with:
flags: GHA_Docker
name: ${{ matrix.docker }}
success:
permissions:
contents: none
needs: build
runs-on: ubuntu-latest
name: Docker Test Successful

View File

@ -2,6 +2,13 @@ name: Test MinGW
on: [push, pull_request, workflow_dispatch]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: windows-latest
@ -70,13 +77,15 @@ jobs:
python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
- name: Upload coverage
run: |
python3 -m pip install codecov
bash <(curl -s https://codecov.io/bash) -F GHA_Windows
env:
CODECOV_NAME: ${{ matrix.name }}
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: GHA_Windows
name: ${{ matrix.name }}
success:
permissions:
contents: none
needs: build
runs-on: ubuntu-latest
name: MinGW Test Successful

View File

@ -13,6 +13,13 @@ on:
- "**.h"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
@ -21,7 +28,7 @@ jobs:
fail-fast: false
matrix:
docker: [
ubuntu-20.04-focal-amd64-valgrind,
ubuntu-22.04-jammy-amd64-valgrind,
]
dockerTag: [main]

View File

@ -2,6 +2,13 @@ name: Test Windows
on: [push, pull_request, workflow_dispatch]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: windows-latest
@ -33,7 +40,7 @@ jobs:
# sets env: pythonLocation
- name: Set up Python
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.architecture }}
@ -52,8 +59,8 @@ jobs:
7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\"
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH
winbuild\depends\gs9561w32.exe /S
echo "C:\Program Files (x86)\gs\gs9.56.1\bin" >> $env:GITHUB_PATH
winbuild\depends\gs1000w32.exe /S
echo "C:\Program Files (x86)\gs\gs10.0.0\bin" >> $env:GITHUB_PATH
xcopy /S /Y winbuild\depends\test_images\* Tests\images\
@ -63,7 +70,7 @@ jobs:
- name: Cache build
id: build-cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: winbuild\build
key:
@ -168,7 +175,7 @@ jobs:
shell: pwsh
- name: Upload coverage
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: GHA_Windows
@ -189,6 +196,8 @@ jobs:
path: dist\*.whl
success:
permissions:
contents: none
needs: build
runs-on: ubuntu-latest
name: Windows Test Successful

View File

@ -2,6 +2,13 @@ name: Test
on: [push, pull_request, workflow_dispatch]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
@ -27,11 +34,6 @@ jobs:
REVERSE: "--reverse"
- python-version: "3.8"
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 }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
@ -40,7 +42,7 @@ jobs:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: pip
@ -96,7 +98,6 @@ jobs:
- name: Docs
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.10
run: |
python3 -m pip install furo sphinx-copybutton sphinx-issues sphinx-removed-in sphinxext-opengraph
make doccheck
- name: After success
@ -104,11 +105,15 @@ jobs:
.ci/after_success.sh
- name: Upload coverage
run: bash <(curl -s https://codecov.io/bash) -F ${{ matrix.codecov-flag }}
env:
CODECOV_NAME: ${{ matrix.os }} Python ${{ matrix.python-version }}
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: ${{ matrix.os == 'macos-latest' && 'GHA_macOS' || 'GHA_Ubuntu' }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
success:
permissions:
contents: none
needs: build
runs-on: ubuntu-latest
name: Test Successful

View File

@ -1,4 +1,5 @@
name: Tidelift Align
on:
schedule:
- cron: "30 2 * * *" # daily at 02:30 UTC
@ -12,6 +13,13 @@ on:
- ".github/workflows/tidelift.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
if: github.repository_owner == 'python-pillow'

View File

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/psf/black
rev: 22.3.0
rev: 22.8.0
hooks:
- id: black
args: ["--target-version", "py37"]
@ -14,18 +14,18 @@ repos:
- id: isort
- repo: https://github.com/asottile/yesqa
rev: v1.3.0
rev: v1.4.0
hooks:
- id: yesqa
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.2.0
rev: v1.3.1
hooks:
- id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
rev: 5.0.4
hooks:
- id: flake8
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
@ -37,13 +37,14 @@ repos:
- id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.2.0
rev: v4.3.0
hooks:
- id: check-merge-conflict
- id: check-json
- id: check-yaml
- repo: https://github.com/sphinx-contrib/sphinx-lint
rev: v0.6
rev: v0.6.1
hooks:
- id: sphinx-lint

View File

@ -2,9 +2,135 @@
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
[Piolie, radarhere]

View File

@ -25,6 +25,7 @@ exclude .coveragerc
exclude .editorconfig
exclude .readthedocs.yml
exclude codecov.yml
exclude renovate.json
global-exclude .git*
global-exclude *.pyc
global-exclude *.so

View File

@ -17,11 +17,12 @@ coverage:
.PHONY: doc
doc:
python3 -c "import PIL" > /dev/null 2>&1 || python3 -m pip install .
$(MAKE) -C docs html
.PHONY: 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.
# We don't control them. But do check, and update them to the target of their redirects.
$(MAKE) -C docs linkcheck || true

View File

@ -74,6 +74,9 @@ As of 2019, Pillow development is
<a href="https://pypi.org/project/Pillow/"><img
alt="Number of PyPI downloads"
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>
</tr>
<tr>

View File

@ -96,8 +96,8 @@ Released as needed privately to individual vendors for critical security-related
## Binary Distributions
### Windows
* [ ] Contact `@cgohlke` for Windows binaries via release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174.
* [ ] Download and extract tarball from `@cgohlke` and copy into `dist/`
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
and copy into `dist/`
### Mac and Linux
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):

Binary file not shown.

BIN
Tests/images/1.eps Normal file

Binary file not shown.

BIN
Tests/images/ati1.dds Normal file

Binary file not shown.

BIN
Tests/images/ati1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 B

BIN
Tests/images/ati2.dds Normal file

Binary file not shown.

BIN
Tests/images/child_ifd.tiff Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

BIN
Tests/images/mmap_error.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
Tests/images/rgba.psd Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -33,9 +33,9 @@ def fuzz_font(data):
# different font objects.
return
font.getsize_multiline("ABC\nAaaa")
font.getbbox("ABC")
font.getmask("test text")
with Image.new(mode="RGBA", size=(200, 200)) as 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")

View File

@ -1,19 +1,18 @@
import PIL
import PIL.Image
from PIL import Image
def test_sanity():
# 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.
im = PIL.Image.new("1", (100, 100))
im = Image.new("1", (100, 100))
assert (im.mode, im.size) == ("1", (100, 100))
assert len(im.tobytes()) == 1300
# Create images in all remaining major modes.
PIL.Image.new("L", (100, 100))
PIL.Image.new("P", (100, 100))
PIL.Image.new("RGB", (100, 100))
PIL.Image.new("I", (100, 100))
PIL.Image.new("F", (100, 100))
Image.new("L", (100, 100))
Image.new("P", (100, 100))
Image.new("RGB", (100, 100))
Image.new("I", (100, 100))
Image.new("F", (100, 100))

View File

@ -61,6 +61,11 @@ class TestDecompressionBomb:
with Image.open("Tests/images/decompression_bomb.gif"):
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):
with pytest.raises(Image.DecompressionBombError):
with Image.open("Tests/images/bmp/b/reallybig.bmp"):

View File

@ -70,14 +70,14 @@ def test_libimagequant_version():
assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant"))
def test_check_modules():
for feature in features.modules:
assert features.check_module(feature) in [True, False]
@pytest.mark.parametrize("feature", features.modules)
def test_check_modules(feature):
assert features.check_module(feature) in [True, False]
def test_check_codecs():
for feature in features.codecs:
assert features.check_codec(feature) in [True, False]
@pytest.mark.parametrize("feature", features.codecs)
def test_check_codecs(feature):
assert features.check_codec(feature) in [True, False]
def test_check_warns_on_nonexistent():

View File

@ -39,13 +39,12 @@ def test_apng_basic():
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
def test_apng_fdat():
with Image.open("Tests/images/apng/split_fdat.png") as im:
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
with Image.open("Tests/images/apng/split_fdat_zero_chunk.png") as im:
@pytest.mark.parametrize(
"filename",
("Tests/images/apng/split_fdat.png", "Tests/images/apng/split_fdat_zero_chunk.png"),
)
def test_apng_fdat(filename):
with Image.open(filename) as im:
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (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)
def test_apng_sequence_errors():
test_files = [
@pytest.mark.parametrize(
"test_file",
(
"sequence_start.png",
"sequence_gap.png",
"sequence_repeat.png",
@ -334,12 +334,13 @@ def test_apng_sequence_errors():
"sequence_reorder.png",
"sequence_reorder_chunk.png",
"sequence_fdat_fctl.png",
]
for f in test_files:
with pytest.raises(SyntaxError):
with Image.open(f"Tests/images/apng/{f}") as im:
im.seek(im.n_frames - 1)
im.load()
),
)
def test_apng_sequence_errors(test_file):
with pytest.raises(SyntaxError):
with Image.open(f"Tests/images/apng/{test_file}") as im:
im.seek(im.n_frames - 1)
im.load()
def test_apng_save(tmp_path):

View File

@ -39,6 +39,13 @@ def test_invalid_file():
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():
output = io.BytesIO()
im = hopper()
@ -51,6 +58,18 @@ def test_save_to_bytes():
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):
outfile = str(tmp_path / "temp.bmp")
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")
# 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():
with Image.open("Tests/images/hopper_rle8.bmp") as im:
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
# to have rows with too much data
with Image.open("Tests/images/hopper_rle8_row_overflow.bmp") as im:

View File

@ -1,3 +1,5 @@
import pytest
from PIL import ContainerIO, Image
from .helper import hopper
@ -59,89 +61,89 @@ def test_seek_mode_2():
assert container.tell() == 100
def test_read_n0():
@pytest.mark.parametrize("bytesmode", (True, False))
def test_read_n0(bytesmode):
# Arrange
for bytesmode in (True, False):
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)
# Act
container.seek(81)
data = container.read()
# Act
container.seek(81)
data = container.read()
# Assert
if bytesmode:
data = data.decode()
assert data == "7\nThis is line 8\n"
# Assert
if bytesmode:
data = data.decode()
assert data == "7\nThis is line 8\n"
def test_read_n():
@pytest.mark.parametrize("bytesmode", (True, False))
def test_read_n(bytesmode):
# Arrange
for bytesmode in (True, False):
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)
# Act
container.seek(81)
data = container.read(3)
# Act
container.seek(81)
data = container.read(3)
# Assert
if bytesmode:
data = data.decode()
assert data == "7\nT"
# Assert
if bytesmode:
data = data.decode()
assert data == "7\nT"
def test_read_eof():
@pytest.mark.parametrize("bytesmode", (True, False))
def test_read_eof(bytesmode):
# Arrange
for bytesmode in (True, False):
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)
# Act
container.seek(100)
data = container.read()
# Act
container.seek(100)
data = container.read()
# Assert
if bytesmode:
data = data.decode()
assert data == ""
# Assert
if bytesmode:
data = data.decode()
assert data == ""
def test_readline():
@pytest.mark.parametrize("bytesmode", (True, False))
def test_readline(bytesmode):
# Arrange
for bytesmode in (True, False):
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 0, 120)
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 0, 120)
# Act
data = container.readline()
# Act
data = container.readline()
# Assert
if bytesmode:
data = data.decode()
assert data == "This is line 1\n"
# Assert
if bytesmode:
data = data.decode()
assert data == "This is line 1\n"
def test_readlines():
@pytest.mark.parametrize("bytesmode", (True, False))
def test_readlines(bytesmode):
# Arrange
for bytesmode in (True, False):
expected = [
"This is line 1\n",
"This is line 2\n",
"This is line 3\n",
"This is line 4\n",
"This is line 5\n",
"This is line 6\n",
"This is line 7\n",
"This is line 8\n",
]
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 0, 120)
expected = [
"This is line 1\n",
"This is line 2\n",
"This is line 3\n",
"This is line 4\n",
"This is line 5\n",
"This is line 6\n",
"This is line 7\n",
"This is line 8\n",
]
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 0, 120)
# Act
data = container.readlines()
# Act
data = container.readlines()
# Assert
if bytesmode:
data = [line.decode() for line in data]
assert data == expected
# Assert
if bytesmode:
data = [line.decode() for line in data]
assert data == expected

View File

@ -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_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_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_UNORM = "Tests/images/bc5_unorm.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"))
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(
("image_path", "expected_path"),
(

View File

@ -124,14 +124,6 @@ def test_file_object(tmp_path):
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")
def test_bytesio_object():
with open(FILE1, "rb") as f:
@ -146,6 +138,11 @@ def test_bytesio_object():
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):
im = hopper("RGBA")
tmpfile = str(tmp_path / "temp.eps")
@ -198,25 +195,23 @@ def test_render_scale2():
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_resize():
files = [FILE1, FILE2, "Tests/images/illu10_preview.eps"]
for fn in files:
with Image.open(fn) as im:
new_size = (100, 100)
im = im.resize(new_size)
assert im.size == new_size
@pytest.mark.parametrize("filename", (FILE1, FILE2, "Tests/images/illu10_preview.eps"))
def test_resize(filename):
with Image.open(filename) as im:
new_size = (100, 100)
im = im.resize(new_size)
assert im.size == new_size
@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
# Arrange
files = [FILE1, FILE2]
for fn in files:
with Image.open(FILE1) as im:
new_size = (100, 100)
im.thumbnail(new_size)
assert max(im.size) == max(new_size)
with Image.open(filename) as im:
new_size = (100, 100)
im.thumbnail(new_size)
assert max(im.size) == max(new_size)
def test_read_binary_preview():
@ -261,20 +256,19 @@ def test_readline(tmp_path):
_test_readline_file_psfile(s, ending)
def test_open_eps():
# https://github.com/python-pillow/Pillow/issues/1104
# Arrange
FILES = [
@pytest.mark.parametrize(
"filename",
(
"Tests/images/illu10_no_preview.eps",
"Tests/images/illu10_preview.eps",
"Tests/images/illuCS6_no_preview.eps",
"Tests/images/illuCS6_preview.eps",
]
# Act / Assert
for filename in FILES:
with Image.open(filename) as img:
assert img.mode == "RGB"
),
)
def test_open_eps(filename):
# https://github.com/python-pillow/Pillow/issues/1104
with Image.open(filename) as img:
assert img.mode == "RGB"
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")

View File

@ -2,11 +2,22 @@ import pytest
from PIL import Image
from .helper import assert_image_equal_tofile
FpxImagePlugin = pytest.importorskip(
"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():
# Test an invalid OLE file
invalid_file = "Tests/images/flower.jpg"

View File

@ -158,6 +158,9 @@ def test_optimize_correctness():
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
# These do optimize the palette
check(256, 511, 256)
check(255, 511, 255)
check(129, 511, 129)
check(128, 511, 128)
check(64, 511, 64)
check(4, 511, 4)
@ -167,11 +170,6 @@ def test_optimize_correctness():
check(64, 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():
im = Image.frombytes("L", (16, 16), bytes(range(256)))
@ -180,6 +178,19 @@ def test_optimize_full_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):
out = str(tmp_path / "temp.gif")
im = hopper()
@ -388,6 +399,11 @@ def test_no_change():
assert im.is_animated
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():
with Image.open(TEST_GIF) as im:
@ -777,24 +793,24 @@ def test_identical_frames(tmp_path):
assert reread.info["duration"] == 4500
def test_identical_frames_to_single_frame(tmp_path):
for duration in ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500):
out = str(tmp_path / "temp.gif")
im_list = [
Image.new("L", (100, 100), "#000"),
Image.new("L", (100, 100), "#000"),
Image.new("L", (100, 100), "#000"),
]
@pytest.mark.parametrize(
"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")
im_list = [
Image.new("L", (100, 100), "#000"),
Image.new("L", (100, 100), "#000"),
Image.new("L", (100, 100), "#000"),
]
im_list[0].save(
out, save_all=True, append_images=im_list[1:], duration=duration
)
with Image.open(out) as reread:
# Assert that all frames were combined
assert reread.n_frames == 1
im_list[0].save(out, save_all=True, append_images=im_list[1:], duration=duration)
with Image.open(out) as reread:
# Assert that all frames were combined
assert reread.n_frames == 1
# Assert that the new duration is the total of the identical frames
assert reread.info["duration"] == 8500
# Assert that the new duration is the total of the identical frames
assert reread.info["duration"] == 8500
def test_number_of_loops(tmp_path):
@ -982,8 +998,8 @@ def test_append_images(tmp_path):
def test_transparent_optimize(tmp_path):
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
# transparency.
# Need a palette that isn't using the 0 color, and one that's > 128 items where the
# transparent color is actually the top palette entry to trigger the bug.
# Need a palette that isn't using the 0 color,
# where the transparent color is actually the top palette entry to trigger the bug.
data = bytes(range(1, 254))
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
@ -993,10 +1009,10 @@ def test_transparent_optimize(tmp_path):
im.putpalette(palette)
out = str(tmp_path / "temp.gif")
im.save(out, transparency=253)
with Image.open(out) as reloaded:
im.save(out, transparency=im.getpixel((252, 0)))
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):
@ -1071,6 +1087,19 @@ def test_palette_save_P(tmp_path):
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):
frames = []
colors = ((255, 0, 0), (0, 255, 0))

View File

@ -78,15 +78,12 @@ def test_eoferror():
im.seek(n_frames - 1)
def test_roundtrip(tmp_path):
def roundtrip(mode):
out = str(tmp_path / "temp.im")
im = hopper(mode)
im.save(out)
assert_image_equal_tofile(im, out)
for mode in ["RGB", "P", "PA"]:
roundtrip(mode)
@pytest.mark.parametrize("mode", ("RGB", "P", "PA"))
def test_roundtrip(mode, tmp_path):
out = str(tmp_path / "temp.im")
im = hopper(mode)
im.save(out)
assert_image_equal_tofile(im, out)
def test_save_unsupported_mode(tmp_path):

View File

@ -150,27 +150,30 @@ class TestFileJpeg:
assert not im1.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
def test(n):
# The ICC APP marker can store 65519 bytes per marker, so
# using a 4-byte test code should allow us to detect out of
# order issues.
icc_profile = (b"Test" * int(n / 4 + 1))[:n]
assert len(icc_profile) == n # sanity
im1 = self.roundtrip(hopper(), icc_profile=icc_profile)
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
# The ICC APP marker can store 65519 bytes per marker, so
# using a 4-byte test code should allow us to detect out of
# order issues.
icc_profile = (b"Test" * int(n / 4 + 1))[:n]
assert len(icc_profile) == n # sanity
im1 = self.roundtrip(hopper(), icc_profile=icc_profile)
assert im1.info.get("icc_profile") == (icc_profile or None)
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
@ -649,11 +652,11 @@ class TestFileJpeg:
# Assert
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()
for mode in ["1", "L", "RGB", "RGBX", "CMYK", "YCbCr"]:
img = Image.new(mode, (20, 20))
img.save(out, "JPEG")
img = Image.new(mode, (20, 20))
img.save(out, "JPEG")
def test_save_wrong_modes(self, tmp_path):
# ref https://github.com/python-pillow/Pillow/issues/2005

View File

@ -126,14 +126,14 @@ def test_prog_res_rt():
assert_image_equal(im, test_card)
def test_default_num_resolutions():
for num_resolutions in range(2, 6):
d = 1 << (num_resolutions - 1)
im = test_card.resize((d - 1, d - 1))
with pytest.raises(OSError):
roundtrip(im, num_resolutions=num_resolutions)
reloaded = roundtrip(im)
assert_image_equal(im, reloaded)
@pytest.mark.parametrize("num_resolutions", range(2, 6))
def test_default_num_resolutions(num_resolutions):
d = 1 << (num_resolutions - 1)
im = test_card.resize((d - 1, d - 1))
with pytest.raises(OSError):
roundtrip(im, num_resolutions=num_resolutions)
reloaded = roundtrip(im)
assert_image_equal(im, reloaded)
def test_reduce():
@ -266,14 +266,11 @@ def test_rgba():
assert jp2.mode == "RGBA"
def test_16bit_monochrome_has_correct_mode():
with Image.open("Tests/images/16bit.cropped.j2k") as j2k:
j2k.load()
assert j2k.mode == "I;16"
with Image.open("Tests/images/16bit.cropped.jp2") as jp2:
jp2.load()
assert jp2.mode == "I;16"
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
def test_16bit_monochrome_has_correct_mode(ext):
with Image.open("Tests/images/16bit.cropped" + ext) as im:
im.load()
assert im.mode == "I;16"
def test_16bit_monochrome_jp2_like_tiff():

View File

@ -135,50 +135,50 @@ class TestFileLibTiff(LibTiffTestCase):
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"""
for legacy_api in [False, True]:
f = str(tmp_path / "temp.tiff")
with Image.open("Tests/images/hopper_g4.tif") as img:
img.save(f, tiffinfo=img.tag)
f = str(tmp_path / "temp.tiff")
with Image.open("Tests/images/hopper_g4.tif") as img:
img.save(f, tiffinfo=img.tag)
if legacy_api:
original = img.tag.named()
else:
original = img.tag_v2.named()
if legacy_api:
original = img.tag.named()
else:
original = img.tag_v2.named()
# PhotometricInterpretation is set from SAVE_INFO,
# not the original image.
ignored = [
"StripByteCounts",
"RowsPerStrip",
"PageNumber",
"PhotometricInterpretation",
]
# PhotometricInterpretation is set from SAVE_INFO,
# not the original image.
ignored = [
"StripByteCounts",
"RowsPerStrip",
"PageNumber",
"PhotometricInterpretation",
]
with Image.open(f) as loaded:
if legacy_api:
reloaded = loaded.tag.named()
else:
reloaded = loaded.tag_v2.named()
with Image.open(f) as loaded:
if legacy_api:
reloaded = loaded.tag.named()
else:
reloaded = loaded.tag_v2.named()
for tag, value in itertools.chain(reloaded.items(), original.items()):
if tag not in ignored:
val = original[tag]
if tag.endswith("Resolution"):
if legacy_api:
assert val[0][0] / val[0][1] == (
4294967295 / 113653537
), f"{tag} didn't roundtrip"
else:
assert val == 37.79000115940079, f"{tag} didn't roundtrip"
for tag, value in itertools.chain(reloaded.items(), original.items()):
if tag not in ignored:
val = original[tag]
if tag.endswith("Resolution"):
if legacy_api:
assert val[0][0] / val[0][1] == (
4294967295 / 113653537
), f"{tag} didn't roundtrip"
else:
assert val == value, f"{tag} didn't roundtrip"
assert val == 37.79000115940079, f"{tag} didn't roundtrip"
else:
assert val == value, f"{tag} didn't roundtrip"
# https://github.com/python-pillow/Pillow/issues/1561
requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"]
for field in requested_fields:
assert field in reloaded, f"{field} not in metadata"
# https://github.com/python-pillow/Pillow/issues/1561
requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"]
for field in requested_fields:
assert field in reloaded, f"{field} not in metadata"
@pytest.mark.valgrind_known_error(reason="Known invalid metadata")
def test_additional_metadata(self, tmp_path):
@ -509,20 +509,13 @@ class TestFileLibTiff(LibTiffTestCase):
# colormap/palette tag
assert len(reloaded.tag_v2[320]) == 768
def xtest_bw_compression_w_rgb(self, tmp_path):
"""This test passes, but when running all tests causes a failure due
to output on stderr from the error thrown by libtiff. We need to
capture that but not now"""
@pytest.mark.parametrize("compression", ("tiff_ccitt", "group3", "group4"))
def test_bw_compression_w_rgb(self, compression, tmp_path):
im = hopper("RGB")
out = str(tmp_path / "temp.tif")
with pytest.raises(OSError):
im.save(out, compression="tiff_ccitt")
with pytest.raises(OSError):
im.save(out, compression="group3")
with pytest.raises(OSError):
im.save(out, compression="group4")
im.save(out, compression=compression)
def test_fp_leak(self):
im = Image.open("Tests/images/hopper_g4_500.tif")
@ -856,7 +849,7 @@ class TestFileLibTiff(LibTiffTestCase):
def test_strip_ycbcr_jpeg_2x2_sampling(self):
infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
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(
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):
infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
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):
infile = "Tests/images/tiff_tiled_cmyk_jpeg.tif"
@ -877,7 +870,7 @@ class TestFileLibTiff(LibTiffTestCase):
def test_tiled_ycbcr_jpeg_1x1_sampling(self):
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
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(
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):
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
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):
# 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 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))
out = str(tmp_path / "temp.tif")
TiffImagePlugin.STRIP_SIZE = 2**18
if not argument:
TiffImagePlugin.STRIP_SIZE = 2**18
try:
im.save(out, compression="tiff_adobe_deflate")
arguments = {"compression": "tiff_adobe_deflate"}
if argument:
arguments["strip_size"] = 2**18
im.save(out, **arguments)
with Image.open(out) as im:
assert len(im.tag_v2[STRIPOFFSETS]) == 1

View File

@ -5,15 +5,19 @@ import pytest
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"]
pytestmark = skip_unless_feature("jpg")
def frame_roundtrip(im, **options):
# Note that for now, there is no MPO saving functionality
def roundtrip(im, **options):
out = BytesIO()
im.save(out, "MPO", **options)
test_bytes = out.tell()
@ -23,13 +27,13 @@ def frame_roundtrip(im, **options):
return im
def test_sanity():
for test_file in test_files:
with Image.open(test_file) as im:
im.load()
assert im.mode == "RGB"
assert im.size == (640, 480)
assert im.format == "MPO"
@pytest.mark.parametrize("test_file", test_files)
def test_sanity(test_file):
with Image.open(test_file) as im:
im.load()
assert im.mode == "RGB"
assert im.size == (640, 480)
assert im.format == "MPO"
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
@ -62,26 +66,25 @@ def test_context_manager():
im.load()
def test_app():
for test_file in test_files:
# Test APP/COM reader (@PIL135)
with Image.open(test_file) as im:
assert im.applist[0][0] == "APP1"
assert im.applist[1][0] == "APP2"
assert (
im.applist[1][1][:16]
== b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00"
)
assert len(im.applist) == 2
@pytest.mark.parametrize("test_file", test_files)
def test_app(test_file):
# Test APP/COM reader (@PIL135)
with Image.open(test_file) as im:
assert im.applist[0][0] == "APP1"
assert im.applist[1][0] == "APP2"
assert (
im.applist[1][1][:16] == b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00"
)
assert len(im.applist) == 2
def test_exif():
for test_file in test_files:
with Image.open(test_file) as im:
info = im._getexif()
assert info[272] == "Nintendo 3DS"
assert info[296] == 2
assert info[34665] == 188
@pytest.mark.parametrize("test_file", test_files)
def test_exif(test_file):
with Image.open(test_file) as im:
info = im._getexif()
assert info[272] == "Nintendo 3DS"
assert info[296] == 2
assert info[34665] == 188
def test_frame_size():
@ -133,12 +136,12 @@ def test_reload_exif_after_seek():
assert 296 in exif
def test_mp():
for test_file in test_files:
with Image.open(test_file) as im:
mpinfo = im._getmp()
assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2
@pytest.mark.parametrize("test_file", test_files)
def test_mp(test_file):
with Image.open(test_file) as im:
mpinfo = im._getmp()
assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2
def test_mp_offset():
@ -158,48 +161,48 @@ def test_mp_no_data():
im.seek(1)
def test_mp_attribute():
for test_file in test_files:
with Image.open(test_file) as im:
mpinfo = im._getmp()
frame_number = 0
for mpentry in mpinfo[0xB002]:
mpattr = mpentry["Attribute"]
if frame_number:
assert not mpattr["RepresentativeImageFlag"]
else:
assert mpattr["RepresentativeImageFlag"]
assert not mpattr["DependentParentImageFlag"]
assert not mpattr["DependentChildImageFlag"]
assert mpattr["ImageDataFormat"] == "JPEG"
assert mpattr["MPType"] == "Multi-Frame Image: (Disparity)"
assert mpattr["Reserved"] == 0
frame_number += 1
@pytest.mark.parametrize("test_file", test_files)
def test_mp_attribute(test_file):
with Image.open(test_file) as im:
mpinfo = im._getmp()
frame_number = 0
for mpentry in mpinfo[0xB002]:
mpattr = mpentry["Attribute"]
if frame_number:
assert not mpattr["RepresentativeImageFlag"]
else:
assert mpattr["RepresentativeImageFlag"]
assert not mpattr["DependentParentImageFlag"]
assert not mpattr["DependentChildImageFlag"]
assert mpattr["ImageDataFormat"] == "JPEG"
assert mpattr["MPType"] == "Multi-Frame Image: (Disparity)"
assert mpattr["Reserved"] == 0
frame_number += 1
def test_seek():
for test_file in test_files:
with Image.open(test_file) as im:
assert im.tell() == 0
# prior to first image raises an error, both blatant and borderline
with pytest.raises(EOFError):
im.seek(-1)
with pytest.raises(EOFError):
im.seek(-523)
# after the final image raises an error,
# both blatant and borderline
with pytest.raises(EOFError):
im.seek(2)
with pytest.raises(EOFError):
im.seek(523)
# bad calls shouldn't change the frame
assert im.tell() == 0
# this one will work
im.seek(1)
assert im.tell() == 1
# and this one, too
im.seek(0)
assert im.tell() == 0
@pytest.mark.parametrize("test_file", test_files)
def test_seek(test_file):
with Image.open(test_file) as im:
assert im.tell() == 0
# prior to first image raises an error, both blatant and borderline
with pytest.raises(EOFError):
im.seek(-1)
with pytest.raises(EOFError):
im.seek(-523)
# after the final image raises an error,
# both blatant and borderline
with pytest.raises(EOFError):
im.seek(2)
with pytest.raises(EOFError):
im.seek(523)
# bad calls shouldn't change the frame
assert im.tell() == 0
# this one will work
im.seek(1)
assert im.tell() == 1
# and this one, too
im.seek(0)
assert im.tell() == 0
def test_n_frames():
@ -221,29 +224,54 @@ def test_eoferror():
im.seek(n_frames - 1)
def test_image_grab():
@pytest.mark.parametrize("test_file", test_files)
def test_image_grab(test_file):
with Image.open(test_file) as im:
assert im.tell() == 0
im0 = im.tobytes()
im.seek(1)
assert im.tell() == 1
im1 = im.tobytes()
im.seek(0)
assert im.tell() == 0
im02 = im.tobytes()
assert im0 == im02
assert im0 != im1
@pytest.mark.parametrize("test_file", test_files)
def test_save(test_file):
with Image.open(test_file) as im:
assert im.tell() == 0
jpg0 = roundtrip(im)
assert_image_similar(im, jpg0, 30)
im.seek(1)
assert im.tell() == 1
jpg1 = roundtrip(im)
assert_image_similar(im, jpg1, 30)
def test_save_all():
for test_file in test_files:
with Image.open(test_file) as im:
assert im.tell() == 0
im0 = im.tobytes()
im.seek(1)
assert im.tell() == 1
im1 = im.tobytes()
im_reloaded = roundtrip(im, save_all=True)
im.seek(0)
assert im.tell() == 0
im02 = im.tobytes()
assert im0 == im02
assert im0 != im1
assert_image_similar(im, im_reloaded, 30)
def test_save():
# Note that only individual frames can be saved at present
for test_file in test_files:
with Image.open(test_file) as im:
assert im.tell() == 0
jpg0 = frame_roundtrip(im)
assert_image_similar(im, jpg0, 30)
im.seek(1)
assert im.tell() == 1
jpg1 = frame_roundtrip(im)
assert_image_similar(im, jpg1, 30)
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

View File

@ -63,19 +63,7 @@ def test_p_mode(tmp_path):
roundtrip(tmp_path, mode)
def test_l_oserror(tmp_path):
# Arrange
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
@pytest.mark.parametrize("mode", ("L", "RGB"))
def test_oserror(tmp_path, mode):
with pytest.raises(OSError):
helper_save_as_palm(tmp_path, mode)

View File

@ -20,6 +20,11 @@ def test_sanity(tmp_path):
for mode in ("1", "L", "P", "RGB"):
_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
f = str(tmp_path / "temp.pcx")
im = hopper("RGBA")
@ -34,14 +39,14 @@ def test_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.
# Not that ImageMagick or GIMP write PCX that way.
# We were not handling properly.
for mode in ("1", "L", "P", "RGB"):
# larger, odd sized images are better here to ensure that
# we handle interrupted scan lines properly.
_roundtrip(tmp_path, hopper(mode).resize((511, 511)))
# larger, odd sized images are better here to ensure that
# we handle interrupted scan lines properly.
_roundtrip(tmp_path, hopper(mode).resize((511, 511)))
def test_odd_read():

View File

@ -6,7 +6,7 @@ import time
import pytest
from PIL import Image, PdfParser
from PIL import Image, PdfParser, features
from .helper import hopper, mark_if_feature_version
@ -37,45 +37,19 @@ def helper_save_as_pdf(tmp_path, mode, **kwargs):
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):
# Arrange
mode = "1"
# Act / Assert
outfile = helper_save_as_pdf(tmp_path, mode)
assert os.path.getsize(outfile) < 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)
assert os.path.getsize(outfile) < (5000 if features.check("libtiff") else 15000)
def test_unsupported_mode(tmp_path):

View File

@ -643,7 +643,9 @@ class TestFilePng:
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):
fp = BytesIO()
with PngImagePlugin.PngStream(fp) as png:

View File

@ -4,7 +4,7 @@ import pytest
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"
@ -107,6 +107,11 @@ def test_open_after_exclusive_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():
with Image.open(test_file) as im:
assert "icc_profile" in im.info

View File

@ -18,51 +18,48 @@ _ORIGINS = ("tl", "bl")
_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
def test_sanity(tmp_path):
for mode in _MODES:
@pytest.mark.parametrize("mode", _MODES)
def test_sanity(mode, tmp_path):
def roundtrip(original_im):
out = str(tmp_path / "temp.tga")
def roundtrip(original_im):
out = str(tmp_path / "temp.tga")
original_im.save(out, rle=rle)
with Image.open(out) as saved_im:
if rle:
assert saved_im.info["compression"] == original_im.info["compression"]
assert saved_im.info["orientation"] == original_im.info["orientation"]
if mode == "P":
assert saved_im.getpalette() == original_im.getpalette()
original_im.save(out, rle=rle)
with Image.open(out) as saved_im:
if rle:
assert_image_equal(saved_im, original_im)
png_paths = glob(os.path.join(_TGA_DIR_COMMON, f"*x*_{mode.lower()}.png"))
for png_path in png_paths:
with Image.open(png_path) as reference_im:
assert reference_im.mode == mode
path_no_ext = os.path.splitext(png_path)[0]
for origin, rle in product(_ORIGINS, (True, False)):
tga_path = "{}_{}_{}.tga".format(
path_no_ext, origin, "rle" if rle else "raw"
)
with Image.open(tga_path) as original_im:
assert original_im.format == "TGA"
assert original_im.get_format_mimetype() == "image/x-tga"
if rle:
assert original_im.info["compression"] == "tga_rle"
assert (
saved_im.info["compression"] == original_im.info["compression"]
original_im.info["orientation"]
== _ORIGIN_TO_ORIENTATION[origin]
)
assert saved_im.info["orientation"] == original_im.info["orientation"]
if mode == "P":
assert saved_im.getpalette() == original_im.getpalette()
if mode == "P":
assert original_im.getpalette() == reference_im.getpalette()
assert_image_equal(saved_im, original_im)
assert_image_equal(original_im, reference_im)
png_paths = glob(os.path.join(_TGA_DIR_COMMON, f"*x*_{mode.lower()}.png"))
for png_path in png_paths:
with Image.open(png_path) as reference_im:
assert reference_im.mode == mode
path_no_ext = os.path.splitext(png_path)[0]
for origin, rle in product(_ORIGINS, (True, False)):
tga_path = "{}_{}_{}.tga".format(
path_no_ext, origin, "rle" if rle else "raw"
)
with Image.open(tga_path) as original_im:
assert original_im.format == "TGA"
assert original_im.get_format_mimetype() == "image/x-tga"
if rle:
assert original_im.info["compression"] == "tga_rle"
assert (
original_im.info["orientation"]
== _ORIGIN_TO_ORIENTATION[origin]
)
if mode == "P":
assert original_im.getpalette() == reference_im.getpalette()
assert_image_equal(original_im, reference_im)
roundtrip(original_im)
roundtrip(original_im)
def test_palette_depth_16(tmp_path):
@ -123,6 +120,18 @@ def test_save(tmp_path):
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):
im = hopper("PA")
out = str(tmp_path / "temp.tga")

View File

@ -84,6 +84,24 @@ class TestFileTiff:
with Image.open("Tests/images/multipage.tiff") as im:
im.load()
@pytest.mark.parametrize(
"path, sizes",
(
("Tests/images/hopper.tif", ()),
("Tests/images/child_ifd.tiff", (16, 8)),
("Tests/images/child_ifd_jpeg.tiff", (20,)),
),
)
def test_get_child_images(self, path, sizes):
with Image.open(path) as im:
ims = im.get_child_images()
assert len(ims) == len(sizes)
for i, im in enumerate(ims):
w = sizes[i]
expected = Image.new("RGB", (w, w), "#f00")
assert_image_similar(im, expected, 1)
def test_mac_tiff(self):
# Read RGBa images from macOS [@PIL136]
@ -293,14 +311,17 @@ class TestFileTiff:
with Image.open("Tests/images/hopper_unknown_pixel_mode.tif"):
pass
def test_n_frames(self):
for path, n_frames in [
["Tests/images/multipage-lastframe.tif", 1],
["Tests/images/multipage.tiff", 3],
]:
with Image.open(path) as im:
assert im.n_frames == n_frames
assert im.is_animated == (n_frames != 1)
@pytest.mark.parametrize(
"path, n_frames",
(
("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:
assert im.n_frames == n_frames
assert im.is_animated == (n_frames != 1)
def test_eoferror(self):
with Image.open("Tests/images/multipage-lastframe.tif") as im:
@ -416,12 +437,12 @@ class TestFileTiff:
len_after = len(dict(im.ifd))
assert len_before == len_after + 1
def test_load_byte(self):
for legacy_api in [False, True]:
ifd = TiffImagePlugin.ImageFileDirectory_v2()
data = b"abc"
ret = ifd.load_byte(data, legacy_api)
assert ret == b"abc"
@pytest.mark.parametrize("legacy_api", (False, True))
def test_load_byte(self, legacy_api):
ifd = TiffImagePlugin.ImageFileDirectory_v2()
data = b"abc"
ret = ifd.load_byte(data, legacy_api)
assert ret == b"abc"
def test_load_string(self):
ifd = TiffImagePlugin.ImageFileDirectory_v2()
@ -667,18 +688,15 @@ class TestFileTiff:
with Image.open(outfile) as reloaded:
assert_image_equal_tofile(reloaded, infile)
def test_palette(self, tmp_path):
def roundtrip(mode):
outfile = str(tmp_path / "temp.tif")
@pytest.mark.parametrize("mode", ("P", "PA"))
def test_palette(self, mode, tmp_path):
outfile = str(tmp_path / "temp.tif")
im = hopper(mode)
im.save(outfile)
im = hopper(mode)
im.save(outfile)
with Image.open(outfile) as reloaded:
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
for mode in ["P", "PA"]:
roundtrip(mode)
with Image.open(outfile) as reloaded:
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
def test_tiff_save_all(self):
mp = BytesIO()

View File

@ -185,6 +185,22 @@ def test_iptc(tmp_path):
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):
# Check that the tag has not been changed since this test was created
tag = TiffTags.TAGS_V2[45059]

View File

@ -66,10 +66,10 @@ def test_load_set_dpi():
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()
for ext in [".wmf", ".emf"]:
tmpfile = str(tmp_path / ("temp" + ext))
with pytest.raises(OSError):
im.save(tmpfile)
tmpfile = str(tmp_path / ("temp" + ext))
with pytest.raises(OSError):
im.save(tmpfile)

View File

@ -49,6 +49,14 @@ def test_sanity(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():
with open("Tests/images/flower.jpg", "rb") as fp:
with pytest.raises(SyntaxError):
@ -68,12 +76,19 @@ def test_textsize(request, tmp_path):
tempname = save_font(request, tmp_path)
font = ImageFont.load(tempname)
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 dx in (0, 10)
assert font.getlength(chr(i)) == dx
with pytest.warns(DeprecationWarning) as log:
assert font.getsize(chr(i)) == (dx, dy)
assert len(log) == 1
for i in range(len(message)):
msg = message[: i + 1]
assert font.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):

View File

@ -1,5 +1,7 @@
import os
import pytest
from PIL import FontFile, Image, ImageDraw, ImageFont, PcfFontFile
from .helper import (
@ -59,23 +61,13 @@ def save_font(request, tmp_path, encoding):
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)
def test_sanity_iso8859_1(request, tmp_path):
_test_sanity(request, tmp_path, "iso8859-1")
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):
@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250"))
def test_draw(request, tmp_path, encoding):
tempname = save_font(request, tmp_path, encoding)
font = ImageFont.load(tempname)
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)
def test_draw_iso8859_1(request, tmp_path):
_test_draw(request, tmp_path, "iso8859-1")
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):
@pytest.mark.parametrize("encoding", ("iso8859-1", "iso8859-2", "cp1250"))
def test_textsize(request, tmp_path, encoding):
tempname = save_font(request, tmp_path, encoding)
font = ImageFont.load(tempname)
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 dx in (0, 10)
assert font.getlength(bytearray([i])) == dx
message = charsets[encoding]["message"].encode(encoding)
for i in range(len(message)):
msg = message[: i + 1]
assert font.getsize(msg) == (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")
assert font.getlength(msg) == len(msg) * 10
assert font.getbbox(msg) == (0, 0, len(msg) * 10, 20)

View File

@ -29,8 +29,9 @@ from .helper import (
class TestImage:
def test_image_modes_success(self):
for mode in [
@pytest.mark.parametrize(
"mode",
(
"1",
"P",
"PA",
@ -51,22 +52,18 @@ class TestImage:
"YCbCr",
"LAB",
"HSV",
]:
Image.new(mode, (1, 1))
),
)
def test_image_modes_success(self, mode):
Image.new(mode, (1, 1))
def test_image_modes_fail(self):
for mode in [
"",
"bad",
"very very long",
"BGR;15",
"BGR;16",
"BGR;24",
"BGR;32",
]:
with pytest.raises(ValueError) as e:
Image.new(mode, (1, 1))
assert str(e.value) == "unrecognized image mode"
@pytest.mark.parametrize(
"mode", ("", "bad", "very very long", "BGR;15", "BGR;16", "BGR;24", "BGR;32")
)
def test_image_modes_fail(self, mode):
with pytest.raises(ValueError) as e:
Image.new(mode, (1, 1))
assert str(e.value) == "unrecognized image mode"
def test_exception_inheritance(self):
assert issubclass(UnidentifiedImageError, OSError)
@ -605,23 +602,22 @@ class TestImage:
with pytest.raises(ValueError):
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
target_file = "Tests/images/linear_gradient.png"
for mode in ["L", "P", "I", "F"]:
# Act
im = Image.linear_gradient(mode)
# Act
im = Image.linear_gradient(mode)
# Assert
assert im.size == (256, 256)
assert im.mode == mode
assert im.getpixel((0, 0)) == 0
assert im.getpixel((255, 255)) == 255
with Image.open(target_file) as target:
target = target.convert(mode)
assert_image_equal(im, target)
# Assert
assert im.size == (256, 256)
assert im.mode == mode
assert im.getpixel((0, 0)) == 0
assert im.getpixel((255, 255)) == 255
with Image.open(target_file) as target:
target = target.convert(mode)
assert_image_equal(im, target)
def test_radial_gradient_wrong_mode(self):
# Arrange
@ -631,23 +627,22 @@ class TestImage:
with pytest.raises(ValueError):
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
target_file = "Tests/images/radial_gradient.png"
for mode in ["L", "P", "I", "F"]:
# Act
im = Image.radial_gradient(mode)
# Act
im = Image.radial_gradient(mode)
# Assert
assert im.size == (256, 256)
assert im.mode == mode
assert im.getpixel((0, 0)) == 255
assert im.getpixel((128, 128)) == 0
with Image.open(target_file) as target:
target = target.convert(mode)
assert_image_equal(im, target)
# Assert
assert im.size == (256, 256)
assert im.mode == mode
assert im.getpixel((0, 0)) == 255
assert im.getpixel((128, 128)) == 0
with Image.open(target_file) as target:
target = target.convert(mode)
assert_image_equal(im, target)
def test_register_extensions(self):
test_format = "a"
@ -691,6 +686,7 @@ class TestImage:
im_remapped = im.remap_palette([1, 0])
assert im_remapped.info["transparency"] == 1
assert len(im_remapped.getpalette()) == 6
# Test unused transparency
im.info["transparency"] = 2

View File

@ -184,8 +184,9 @@ class TestImageGetPixel(AccessTest):
with pytest.raises(error):
im.getpixel((-1, -1))
def test_basic(self):
for mode in (
@pytest.mark.parametrize(
"mode",
(
"1",
"L",
"LA",
@ -200,23 +201,28 @@ class TestImageGetPixel(AccessTest):
"RGBX",
"CMYK",
"YCbCr",
):
self.check(mode)
),
)
def test_basic(self, 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
# 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)
self.check(mode, 2**15 + 1)
self.check(mode, 2**16 - 1)
self.check(mode, 2**15 - 1)
self.check(mode, 2**15)
self.check(mode, 2**15 + 1)
self.check(mode, 2**16 - 1)
def test_p_putpixel_rgb_rgba(self):
for color in [(255, 0, 0), (255, 0, 0, 255)]:
im = Image.new("P", (1, 1), 0)
im.putpixel((0, 0), color)
assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
@pytest.mark.parametrize("mode", ("P", "PA"))
@pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))
def test_p_putpixel_rgb_rgba(self, mode, color):
im = Image.new(mode, (1, 1))
im.putpixel((0, 0), color)
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")
@ -337,12 +343,15 @@ class TestCffi(AccessTest):
# pixels can contain garbage if image is released
assert px[i, 0] == 0
def test_p_putpixel_rgb_rgba(self):
for color in [(255, 0, 0), (255, 0, 0, 255)]:
im = Image.new("P", (1, 1), 0)
@pytest.mark.parametrize("mode", ("P", "PA"))
def test_p_putpixel_rgb_rgba(self, mode):
for color in [(255, 0, 0), (255, 0, 0, 127)]:
im = Image.new(mode, (1, 1))
access = PyAccess.new(im, False)
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):

View File

@ -1,4 +1,5 @@
import pytest
from packaging.version import parse as parse_version
from PIL import Image
@ -34,9 +35,10 @@ def test_toarray():
test_with_dtype(numpy.float64)
test_with_dtype(numpy.uint8)
with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated:
with pytest.raises(OSError):
numpy.array(im_truncated)
if parse_version(numpy.__version__) >= parse_version("1.23"):
with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated:
with pytest.raises(OSError):
numpy.array(im_truncated)
def test_fromarray():

View File

@ -236,6 +236,12 @@ def test_p2pa_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():
# Arrange
im = hopper("CMYK")
@ -268,36 +274,33 @@ def test_matrix_wrong_mode():
im.convert(mode="L", matrix=matrix)
def test_matrix_xyz():
def matrix_convert(mode):
# Arrange
im = hopper("RGB")
im.info["transparency"] = (255, 0, 0)
# fmt: off
matrix = (
0.412453, 0.357580, 0.180423, 0,
0.212671, 0.715160, 0.072169, 0,
0.019334, 0.119193, 0.950227, 0)
# fmt: on
assert im.mode == "RGB"
@pytest.mark.parametrize("mode", ("RGB", "L"))
def test_matrix_xyz(mode):
# Arrange
im = hopper("RGB")
im.info["transparency"] = (255, 0, 0)
# fmt: off
matrix = (
0.412453, 0.357580, 0.180423, 0,
0.212671, 0.715160, 0.072169, 0,
0.019334, 0.119193, 0.950227, 0)
# fmt: on
assert im.mode == "RGB"
# Act
# Convert an RGB image to the CIE XYZ colour space
converted_im = im.convert(mode=mode, matrix=matrix)
# Act
# Convert an RGB image to the CIE XYZ colour space
converted_im = im.convert(mode=mode, matrix=matrix)
# Assert
assert converted_im.mode == mode
assert converted_im.size == im.size
with Image.open("Tests/images/hopper-XYZ.png") as target:
if converted_im.mode == "RGB":
assert_image_similar(converted_im, target, 3)
assert converted_im.info["transparency"] == (105, 54, 4)
else:
assert_image_similar(converted_im, target.getchannel(0), 1)
assert converted_im.info["transparency"] == 105
matrix_convert("RGB")
matrix_convert("L")
# Assert
assert converted_im.mode == mode
assert converted_im.size == im.size
with Image.open("Tests/images/hopper-XYZ.png") as target:
if converted_im.mode == "RGB":
assert_image_similar(converted_im, target, 3)
assert converted_im.info["transparency"] == (105, 54, 4)
else:
assert_image_similar(converted_im, target.getchannel(0), 1)
assert converted_im.info["transparency"] == 105
def test_matrix_identity():

View File

@ -1,37 +1,40 @@
import copy
import pytest
from PIL import Image
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_size = (10, 10)
for mode in "1", "P", "L", "RGB", "I", "F":
# Internal copy method
im = hopper(mode)
out = im.copy()
assert out.mode == im.mode
assert out.size == im.size
# Python's copy method
im = hopper(mode)
out = copy.copy(im)
assert out.mode == im.mode
assert out.size == im.size
# Internal copy method
im = hopper(mode)
out = im.copy()
assert out.mode == im.mode
assert out.size == im.size
# Internal copy method on a cropped image
im = hopper(mode)
out = im.crop(cropped_coordinates).copy()
assert out.mode == im.mode
assert out.size == cropped_size
# Python's copy method
im = hopper(mode)
out = copy.copy(im)
assert out.mode == im.mode
assert out.size == im.size
# Python's copy method on a cropped image
im = hopper(mode)
out = copy.copy(im.crop(cropped_coordinates))
assert out.mode == im.mode
assert out.size == cropped_size
# Internal copy method on a cropped image
im = hopper(mode)
out = im.crop(cropped_coordinates).copy()
assert out.mode == im.mode
assert out.size == cropped_size
# Python's copy method on a cropped image
im = hopper(mode)
out = copy.copy(im.crop(cropped_coordinates))
assert out.mode == im.mode
assert out.size == cropped_size
def test_copy_zero():

View File

@ -5,17 +5,14 @@ from PIL import Image
from .helper import assert_image_equal, hopper
def test_crop():
def crop(mode):
im = hopper(mode)
assert_image_equal(im.crop(), im)
@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
def test_crop(mode):
im = hopper(mode)
assert_image_equal(im.crop(), im)
cropped = im.crop((50, 50, 100, 100))
assert cropped.mode == mode
assert cropped.size == (50, 50)
for mode in "1", "P", "L", "RGB", "I", "F":
crop(mode)
cropped = im.crop((50, 50, 100, 100))
assert cropped.mode == mode
assert cropped.size == (50, 50)
def test_wide_crop():

View File

@ -9,7 +9,7 @@ def test_entropy():
assert round(abs(entropy("L") - 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("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("RGBA") - 7.42724306524488), 7) == 0
assert round(abs(entropy("CMYK") - 7.4272430652448795), 7) == 0

View File

@ -5,90 +5,109 @@ from PIL import Image, ImageFilter
from .helper import assert_image_equal, hopper
def test_sanity():
def apply_filter(filter_to_apply):
for mode in ["L", "RGB", "CMYK"]:
im = hopper(mode)
out = im.filter(filter_to_apply)
assert out.mode == im.mode
assert out.size == im.size
@pytest.mark.parametrize(
"filter_to_apply",
(
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)
out = im.filter(filter_to_apply)
assert out.mode == im.mode
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):
apply_filter("hello")
im = hopper(mode)
im.filter("hello")
def test_crash():
# crashes on small images
im = Image.new("RGB", (1, 1))
im.filter(ImageFilter.SMOOTH)
im = Image.new("RGB", (2, 2))
im.filter(ImageFilter.SMOOTH)
im = Image.new("RGB", (3, 3))
# crashes on small images
@pytest.mark.parametrize("size", ((1, 1), (2, 2), (3, 3)))
def test_crash(size):
im = Image.new("RGB", size)
im.filter(ImageFilter.SMOOTH)
def test_modefilter():
def modefilter(mode):
im = Image.new(mode, (3, 3), None)
im.putdata(list(range(9)))
# image is:
# 0 1 2
# 3 4 5
# 6 7 8
mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1))
im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0
mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1))
return mod, mod2
assert modefilter("1") == (4, 0)
assert modefilter("L") == (4, 0)
assert modefilter("P") == (4, 0)
assert modefilter("RGB") == ((4, 0, 0), (0, 0, 0))
@pytest.mark.parametrize(
"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.putdata(list(range(9)))
# image is:
# 0 1 2
# 3 4 5
# 6 7 8
mod = im.filter(ImageFilter.ModeFilter).getpixel((1, 1))
im.putdata([0, 0, 1, 2, 5, 1, 5, 2, 0]) # mode=0
mod2 = im.filter(ImageFilter.ModeFilter).getpixel((1, 1))
assert (mod, mod2) == expected
def test_rankfilter():
def rankfilter(mode):
im = Image.new(mode, (3, 3), None)
im.putdata(list(range(9)))
# image is:
# 0 1 2
# 3 4 5
# 6 7 8
minimum = im.filter(ImageFilter.MinFilter).getpixel((1, 1))
med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1))
maximum = im.filter(ImageFilter.MaxFilter).getpixel((1, 1))
return minimum, med, maximum
@pytest.mark.parametrize(
"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.putdata(list(range(9)))
# image is:
# 0 1 2
# 3 4 5
# 6 7 8
minimum = im.filter(ImageFilter.MinFilter).getpixel((1, 1))
med = im.filter(ImageFilter.MedianFilter).getpixel((1, 1))
maximum = im.filter(ImageFilter.MaxFilter).getpixel((1, 1))
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):
rankfilter("P")
assert rankfilter("RGB") == ((0, 0, 0), (4, 0, 0), (8, 0, 0))
assert rankfilter("I") == (0, 4, 8)
assert rankfilter("F") == (0.0, 4.0, 8.0)
im = Image.new("P", (3, 3), None)
im.putdata(list(range(9)))
# image is:
# 0 1 2
# 3 4 5
# 6 7 8
im.filter(filter).getpixel((1, 1))
def test_rankfilter_properties():
@ -110,7 +129,8 @@ def test_kernel_not_enough_coefficients():
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_emboss.bmp") as reference:
kernel = ImageFilter.Kernel(
@ -125,14 +145,14 @@ def test_consistency_3x3():
source = source.split() * 2
reference = reference.split() * 2
for mode in ["L", "LA", "RGB", "CMYK"]:
assert_image_equal(
Image.merge(mode, source[: len(mode)]).filter(kernel),
Image.merge(mode, reference[: len(mode)]),
)
assert_image_equal(
Image.merge(mode, source[: len(mode)]).filter(kernel),
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_emboss_more.bmp") as reference:
kernel = ImageFilter.Kernel(
@ -149,8 +169,7 @@ def test_consistency_5x5():
source = source.split() * 2
reference = reference.split() * 2
for mode in ["L", "LA", "RGB", "CMYK"]:
assert_image_equal(
Image.merge(mode, source[: len(mode)]).filter(kernel),
Image.merge(mode, reference[: len(mode)]),
)
assert_image_equal(
Image.merge(mode, source[: len(mode)]).filter(kernel),
Image.merge(mode, reference[: len(mode)]),
)

View File

@ -16,7 +16,7 @@ def test_getcolors():
assert getcolors("L") == 255
assert getcolors("I") == 255
assert getcolors("F") == 255
assert getcolors("P") == 90 # fixed palette
assert getcolors("P") == 96 # fixed palette
assert getcolors("RGB") is None
assert getcolors("RGBA") is None
assert getcolors("CMYK") is None

View File

@ -10,7 +10,7 @@ def test_histogram():
assert histogram("L") == (256, 0, 662)
assert histogram("I") == (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("RGBA") == (1024, 0, 16384)
assert histogram("CMYK") == (1024, 0, 16384)

View File

@ -1,3 +1,5 @@
import pytest
from PIL import Image
from .helper import CachedProperty, assert_image_equal
@ -101,226 +103,226 @@ class TestImagingPaste:
],
)
def test_image_solid(self):
for mode in ("RGBA", "RGB", "L"):
im = Image.new(mode, (200, 200), "red")
im2 = getattr(self, "gradient_" + mode)
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_image_solid(self, mode):
im = Image.new(mode, (200, 200), "red")
im2 = getattr(self, "gradient_" + mode)
im.paste(im2, (12, 23))
im.paste(im2, (12, 23))
im = im.crop((12, 23, im2.width + 12, im2.height + 23))
assert_image_equal(im, im2)
im = im.crop((12, 23, im2.width + 12, im2.height + 23))
assert_image_equal(im, im2)
def test_image_mask_1(self):
for mode in ("RGBA", "RGB", "L"):
im = Image.new(mode, (200, 200), "white")
im2 = getattr(self, "gradient_" + mode)
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_image_mask_1(self, mode):
im = Image.new(mode, (200, 200), "white")
im2 = getattr(self, "gradient_" + mode)
self.assert_9points_paste(
im,
im2,
self.mask_1,
[
(255, 255, 255, 255),
(255, 255, 255, 255),
(127, 254, 127, 0),
(255, 255, 255, 255),
(255, 255, 255, 255),
(191, 190, 63, 64),
(127, 0, 127, 254),
(191, 64, 63, 190),
(255, 255, 255, 255),
],
)
self.assert_9points_paste(
im,
im2,
self.mask_1,
[
(255, 255, 255, 255),
(255, 255, 255, 255),
(127, 254, 127, 0),
(255, 255, 255, 255),
(255, 255, 255, 255),
(191, 190, 63, 64),
(127, 0, 127, 254),
(191, 64, 63, 190),
(255, 255, 255, 255),
],
)
def test_image_mask_L(self):
for mode in ("RGBA", "RGB", "L"):
im = Image.new(mode, (200, 200), "white")
im2 = getattr(self, "gradient_" + mode)
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_image_mask_L(self, mode):
im = Image.new(mode, (200, 200), "white")
im2 = getattr(self, "gradient_" + mode)
self.assert_9points_paste(
im,
im2,
self.mask_L,
[
(128, 191, 255, 191),
(208, 239, 239, 208),
(255, 255, 255, 255),
(112, 111, 206, 207),
(192, 191, 191, 191),
(239, 239, 207, 207),
(128, 1, 128, 254),
(207, 113, 112, 207),
(255, 191, 128, 191),
],
)
self.assert_9points_paste(
im,
im2,
self.mask_L,
[
(128, 191, 255, 191),
(208, 239, 239, 208),
(255, 255, 255, 255),
(112, 111, 206, 207),
(192, 191, 191, 191),
(239, 239, 207, 207),
(128, 1, 128, 254),
(207, 113, 112, 207),
(255, 191, 128, 191),
],
)
def test_image_mask_LA(self):
for mode in ("RGBA", "RGB", "L"):
im = Image.new(mode, (200, 200), "white")
im2 = getattr(self, "gradient_" + mode)
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_image_mask_LA(self, mode):
im = Image.new(mode, (200, 200), "white")
im2 = getattr(self, "gradient_" + mode)
self.assert_9points_paste(
im,
im2,
self.gradient_LA,
[
(128, 191, 255, 191),
(112, 207, 206, 111),
(128, 254, 128, 1),
(208, 208, 239, 239),
(192, 191, 191, 191),
(207, 207, 112, 113),
(255, 255, 255, 255),
(239, 207, 207, 239),
(255, 191, 128, 191),
],
)
self.assert_9points_paste(
im,
im2,
self.gradient_LA,
[
(128, 191, 255, 191),
(112, 207, 206, 111),
(128, 254, 128, 1),
(208, 208, 239, 239),
(192, 191, 191, 191),
(207, 207, 112, 113),
(255, 255, 255, 255),
(239, 207, 207, 239),
(255, 191, 128, 191),
],
)
def test_image_mask_RGBA(self):
for mode in ("RGBA", "RGB", "L"):
im = Image.new(mode, (200, 200), "white")
im2 = getattr(self, "gradient_" + mode)
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_image_mask_RGBA(self, mode):
im = Image.new(mode, (200, 200), "white")
im2 = getattr(self, "gradient_" + mode)
self.assert_9points_paste(
im,
im2,
self.gradient_RGBA,
[
(128, 191, 255, 191),
(208, 239, 239, 208),
(255, 255, 255, 255),
(112, 111, 206, 207),
(192, 191, 191, 191),
(239, 239, 207, 207),
(128, 1, 128, 254),
(207, 113, 112, 207),
(255, 191, 128, 191),
],
)
self.assert_9points_paste(
im,
im2,
self.gradient_RGBA,
[
(128, 191, 255, 191),
(208, 239, 239, 208),
(255, 255, 255, 255),
(112, 111, 206, 207),
(192, 191, 191, 191),
(239, 239, 207, 207),
(128, 1, 128, 254),
(207, 113, 112, 207),
(255, 191, 128, 191),
],
)
def test_image_mask_RGBa(self):
for mode in ("RGBA", "RGB", "L"):
im = Image.new(mode, (200, 200), "white")
im2 = getattr(self, "gradient_" + mode)
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_image_mask_RGBa(self, mode):
im = Image.new(mode, (200, 200), "white")
im2 = getattr(self, "gradient_" + mode)
self.assert_9points_paste(
im,
im2,
self.gradient_RGBa,
[
(128, 255, 126, 255),
(0, 127, 126, 255),
(126, 253, 126, 255),
(128, 127, 254, 255),
(0, 255, 254, 255),
(126, 125, 254, 255),
(128, 1, 128, 255),
(0, 129, 128, 255),
(126, 255, 128, 255),
],
)
self.assert_9points_paste(
im,
im2,
self.gradient_RGBa,
[
(128, 255, 126, 255),
(0, 127, 126, 255),
(126, 253, 126, 255),
(128, 127, 254, 255),
(0, 255, 254, 255),
(126, 125, 254, 255),
(128, 1, 128, 255),
(0, 129, 128, 255),
(126, 255, 128, 255),
],
)
def test_color_solid(self):
for mode in ("RGBA", "RGB", "L"):
im = Image.new(mode, (200, 200), "black")
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_color_solid(self, mode):
im = Image.new(mode, (200, 200), "black")
rect = (12, 23, 128 + 12, 128 + 23)
im.paste("white", rect)
rect = (12, 23, 128 + 12, 128 + 23)
im.paste("white", rect)
hist = im.crop(rect).histogram()
while hist:
head, hist = hist[:256], hist[256:]
assert head[255] == 128 * 128
assert sum(head[:255]) == 0
hist = im.crop(rect).histogram()
while hist:
head, hist = hist[:256], hist[256:]
assert head[255] == 128 * 128
assert sum(head[:255]) == 0
def test_color_mask_1(self):
for mode in ("RGBA", "RGB", "L"):
im = Image.new(mode, (200, 200), (50, 60, 70, 80)[: len(mode)])
color = (10, 20, 30, 40)[: len(mode)]
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_color_mask_1(self, mode):
im = Image.new(mode, (200, 200), (50, 60, 70, 80)[: len(mode)])
color = (10, 20, 30, 40)[: len(mode)]
self.assert_9points_paste(
im,
color,
self.mask_1,
[
(50, 60, 70, 80),
(50, 60, 70, 80),
(10, 20, 30, 40),
(50, 60, 70, 80),
(50, 60, 70, 80),
(10, 20, 30, 40),
(10, 20, 30, 40),
(10, 20, 30, 40),
(50, 60, 70, 80),
],
)
self.assert_9points_paste(
im,
color,
self.mask_1,
[
(50, 60, 70, 80),
(50, 60, 70, 80),
(10, 20, 30, 40),
(50, 60, 70, 80),
(50, 60, 70, 80),
(10, 20, 30, 40),
(10, 20, 30, 40),
(10, 20, 30, 40),
(50, 60, 70, 80),
],
)
def test_color_mask_L(self):
for mode in ("RGBA", "RGB", "L"):
im = getattr(self, "gradient_" + mode).copy()
color = "white"
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_color_mask_L(self, mode):
im = getattr(self, "gradient_" + mode).copy()
color = "white"
self.assert_9points_paste(
im,
color,
self.mask_L,
[
(127, 191, 254, 191),
(111, 207, 206, 110),
(127, 254, 127, 0),
(207, 207, 239, 239),
(191, 191, 190, 191),
(207, 206, 111, 112),
(254, 254, 254, 255),
(239, 206, 206, 238),
(254, 191, 127, 191),
],
)
self.assert_9points_paste(
im,
color,
self.mask_L,
[
(127, 191, 254, 191),
(111, 207, 206, 110),
(127, 254, 127, 0),
(207, 207, 239, 239),
(191, 191, 190, 191),
(207, 206, 111, 112),
(254, 254, 254, 255),
(239, 206, 206, 238),
(254, 191, 127, 191),
],
)
def test_color_mask_RGBA(self):
for mode in ("RGBA", "RGB", "L"):
im = getattr(self, "gradient_" + mode).copy()
color = "white"
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_color_mask_RGBA(self, mode):
im = getattr(self, "gradient_" + mode).copy()
color = "white"
self.assert_9points_paste(
im,
color,
self.gradient_RGBA,
[
(127, 191, 254, 191),
(111, 207, 206, 110),
(127, 254, 127, 0),
(207, 207, 239, 239),
(191, 191, 190, 191),
(207, 206, 111, 112),
(254, 254, 254, 255),
(239, 206, 206, 238),
(254, 191, 127, 191),
],
)
self.assert_9points_paste(
im,
color,
self.gradient_RGBA,
[
(127, 191, 254, 191),
(111, 207, 206, 110),
(127, 254, 127, 0),
(207, 207, 239, 239),
(191, 191, 190, 191),
(207, 206, 111, 112),
(254, 254, 254, 255),
(239, 206, 206, 238),
(254, 191, 127, 191),
],
)
def test_color_mask_RGBa(self):
for mode in ("RGBA", "RGB", "L"):
im = getattr(self, "gradient_" + mode).copy()
color = "white"
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
def test_color_mask_RGBa(self, mode):
im = getattr(self, "gradient_" + mode).copy()
color = "white"
self.assert_9points_paste(
im,
color,
self.gradient_RGBa,
[
(255, 63, 126, 63),
(47, 143, 142, 46),
(126, 253, 126, 255),
(15, 15, 47, 47),
(63, 63, 62, 63),
(142, 141, 46, 47),
(255, 255, 255, 0),
(48, 15, 15, 47),
(126, 63, 255, 63),
],
)
self.assert_9points_paste(
im,
color,
self.gradient_RGBa,
[
(255, 63, 126, 63),
(47, 143, 142, 46),
(126, 253, 126, 255),
(15, 15, 47, 47),
(63, 63, 62, 63),
(142, 141, 46, 47),
(255, 255, 255, 0),
(48, 15, 15, 47),
(126, 63, 255, 63),
],
)
def test_different_sizes(self):
im = Image.new("RGB", (100, 100))

View File

@ -65,6 +65,22 @@ def test_quantize_no_dither():
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():
image = hopper()
with Image.open("Tests/images/caption_6_33_22.png") as palette:

View File

@ -38,58 +38,64 @@ gradients_image = Image.open("Tests/images/radial_gradients.png")
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))
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))
assert expected == im.reduce(size).size
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))
assert (5, 5) == im.reduce(2, (0, 0, 10, 10)).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))
with pytest.raises(expected_error):
im.reduce(size)
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))
with pytest.raises(ValueError):
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):
mode_info = ImageMode.getmode(mode)
@ -190,70 +196,76 @@ 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")
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
compare_reduce_with_reference(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")
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.
im.putalpha(Image.new("L", im.size, 255))
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
compare_reduce_with_reference(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")
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
compare_reduce_with_reference(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")
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
compare_reduce_with_reference(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")
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.
im.putalpha(Image.new("L", im.size, 255))
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
compare_reduce_with_reference(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")
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
compare_reduce_with_reference(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")
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor)
compare_reduce_with_box(im, factor)
compare_reduce_with_reference(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")
for factor in remarkable_factors:
compare_reduce_with_reference(im, factor, 0, 0)
compare_reduce_with_box(im, factor)
compare_reduce_with_reference(im, factor, 0, 0)
compare_reduce_with_box(im, factor)
@skip_unless_feature("jpg_2000")

View File

@ -100,40 +100,41 @@ class TestImagingCoreResampleAccuracy:
for y in range(image.size[1])
)
def test_reduce_box(self):
for mode in ["RGBX", "RGB", "La", "L"]:
case = self.make_case(mode, (8, 8), 0xE1)
case = case.resize((4, 4), Image.Resampling.BOX)
# fmt: off
data = ("e1 e1"
"e1 e1")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
def test_reduce_box(self, mode):
case = self.make_case(mode, (8, 8), 0xE1)
case = case.resize((4, 4), Image.Resampling.BOX)
# fmt: off
data = ("e1 e1"
"e1 e1")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
def test_reduce_bilinear(self):
for mode in ["RGBX", "RGB", "La", "L"]:
case = self.make_case(mode, (8, 8), 0xE1)
case = case.resize((4, 4), Image.Resampling.BILINEAR)
# fmt: off
data = ("e1 c9"
"c9 b7")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
def test_reduce_bilinear(self, mode):
case = self.make_case(mode, (8, 8), 0xE1)
case = case.resize((4, 4), Image.Resampling.BILINEAR)
# fmt: off
data = ("e1 c9"
"c9 b7")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
def test_reduce_hamming(self):
for mode in ["RGBX", "RGB", "La", "L"]:
case = self.make_case(mode, (8, 8), 0xE1)
case = case.resize((4, 4), Image.Resampling.HAMMING)
# fmt: off
data = ("e1 da"
"da d3")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
def test_reduce_hamming(self, mode):
case = self.make_case(mode, (8, 8), 0xE1)
case = case.resize((4, 4), Image.Resampling.HAMMING)
# fmt: off
data = ("e1 da"
"da d3")
# fmt: on
for channel in case.split():
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"]:
case = self.make_case(mode, (12, 12), 0xE1)
case = case.resize((6, 6), Image.Resampling.BICUBIC)
@ -145,79 +146,79 @@ class TestImagingCoreResampleAccuracy:
for channel in case.split():
self.check_case(channel, self.make_sample(data, (6, 6)))
def test_reduce_lanczos(self):
for mode in ["RGBX", "RGB", "La", "L"]:
case = self.make_case(mode, (16, 16), 0xE1)
case = case.resize((8, 8), Image.Resampling.LANCZOS)
# fmt: off
data = ("e1 e0 e4 d7"
"e0 df e3 d6"
"e4 e3 e7 da"
"d7 d6 d9 ce")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (8, 8)))
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
def test_reduce_lanczos(self, mode):
case = self.make_case(mode, (16, 16), 0xE1)
case = case.resize((8, 8), Image.Resampling.LANCZOS)
# fmt: off
data = ("e1 e0 e4 d7"
"e0 df e3 d6"
"e4 e3 e7 da"
"d7 d6 d9 ce")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (8, 8)))
def test_enlarge_box(self):
for mode in ["RGBX", "RGB", "La", "L"]:
case = self.make_case(mode, (2, 2), 0xE1)
case = case.resize((4, 4), Image.Resampling.BOX)
# fmt: off
data = ("e1 e1"
"e1 e1")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
def test_enlarge_box(self, mode):
case = self.make_case(mode, (2, 2), 0xE1)
case = case.resize((4, 4), Image.Resampling.BOX)
# fmt: off
data = ("e1 e1"
"e1 e1")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
def test_enlarge_bilinear(self):
for mode in ["RGBX", "RGB", "La", "L"]:
case = self.make_case(mode, (2, 2), 0xE1)
case = case.resize((4, 4), Image.Resampling.BILINEAR)
# fmt: off
data = ("e1 b0"
"b0 98")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
def test_enlarge_bilinear(self, mode):
case = self.make_case(mode, (2, 2), 0xE1)
case = case.resize((4, 4), Image.Resampling.BILINEAR)
# fmt: off
data = ("e1 b0"
"b0 98")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
def test_enlarge_hamming(self):
for mode in ["RGBX", "RGB", "La", "L"]:
case = self.make_case(mode, (2, 2), 0xE1)
case = case.resize((4, 4), Image.Resampling.HAMMING)
# fmt: off
data = ("e1 d2"
"d2 c5")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
def test_enlarge_hamming(self, mode):
case = self.make_case(mode, (2, 2), 0xE1)
case = case.resize((4, 4), Image.Resampling.HAMMING)
# fmt: off
data = ("e1 d2"
"d2 c5")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (4, 4)))
def test_enlarge_bicubic(self):
for mode in ["RGBX", "RGB", "La", "L"]:
case = self.make_case(mode, (4, 4), 0xE1)
case = case.resize((8, 8), Image.Resampling.BICUBIC)
# fmt: off
data = ("e1 e5 ee b9"
"e5 e9 f3 bc"
"ee f3 fd c1"
"b9 bc c1 a2")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (8, 8)))
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
def test_enlarge_bicubic(self, mode):
case = self.make_case(mode, (4, 4), 0xE1)
case = case.resize((8, 8), Image.Resampling.BICUBIC)
# fmt: off
data = ("e1 e5 ee b9"
"e5 e9 f3 bc"
"ee f3 fd c1"
"b9 bc c1 a2")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (8, 8)))
def test_enlarge_lanczos(self):
for mode in ["RGBX", "RGB", "La", "L"]:
case = self.make_case(mode, (6, 6), 0xE1)
case = case.resize((12, 12), Image.Resampling.LANCZOS)
data = (
"e1 e0 db ed f5 b8"
"e0 df da ec f3 b7"
"db db d6 e7 ee b5"
"ed ec e6 fb ff bf"
"f5 f4 ee ff ff c4"
"b8 b7 b4 bf c4 a0"
)
for channel in case.split():
self.check_case(channel, self.make_sample(data, (12, 12)))
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
def test_enlarge_lanczos(self, mode):
case = self.make_case(mode, (6, 6), 0xE1)
case = case.resize((12, 12), Image.Resampling.LANCZOS)
data = (
"e1 e0 db ed f5 b8"
"e0 df da ec f3 b7"
"db db d6 e7 ee b5"
"ed ec e6 fb ff bf"
"f5 f4 ee ff ff c4"
"b8 b7 b4 bf c4 a0"
)
for channel in case.split():
self.check_case(channel, self.make_sample(data, (12, 12)))
def test_box_filter_correct_range(self):
im = Image.new("RGB", (8, 8), "#1688ff").resize(
@ -419,40 +420,43 @@ class TestCoreResampleCoefficients:
class TestCoreResampleBox:
def test_wrong_arguments(self):
im = hopper()
for resample in (
@pytest.mark.parametrize(
"resample",
(
Image.Resampling.NEAREST,
Image.Resampling.BOX,
Image.Resampling.BILINEAR,
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
):
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, 20, 100))
im.resize((32, 32), resample, (20, 20, 100, 20))
),
)
def test_wrong_arguments(self, resample):
im = hopper()
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, 20, 100))
im.resize((32, 32), resample, (20, 20, 100, 20))
with pytest.raises(TypeError, match="must be sequence of length 4"):
im.resize((32, 32), resample, (im.width, im.height))
with pytest.raises(TypeError, match="must be sequence of length 4"):
im.resize((32, 32), resample, (im.width, im.height))
with pytest.raises(ValueError, match="can't be negative"):
im.resize((32, 32), resample, (-20, 20, 100, 100))
with pytest.raises(ValueError, match="can't be negative"):
im.resize((32, 32), resample, (20, -20, 100, 100))
with pytest.raises(ValueError, match="can't be negative"):
im.resize((32, 32), resample, (-20, 20, 100, 100))
with pytest.raises(ValueError, match="can't be negative"):
im.resize((32, 32), resample, (20, -20, 100, 100))
with pytest.raises(ValueError, match="can't be empty"):
im.resize((32, 32), resample, (20.1, 20, 20, 100))
with pytest.raises(ValueError, match="can't be empty"):
im.resize((32, 32), resample, (20, 20.1, 100, 20))
with pytest.raises(ValueError, match="can't be empty"):
im.resize((32, 32), resample, (20.1, 20.1, 20, 20))
with pytest.raises(ValueError, match="can't be empty"):
im.resize((32, 32), resample, (20.1, 20, 20, 100))
with pytest.raises(ValueError, match="can't be empty"):
im.resize((32, 32), resample, (20, 20.1, 100, 20))
with pytest.raises(ValueError, match="can't be empty"):
im.resize((32, 32), resample, (20.1, 20.1, 20, 20))
with pytest.raises(ValueError, match="can't exceed"):
im.resize((32, 32), resample, (0, 0, im.width + 1, im.height))
with pytest.raises(ValueError, match="can't exceed"):
im.resize((32, 32), resample, (0, 0, im.width, im.height + 1))
with pytest.raises(ValueError, match="can't exceed"):
im.resize((32, 32), resample, (0, 0, im.width + 1, im.height))
with pytest.raises(ValueError, match="can't exceed"):
im.resize((32, 32), resample, (0, 0, im.width, im.height + 1))
def resize_tiled(self, im, dst_size, xtiles, ytiles):
def split_range(size, tiles):
@ -509,14 +513,16 @@ class TestCoreResampleBox:
with pytest.raises(AssertionError, match=r"difference 29\."):
assert_image_similar(reference, without_box, 5)
def test_formats(self):
for resample in [Image.Resampling.NEAREST, Image.Resampling.BILINEAR]:
for mode in ["RGB", "L", "RGBA", "LA", "I", ""]:
im = hopper(mode)
box = (20, 20, im.size[0] - 20, im.size[1] - 20)
with_box = im.resize((32, 32), resample, box)
cropped = im.crop(box).resize((32, 32), resample)
assert_image_similar(cropped, with_box, 0.4)
@pytest.mark.parametrize("mode", ("RGB", "L", "RGBA", "LA", "I", ""))
@pytest.mark.parametrize(
"resample", (Image.Resampling.NEAREST, Image.Resampling.BILINEAR)
)
def test_formats(self, mode, resample):
im = hopper(mode)
box = (20, 20, im.size[0] - 20, im.size[1] - 20)
with_box = im.resize((32, 32), resample, box)
cropped = im.crop(box).resize((32, 32), resample)
assert_image_similar(cropped, with_box, 0.4)
def test_passthrough(self):
# When no resize is required
@ -548,44 +554,48 @@ class TestCoreResampleBox:
# check that the difference at least that much
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
im = hopper()
for flt in [Image.Resampling.NEAREST, Image.Resampling.BICUBIC]:
for size, box in [
((40, 50), (0, 0, 40, 90)),
((40, 50), (0, 20, 40, 90)),
((40, 50), (10, 0, 50, 90)),
((40, 50), (10, 20, 50, 90)),
]:
res = im.resize(size, flt, box)
assert res.size == size
# Borders should be slightly different
assert_image_similar(
res,
im.crop(box).resize(size, flt),
0.4,
f">>> {size} {box} {flt}",
)
for size, box in [
((40, 50), (0, 0, 40, 90)),
((40, 50), (0, 20, 40, 90)),
((40, 50), (10, 0, 50, 90)),
((40, 50), (10, 20, 50, 90)),
]:
res = im.resize(size, flt, box)
assert res.size == size
# Borders should be slightly different
assert_image_similar(
res,
im.crop(box).resize(size, flt),
0.4,
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
im = hopper()
for flt in [Image.Resampling.NEAREST, Image.Resampling.BICUBIC]:
for size, box in [
((40, 50), (0, 0, 90, 50)),
((40, 50), (20, 0, 90, 50)),
((40, 50), (0, 10, 90, 60)),
((40, 50), (20, 10, 90, 60)),
]:
res = im.resize(size, flt, box)
assert res.size == size
# Borders should be slightly different
assert_image_similar(
res,
im.crop(box).resize(size, flt),
0.4,
f">>> {size} {box} {flt}",
)
for size, box in [
((40, 50), (0, 0, 90, 50)),
((40, 50), (20, 0, 90, 50)),
((40, 50), (0, 10, 90, 60)),
((40, 50), (20, 10, 90, 60)),
]:
res = im.resize(size, flt, box)
assert res.size == size
# Borders should be slightly different
assert_image_similar(
res,
im.crop(box).resize(size, flt),
0.4,
f">>> {size} {box} {flt}",
)

View File

@ -22,24 +22,15 @@ class TestImagingCoreResize:
im.load()
return im._new(im.im.resize(size, f))
def test_nearest_mode(self):
for mode in [
"1",
"P",
"L",
"I",
"F",
"RGB",
"RGBA",
"CMYK",
"YCbCr",
"I;16",
]: # exotic mode
im = hopper(mode)
r = self.resize(im, (15, 12), Image.Resampling.NEAREST)
assert r.mode == mode
assert r.size == (15, 12)
assert r.im.bands == im.im.bands
@pytest.mark.parametrize(
"mode", ("1", "P", "L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr", "I;16")
)
def test_nearest_mode(self, mode):
im = hopper(mode)
r = self.resize(im, (15, 12), Image.Resampling.NEAREST)
assert r.mode == mode
assert r.size == (15, 12)
assert r.im.bands == im.im.bands
def test_convolution_modes(self):
with pytest.raises(ValueError):
@ -55,33 +46,58 @@ class TestImagingCoreResize:
assert r.size == (15, 12)
assert r.im.bands == im.im.bands
def test_reduce_filters(self):
for f in [
@pytest.mark.parametrize(
"resample",
(
Image.Resampling.NEAREST,
Image.Resampling.BOX,
Image.Resampling.BILINEAR,
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
]:
r = self.resize(hopper("RGB"), (15, 12), f)
assert r.mode == "RGB"
assert r.size == (15, 12)
),
)
def test_reduce_filters(self, resample):
r = self.resize(hopper("RGB"), (15, 12), resample)
assert r.mode == "RGB"
assert r.size == (15, 12)
def test_enlarge_filters(self):
for f in [
@pytest.mark.parametrize(
"resample",
(
Image.Resampling.NEAREST,
Image.Resampling.BOX,
Image.Resampling.BILINEAR,
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
]:
r = self.resize(hopper("RGB"), (212, 195), f)
assert r.mode == "RGB"
assert r.size == (212, 195)
),
)
def test_enlarge_filters(self, resample):
r = self.resize(hopper("RGB"), (212, 195), resample)
assert r.mode == "RGB"
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.
# When resized, that channel should be the same as a GS image.
# Other channels should be unaffected.
@ -95,47 +111,37 @@ class TestImagingCoreResize:
}
samples["dirty"].putpixel((1, 1), 128)
for f in [
# samples resized with current filter
references = {
name: self.resize(ch, (4, 4), resample) for name, ch in samples.items()
}
for channels in set(permutations(channels_set)):
# compile image from different channels permutations
im = Image.merge(mode, [samples[ch] for ch in channels])
resized = self.resize(im, (4, 4), resample)
for i, ch in enumerate(resized.split()):
# check what resized channel in image is the same
# as separately resized channel
assert_image_equal(ch, references[channels[i]])
@pytest.mark.parametrize(
"resample",
(
Image.Resampling.NEAREST,
Image.Resampling.BOX,
Image.Resampling.BILINEAR,
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
]:
# samples resized with current filter
references = {
name: self.resize(ch, (4, 4), f) 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)):
# compile image from different channels permutations
im = Image.merge(mode, [samples[ch] for ch in channels])
resized = self.resize(im, (4, 4), f)
for i, ch in enumerate(resized.split()):
# check what resized channel in image is the same
# as separately resized channel
assert_image_equal(ch, references[channels[i]])
def test_enlarge_zero(self):
for f in [
Image.Resampling.NEAREST,
Image.Resampling.BOX,
Image.Resampling.BILINEAR,
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
]:
r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), f)
assert r.mode == "RGB"
assert r.size == (212, 195)
assert r.getdata()[0] == (0, 0, 0)
),
)
def test_enlarge_zero(self, resample):
r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), resample)
assert r.mode == "RGB"
assert r.size == (212, 195)
assert r.getdata()[0] == (0, 0, 0)
def test_unknown_filter(self):
with pytest.raises(ValueError):
@ -179,74 +185,71 @@ class TestReducingGapResize:
(52, 34), Image.Resampling.BICUBIC, reducing_gap=0.99
)
def test_reducing_gap_1(self, gradients_image):
for box, epsilon in [
(None, 4),
((1.1, 2.2, 510.8, 510.9), 4),
((3, 10, 410, 256), 10),
]:
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
im = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
)
with pytest.raises(AssertionError):
assert_image_equal(ref, im)
assert_image_similar(ref, im, epsilon)
def test_reducing_gap_2(self, gradients_image):
for box, epsilon in [
(None, 1.5),
((1.1, 2.2, 510.8, 510.9), 1.5),
((3, 10, 410, 256), 1),
]:
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
im = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0
)
with pytest.raises(AssertionError):
assert_image_equal(ref, im)
assert_image_similar(ref, im, epsilon)
def test_reducing_gap_3(self, gradients_image):
for box, epsilon in [
(None, 1),
((1.1, 2.2, 510.8, 510.9), 1),
((3, 10, 410, 256), 0.5),
]:
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
im = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0
)
with pytest.raises(AssertionError):
assert_image_equal(ref, im)
assert_image_similar(ref, im, epsilon)
def test_reducing_gap_8(self, gradients_image):
for box in [None, (1.1, 2.2, 510.8, 510.9), (3, 10, 410, 256)]:
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
im = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=8.0
)
@pytest.mark.parametrize(
"box, epsilon",
((None, 4), ((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)
im = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
)
with pytest.raises(AssertionError):
assert_image_equal(ref, im)
def test_box_filter(self, gradients_image):
for box, epsilon in [
((0, 0, 512, 512), 5.5),
((0.9, 1.7, 128, 128), 9.5),
]:
ref = gradients_image.resize((52, 34), Image.Resampling.BOX, box=box)
im = gradients_image.resize(
(52, 34), Image.Resampling.BOX, box=box, reducing_gap=1.0
)
assert_image_similar(ref, im, epsilon)
assert_image_similar(ref, im, epsilon)
@pytest.mark.parametrize(
"box, epsilon",
((None, 1.5), ((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)
im = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0
)
with pytest.raises(AssertionError):
assert_image_equal(ref, im)
assert_image_similar(ref, im, epsilon)
@pytest.mark.parametrize(
"box, epsilon",
((None, 1), ((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)
im = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0
)
with pytest.raises(AssertionError):
assert_image_equal(ref, im)
assert_image_similar(ref, im, epsilon)
@pytest.mark.parametrize("box", (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)
im = gradients_image.resize(
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=8.0
)
assert_image_equal(ref, im)
@pytest.mark.parametrize(
"box, epsilon",
(((0, 0, 512, 512), 5.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)
im = gradients_image.resize(
(52, 34), Image.Resampling.BOX, box=box, reducing_gap=1.0
)
assert_image_similar(ref, im, epsilon)
class TestImageResize:
@ -273,15 +276,14 @@ class TestImageResize:
im = im.resize((64, 64))
assert im.size == (64, 64)
def test_default_filter(self):
for mode in "L", "RGB", "I", "F":
im = hopper(mode)
assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
@pytest.mark.parametrize("mode", ("L", "RGB", "I", "F"))
def test_default_filter_bicubic(self, mode):
im = hopper(mode)
assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
for mode in "1", "P":
im = hopper(mode)
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
for mode in "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16":
im = hopper(mode)
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
@pytest.mark.parametrize(
"mode", ("1", "P", "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16")
)
def test_default_filter_nearest(self, mode):
im = hopper(mode)
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))

View File

@ -1,3 +1,5 @@
import pytest
from PIL import Image
from .helper import (
@ -22,26 +24,26 @@ def rotate(im, mode, angle, center=None, translate=None):
assert out.size != im.size
def test_mode():
for mode in ("1", "P", "L", "RGB", "I", "F"):
im = hopper(mode)
rotate(im, mode, 45)
@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
def test_mode(mode):
im = hopper(mode)
rotate(im, mode, 45)
def test_angle():
for angle in (0, 90, 180, 270):
with Image.open("Tests/images/test-card.png") as im:
rotate(im, im.mode, angle)
im = hopper()
assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1))
def test_zero():
for angle in (0, 45, 90, 180, 270):
im = Image.new("RGB", (0, 0))
@pytest.mark.parametrize("angle", (0, 90, 180, 270))
def test_angle(angle):
with Image.open("Tests/images/test-card.png") as im:
rotate(im, im.mode, angle)
im = hopper()
assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1))
@pytest.mark.parametrize("angle", (0, 45, 90, 180, 270))
def test_zero(angle):
im = Image.new("RGB", (0, 0))
rotate(im, im.mode, angle)
def test_resample():
# Target image creation, inspected by eye.

View File

@ -1,3 +1,5 @@
import pytest
from PIL import Image, features
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)]
def test_split_merge():
def split_merge(mode):
return Image.merge(mode, hopper(mode).split())
assert_image_equal(hopper("1"), split_merge("1"))
assert_image_equal(hopper("L"), split_merge("L"))
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"))
@pytest.mark.parametrize(
"mode", ("1", "L", "I", "F", "P", "RGB", "RGBA", "CMYK", "YCbCr")
)
def test_split_merge(mode):
expected = Image.merge(mode, hopper(mode).split())
assert_image_equal(hopper(mode), expected)
def test_split_open(tmp_path):

View File

@ -97,6 +97,28 @@ def test_load_first():
im.thumbnail((64, 64))
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
@pytest.mark.valgrind_known_error(reason="Known Failing")

View File

@ -75,23 +75,25 @@ class TestImageTransform:
assert_image_equal(transformed, scaled)
def test_fill(self):
for mode, pixel in [
["RGB", (255, 0, 0)],
["RGBA", (255, 0, 0, 255)],
["LA", (76, 0)],
]:
im = hopper(mode)
(w, h) = im.size
transformed = im.transform(
im.size,
Image.Transform.EXTENT,
(0, 0, w * 2, h * 2),
Image.Resampling.BILINEAR,
fillcolor="red",
)
assert transformed.getpixel((w - 1, h - 1)) == pixel
@pytest.mark.parametrize(
"mode, expected_pixel",
(
("RGB", (255, 0, 0)),
("RGBA", (255, 0, 0, 255)),
("LA", (76, 0)),
),
)
def test_fill(self, mode, expected_pixel):
im = hopper(mode)
(w, h) = im.size
transformed = im.transform(
im.size,
Image.Transform.EXTENT,
(0, 0, w * 2, h * 2),
Image.Resampling.BILINEAR,
fillcolor="red",
)
assert transformed.getpixel((w - 1, h - 1)) == expected_pixel
def test_mesh(self):
# this should be a checkerboard of halfsized hoppers in ul, lr
@ -222,14 +224,12 @@ class TestImageTransform:
with pytest.raises(ValueError):
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:
(w, h) = im.size
for resample in (Image.Resampling.BOX, "unknown"):
with pytest.raises(ValueError):
im.transform(
(100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample
)
with pytest.raises(ValueError):
im.transform((100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample)
class TestImageTransformAffine:
@ -239,7 +239,16 @@ class TestImageTransformAffine:
im = hopper("RGB")
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()
angle = -math.radians(deg)
@ -271,77 +280,65 @@ class TestImageTransformAffine:
)
assert_image_equal(transposed, transformed)
def test_rotate_0_deg(self):
self._test_rotate(0, None)
def test_rotate_90_deg(self):
self._test_rotate(90, Image.Transpose.ROTATE_90)
def test_rotate_180_deg(self):
self._test_rotate(180, Image.Transpose.ROTATE_180)
def test_rotate_270_deg(self):
self._test_rotate(270, Image.Transpose.ROTATE_270)
def _test_resize(self, scale, epsilonscale):
@pytest.mark.parametrize(
"scale, epsilon_scale",
(
(1.1, 6.9),
(1.5, 5.5),
(2.0, 5.5),
(2.3, 3.7),
(2.5, 3.7),
),
)
@pytest.mark.parametrize(
"resample,epsilon",
(
(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()
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_down = [scale, 0, 0, 0, scale, 0, 0, 0]
for resample, epsilon in [
transformed = im.transform(size_up, self.transform, matrix_up, resample)
transformed = transformed.transform(
im.size, self.transform, matrix_down, resample
)
assert_image_similar(transformed, im, epsilon * epsilon_scale)
@pytest.mark.parametrize(
"x, y, epsilon_scale",
(
(0.1, 0, 3.7),
(0.6, 0, 9.1),
(50, 50, 0),
),
)
@pytest.mark.parametrize(
"resample, epsilon",
(
(Image.Resampling.NEAREST, 0),
(Image.Resampling.BILINEAR, 2),
(Image.Resampling.BILINEAR, 1.5),
(Image.Resampling.BICUBIC, 1),
]:
transformed = im.transform(size_up, self.transform, matrix_up, resample)
transformed = transformed.transform(
im.size, self.transform, matrix_down, resample
)
assert_image_similar(transformed, im, epsilon * epsilonscale)
def test_resize_1_1x(self):
self._test_resize(1.1, 6.9)
def test_resize_1_5x(self):
self._test_resize(1.5, 5.5)
def test_resize_2_0x(self):
self._test_resize(2.0, 5.5)
def test_resize_2_3x(self):
self._test_resize(2.3, 3.7)
def test_resize_2_5x(self):
self._test_resize(2.5, 3.7)
def _test_translate(self, x, y, epsilonscale):
),
)
def test_translate(self, x, y, epsilon_scale, resample, epsilon):
im = self._test_image()
size_up = int(round(im.width + x)), int(round(im.height + y))
matrix_up = [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 = transformed.transform(
im.size, self.transform, matrix_down, resample
)
assert_image_similar(transformed, im, epsilon * epsilonscale)
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)
transformed = im.transform(size_up, self.transform, matrix_up, resample)
transformed = transformed.transform(
im.size, self.transform, matrix_down, resample
)
assert_image_similar(transformed, im, epsilon * epsilon_scale)
class TestImageTransformPerspective(TestImageTransformAffine):

View File

@ -1,3 +1,5 @@
import pytest
from PIL.Image import Transpose
from . import helper
@ -9,157 +11,136 @@ HOPPER = {
}
def test_flip_left_right():
def transpose(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.FLIP_LEFT_RIGHT)
assert out.mode == mode
assert out.size == im.size
@pytest.mark.parametrize("mode", HOPPER)
def test_flip_left_right(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.FLIP_LEFT_RIGHT)
assert out.mode == mode
assert out.size == im.size
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((x - 2, 1))
assert im.getpixel((x - 2, 1)) == out.getpixel((1, 1))
assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, y - 2))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, y - 2))
for mode in HOPPER:
transpose(mode)
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((x - 2, 1))
assert im.getpixel((x - 2, 1)) == out.getpixel((1, 1))
assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, y - 2))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, y - 2))
def test_flip_top_bottom():
def transpose(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.FLIP_TOP_BOTTOM)
assert out.mode == mode
assert out.size == im.size
@pytest.mark.parametrize("mode", HOPPER)
def test_flip_top_bottom(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.FLIP_TOP_BOTTOM)
assert out.mode == mode
assert out.size == im.size
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((1, y - 2))
assert im.getpixel((x - 2, 1)) == out.getpixel((x - 2, y - 2))
assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((x - 2, 1))
for mode in HOPPER:
transpose(mode)
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((1, y - 2))
assert im.getpixel((x - 2, 1)) == out.getpixel((x - 2, y - 2))
assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((x - 2, 1))
def test_rotate_90():
def transpose(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.ROTATE_90)
assert out.mode == mode
assert out.size == im.size[::-1]
@pytest.mark.parametrize("mode", HOPPER)
def test_rotate_90(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.ROTATE_90)
assert out.mode == mode
assert out.size == im.size[::-1]
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((1, x - 2))
assert im.getpixel((x - 2, 1)) == out.getpixel((1, 1))
assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, x - 2))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, 1))
for mode in HOPPER:
transpose(mode)
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((1, x - 2))
assert im.getpixel((x - 2, 1)) == out.getpixel((1, 1))
assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, x - 2))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, 1))
def test_rotate_180():
def transpose(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.ROTATE_180)
assert out.mode == mode
assert out.size == im.size
@pytest.mark.parametrize("mode", HOPPER)
def test_rotate_180(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.ROTATE_180)
assert out.mode == mode
assert out.size == im.size
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((x - 2, y - 2))
assert im.getpixel((x - 2, 1)) == out.getpixel((1, y - 2))
assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, 1))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
for mode in HOPPER:
transpose(mode)
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((x - 2, y - 2))
assert im.getpixel((x - 2, 1)) == out.getpixel((1, y - 2))
assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, 1))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
def test_rotate_270():
def transpose(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.ROTATE_270)
assert out.mode == mode
assert out.size == im.size[::-1]
@pytest.mark.parametrize("mode", HOPPER)
def test_rotate_270(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.ROTATE_270)
assert out.mode == mode
assert out.size == im.size[::-1]
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((y - 2, 1))
assert im.getpixel((x - 2, 1)) == out.getpixel((y - 2, x - 2))
assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, x - 2))
for mode in HOPPER:
transpose(mode)
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((y - 2, 1))
assert im.getpixel((x - 2, 1)) == out.getpixel((y - 2, x - 2))
assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, x - 2))
def test_transpose():
def transpose(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.TRANSPOSE)
assert out.mode == mode
assert out.size == im.size[::-1]
@pytest.mark.parametrize("mode", HOPPER)
def test_transpose(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.TRANSPOSE)
assert out.mode == mode
assert out.size == im.size[::-1]
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((1, 1))
assert im.getpixel((x - 2, 1)) == out.getpixel((1, x - 2))
assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, 1))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, x - 2))
for mode in HOPPER:
transpose(mode)
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((1, 1))
assert im.getpixel((x - 2, 1)) == out.getpixel((1, x - 2))
assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, 1))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, x - 2))
def test_tranverse():
def transpose(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.TRANSVERSE)
assert out.mode == mode
assert out.size == im.size[::-1]
@pytest.mark.parametrize("mode", HOPPER)
def test_tranverse(mode):
im = HOPPER[mode]
out = im.transpose(Transpose.TRANSVERSE)
assert out.mode == mode
assert out.size == im.size[::-1]
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((y - 2, x - 2))
assert im.getpixel((x - 2, 1)) == out.getpixel((y - 2, 1))
assert im.getpixel((1, y - 2)) == out.getpixel((1, x - 2))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
for mode in HOPPER:
transpose(mode)
x, y = im.size
assert im.getpixel((1, 1)) == out.getpixel((y - 2, x - 2))
assert im.getpixel((x - 2, 1)) == out.getpixel((y - 2, 1))
assert im.getpixel((1, y - 2)) == out.getpixel((1, x - 2))
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
def test_roundtrip():
for mode in HOPPER:
im = HOPPER[mode]
@pytest.mark.parametrize("mode", HOPPER)
def test_roundtrip(mode):
im = HOPPER[mode]
def transpose(first, second):
return im.transpose(first).transpose(second)
def transpose(first, second):
return im.transpose(first).transpose(second)
assert_image_equal(
im, transpose(Transpose.FLIP_LEFT_RIGHT, Transpose.FLIP_LEFT_RIGHT)
)
assert_image_equal(
im, transpose(Transpose.FLIP_TOP_BOTTOM, Transpose.FLIP_TOP_BOTTOM)
)
assert_image_equal(im, transpose(Transpose.ROTATE_90, Transpose.ROTATE_270))
assert_image_equal(im, transpose(Transpose.ROTATE_180, Transpose.ROTATE_180))
assert_image_equal(
im.transpose(Transpose.TRANSPOSE),
transpose(Transpose.ROTATE_90, Transpose.FLIP_TOP_BOTTOM),
)
assert_image_equal(
im.transpose(Transpose.TRANSPOSE),
transpose(Transpose.ROTATE_270, Transpose.FLIP_LEFT_RIGHT),
)
assert_image_equal(
im.transpose(Transpose.TRANSVERSE),
transpose(Transpose.ROTATE_90, Transpose.FLIP_LEFT_RIGHT),
)
assert_image_equal(
im.transpose(Transpose.TRANSVERSE),
transpose(Transpose.ROTATE_270, Transpose.FLIP_TOP_BOTTOM),
)
assert_image_equal(
im.transpose(Transpose.TRANSVERSE),
transpose(Transpose.ROTATE_180, Transpose.TRANSPOSE),
)
assert_image_equal(
im, transpose(Transpose.FLIP_LEFT_RIGHT, Transpose.FLIP_LEFT_RIGHT)
)
assert_image_equal(
im, transpose(Transpose.FLIP_TOP_BOTTOM, Transpose.FLIP_TOP_BOTTOM)
)
assert_image_equal(im, transpose(Transpose.ROTATE_90, Transpose.ROTATE_270))
assert_image_equal(im, transpose(Transpose.ROTATE_180, Transpose.ROTATE_180))
assert_image_equal(
im.transpose(Transpose.TRANSPOSE),
transpose(Transpose.ROTATE_90, Transpose.FLIP_TOP_BOTTOM),
)
assert_image_equal(
im.transpose(Transpose.TRANSPOSE),
transpose(Transpose.ROTATE_270, Transpose.FLIP_LEFT_RIGHT),
)
assert_image_equal(
im.transpose(Transpose.TRANSVERSE),
transpose(Transpose.ROTATE_90, Transpose.FLIP_LEFT_RIGHT),
)
assert_image_equal(
im.transpose(Transpose.TRANSVERSE),
transpose(Transpose.ROTATE_270, Transpose.FLIP_TOP_BOTTOM),
)
assert_image_equal(
im.transpose(Transpose.TRANSVERSE),
transpose(Transpose.ROTATE_180, Transpose.TRANSPOSE),
)

View File

@ -174,19 +174,24 @@ def test_exceptions():
psRGB = ImageCms.createProfile("sRGB")
pLab = ImageCms.createProfile("LAB")
t = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB")
with pytest.raises(ValueError):
with pytest.raises(ValueError, match="mode mismatch"):
t.apply_in_place(hopper("RGBA"))
# the procedural pyCMS API uses PyCMSError for all sorts of errors
with hopper() as im:
with pytest.raises(ImageCms.PyCMSError):
with pytest.raises(ImageCms.PyCMSError, match="cannot open profile file"):
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")
with pytest.raises(ImageCms.PyCMSError):
with pytest.raises(ImageCms.PyCMSError, match="Invalid type for Profile"):
ImageCms.getProfileName(None)
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)
@ -201,15 +206,32 @@ def test_lab_color_profile():
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")
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")
@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():
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
"""
with pytest.raises(TypeError):
with pytest.raises(TypeError, match="Invalid type for Profile"):
ImageCms.ImageCmsProfile(0).tobytes()
with pytest.raises(TypeError):
with pytest.raises(TypeError, match="Invalid type for Profile"):
ImageCms.ImageCmsProfile(1).tobytes()

View File

@ -64,7 +64,9 @@ def test_mode_mismatch():
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
im = Image.new("RGB", (W, H))
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)
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():
# Arrange
im = Image.new("RGB", (W, H))
@ -192,29 +184,21 @@ def test_bitmap():
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
im = Image.new(mode, (W, H))
draw = ImageDraw.Draw(im)
expected = f"Tests/images/imagedraw_chord_{mode}.png"
# Act
draw.chord(bbox, start, end, fill="red", outline="yellow")
draw.chord(bbox, 0, 180, fill="red", outline="yellow")
# Assert
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():
# Arrange
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")
def helper_ellipse(mode, bbox):
@pytest.mark.parametrize("mode", ("RGB", "L"))
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
def test_ellipse(mode, bbox):
# Arrange
im = Image.new(mode, (W, H))
draw = ImageDraw.Draw(im)
@ -276,16 +262,6 @@ def helper_ellipse(mode, bbox):
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():
# Arrange
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
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
@ -417,14 +394,6 @@ def helper_line(points):
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():
# Arrange
im = Image.new("RGB", (100, 100), "white")
@ -484,7 +453,9 @@ def test_transform():
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
im = Image.new("RGB", (W, H))
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)
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():
# Arrange
im = Image.new("RGB", (W, H))
@ -585,7 +546,8 @@ def test_pieslice_no_spikes():
assert_image_equal(im, im_pre_erase)
def helper_point(points):
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
def test_point(points):
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
@ -597,15 +559,8 @@ def helper_point(points):
assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png")
def test_point1():
helper_point(POINTS1)
def test_point2():
helper_point(POINTS2)
def helper_polygon(points):
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
def test_polygon(points):
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
@ -617,28 +572,20 @@ def helper_polygon(points):
assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png")
def test_polygon1():
helper_polygon(POINTS1)
def test_polygon2():
helper_polygon(POINTS2)
def test_polygon_kite():
@pytest.mark.parametrize("mode", ("RGB", "L"))
def test_polygon_kite(mode):
# Test drawing lines of different gradients (dx>dy, dy>dx) and
# vertical (dx==0) and horizontal (dy==0) lines
for mode in ["RGB", "L"]:
# Arrange
im = Image.new(mode, (W, H))
draw = ImageDraw.Draw(im)
expected = f"Tests/images/imagedraw_polygon_kite_{mode}.png"
# Arrange
im = Image.new(mode, (W, H))
draw = ImageDraw.Draw(im)
expected = f"Tests/images/imagedraw_polygon_kite_{mode}.png"
# Act
draw.polygon(KITE_POINTS, fill="blue", outline="yellow")
# Act
draw.polygon(KITE_POINTS, fill="blue", outline="yellow")
# Assert
assert_image_equal_tofile(im, expected)
# Assert
assert_image_equal_tofile(im, expected)
def test_polygon_1px_high():
@ -682,7 +629,8 @@ def test_polygon_translucent():
assert_image_equal_tofile(im, expected)
def helper_rectangle(bbox):
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
def test_rectangle(bbox):
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
@ -694,14 +642,6 @@ def helper_rectangle(bbox):
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():
# Test drawing a rectangle bigger than the image
# Arrange
@ -1232,21 +1172,39 @@ def test_textsize_empty_string():
# Act
# Should not cause 'SystemError: <built-in method getsize of
# ImagingFont object at 0x...> returned NULL without setting an error'
draw.textsize("")
draw.textsize("\n")
draw.textsize("test\n")
draw.textbbox((0, 0), "")
draw.textbbox((0, 0), "\n")
draw.textbbox((0, 0), "test\n")
draw.textlength("")
@skip_unless_feature("freetype2")
def test_textsize_stroke():
def test_textbbox_stroke():
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
# Act / Assert
assert draw.textsize("A", font, stroke_width=2) == (16, 20)
assert draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2) == (52, 44)
assert draw.textbbox((2, 2), "A", font, stroke_width=2) == (0, 4, 16, 20)
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")
@ -1296,6 +1254,23 @@ def test_stroke_multiline():
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():
# Prepare shape
x0, y0 = 5, 5
@ -1468,7 +1443,7 @@ def test_discontiguous_corners_polygon():
assert_image_similar_tofile(img, expected, 1)
def test_polygon():
def test_polygon2():
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red")

View File

@ -1,5 +1,7 @@
import os.path
import pytest
from PIL import Image, ImageDraw, ImageDraw2
from .helper import (
@ -50,27 +52,19 @@ def test_sanity():
draw.line(list(range(10)), pen)
def helper_ellipse(mode, bbox):
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
def test_ellipse(bbox):
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw2.Draw(im)
pen = ImageDraw2.Pen("blue", width=2)
brush = ImageDraw2.Brush("green")
expected = f"Tests/images/imagedraw_ellipse_{mode}.png"
# Act
draw.ellipse(bbox, pen, brush)
# Assert
assert_image_similar_tofile(im, expected, 1)
def test_ellipse1():
helper_ellipse("RGB", BBOX1)
def test_ellipse2():
helper_ellipse("RGB", BBOX2)
assert_image_similar_tofile(im, "Tests/images/imagedraw_ellipse_RGB.png", 1)
def test_ellipse_edge():
@ -86,7 +80,8 @@ def test_ellipse_edge():
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
im = Image.new("RGB", (W, H))
draw = ImageDraw2.Draw(im)
@ -99,14 +94,6 @@ def helper_line(points):
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():
# Arrange
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")
def helper_polygon(points):
@pytest.mark.parametrize("points", (POINTS1, POINTS2))
def test_polygon(points):
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw2.Draw(im)
@ -136,15 +124,8 @@ def helper_polygon(points):
assert_image_equal_tofile(im, "Tests/images/imagedraw_polygon.png")
def test_polygon1():
helper_polygon(POINTS1)
def test_polygon2():
helper_polygon(POINTS2)
def helper_rectangle(bbox):
@pytest.mark.parametrize("bbox", (BBOX1, BBOX2))
def test_rectangle(bbox):
# Arrange
im = Image.new("RGB", (W, H))
draw = ImageDraw2.Draw(im)
@ -158,14 +139,6 @@ def helper_rectangle(bbox):
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():
# Test drawing a rectangle bigger than the image
# Arrange
@ -205,7 +178,9 @@ def test_textsize():
font = ImageDraw2.Font("white", FONT_PATH)
# Act
size = draw.textsize("ImageDraw2", font)
with pytest.warns(DeprecationWarning) as log:
size = draw.textsize("ImageDraw2", font)
assert len(log) == 1
# Assert
assert size[1] == 12
@ -221,9 +196,10 @@ def test_textsize_empty_string():
# Act
# Should not cause 'SystemError: <built-in method getsize of
# ImagingFont object at 0x...> returned NULL without setting an error'
draw.textsize("", font)
draw.textsize("\n", font)
draw.textsize("test\n", font)
draw.textbbox((0, 0), "", font)
draw.textbbox((0, 0), "\n", font)
draw.textbbox((0, 0), "test\n", font)
draw.textlength("", font)
@skip_unless_feature("freetype2")

View File

@ -1,3 +1,5 @@
import pytest
from PIL import Image, ImageEnhance
from .helper import assert_image_equal, hopper
@ -39,17 +41,17 @@ 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
# Is alpha preserved through image enhancement?
original = _half_transparent_image()
for op in ["Color", "Brightness", "Contrast", "Sharpness"]:
for amount in [0, 0.5, 1.0]:
_check_alpha(
getattr(ImageEnhance, op)(original).enhance(amount),
original,
op,
amount,
)
for amount in [0, 0.5, 1.0]:
_check_alpha(
getattr(ImageEnhance, op)(original).enhance(amount),
original,
op,
amount,
)

File diff suppressed because it is too large Load Diff

View File

@ -140,8 +140,8 @@ def test_ligature_features():
target = "Tests/images/test_ligature_features.png"
assert_image_similar_tofile(im, target, 0.5)
liga_size = ttf.getsize("fi", features=["-liga"])
assert liga_size == (13, 19)
liga_bbox = ttf.getbbox("fi", features=["-liga"])
assert liga_bbox == (0, 4, 13, 19)
def test_kerning_features():

View File

@ -65,14 +65,16 @@ def create_lut():
# create_lut()
def test_lut():
for op in ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge"):
lb = ImageMorph.LutBuilder(op_name=op)
assert lb.get_lut() is None
@pytest.mark.parametrize(
"op", ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge")
)
def test_lut(op):
lb = ImageMorph.LutBuilder(op_name=op)
assert lb.get_lut() is None
lut = lb.build_lut()
with open(f"Tests/images/{op}.lut", "rb") as f:
assert lut == bytearray(f.read())
lut = lb.build_lut()
with open(f"Tests/images/{op}.lut", "rb") as f:
assert lut == bytearray(f.read())
def test_no_operator_loaded():

View File

@ -110,6 +110,16 @@ def test_contain(new_size):
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():
# Same ratio
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():
# Division by zero in equalize if < 255 pixels in image (@PIL163)
@ -345,11 +379,15 @@ def test_exif_transpose():
check(orientation_im)
# Orientation from "XML:com.adobe.xmp" info key
with Image.open("Tests/images/xmp_tags_orientation.png") as im:
assert im.getexif()[0x0112] == 3
for suffix in ("", "_exiftool"):
with Image.open("Tests/images/xmp_tags_orientation" + suffix + ".png") as im:
assert im.getexif()[0x0112] == 3
transposed_im = ImageOps.exif_transpose(im)
assert 0x0112 not in transposed_im.getexif()
transposed_im = ImageOps.exif_transpose(im)
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
# This test image has been manually hexedited from exif_imagemagick.png

View File

@ -45,10 +45,10 @@ def test_viewer_show(order):
not on_ci() or is_win32(),
reason="Only run on CIs; hangs on Windows CIs",
)
def test_show():
for mode in ("1", "I;16", "LA", "RGB", "RGBA"):
im = hopper(mode)
assert ImageShow.show(im)
@pytest.mark.parametrize("mode", ("1", "I;16", "LA", "RGB", "RGBA"))
def test_show(mode):
im = hopper(mode)
assert ImageShow.show(im)
def test_show_without_viewers():
@ -70,12 +70,12 @@ def test_viewer():
viewer.get_command(None)
def test_viewers():
for viewer in ImageShow._viewers:
try:
viewer.get_command("test.jpg")
except NotImplementedError:
pass
@pytest.mark.parametrize("viewer", ImageShow._viewers)
def test_viewers(viewer):
try:
viewer.get_command("test.jpg")
except NotImplementedError:
pass
def test_ipythonviewer():
@ -95,14 +95,14 @@ def test_ipythonviewer():
not on_ci() or is_win32(),
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")
for viewer in ImageShow._viewers:
hopper().save(f)
with pytest.warns(DeprecationWarning):
try:
viewer.show_file(file=f)
except NotImplementedError:
pass
with pytest.raises(TypeError):
viewer.show_file()
hopper().save(f)
with pytest.warns(DeprecationWarning):
try:
viewer.show_file(file=f)
except NotImplementedError:
pass
with pytest.raises(TypeError):
viewer.show_file()

View File

@ -54,32 +54,39 @@ def test_kw():
assert im is None
def test_photoimage():
for mode in TK_MODES:
# test as image:
im = hopper(mode)
@pytest.mark.parametrize("mode", TK_MODES)
def test_photoimage(mode):
# test as image:
im = hopper(mode)
# this should not crash
# this should not crash
im_tk = ImageTk.PhotoImage(im)
assert im_tk.width() == im.width
assert im_tk.height() == im.height
reloaded = ImageTk.getimage(im_tk)
assert_image_equal(reloaded, im.convert("RGBA"))
def test_photoimage_apply_transparency():
with Image.open("Tests/images/pil123p.png") as im:
im_tk = ImageTk.PhotoImage(im)
assert im_tk.width() == im.width
assert im_tk.height() == im.height
reloaded = ImageTk.getimage(im_tk)
assert_image_equal(reloaded, im.convert("RGBA"))
def test_photoimage_blank():
@pytest.mark.parametrize("mode", TK_MODES)
def test_photoimage_blank(mode):
# 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.height() == 100
assert im_tk.width() == 100
assert im_tk.height() == 100
im = Image.new(mode, (100, 100))
reloaded = ImageTk.getimage(im_tk)
assert_image_equal(reloaded.convert(mode), im)
im = Image.new(mode, (100, 100))
reloaded = ImageTk.getimage(im_tk)
assert_image_equal(reloaded.convert(mode), im)
def test_box_deprecation():

View File

@ -1,3 +1,5 @@
import pytest
from PIL import Image
from .helper import hopper
@ -20,65 +22,56 @@ def verify(im1):
), 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
# create/copy/transform and save works as expected.
def basic(mode):
im_in = original.convert(mode)
verify(im_in)
im_in = original.convert(mode)
verify(im_in)
w, h = im_in.size
w, h = im_in.size
im_out = im_in.copy()
verify(im_out) # copy
im_out = im_in.copy()
verify(im_out) # copy
im_out = im_in.transform((w, h), Image.Transform.EXTENT, (0, 0, w, h))
verify(im_out) # transform
im_out = im_in.transform((w, h), Image.Transform.EXTENT, (0, 0, w, h))
verify(im_out) # transform
filename = str(tmp_path / "temp.im")
im_in.save(filename)
filename = str(tmp_path / "temp.im")
im_in.save(filename)
with Image.open(filename) as im_out:
verify(im_in)
verify(im_out)
im_out = im_in.crop((0, 0, w, h))
verify(im_out)
im_out = Image.new(mode, (w, h), None)
im_out.paste(im_in.crop((0, 0, w // 2, h)), (0, 0))
im_out.paste(im_in.crop((w // 2, 0, w, h)), (w // 2, 0))
with Image.open(filename) as im_out:
verify(im_in)
verify(im_out)
im_in = Image.new(mode, (1, 1), 1)
assert im_in.getpixel((0, 0)) == 1
im_out = im_in.crop((0, 0, w, h))
verify(im_out)
im_in.putpixel((0, 0), 2)
assert im_in.getpixel((0, 0)) == 2
im_out = Image.new(mode, (w, h), None)
im_out.paste(im_in.crop((0, 0, w // 2, h)), (0, 0))
im_out.paste(im_in.crop((w // 2, 0, w, h)), (w // 2, 0))
if mode == "L":
maximum = 255
else:
maximum = 32767
verify(im_in)
verify(im_out)
im_in = Image.new(mode, (1, 1), 256)
assert im_in.getpixel((0, 0)) == min(256, maximum)
im_in = Image.new(mode, (1, 1), 1)
assert im_in.getpixel((0, 0)) == 1
im_in.putpixel((0, 0), 512)
assert im_in.getpixel((0, 0)) == min(512, maximum)
im_in.putpixel((0, 0), 2)
assert im_in.getpixel((0, 0)) == 2
basic("L")
if mode == "L":
maximum = 255
else:
maximum = 32767
basic("I;16")
basic("I;16B")
basic("I;16L")
im_in = Image.new(mode, (1, 1), 256)
assert im_in.getpixel((0, 0)) == min(256, maximum)
basic("I")
im_in.putpixel((0, 0), 512)
assert im_in.getpixel((0, 0)) == min(512, maximum)
def test_tobytes():

View File

@ -137,19 +137,9 @@ def test_save_tiff_uint16():
assert img_px[0, 0] == pixel_value
def test_to_array():
def _to_array(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 = [
@pytest.mark.parametrize(
"mode, dtype",
(
("L", numpy.uint8),
("I", numpy.int32),
("F", numpy.float32),
@ -163,10 +153,18 @@ def test_to_array():
("I;16B", ">u2"),
("I;16L", "<u2"),
("HSV", numpy.uint8),
]
),
)
def test_to_array(mode, dtype):
img = hopper(mode)
for mode in modes:
_to_array(*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
def test_point_lut():

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