Merge branch 'main' into i16n

This commit is contained in:
Andrew Murray 2023-03-13 14:33:26 +11:00
commit f8d93267e1
262 changed files with 3275 additions and 1961 deletions

View File

@ -21,9 +21,11 @@ environment:
install:
- '%PYTHON%\%EXECUTABLE% --version'
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
- 7z x pillow-depends.zip -oc:\
- 7z x pillow-test-images.zip -oc:\
- mv c:\pillow-depends-main c:\pillow-depends
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
- 7z x ..\pillow-depends\nasm-2.15.05-win64.zip -oc:\
- ..\pillow-depends\gs1000w32.exe /S
- path c:\nasm-2.15.05;C:\Program Files (x86)\gs\gs10.0.0\bin;%PATH%

View File

@ -37,7 +37,8 @@ python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
if [[ $(uname) != CYGWIN* ]]; then
python3 -m pip install numpy
# TODO Remove condition when NumPy supports 3.12
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
# PyQt6 doesn't support PyPy3
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then

View File

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

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

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

View File

@ -13,7 +13,8 @@ python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
python3 -m pip install numpy
# TODO Remove condition when NumPy supports 3.12
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
# extra test images
pushd depends && ./install_extra_test_images.sh && popd

View File

@ -1,6 +1,15 @@
name: Test Cygwin
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read
@ -34,18 +43,34 @@ jobs:
with:
platform: x86_64
packages: >
ImageMagick gcc-g++ ghostscript jpeg libfreetype-devel
libimagequant-devel libjpeg-devel liblapack-devel
liblcms2-devel libopenjp2-devel libraqm-devel
libtiff-devel libwebp-devel libxcb-devel libxcb-xinerama0
make netpbm perl
gcc-g++
ghostscript
ImageMagick
jpeg
libfreetype-devel
libimagequant-devel
libjpeg-devel
liblapack-devel
liblcms2-devel
libopenjp2-devel
libraqm-devel
libtiff-devel
libwebp-devel
libxcb-devel
libxcb-xinerama0
make
netpbm
perl
python3${{ matrix.python-minor-version }}-cffi
python3${{ matrix.python-minor-version }}-cython
python3${{ matrix.python-minor-version }}-devel
python3${{ matrix.python-minor-version }}-numpy
python3${{ matrix.python-minor-version }}-sip
python3${{ matrix.python-minor-version }}-tkinter
qt5-devel-tools subversion xorg-server-extra zlib-devel
qt5-devel-tools
wget
xorg-server-extra
zlib-devel
- name: Add Lapack to PATH
uses: egor-tensin/cleanup-path@v3

View File

@ -1,6 +1,15 @@
name: Test Docker
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read
@ -87,6 +96,7 @@ jobs:
with:
flags: GHA_Docker
name: ${{ matrix.docker }}
gcov: true
success:
permissions:

View File

@ -1,11 +1,20 @@
name: Test MinGW
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read
concurrency:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@ -45,12 +54,6 @@ jobs:
- name: Install dependencies
run: |
pacman -S --noconfirm \
${{ matrix.package }}-python3-cffi \
${{ matrix.package }}-python3-numpy \
${{ matrix.package }}-python3-olefile \
${{ matrix.package }}-python3-pip \
${{ matrix.package }}-python-pyqt6 \
${{ matrix.package }}-python3-setuptools \
${{ matrix.package }}-freetype \
${{ matrix.package }}-gcc \
${{ matrix.package }}-ghostscript \
@ -61,7 +64,16 @@ jobs:
${{ matrix.package }}-libtiff \
${{ matrix.package }}-libwebp \
${{ matrix.package }}-openjpeg2 \
subversion
${{ matrix.package }}-python3-cffi \
${{ matrix.package }}-python3-numpy \
${{ matrix.package }}-python3-olefile \
${{ matrix.package }}-python3-pip \
${{ matrix.package }}-python3-setuptools
if [ ${{ matrix.package }} == "mingw-w64-x86_64" ]; then
pacman -S --noconfirm \
${{ matrix.package }}-python-pyqt6
fi
python3 -m pip install pyroma pytest pytest-cov pytest-timeout

View File

@ -5,10 +5,12 @@ name: Test Valgrind
on:
push:
paths:
- ".github/workflows/test-valgrind.yml"
- "**.c"
- "**.h"
pull_request:
paths:
- ".github/workflows/test-valgrind.yml"
- "**.c"
- "**.h"
workflow_dispatch:
@ -16,7 +18,7 @@ on:
permissions:
contents: read
concurrency:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@ -48,5 +50,5 @@ jobs:
run: |
# The Pillow user in the docker container is UID 1000
sudo chown -R 1000 $GITHUB_WORKSPACE
docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
sudo chown -R runner $GITHUB_WORKSPACE

View File

@ -1,6 +1,15 @@
name: Test Windows
on: [push, pull_request, workflow_dispatch]
on:
push:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
pull_request:
paths-ignore:
- ".github/workflows/docs.yml"
- "docs/**"
workflow_dispatch:
permissions:
contents: read
@ -15,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12-dev"]
architecture: ["x86", "x64"]
include:
# PyPy 7.3.4+ only ships 64-bit binaries for Windows
@ -38,6 +47,12 @@ jobs:
repository: python-pillow/pillow-depends
path: winbuild\depends
- name: Checkout extra test images
uses: actions/checkout@v3
with:
repository: python-pillow/test-images
path: Tests\test-images
# sets env: pythonLocation
- name: Set up Python
uses: actions/setup-python@v4
@ -62,7 +77,8 @@ jobs:
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\
# Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images
# make cache key depend on VS version
& "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" `

View File

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

2
.gitignore vendored
View File

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

View File

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/psf/black
rev: 22.12.0
rev: 23.1.0
hooks:
- id: black
args: [--target-version=py37]
@ -9,7 +9,7 @@ repos:
types: []
- repo: https://github.com/PyCQA/isort
rev: 5.11.1
rev: 5.12.0
hooks:
- id: isort
@ -26,7 +26,7 @@ repos:
- id: yesqa
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.3.1
rev: v1.4.2
hooks:
- id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
@ -35,10 +35,11 @@ repos:
rev: 6.0.0
hooks:
- id: flake8
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
additional_dependencies:
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.9.0
rev: v1.10.0
hooks:
- id: python-check-blanket-noqa
- id: rst-backticks
@ -56,7 +57,7 @@ repos:
- id: sphinx-lint
- repo: https://github.com/tox-dev/tox-ini-fmt
rev: 0.5.2
rev: 0.6.1
hooks:
- id: tox-ini-fmt

View File

@ -2,9 +2,111 @@
Changelog (Pillow)
==================
9.4.0 (unreleased)
9.5.0 (unreleased)
------------------
- Added QOI reading #6852
[radarhere, hugovk]
- Added saving RGBA images as PDFs #6925
[radarhere]
- Do not raise an error if os.environ does not contain PATH #6935
[radarhere, hugovk]
- Close OleFileIO instance when closing or exiting FPX or MIC #7005
[radarhere]
- Added __int__ to IFDRational for Python >= 3.11 #6998
[radarhere]
- Added memoryview support to Dib.frombytes() #6988
[radarhere, nulano]
- Close file pointer copy in the libtiff encoder if still open #6986
[fcarron, radarhere]
- Raise an error if ImageDraw co-ordinates are incorrectly ordered #6978
[radarhere]
- Added "corners" argument to ImageDraw rounded_rectangle() #6954
[radarhere]
- Added memoryview support to frombytes() #6974
[radarhere]
- Allow comments in FITS images #6973
[radarhere]
- Support saving PDF with different X and Y resolutions #6961
[jvanderneutstulen, radarhere, hugovk]
- Fixed writing int as UNDEFINED tag #6950
[radarhere]
- Raise an error if EXIF data is too long when saving JPEG #6939
[radarhere]
- Handle more than one directory returned by pkg-config #6896
[sebastic, radarhere]
- Do not retry past formats when loading all formats for the first time #6902
[radarhere]
- Do not retry specified formats if they failed when opening #6893
[radarhere]
- Do not unintentionally load TIFF format at first #6892
[radarhere]
- Stop reading when EPS line becomes too long #6897
[radarhere]
- Allow writing IFDRational to BYTE tag #6890
[radarhere]
- Raise ValueError for BoxBlur filter with negative radius #6874
[hugovk, radarhere]
- Support arbitrary number of loaded modules on Windows #6761
[javidcf, radarhere, nulano]
9.4.0 (2023-01-02)
------------------
- Fixed null pointer dereference crash with malformed font #6846
[wiredfool, radarhere]
- Return from ImagingFill early if image has a zero dimension #6842
[radarhere]
- Reversed deprecations for Image constants, except for duplicate Resampling attributes #6830
[radarhere]
- Improve exception traceback readability #6836
[hugovk, radarhere]
- Do not attempt to read IFD1 if absent #6840
[radarhere]
- Fixed writing int as ASCII tag #6800
[radarhere]
- If available, use wl-paste or xclip for grabclipboard() on Linux #6783
[radarhere]
- Added signed option when saving JPEG2000 images #6709
[radarhere]
- Patch OpenJPEG to include ARM64 fix #6718
[radarhere]
- Added support for I;16 modes in putdata() #6825
[radarhere]
- Added conversion from RGBa to RGB #6708
[radarhere]
- Added DDS support for uncompressed L and LA images #6820
[radarhere, REDxEYE]

View File

@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
Pillow is the friendly PIL fork. It is
Copyright © 2010-2022 by Alex Clark and contributors
Copyright © 2010-2023 by Jeffrey A. Clark (Alex) and contributors.
Like PIL, Pillow is licensed under the open source HPND License:
@ -13,8 +13,8 @@ By obtaining, using, and/or copying this software and/or its associated
documentation, you agree that you have read, understood, and will comply
with the following terms and conditions:
Permission to use, copy, modify, and distribute this software and its
associated documentation for any purpose and without fee is hereby granted,
Permission to use, copy, modify and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appears in all copies, and that
both that copyright notice and this permission notice appear in supporting
documentation, and that the name of Secret Labs AB or the author not be

View File

@ -6,8 +6,8 @@
## Python Imaging Library (Fork)
Pillow is the friendly PIL fork by [Alex Clark and
Contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
Pillow is the friendly PIL fork by [Jeffrey A. Clark (Alex) and
contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
As of 2019, Pillow development is
[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
@ -88,6 +88,10 @@ As of 2019, Pillow development is
<a href="https://twitter.com/PythonPillow"><img
alt="Follow on https://twitter.com/PythonPillow"
src="https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg"></a>
<a href="https://fosstodon.org/@pillow"><img
alt="Follow on https://fosstodon.org/@pillow"
src="https://img.shields.io/badge/publish-on%20Mastodon-595aff.svg"
rel="me"></a>
</td>
</tr>
</table>

View File

@ -111,7 +111,7 @@ Released as needed privately to individual vendors for critical security-related
## Publicize Release
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
## Documentation

View File

@ -4,7 +4,6 @@ TEST_FILE = "Tests/images/fli_overflow.fli"
def test_fli_overflow():
# this should not crash with a malloc error or access violation
with Image.open(TEST_FILE) as im:
im.load()

View File

@ -23,7 +23,6 @@ def test_ignore_dos_text():
def test_dos_text():
try:
im = Image.open(TEST_FILE)
im.load()

View File

@ -0,0 +1,10 @@
STARTFONT
FONT ÿ
SIZE 10
FONTBOUNDINGBOX
CHARS
STARTCHAR
ENCODING
BBX 2 5
ENDCHAR
ENDFONT

View File

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

BIN
Tests/images/hopper.qoi Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

BIN
Tests/images/pil123rgba.qoi Normal file

Binary file not shown.

View File

@ -57,6 +57,6 @@ def test_fuzz_fonts(path):
with open(path, "rb") as f:
try:
fuzzers.fuzz_font(f.read())
except (Image.DecompressionBombError, Image.DecompressionBombWarning):
except (Image.DecompressionBombError, Image.DecompressionBombWarning, OSError):
pass
assert True

View File

@ -18,7 +18,6 @@ def test_bad():
"""These shouldn't crash/dos, but they shouldn't return anything
either"""
for f in get_files("b"):
# Assert that there is no unclosed file warning
with warnings.catch_warnings():
try:

View File

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

View File

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

View File

@ -11,6 +11,11 @@ from PIL import _deprecate
"Old thing is deprecated and will be removed in Pillow 10 "
r"\(2023-07-01\)\. Use new thing instead\.",
),
(
11,
"Old thing is deprecated and will be removed in Pillow 11 "
r"\(2024-10-15\)\. Use new thing instead\.",
),
(
None,
r"Old thing is deprecated and will be removed in a future version\. "

View File

@ -263,13 +263,11 @@ def test_apng_chunk_errors():
with Image.open("Tests/images/apng/chunk_no_actl.png") as im:
assert not im.is_animated
def open():
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/chunk_multi_actl.png") as im:
im.load()
assert not im.is_animated
pytest.warns(UserWarning, open)
with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im:
assert not im.is_animated
@ -287,21 +285,17 @@ def test_apng_chunk_errors():
def test_apng_syntax_errors():
def open_frames_zero():
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im:
assert not im.is_animated
with pytest.raises(OSError):
im.load()
pytest.warns(UserWarning, open_frames_zero)
def open_frames_zero_default():
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im:
assert not im.is_animated
im.load()
pytest.warns(UserWarning, open_frames_zero_default)
# we can handle this case gracefully
exception = None
with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im:
@ -316,13 +310,11 @@ def test_apng_syntax_errors():
im.seek(im.n_frames - 1)
im.load()
def open():
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im:
assert not im.is_animated
im.load()
pytest.warns(UserWarning, open)
@pytest.mark.parametrize(
"test_file",

View File

@ -141,7 +141,6 @@ def test_rgba_bitfields():
# This test image has been manually hexedited
# to change the bitfield compression in the header from XBGR to RGBA
with Image.open("Tests/images/rgb32bf-rgba.bmp") as im:
# So before the comparing the image, swap the channels
b, g, r = im.split()[1:]
im = Image.merge("RGB", (r, g, b))

View File

@ -10,7 +10,6 @@ TEST_FILE = "Tests/images/gfs.t06z.rassda.tm00.bufr_d"
def test_open():
# Act
with Image.open(TEST_FILE) as im:
# Assert
assert im.format == "BUFR"
@ -31,7 +30,6 @@ def test_invalid_file():
def test_load():
# Arrange
with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler
with pytest.raises(OSError):
im.load()
@ -58,6 +56,7 @@ def test_handler(tmp_path):
def load(self, im):
self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1))
def save(self, im, fp, filename):

View File

@ -15,7 +15,6 @@ def test_sanity():
# Act
with Image.open(TEST_FILE) as im:
# Assert
assert im.size == (128, 128)
assert isinstance(im, DcxImagePlugin.DcxImageFile)
@ -29,7 +28,8 @@ def test_unclosed_file():
im = Image.open(TEST_FILE)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():
@ -54,7 +54,6 @@ def test_invalid_file():
def test_tell():
# Arrange
with Image.open(TEST_FILE) as im:
# Act
frame = im.tell()

View File

@ -80,7 +80,6 @@ def test_invalid_file():
@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
def test_cmyk():
with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image:
assert cmyk_image.mode == "CMYK"
assert cmyk_image.size == (100, 100)
assert cmyk_image.format == "EPS"

View File

@ -12,7 +12,6 @@ TEST_FILE = "Tests/images/hopper.fits"
def test_open():
# Act
with Image.open(TEST_FILE) as im:
# Assert
assert im.format == "FITS"
assert im.size == (128, 128)
@ -45,6 +44,12 @@ def test_naxis_zero():
pass
def test_comment():
image_data = b"SIMPLE = T / comment string"
with pytest.raises(OSError):
FitsImagePlugin.FitsImageFile(BytesIO(image_data))
def test_stub_deprecated():
class Handler:
opened = False
@ -55,6 +60,7 @@ def test_stub_deprecated():
def load(self, im):
self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1))
handler = Handler()

View File

@ -36,7 +36,8 @@ def test_unclosed_file():
im = Image.open(static_test_file)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():
@ -64,7 +65,6 @@ def test_context_manager():
def test_tell():
# Arrange
with Image.open(static_test_file) as im:
# Act
frame = im.tell()
@ -110,7 +110,6 @@ def test_eoferror():
def test_seek_tell():
with Image.open(animated_test_file) as im:
layer_number = im.tell()
assert layer_number == 0

View File

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

View File

@ -36,7 +36,8 @@ def test_unclosed_file():
im = Image.open(TEST_GIF)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():
@ -158,39 +159,42 @@ def test_optimize():
assert test_bilevel(1) == 799
def test_optimize_correctness():
# 256 color Palette image, posterize to > 128 and < 128 levels
# Size bigger and smaller than 512x512
@pytest.mark.parametrize(
"colors, size, expected_palette_length",
(
# These do optimize the palette
(256, 511, 256),
(255, 511, 255),
(129, 511, 129),
(128, 511, 128),
(64, 511, 64),
(4, 511, 4),
# These don't optimize the palette
(128, 513, 256),
(64, 513, 256),
(4, 513, 256),
),
)
def test_optimize_correctness(colors, size, expected_palette_length):
# 256 color Palette image, posterize to > 128 and < 128 levels.
# Size bigger and smaller than 512x512.
# Check the palette for number of colors allocated.
# Check for correctness after conversion back to RGB
def check(colors, size, expected_palette_length):
# make an image with empty colors in the start of the palette range
im = Image.frombytes(
"P", (colors, colors), bytes(range(256 - colors, 256)) * colors
)
im = im.resize((size, size))
outfile = BytesIO()
im.save(outfile, "GIF")
outfile.seek(0)
with Image.open(outfile) as reloaded:
# check palette length
palette_length = max(i + 1 for i, v in enumerate(reloaded.histogram()) if v)
assert expected_palette_length == palette_length
# Check for correctness after conversion back to RGB.
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
# make an image with empty colors in the start of the palette range
im = Image.frombytes(
"P", (colors, colors), bytes(range(256 - colors, 256)) * colors
)
im = im.resize((size, size))
outfile = BytesIO()
im.save(outfile, "GIF")
outfile.seek(0)
with Image.open(outfile) as reloaded:
# check palette length
palette_length = max(i + 1 for i, v in enumerate(reloaded.histogram()) if v)
assert expected_palette_length == palette_length
# 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)
# These don't optimize the palette
check(128, 513, 256)
check(64, 513, 256)
check(4, 513, 256)
assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
def test_optimize_full_l():
@ -206,7 +210,7 @@ def test_optimize_if_palette_can_be_reduced_by_half():
im = im.resize((591, 443))
im_rgb = im.convert("RGB")
for (optimize, colors) in ((False, 256), (True, 8)):
for optimize, colors in ((False, 256), (True, 8)):
out = BytesIO()
im_rgb.save(out, "GIF", optimize=optimize)
with Image.open(out) as reloaded:
@ -218,7 +222,6 @@ def test_roundtrip(tmp_path):
im = hopper()
im.save(out)
with Image.open(out) as reread:
assert_image_similar(reread.convert("RGB"), im, 50)
@ -229,7 +232,6 @@ def test_roundtrip2(tmp_path):
im2 = im.copy()
im2.save(out)
with Image.open(out) as reread:
assert_image_similar(reread.convert("RGB"), hopper(), 50)
@ -239,7 +241,6 @@ def test_roundtrip_save_all(tmp_path):
im = hopper()
im.save(out, save_all=True)
with Image.open(out) as reread:
assert_image_similar(reread.convert("RGB"), im, 50)
# Multiframe image
@ -281,13 +282,11 @@ def test_headers_saving_for_animated_gifs(tmp_path):
important_headers = ["background", "version", "duration", "loop"]
# Multiframe image
with Image.open("Tests/images/dispose_bgnd.gif") as im:
info = im.info.copy()
out = str(tmp_path / "temp.gif")
im.save(out, save_all=True)
with Image.open(out) as reread:
for header in important_headers:
assert info[header] == reread.info[header]
@ -305,7 +304,6 @@ def test_palette_handling(tmp_path):
im2.save(f, optimize=True)
with Image.open(f) as reloaded:
assert_image_similar(im, reloaded.convert("RGB"), 10)
@ -321,7 +319,6 @@ def test_palette_434(tmp_path):
orig = "Tests/images/test.colors.gif"
with Image.open(orig) as im:
with roundtrip(im) as reloaded:
assert_image_similar(im, reloaded, 1)
with roundtrip(im, optimize=True) as reloaded:
@ -572,7 +569,6 @@ def test_save_dispose(tmp_path):
)
with Image.open(out) as img:
for i in range(2):
img.seek(img.tell() + 1)
assert img.disposal_method == i + 1
@ -770,7 +766,6 @@ def test_multiple_duration(tmp_path):
out, save_all=True, append_images=im_list[1:], duration=duration_list
)
with Image.open(out) as reread:
for duration in duration_list:
assert reread.info["duration"] == duration
try:
@ -783,7 +778,6 @@ def test_multiple_duration(tmp_path):
out, save_all=True, append_images=im_list[1:], duration=tuple(duration_list)
)
with Image.open(out) as reread:
for duration in duration_list:
assert reread.info["duration"] == duration
try:
@ -841,7 +835,6 @@ def test_identical_frames(tmp_path):
out, save_all=True, append_images=im_list[1:], duration=duration_list
)
with Image.open(out) as reread:
# Assert that the first three frames were combined
assert reread.n_frames == 2
@ -1095,7 +1088,8 @@ def test_rgb_transparency(tmp_path):
im = Image.new("RGB", (1, 1))
im.info["transparency"] = b""
ims = [Image.new("RGB", (1, 1))]
pytest.warns(UserWarning, im.save, out, save_all=True, append_images=ims)
with pytest.warns(UserWarning):
im.save(out, save_all=True, append_images=ims)
with Image.open(out) as reloaded:
assert "transparency" not in reloaded.info

View File

@ -10,7 +10,6 @@ TEST_FILE = "Tests/images/WAlaska.wind.7days.grb"
def test_open():
# Act
with Image.open(TEST_FILE) as im:
# Assert
assert im.format == "GRIB"
@ -31,7 +30,6 @@ def test_invalid_file():
def test_load():
# Arrange
with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler
with pytest.raises(OSError):
im.load()
@ -58,6 +56,7 @@ def test_handler(tmp_path):
def load(self, im):
self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1))
def save(self, im, fp, filename):

View File

@ -8,7 +8,6 @@ TEST_FILE = "Tests/images/hdf5.h5"
def test_open():
# Act
with Image.open(TEST_FILE) as im:
# Assert
assert im.format == "HDF5"
@ -29,7 +28,6 @@ def test_invalid_file():
def test_load():
# Arrange
with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler
with pytest.raises(OSError):
im.load()
@ -59,6 +57,7 @@ def test_handler(tmp_path):
def load(self, im):
self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1))
def save(self, im, fp, filename):

View File

@ -16,7 +16,6 @@ def test_sanity():
# Loading this icon by default should result in the largest size
# (512x512@2x) being loaded
with Image.open(TEST_FILE) as im:
# Assert that there is no unclosed file warning
with warnings.catch_warnings():
im.load()

View File

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

View File

@ -32,7 +32,8 @@ def test_unclosed_file():
im = Image.open(TEST_IM)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():
@ -51,7 +52,6 @@ def test_context_manager():
def test_tell():
# Arrange
with Image.open(TEST_IM) as im:
# Act
frame = im.tell()

View File

@ -11,7 +11,6 @@ TEST_FILE = "Tests/images/iptc.jpg"
def test_getiptcinfo_jpg_none():
# Arrange
with hopper() as im:
# Act
iptc = IptcImagePlugin.getiptcinfo(im)
@ -22,7 +21,6 @@ def test_getiptcinfo_jpg_none():
def test_getiptcinfo_jpg_found():
# Arrange
with Image.open(TEST_FILE) as im:
# Act
iptc = IptcImagePlugin.getiptcinfo(im)
@ -35,7 +33,6 @@ def test_getiptcinfo_jpg_found():
def test_getiptcinfo_tiff_none():
# Arrange
with Image.open("Tests/images/hopper.tif") as im:
# Act
iptc = IptcImagePlugin.getiptcinfo(im)

View File

@ -57,7 +57,6 @@ class TestFileJpeg:
return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode)))
def test_sanity(self):
# internal version number
assert re.search(r"\d+\.\d+$", features.version_codec("jpg"))
@ -271,7 +270,10 @@ class TestFileJpeg:
# https://github.com/python-pillow/Pillow/issues/148
f = str(tmp_path / "temp.jpg")
im = hopper()
im.save(f, "JPEG", quality=90, exif=b"1" * 65532)
im.save(f, "JPEG", quality=90, exif=b"1" * 65533)
with pytest.raises(ValueError):
im.save(f, "JPEG", quality=90, exif=b"1" * 65534)
def test_exif_typeerror(self):
with Image.open("Tests/images/exif_typeerror.jpg") as im:
@ -368,7 +370,6 @@ class TestFileJpeg:
def test_exif_gps_typeerror(self):
with Image.open("Tests/images/exif_gps_typeerror.jpg") as im:
# Should not raise a TypeError
im._getexif()
@ -447,7 +448,7 @@ class TestFileJpeg:
ims = im.get_child_images()
assert len(ims) == 1
assert_image_equal_tofile(ims[0], "Tests/images/flower_thumbnail.png")
assert_image_similar_tofile(ims[0], "Tests/images/flower_thumbnail.png", 2.1)
def test_mp(self):
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
@ -682,7 +683,6 @@ class TestFileJpeg:
# Shouldn't raise error
fn = "Tests/images/sugarshack_bad_mpo_header.jpg"
with pytest.warns(UserWarning, Image.open, fn) as im:
# Assert
assert im.format == "JPEG"
@ -704,7 +704,6 @@ class TestFileJpeg:
# Arrange
outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/hopper.tif") as im:
# Act
im.save(outfile, "JPEG", dpi=im.info["dpi"])
@ -731,7 +730,6 @@ class TestFileJpeg:
# This Photoshop CC 2017 image has DPI in EXIF not metadata
# EXIF XResolution is (2000000, 10000)
with Image.open("Tests/images/photoshop-200dpi.jpg") as im:
# Act / Assert
assert im.info.get("dpi") == (200, 200)
@ -740,7 +738,6 @@ class TestFileJpeg:
# This image has DPI in EXIF not metadata
# EXIF XResolution is 72
with Image.open("Tests/images/exif-72dpi-int.jpg") as im:
# Act / Assert
assert im.info.get("dpi") == (72, 72)
@ -749,7 +746,6 @@ class TestFileJpeg:
# This is photoshop-200dpi.jpg with EXIF resolution unit set to cm:
# exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg
with Image.open("Tests/images/exif-200dpcm.jpg") as im:
# Act / Assert
assert im.info.get("dpi") == (508, 508)
@ -758,7 +754,6 @@ class TestFileJpeg:
# This is photoshop-200dpi.jpg with EXIF resolution set to 0/0:
# exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg
with Image.open("Tests/images/exif-dpi-zerodivision.jpg") as im:
# Act / Assert
# This should return the default, and not raise a ZeroDivisionError
assert im.info.get("dpi") == (72, 72)
@ -767,7 +762,6 @@ class TestFileJpeg:
# Arrange
# 0x011A tag in this exif contains string '300300\x02'
with Image.open("Tests/images/broken_exif_dpi.jpg") as im:
# Act / Assert
# This should return the default
assert im.info.get("dpi") == (72, 72)
@ -777,7 +771,6 @@ class TestFileJpeg:
# This is photoshop-200dpi.jpg with resolution removed from EXIF:
# exiftool "-*resolution*"= photoshop-200dpi.jpg
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:
# Act / Assert
# "When the image resolution is unknown, 72 [dpi] is designated."
# https://exiv2.org/tags.html
@ -787,7 +780,6 @@ class TestFileJpeg:
# This is no-dpi-in-exif with the tiff header of the exif block
# hexedited from MM * to FF FF FF FF
with Image.open("Tests/images/invalid-exif.jpg") as im:
# This should return the default, and not a SyntaxError or
# OSError for unidentified image.
assert im.info.get("dpi") == (72, 72)
@ -810,7 +802,6 @@ class TestFileJpeg:
def test_invalid_exif_x_resolution(self):
# When no x or y resolution is defined in EXIF
with Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") as im:
# This should return the default, and not a ValueError or
# OSError for an unidentified image.
assert im.info.get("dpi") == (72, 72)
@ -820,7 +811,6 @@ class TestFileJpeg:
# This image has been manually hexedited to have an IFD offset of 10,
# in contrast to normal 8
with Image.open("Tests/images/exif-ifd-offset.jpg") as im:
# Act / Assert
assert im._getexif()[306] == "2017:03:13 23:03:09"

View File

@ -252,11 +252,24 @@ def test_mct():
assert_image_similar(im, jp2, 1.0e-3)
def test_sgnd(tmp_path):
outfile = str(tmp_path / "temp.jp2")
im = Image.new("L", (1, 1))
im.save(outfile)
with Image.open(outfile) as reloaded:
assert reloaded.getpixel((0, 0)) == 0
im = Image.new("L", (1, 1))
im.save(outfile, signed=True)
with Image.open(outfile) as reloaded_signed:
assert reloaded_signed.getpixel((0, 0)) == 128
def test_rgba():
# Arrange
with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2:
# Act
j2k.load()
jp2.load()

View File

@ -645,7 +645,6 @@ class TestFileLibTiff(LibTiffTestCase):
pilim = hopper()
def save_bytesio(compression=None):
buffer_io = io.BytesIO()
pilim.save(buffer_io, format="tiff", compression=compression)
buffer_io.seek(0)
@ -740,7 +739,6 @@ class TestFileLibTiff(LibTiffTestCase):
def test_multipage_compression(self):
with Image.open("Tests/images/compression.tif") as im:
im.seek(0)
assert im._compression == "tiff_ccitt"
assert im.size == (10, 10)
@ -986,6 +984,36 @@ class TestFileLibTiff(LibTiffTestCase):
) as im:
assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png")
@pytest.mark.parametrize(
"file_name, mode, size, tile",
[
(
"tiff_wrong_bits_per_sample.tiff",
"RGBA",
(52, 53),
[("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
),
(
"tiff_wrong_bits_per_sample_2.tiff",
"RGB",
(16, 16),
[("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
),
(
"tiff_wrong_bits_per_sample_3.tiff",
"RGBA",
(512, 256),
[("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
),
],
)
def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
with Image.open("Tests/images/" + file_name) as im:
assert im.mode == mode
assert im.size == size
assert im.tile == tile
im.load()
def test_no_rows_per_strip(self):
# This image does not have a RowsPerStrip TIFF tag
infile = "Tests/images/no_rows_per_strip.tif"
@ -1067,3 +1095,27 @@ class TestFileLibTiff(LibTiffTestCase):
out = str(tmp_path / "temp.tif")
with pytest.raises(SystemError):
im.save(out, compression=compression)
def test_save_many_compressed(self, tmp_path):
im = hopper()
out = str(tmp_path / "temp.tif")
for _ in range(10000):
im.save(out, compression="jpeg")
@pytest.mark.parametrize(
"path, sizes",
(
("Tests/images/hopper.tif", ()),
("Tests/images/child_ifd.tiff", (16, 8)),
("Tests/images/child_ifd_jpeg.tiff", (20,)),
),
)
def test_get_child_images(self, path, sizes):
with Image.open(path) as im:
ims = im.get_child_images()
assert len(ims) == len(sizes)
for i, im in enumerate(ims):
w = sizes[i]
expected = Image.new("RGB", (w, w), "#f00")
assert_image_similar(im, expected, 1)

View File

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

View File

@ -42,7 +42,8 @@ def test_unclosed_file():
im = Image.open(test_files[0])
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():
@ -168,8 +169,7 @@ def test_mp_no_data():
def test_mp_attribute(test_file):
with Image.open(test_file) as im:
mpinfo = im._getmp()
frame_number = 0
for mpentry in mpinfo[0xB002]:
for frame_number, mpentry in enumerate(mpinfo[0xB002]):
mpattr = mpentry["Attribute"]
if frame_number:
assert not mpattr["RepresentativeImageFlag"]
@ -180,7 +180,6 @@ def test_mp_attribute(test_file):
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)

View File

@ -44,7 +44,6 @@ def test_open_windows_v1():
# Arrange
# Act
with Image.open(TEST_FILE) as im:
# Assert
assert_image_equal(im, hopper("1"))
assert isinstance(im, MspImagePlugin.MspImageFile)
@ -59,7 +58,6 @@ def _assert_file_image_equal(source_path, target_path):
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
)
def test_open_windows_v2():
files = (
os.path.join(EXTRA_DIR, f)
for f in os.listdir(EXTRA_DIR)

View File

@ -8,7 +8,7 @@ import pytest
from PIL import Image, PdfParser, features
from .helper import hopper, mark_if_feature_version
from .helper import hopper, mark_if_feature_version, skip_unless_feature
def helper_save_as_pdf(tmp_path, mode, **kwargs):
@ -42,6 +42,11 @@ def test_save(tmp_path, mode):
helper_save_as_pdf(tmp_path, mode)
@skip_unless_feature("jpg_2000")
def test_save_rgba(tmp_path):
helper_save_as_pdf(tmp_path, "RGBA")
def test_monochrome(tmp_path):
# Arrange
mode = "1"
@ -80,6 +85,34 @@ def test_resolution(tmp_path):
assert size == (61.44, 61.44)
@pytest.mark.parametrize(
"params",
(
{"dpi": (75, 150)},
{"dpi": (75, 150), "resolution": 200},
),
)
def test_dpi(params, tmp_path):
im = hopper()
outfile = str(tmp_path / "temp.pdf")
im.save(outfile, **params)
with open(outfile, "rb") as fp:
contents = fp.read()
size = tuple(
float(d)
for d in contents.split(b"stream\nq ")[1].split(b" 0 0 cm")[0].split(b" 0 0 ")
)
assert size == (122.88, 61.44)
size = tuple(
float(d) for d in contents.split(b"/MediaBox [ 0 0 ")[1].split(b"]")[0].split()
)
assert size == (122.88, 61.44)
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
)
@ -89,7 +122,6 @@ def test_save_all(tmp_path):
# Multiframe image
with Image.open("Tests/images/dispose_bgnd.gif") as im:
outfile = str(tmp_path / "temp.pdf")
im.save(outfile, save_all=True)
@ -123,7 +155,6 @@ def test_save_all(tmp_path):
def test_multiframe_normal_save(tmp_path):
# Test saving a multiframe image without save_all
with Image.open("Tests/images/dispose_bgnd.gif") as im:
outfile = str(tmp_path / "temp.pdf")
im.save(outfile)
@ -286,6 +317,7 @@ def test_pdf_append_to_bytesio():
@pytest.mark.timeout(1)
@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
def test_redos(newline):
malicious = b" trailer<<>>" + newline * 3456

View File

@ -78,7 +78,6 @@ class TestFilePng:
return chunks
def test_sanity(self, tmp_path):
# internal version number
assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib"))
@ -156,7 +155,6 @@ class TestFilePng:
assert im.info == {"spam": "egg"}
def test_bad_itxt(self):
im = load(HEAD + chunk(b"iTXt") + TAIL)
assert im.info == {}
@ -201,7 +199,6 @@ class TestFilePng:
assert im.info["spam"].tkey == "Spam"
def test_interlace(self):
test_file = "Tests/images/pil123p.png"
with Image.open(test_file) as im:
assert_image(im, "P", (162, 150))
@ -495,7 +492,6 @@ class TestFilePng:
# Check reading images with null tRNS value, issue #1239
test_file = "Tests/images/tRNS_null_1x1.png"
with Image.open(test_file) as im:
assert im.info["transparency"] == 0
def test_save_icc_profile(self):
@ -593,7 +589,7 @@ class TestFilePng:
def test_textual_chunks_after_idat(self):
with Image.open("Tests/images/hopper.png") as im:
assert "comment" in im.text.keys()
assert "comment" in im.text
for k, v in {
"date:create": "2014-09-04T09:37:08+03:00",
"date:modify": "2014-09-04T09:37:08+03:00",

View File

@ -27,7 +27,8 @@ def test_unclosed_file():
im = Image.open(test_file)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():
@ -77,7 +78,6 @@ def test_eoferror():
def test_seek_tell():
with Image.open(test_file) as im:
layer_number = im.tell()
assert layer_number == 1
@ -95,7 +95,6 @@ def test_seek_tell():
def test_seek_eoferror():
with Image.open(test_file) as im:
with pytest.raises(EOFError):
im.seek(-1)

28
Tests/test_file_qoi.py Normal file
View File

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

View File

@ -25,7 +25,8 @@ def test_unclosed_file():
im = Image.open(TEST_FILE)
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file():
@ -79,7 +80,6 @@ def test_is_spider_image():
def test_tell():
# Arrange
with Image.open(TEST_FILE) as im:
# Act
index = im.tell()

View File

@ -16,7 +16,6 @@ def test_sanity():
# Act
with Image.open(test_file) as im:
# Assert
assert im.size == (128, 128)

View File

@ -10,27 +10,28 @@ from .helper import is_pypy
TEST_TAR_FILE = "Tests/images/hopper.tar"
def test_sanity():
for codec, test_path, format in [
["zlib", "hopper.png", "PNG"],
["jpg", "hopper.jpg", "JPEG"],
]:
if features.check(codec):
with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar:
with Image.open(tar) as im:
im.load()
assert im.mode == "RGB"
assert im.size == (128, 128)
assert im.format == format
@pytest.mark.parametrize(
"codec, test_path, format",
(
("zlib", "hopper.png", "PNG"),
("jpg", "hopper.jpg", "JPEG"),
),
)
def test_sanity(codec, test_path, format):
if features.check(codec):
with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar:
with Image.open(tar) as im:
im.load()
assert im.mode == "RGB"
assert im.size == (128, 128)
assert im.format == format
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file():
def open():
with pytest.warns(ResourceWarning):
TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg")
pytest.warns(ResourceWarning, open)
def test_close():
with warnings.catch_warnings():

View File

@ -78,7 +78,6 @@ def test_id_field():
# Act
with Image.open(test_file) as im:
# Assert
assert im.size == (100, 100)
@ -89,7 +88,6 @@ def test_id_field_rle():
# Act
with Image.open(test_file) as im:
# Assert
assert im.size == (199, 199)
@ -165,13 +163,14 @@ def test_save_id_section(tmp_path):
# Save with custom id section greater than 255 characters
id_section = b"Test content" * 25
pytest.warns(UserWarning, lambda: im.save(out, id_section=id_section))
with pytest.warns(UserWarning):
im.save(out, id_section=id_section)
with Image.open(out) as test_im:
assert test_im.info["id_section"] == id_section[:255]
test_file = "Tests/images/tga_id_field.tga"
with Image.open(test_file) as im:
# Save with no id section
im.save(out, id_section="")
with Image.open(out) as test_im:

View File

@ -25,7 +25,6 @@ except ImportError:
class TestFileTiff:
def test_sanity(self, tmp_path):
filename = str(tmp_path / "temp.tif")
hopper("RGB").save(filename)
@ -62,7 +61,8 @@ class TestFileTiff:
im = Image.open("Tests/images/multipage.tiff")
im.load()
pytest.warns(ResourceWarning, open)
with pytest.warns(ResourceWarning):
open()
def test_closed_file(self):
with warnings.catch_warnings():
@ -84,24 +84,6 @@ class TestFileTiff:
with Image.open("Tests/images/multipage.tiff") as im:
im.load()
@pytest.mark.parametrize(
"path, sizes",
(
("Tests/images/hopper.tif", ()),
("Tests/images/child_ifd.tiff", (16, 8)),
("Tests/images/child_ifd_jpeg.tiff", (20,)),
),
)
def test_get_child_images(self, path, sizes):
with Image.open(path) as im:
ims = im.get_child_images()
assert len(ims) == len(sizes)
for i, im in enumerate(ims):
w = sizes[i]
expected = Image.new("RGB", (w, w), "#f00")
assert_image_similar(im, expected, 1)
def test_mac_tiff(self):
# Read RGBa images from macOS [@PIL136]
@ -118,36 +100,6 @@ class TestFileTiff:
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
assert_image_equal_tofile(im, "Tests/images/hopper.tif")
@pytest.mark.parametrize(
"file_name,mode,size,tile",
[
(
"tiff_wrong_bits_per_sample.tiff",
"RGBA",
(52, 53),
[("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))],
),
(
"tiff_wrong_bits_per_sample_2.tiff",
"RGB",
(16, 16),
[("raw", (0, 0, 16, 16), 8, ("RGB", 0, 1))],
),
(
"tiff_wrong_bits_per_sample_3.tiff",
"RGBA",
(512, 256),
[("libtiff", (0, 0, 512, 256), 0, ("RGBA", "tiff_lzw", False, 48782))],
),
],
)
def test_wrong_bits_per_sample(self, file_name, mode, size, tile):
with Image.open("Tests/images/" + file_name) as im:
assert im.mode == mode
assert im.size == size
assert im.tile == tile
im.load()
def test_set_legacy_api(self):
ifd = TiffImagePlugin.ImageFileDirectory_v2()
with pytest.raises(Exception) as e:
@ -157,7 +109,6 @@ class TestFileTiff:
def test_xyres_tiff(self):
filename = "Tests/images/pil168.tif"
with Image.open(filename) as im:
# legacy api
assert isinstance(im.tag[X_RESOLUTION][0], tuple)
assert isinstance(im.tag[Y_RESOLUTION][0], tuple)
@ -171,7 +122,6 @@ class TestFileTiff:
def test_xyres_fallback_tiff(self):
filename = "Tests/images/compression.tif"
with Image.open(filename) as im:
# v2 api
assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)
assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)
@ -186,7 +136,6 @@ class TestFileTiff:
def test_int_resolution(self):
filename = "Tests/images/pil168.tif"
with Image.open(filename) as im:
# Try to read a file where X,Y_RESOLUTION are ints
im.tag_v2[X_RESOLUTION] = 71
im.tag_v2[Y_RESOLUTION] = 71
@ -235,7 +184,8 @@ class TestFileTiff:
def test_bad_exif(self):
with Image.open("Tests/images/hopper_bad_exif.jpg") as i:
# Should not raise struct.error.
pytest.warns(UserWarning, i._getexif)
with pytest.warns(UserWarning):
i._getexif()
def test_save_rgba(self, tmp_path):
im = hopper("RGBA")
@ -381,7 +331,6 @@ class TestFileTiff:
def test___str__(self):
filename = "Tests/images/pil136.tiff"
with Image.open(filename) as im:
# Act
ret = str(im.ifd)
@ -392,7 +341,6 @@ class TestFileTiff:
# Arrange
filename = "Tests/images/pil136.tiff"
with Image.open(filename) as im:
# v2 interface
v2_tags = {
256: 55,
@ -630,7 +578,6 @@ class TestFileTiff:
filename = str(tmp_path / "temp.tif")
hopper("RGB").save(filename, **kwargs)
with Image.open(filename) as im:
# legacy interface
assert im.tag[X_RESOLUTION][0][0] == 72
assert im.tag[Y_RESOLUTION][0][0] == 36

View File

@ -54,7 +54,6 @@ def test_rt_metadata(tmp_path):
img.save(f, tiffinfo=info)
with Image.open(f) as loaded:
assert loaded.tag[ImageJMetaDataByteCounts] == (len(bin_data),)
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bin_data),)
@ -74,14 +73,12 @@ def test_rt_metadata(tmp_path):
info[ImageJMetaDataByteCounts] = (8, len(bin_data) - 8)
img.save(f, tiffinfo=info)
with Image.open(f) as loaded:
assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
def test_read_metadata():
with Image.open("Tests/images/hopper_g4.tif") as img:
assert {
"YResolution": IFDRational(4294967295, 113653537),
"PlanarConfiguration": 1,
@ -185,30 +182,32 @@ def test_iptc(tmp_path):
im.save(out)
def test_writing_bytes_to_ascii(tmp_path):
im = hopper()
@pytest.mark.parametrize("value, expected", ((b"test", "test"), (1, "1")))
def test_writing_other_types_to_ascii(value, expected, tmp_path):
info = TiffImagePlugin.ImageFileDirectory_v2()
tag = TiffTags.TAGS_V2[271]
assert tag.type == TiffTags.ASCII
info[271] = b"test"
info[271] = value
im = hopper()
out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info)
with Image.open(out) as reloaded:
assert reloaded.tag_v2[271] == "test"
assert reloaded.tag_v2[271] == expected
def test_writing_int_to_bytes(tmp_path):
@pytest.mark.parametrize("value", (1, IFDRational(1)))
def test_writing_other_types_to_bytes(value, tmp_path):
im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2()
tag = TiffTags.TAGS_V2[700]
assert tag.type == TiffTags.BYTE
info[700] = 1
info[700] = value
out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info)
@ -217,6 +216,22 @@ def test_writing_int_to_bytes(tmp_path):
assert reloaded.tag_v2[700] == b"\x01"
def test_writing_other_types_to_undefined(tmp_path):
im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2()
tag = TiffTags.TAGS_V2[33723]
assert tag.type == TiffTags.UNDEFINED
info[33723] = 1
out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info)
with Image.open(out) as reloaded:
assert reloaded.tag_v2[33723] == b"1"
def test_undefined_zero(tmp_path):
# Check that the tag has not been changed since this test was created
tag = TiffTags.TAGS_V2[45059]
@ -237,7 +252,8 @@ def test_empty_metadata():
head = f.read(8)
info = TiffImagePlugin.ImageFileDirectory(head)
# Should not raise struct.error.
pytest.warns(UserWarning, info.load, f)
with pytest.warns(UserWarning):
info.load(f)
def test_iccprofile(tmp_path):
@ -403,11 +419,12 @@ def test_too_many_entries():
ifd = TiffImagePlugin.ImageFileDirectory_v2()
# 277: ("SamplesPerPixel", SHORT, 1),
ifd._tagdata[277] = struct.pack("hh", 4, 4)
ifd._tagdata[277] = struct.pack("<hh", 4, 4)
ifd.tagtype[277] = TiffTags.SHORT
# Should not raise ValueError.
pytest.warns(UserWarning, lambda: ifd[277])
with pytest.warns(UserWarning):
assert ifd[277] == 4
def test_tag_group_data():

View File

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

View File

@ -18,10 +18,8 @@ except ImportError:
def test_read_exif_metadata():
file_path = "Tests/images/flower.webp"
with Image.open(file_path) as image:
assert image.format == "WEBP"
exif_data = image.info.get("exif", None)
assert exif_data
@ -64,10 +62,8 @@ def test_write_exif_metadata():
def test_read_icc_profile():
file_path = "Tests/images/flower2.webp"
with Image.open(file_path) as image:
assert image.format == "WEBP"
assert image.info.get("icc_profile", None)

View File

@ -6,7 +6,6 @@ from .helper import assert_image_similar_tofile, hopper
def test_load_raw():
# Test basic EMF open and rendering
with Image.open("Tests/images/drawing.emf") as im:
if hasattr(Image.core, "drawwmf"):

View File

@ -44,7 +44,6 @@ def test_open():
# Act
with Image.open(filename) as im:
# Assert
assert im.mode == "1"
assert im.size == (128, 128)
@ -57,7 +56,6 @@ def test_open_filename_with_underscore():
# Act
with Image.open(filename) as im:
# Assert
assert im.mode == "1"
assert im.size == (128, 128)

View File

@ -10,7 +10,6 @@ TEST_FILE = "Tests/images/hopper.p7"
def test_open():
# Act
with Image.open(TEST_FILE) as im:
# Assert
assert im.format == "XVThumb"

22
Tests/test_font_crash.py Normal file
View File

@ -0,0 +1,22 @@
import pytest
from PIL import Image, ImageDraw, ImageFont
from .helper import skip_unless_feature
class TestFontCrash:
def _fuzz_font(self, font):
# from fuzzers.fuzz_font
font.getbbox("ABC")
font.getmask("test text")
with Image.new(mode="RGBA", size=(200, 200)) as im:
draw = ImageDraw.Draw(im)
draw.multiline_textbbox((10, 10), "ABC\nAaaa", font, stroke_width=2)
draw.text((10, 10), "Test Text", font=font, fill="#000")
@skip_unless_feature("freetype2")
def test_segfault(self):
with pytest.raises(OSError):
font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784")
self._fuzz_font(font)

View File

@ -69,7 +69,6 @@ class TestImage:
assert issubclass(UnidentifiedImageError, OSError)
def test_sanity(self):
im = Image.new("L", (100, 100))
assert repr(im)[:45] == "<PIL.Image.Image image mode=L size=100x100 at"
assert im.mode == "L"
@ -398,6 +397,17 @@ class TestImage:
with pytest.raises(ValueError):
source.alpha_composite(over, (0, 0), (0, -1))
def test_register_open_duplicates(self):
# Arrange
factory, accept = Image.OPEN["JPEG"]
id_length = len(Image.ID)
# Act
Image.register_open("JPEG", factory, accept)
# Assert
assert len(Image.ID) == id_length
def test_registered_extensions_uninitialized(self):
# Arrange
Image._initialized = 0
@ -512,6 +522,14 @@ class TestImage:
i = Image.new("RGB", [1, 1])
assert isinstance(i.size, tuple)
@pytest.mark.timeout(0.75)
@pytest.mark.skipif(
"PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower"
)
@pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
def test_empty_image(self, size):
Image.new("RGB", size)
def test_storage_neg(self):
# Storage.c accepted negative values for xsize, ysize. Was
# test_neg_ppm, but the core function for that has been
@ -921,12 +939,7 @@ class TestImage:
with pytest.warns(DeprecationWarning):
assert Image.CONTAINER == 2
def test_constants_deprecation(self):
with pytest.warns(DeprecationWarning):
assert Image.NEAREST == 0
with pytest.warns(DeprecationWarning):
assert Image.NONE == 0
def test_constants(self):
with pytest.warns(DeprecationWarning):
assert Image.LINEAR == Image.Resampling.BILINEAR
with pytest.warns(DeprecationWarning):
@ -943,8 +956,7 @@ class TestImage:
Image.Quantize,
):
for name in enum.__members__:
with pytest.warns(DeprecationWarning):
assert getattr(Image, name) == enum[name]
assert getattr(Image, name) == enum[name]
@pytest.mark.parametrize(
"path",
@ -994,7 +1006,6 @@ def mock_encode(*args):
class TestRegistry:
def test_encode_registry(self):
Image.register_encoder("MOCK", mock_encode)
assert "MOCK" in Image.ENCODERS

View File

@ -132,22 +132,26 @@ class TestImageGetPixel(AccessTest):
return 1
return tuple(range(1, bands + 1))
def check(self, mode, c=None):
if not c:
c = self.color(mode)
def check(self, mode, expected_color=None):
if not expected_color:
expected_color = self.color(mode)
# check putpixel
im = Image.new(mode, (1, 1), None)
im.putpixel((0, 0), c)
assert (
im.getpixel((0, 0)) == c
), f"put/getpixel roundtrip failed for mode {mode}, color {c}"
im.putpixel((0, 0), expected_color)
actual_color = im.getpixel((0, 0))
assert actual_color == expected_color, (
f"put/getpixel roundtrip failed for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# check putpixel negative index
im.putpixel((-1, -1), c)
assert (
im.getpixel((-1, -1)) == c
), f"put/getpixel roundtrip negative index failed for mode {mode}, color {c}"
im.putpixel((-1, -1), expected_color)
actual_color = im.getpixel((-1, -1))
assert actual_color == expected_color, (
f"put/getpixel roundtrip negative index failed for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# Check 0
im = Image.new(mode, (0, 0), None)
@ -155,27 +159,32 @@ class TestImageGetPixel(AccessTest):
error = ValueError if self._need_cffi_access else IndexError
with pytest.raises(error):
im.putpixel((0, 0), c)
im.putpixel((0, 0), expected_color)
with pytest.raises(error):
im.getpixel((0, 0))
# Check 0 negative index
with pytest.raises(error):
im.putpixel((-1, -1), c)
im.putpixel((-1, -1), expected_color)
with pytest.raises(error):
im.getpixel((-1, -1))
# check initial color
im = Image.new(mode, (1, 1), c)
assert (
im.getpixel((0, 0)) == c
), f"initial color failed for mode {mode}, color {c} "
im = Image.new(mode, (1, 1), expected_color)
actual_color = im.getpixel((0, 0))
assert actual_color == expected_color, (
f"initial color failed for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# check initial color negative index
assert (
im.getpixel((-1, -1)) == c
), f"initial color failed with negative index for mode {mode}, color {c} "
actual_color = im.getpixel((-1, -1))
assert actual_color == expected_color, (
f"initial color failed with negative index for mode {mode}, "
f"expected {expected_color} got {actual_color}"
)
# Check 0
im = Image.new(mode, (0, 0), c)
im = Image.new(mode, (0, 0), expected_color)
with pytest.raises(error):
im.getpixel((0, 0))
# Check 0 negative index
@ -205,13 +214,13 @@ class TestImageGetPixel(AccessTest):
self.check(mode)
@pytest.mark.parametrize("mode", ("I;16", "I;16B"))
def test_signedness(self, mode):
@pytest.mark.parametrize(
"expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)
)
def test_signedness(self, mode, expected_color):
# see https://github.com/python-pillow/Pillow/issues/452
# pixelaccess is using signed int* instead of uint*
self.check(mode, 2**15 - 1)
self.check(mode, 2**15)
self.check(mode, 2**15 + 1)
self.check(mode, 2**16 - 1)
self.check(mode, expected_color)
@pytest.mark.parametrize("mode", ("P", "PA"))
@pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))

View File

@ -45,7 +45,6 @@ def test_unsupported_conversion():
def test_default():
im = hopper("P")
assert im.mode == "P"
converted_im = im.convert()
@ -104,6 +103,13 @@ def test_rgba_p():
assert_image_similar(im, comparable, 20)
def test_rgba():
with Image.open("Tests/images/transparent.png") as im:
assert im.mode == "RGBA"
assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)
def test_trns_p(tmp_path):
im = hopper("P")
im.info["transparency"] = 0
@ -248,17 +254,6 @@ def test_p2pa_palette():
assert im_pa.getpalette() == im.getpalette()
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
def test_rgb_lab(mode):
im = Image.new(mode, (1, 1))
converted_im = im.convert("LAB")
assert converted_im.getpixel((0, 0)) == (0, 128, 128)
im = Image.new("LAB", (1, 1), (255, 0, 0))
converted_im = im.convert(mode)
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)
def test_matrix_illegal_conversion():
# Arrange
im = hopper("CMYK")

View File

@ -86,7 +86,6 @@ def test_crop_crash():
def test_crop_zero():
im = Image.new("RGB", (0, 0), "white")
cropped = im.crop((0, 0, 0, 0))

View File

@ -24,6 +24,7 @@ from .helper import assert_image_equal, hopper
ImageFilter.ModeFilter,
ImageFilter.GaussianBlur,
ImageFilter.GaussianBlur(5),
ImageFilter.BoxBlur(0),
ImageFilter.BoxBlur(5),
ImageFilter.UnsharpMask,
ImageFilter.UnsharpMask(10),
@ -173,3 +174,14 @@ def test_consistency_5x5(mode):
Image.merge(mode, source[: len(mode)]).filter(kernel),
Image.merge(mode, reference[: len(mode)]),
)
def test_invalid_box_blur_filter():
with pytest.raises(ValueError):
ImageFilter.BoxBlur(-2)
im = hopper()
box_blur_filter = ImageFilter.BoxBlur(2)
box_blur_filter.radius = -2
with pytest.raises(ValueError):
im.filter(box_blur_filter)

View File

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

View File

@ -4,7 +4,6 @@ from .helper import hopper
def test_sanity():
bbox = hopper().getbbox()
assert isinstance(bbox, tuple)

View File

@ -1,10 +1,11 @@
import pytest
from PIL import Image, ImageMode
from .helper import hopper
def test_sanity():
with hopper() as im:
im.mode
@ -49,23 +50,25 @@ def test_sanity():
assert m.typestr == "|u1"
def test_properties():
def check(mode, *result):
signature = (
Image.getmodebase(mode),
Image.getmodetype(mode),
Image.getmodebands(mode),
Image.getmodebandnames(mode),
)
assert signature == result
check("1", "L", "L", 1, ("1",))
check("L", "L", "L", 1, ("L",))
check("P", "P", "L", 1, ("P",))
check("I", "L", "I", 1, ("I",))
check("F", "L", "F", 1, ("F",))
check("RGB", "RGB", "L", 3, ("R", "G", "B"))
check("RGBA", "RGB", "L", 4, ("R", "G", "B", "A"))
check("RGBX", "RGB", "L", 4, ("R", "G", "B", "X"))
check("CMYK", "RGB", "L", 4, ("C", "M", "Y", "K"))
check("YCbCr", "RGB", "L", 3, ("Y", "Cb", "Cr"))
@pytest.mark.parametrize(
"mode, expected_base, expected_type, expected_bands, expected_band_names",
(
("1", "L", "L", 1, ("1",)),
("L", "L", "L", 1, ("L",)),
("P", "P", "L", 1, ("P",)),
("I", "L", "I", 1, ("I",)),
("F", "L", "F", 1, ("F",)),
("RGB", "RGB", "L", 3, ("R", "G", "B")),
("RGBA", "RGB", "L", 4, ("R", "G", "B", "A")),
("RGBX", "RGB", "L", 4, ("R", "G", "B", "X")),
("CMYK", "RGB", "L", 4, ("C", "M", "Y", "K")),
("YCbCr", "RGB", "L", 3, ("Y", "Cb", "Cr")),
),
)
def test_properties(
mode, expected_base, expected_type, expected_bands, expected_band_names
):
assert Image.getmodebase(mode) == expected_base
assert Image.getmodetype(mode) == expected_type
assert Image.getmodebands(mode) == expected_bands
assert Image.getmodebandnames(mode) == expected_band_names

View File

@ -55,10 +55,11 @@ def test_mode_with_L_with_float():
assert im.getpixel((0, 0)) == 2
def test_mode_i():
@pytest.mark.parametrize("mode", ("I", "I;16", "I;16L", "I;16B"))
def test_mode_i(mode):
src = hopper("L")
data = list(src.getdata())
im = Image.new("I", src.size, 0)
im = Image.new(mode, src.size, 0)
im.putdata(data, 2, 256)
target = [2 * elt + 256 for elt in data]

View File

@ -135,16 +135,15 @@ class TestImagingCoreResampleAccuracy:
@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)
# fmt: off
data = ("e1 e3 d4"
"e3 e5 d6"
"d4 d6 c9")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (6, 6)))
case = self.make_case(mode, (12, 12), 0xE1)
case = case.resize((6, 6), Image.Resampling.BICUBIC)
# fmt: off
data = ("e1 e3 d4"
"e3 e5 d6"
"d4 d6 c9")
# fmt: on
for channel in case.split():
self.check_case(channel, self.make_sample(data, (6, 6)))
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
def test_reduce_lanczos(self, mode):

View File

@ -52,7 +52,7 @@ def test_resample():
# >>> im.save('Tests/images/hopper_45.png')
with Image.open("Tests/images/hopper_45.png") as target:
for (resample, epsilon) in (
for resample, epsilon in (
(Image.Resampling.NEAREST, 10),
(Image.Resampling.BILINEAR, 5),
(Image.Resampling.BICUBIC, 0),

View File

@ -4,7 +4,6 @@ from .helper import assert_image_equal, fromstring, hopper
def test_sanity():
with pytest.raises(ValueError):
hopper().tobitmap()

View File

@ -42,12 +42,12 @@ class TestImageTransform:
def test_extent(self):
im = hopper("RGB")
(w, h) = im.size
# fmt: off
transformed = im.transform(im.size, Image.Transform.EXTENT,
(0, 0,
w//2, h//2), # ul -> lr
Image.Resampling.BILINEAR)
# fmt: on
transformed = im.transform(
im.size,
Image.Transform.EXTENT,
(0, 0, w // 2, h // 2), # ul -> lr
Image.Resampling.BILINEAR,
)
scaled = im.resize((w * 2, h * 2), Image.Resampling.BILINEAR).crop((0, 0, w, h))
@ -58,13 +58,12 @@ class TestImageTransform:
# one simple quad transform, equivalent to scale & crop upper left quad
im = hopper("RGB")
(w, h) = im.size
# fmt: off
transformed = im.transform(im.size, Image.Transform.QUAD,
(0, 0, 0, h//2,
# ul -> ccw around quad:
w//2, h//2, w//2, 0),
Image.Resampling.BILINEAR)
# fmt: on
transformed = im.transform(
im.size,
Image.Transform.QUAD,
(0, 0, 0, h // 2, w // 2, h // 2, w // 2, 0), # ul -> ccw around quad
Image.Resampling.BILINEAR,
)
scaled = im.transform(
(w, h),
@ -99,16 +98,21 @@ class TestImageTransform:
# this should be a checkerboard of halfsized hoppers in ul, lr
im = hopper("RGBA")
(w, h) = im.size
# fmt: off
transformed = im.transform(im.size, Image.Transform.MESH,
[((0, 0, w//2, h//2), # box
(0, 0, 0, h,
w, h, w, 0)), # ul -> ccw around quad
((w//2, h//2, w, h), # box
(0, 0, 0, h,
w, h, w, 0))], # ul -> ccw around quad
Image.Resampling.BILINEAR)
# fmt: on
transformed = im.transform(
im.size,
Image.Transform.MESH,
(
(
(0, 0, w // 2, h // 2), # box
(0, 0, 0, h, w, h, w, 0), # ul -> ccw around quad
),
(
(w // 2, h // 2, w, h), # box
(0, 0, 0, h, w, h, w, 0), # ul -> ccw around quad
),
),
Image.Resampling.BILINEAR,
)
scaled = im.transform(
(w // 2, h // 2),
@ -174,11 +178,13 @@ class TestImageTransform:
im = op(im, (40, 10))
colors = im.getcolors()
assert colors == [
(20 * 10, opaque),
(20 * 10, transparent),
]
colors = sorted(im.getcolors())
assert colors == sorted(
(
(20 * 10, opaque),
(20 * 10, transparent),
)
)
@pytest.mark.parametrize("mode", ("RGBA", "LA"))
def test_nearest_resize(self, mode):

View File

@ -50,7 +50,6 @@ def test_add():
# Arrange
with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
# Act
new = ImageChops.add(im1, im2)
@ -63,7 +62,6 @@ def test_add_scale_offset():
# Arrange
with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
# Act
new = ImageChops.add(im1, im2, scale=2.5, offset=100)
@ -87,7 +85,6 @@ def test_add_modulo():
# Arrange
with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
# Act
new = ImageChops.add_modulo(im1, im2)
@ -111,7 +108,6 @@ def test_blend():
# Arrange
with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
# Act
new = ImageChops.blend(im1, im2, 0.5)
@ -137,7 +133,6 @@ def test_darker_image():
# Arrange
with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
# Act
new = ImageChops.darker(im1, im2)
@ -149,7 +144,6 @@ def test_darker_pixel():
# Arrange
im1 = hopper()
with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
# Act
new = ImageChops.darker(im1, im2)
@ -161,7 +155,6 @@ def test_difference():
# Arrange
with Image.open("Tests/images/imagedraw_arc_end_le_start.png") as im1:
with Image.open("Tests/images/imagedraw_arc_no_loops.png") as im2:
# Act
new = ImageChops.difference(im1, im2)
@ -173,7 +166,6 @@ def test_difference_pixel():
# Arrange
im1 = hopper()
with Image.open("Tests/images/imagedraw_polygon_kite_RGB.png") as im2:
# Act
new = ImageChops.difference(im1, im2)
@ -195,7 +187,6 @@ def test_duplicate():
def test_invert():
# Arrange
with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im:
# Act
new = ImageChops.invert(im)
@ -209,7 +200,6 @@ def test_lighter_image():
# Arrange
with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
# Act
new = ImageChops.lighter(im1, im2)
@ -221,7 +211,6 @@ def test_lighter_pixel():
# Arrange
im1 = hopper()
with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
# Act
new = ImageChops.lighter(im1, im2)
@ -275,7 +264,6 @@ def test_offset():
xoffset = 45
yoffset = 20
with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im:
# Act
new = ImageChops.offset(im, xoffset, yoffset)
@ -292,7 +280,6 @@ def test_screen():
# Arrange
with Image.open("Tests/images/imagedraw_ellipse_RGB.png") as im1:
with Image.open("Tests/images/imagedraw_floodfill_RGB.png") as im2:
# Act
new = ImageChops.screen(im1, im2)
@ -305,7 +292,6 @@ def test_subtract():
# Arrange
with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
# Act
new = ImageChops.subtract(im1, im2)
@ -319,7 +305,6 @@ def test_subtract_scale_offset():
# Arrange
with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
# Act
new = ImageChops.subtract(im1, im2, scale=2.5, offset=100)
@ -332,7 +317,6 @@ def test_subtract_clip():
# Arrange
im1 = hopper()
with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
# Act
new = ImageChops.subtract(im1, im2)
@ -344,7 +328,6 @@ def test_subtract_modulo():
# Arrange
with Image.open("Tests/images/imagedraw_chord_RGB.png") as im1:
with Image.open("Tests/images/imagedraw_outline_chord_RGB.png") as im2:
# Act
new = ImageChops.subtract_modulo(im1, im2)
@ -358,7 +341,6 @@ def test_subtract_modulo_no_clip():
# Arrange
im1 = hopper()
with Image.open("Tests/images/imagedraw_chord_RGB.png") as im2:
# Act
new = ImageChops.subtract_modulo(im1, im2)
@ -370,7 +352,6 @@ def test_soft_light():
# Arrange
with Image.open("Tests/images/hopper.png") as im1:
with Image.open("Tests/images/hopper-XYZ.png") as im2:
# Act
new = ImageChops.soft_light(im1, im2)
@ -383,7 +364,6 @@ def test_hard_light():
# Arrange
with Image.open("Tests/images/hopper.png") as im1:
with Image.open("Tests/images/hopper-XYZ.png") as im2:
# Act
new = ImageChops.hard_light(im1, im2)
@ -396,7 +376,6 @@ def test_overlay():
# Arrange
with Image.open("Tests/images/hopper.png") as im1:
with Image.open("Tests/images/hopper-XYZ.png") as im2:
# Act
new = ImageChops.overlay(im1, im2)

View File

@ -625,3 +625,14 @@ def test_constants_deprecation():
for name in enum.__members__:
with pytest.warns(DeprecationWarning):
assert getattr(ImageCms, prefix + name) == enum[name]
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
def test_rgb_lab(mode):
im = Image.new(mode, (1, 1))
converted_im = im.convert("LAB")
assert converted_im.getpixel((0, 0)) == (0, 128, 128)
im = Image.new("LAB", (1, 1), (255, 0, 0))
converted_im = im.convert(mode)
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)

View File

@ -52,7 +52,6 @@ def test_sanity():
def test_valueerror():
with Image.open("Tests/images/chi.gif") as im:
draw = ImageDraw.Draw(im)
draw.line((0, 0), fill=(0, 0, 0))
@ -356,7 +355,13 @@ def ellipse_various_sizes_helper(filled):
for w in ellipse_sizes:
y = 1
for h in ellipse_sizes:
border = [x, y, x + w - 1, y + h - 1]
x1 = x + w
if w:
x1 -= 1
y1 = y + h
if h:
y1 -= 1
border = [x, y, x1, y1]
if filled:
draw.ellipse(border, fill="white")
else:
@ -736,6 +741,36 @@ def test_rounded_rectangle(xy):
assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png")
@pytest.mark.parametrize("top_left", (True, False))
@pytest.mark.parametrize("top_right", (True, False))
@pytest.mark.parametrize("bottom_right", (True, False))
@pytest.mark.parametrize("bottom_left", (True, False))
def test_rounded_rectangle_corners(top_left, top_right, bottom_right, bottom_left):
corners = (top_left, top_right, bottom_right, bottom_left)
# Arrange
im = Image.new("RGB", (200, 200))
draw = ImageDraw.Draw(im)
# Act
draw.rounded_rectangle(
(10, 20, 190, 180), 30, fill="red", outline="green", width=5, corners=corners
)
# Assert
suffix = "".join(
(
("y" if top_left else "n"),
("y" if top_right else "n"),
("y" if bottom_right else "n"),
("y" if bottom_left else "n"),
)
)
assert_image_equal_tofile(
im, "Tests/images/imagedraw_rounded_rectangle_corners_" + suffix + ".png"
)
@pytest.mark.parametrize(
"xy, radius, type",
[
@ -903,9 +938,6 @@ def test_square():
img, draw = create_base_image_draw((10, 10))
draw.rectangle((2, 2, 7, 7), BLACK)
assert_image_equal_tofile(img, expected, "square as normal rectangle failed")
img, draw = create_base_image_draw((10, 10))
draw.rectangle((7, 7, 2, 2), BLACK)
assert_image_equal_tofile(img, expected, "square as inverted rectangle failed")
def test_triangle_right():
@ -1470,3 +1502,21 @@ def test_polygon2():
draw.polygon([(18, 30), (19, 31), (18, 30), (85, 30), (60, 72)], "red")
expected = "Tests/images/imagedraw_outline_polygon_RGB.png"
assert_image_similar_tofile(im, expected, 1)
@pytest.mark.parametrize("xy", ((1, 1, 0, 1), (1, 1, 1, 0)))
def test_incorrectly_ordered_coordinates(xy):
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
with pytest.raises(ValueError):
draw.arc(xy, 10, 260)
with pytest.raises(ValueError):
draw.chord(xy, 10, 260)
with pytest.raises(ValueError):
draw.ellipse(xy)
with pytest.raises(ValueError):
draw.pieslice(xy, 10, 260)
with pytest.raises(ValueError):
draw.rectangle(xy)
with pytest.raises(ValueError):
draw.rounded_rectangle(xy)

View File

@ -30,7 +30,6 @@ SAFEBLOCK = ImageFile.SAFEBLOCK
class TestImageFile:
def test_parser(self):
def roundtrip(format):
im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST)
if format in ("MSP", "XBM"):
im = im.convert("1")

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