mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-27 01:34:24 +03:00
Merge branch 'main' into pyaccess_pa
This commit is contained in:
commit
2eca2989a2
|
@ -35,11 +35,9 @@ python3 -m pip install -U pytest
|
||||||
python3 -m pip install -U pytest-cov
|
python3 -m pip install -U pytest-cov
|
||||||
python3 -m pip install -U pytest-timeout
|
python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
python3 -m pip install test-image-results
|
|
||||||
|
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
if [[ $(uname) != CYGWIN* ]]; then
|
||||||
# TODO Remove condition when NumPy supports 3.11
|
python3 -m pip install numpy
|
||||||
if ! [ "$GHA_PYTHON_VERSION" == "3.11-dev" ]; then python3 -m pip install numpy ; fi
|
|
||||||
|
|
||||||
# PyQt6 doesn't support PyPy3
|
# PyQt6 doesn't support PyPy3
|
||||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||||
|
|
3
.github/workflows/cifuzz.yml
vendored
3
.github/workflows/cifuzz.yml
vendored
|
@ -11,6 +11,9 @@ on:
|
||||||
- "**.h"
|
- "**.h"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Fuzzing:
|
Fuzzing:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
4
.github/workflows/macos-install.sh
vendored
4
.github/workflows/macos-install.sh
vendored
|
@ -12,11 +12,9 @@ python3 -m pip install -U pytest
|
||||||
python3 -m pip install -U pytest-cov
|
python3 -m pip install -U pytest-cov
|
||||||
python3 -m pip install -U pytest-timeout
|
python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
python3 -m pip install test-image-results
|
|
||||||
|
|
||||||
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
|
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
|
||||||
# TODO Remove condition when NumPy supports 3.11
|
python3 -m pip install numpy
|
||||||
if ! [ "$GHA_PYTHON_VERSION" == "3.11-dev" ]; then python3 -m pip install numpy ; fi
|
|
||||||
|
|
||||||
# extra test images
|
# extra test images
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
3
.github/workflows/test-cygwin.yml
vendored
3
.github/workflows/test-cygwin.yml
vendored
|
@ -2,6 +2,9 @@ name: Test Cygwin
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
|
@ -5,6 +5,15 @@ Changelog (Pillow)
|
||||||
9.3.0 (unreleased)
|
9.3.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- 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
|
- Save 1 mode PDF using CCITTFaxDecode filter #6470
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
|
BIN
Tests/images/1.eps
Normal file
BIN
Tests/images/1.eps
Normal file
Binary file not shown.
BIN
Tests/images/mmap_error.bmp
Normal file
BIN
Tests/images/mmap_error.bmp
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.0 KiB |
|
@ -325,8 +325,9 @@ def test_apng_syntax_errors():
|
||||||
pytest.warns(UserWarning, open)
|
pytest.warns(UserWarning, open)
|
||||||
|
|
||||||
|
|
||||||
def test_apng_sequence_errors():
|
@pytest.mark.parametrize(
|
||||||
test_files = [
|
"test_file",
|
||||||
|
(
|
||||||
"sequence_start.png",
|
"sequence_start.png",
|
||||||
"sequence_gap.png",
|
"sequence_gap.png",
|
||||||
"sequence_repeat.png",
|
"sequence_repeat.png",
|
||||||
|
@ -334,12 +335,13 @@ def test_apng_sequence_errors():
|
||||||
"sequence_reorder.png",
|
"sequence_reorder.png",
|
||||||
"sequence_reorder_chunk.png",
|
"sequence_reorder_chunk.png",
|
||||||
"sequence_fdat_fctl.png",
|
"sequence_fdat_fctl.png",
|
||||||
]
|
),
|
||||||
for f in test_files:
|
)
|
||||||
with pytest.raises(SyntaxError):
|
def test_apng_sequence_errors(test_file):
|
||||||
with Image.open(f"Tests/images/apng/{f}") as im:
|
with pytest.raises(SyntaxError):
|
||||||
im.seek(im.n_frames - 1)
|
with Image.open(f"Tests/images/apng/{test_file}") as im:
|
||||||
im.load()
|
im.seek(im.n_frames - 1)
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
def test_apng_save(tmp_path):
|
def test_apng_save(tmp_path):
|
||||||
|
|
|
@ -39,6 +39,13 @@ def test_invalid_file():
|
||||||
BmpImagePlugin.BmpImageFile(fp)
|
BmpImagePlugin.BmpImageFile(fp)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fallback_if_mmap_errors():
|
||||||
|
# This image has been truncated,
|
||||||
|
# so that the buffer is not large enough when using mmap
|
||||||
|
with Image.open("Tests/images/mmap_error.bmp") as im:
|
||||||
|
assert_image_equal_tofile(im, "Tests/images/pal8_offset.bmp")
|
||||||
|
|
||||||
|
|
||||||
def test_save_to_bytes():
|
def test_save_to_bytes():
|
||||||
output = io.BytesIO()
|
output = io.BytesIO()
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import ContainerIO, Image
|
from PIL import ContainerIO, Image
|
||||||
|
|
||||||
from .helper import hopper
|
from .helper import hopper
|
||||||
|
@ -59,89 +61,89 @@ def test_seek_mode_2():
|
||||||
assert container.tell() == 100
|
assert container.tell() == 100
|
||||||
|
|
||||||
|
|
||||||
def test_read_n0():
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_read_n0(bytesmode):
|
||||||
# Arrange
|
# Arrange
|
||||||
for bytesmode in (True, False):
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
container.seek(81)
|
container.seek(81)
|
||||||
data = container.read()
|
data = container.read()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
if bytesmode:
|
if bytesmode:
|
||||||
data = data.decode()
|
data = data.decode()
|
||||||
assert data == "7\nThis is line 8\n"
|
assert data == "7\nThis is line 8\n"
|
||||||
|
|
||||||
|
|
||||||
def test_read_n():
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_read_n(bytesmode):
|
||||||
# Arrange
|
# Arrange
|
||||||
for bytesmode in (True, False):
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
container.seek(81)
|
container.seek(81)
|
||||||
data = container.read(3)
|
data = container.read(3)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
if bytesmode:
|
if bytesmode:
|
||||||
data = data.decode()
|
data = data.decode()
|
||||||
assert data == "7\nT"
|
assert data == "7\nT"
|
||||||
|
|
||||||
|
|
||||||
def test_read_eof():
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_read_eof(bytesmode):
|
||||||
# Arrange
|
# Arrange
|
||||||
for bytesmode in (True, False):
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
container.seek(100)
|
container.seek(100)
|
||||||
data = container.read()
|
data = container.read()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
if bytesmode:
|
if bytesmode:
|
||||||
data = data.decode()
|
data = data.decode()
|
||||||
assert data == ""
|
assert data == ""
|
||||||
|
|
||||||
|
|
||||||
def test_readline():
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_readline(bytesmode):
|
||||||
# Arrange
|
# Arrange
|
||||||
for bytesmode in (True, False):
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||||
container = ContainerIO.ContainerIO(fh, 0, 120)
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
data = container.readline()
|
data = container.readline()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
if bytesmode:
|
if bytesmode:
|
||||||
data = data.decode()
|
data = data.decode()
|
||||||
assert data == "This is line 1\n"
|
assert data == "This is line 1\n"
|
||||||
|
|
||||||
|
|
||||||
def test_readlines():
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_readlines(bytesmode):
|
||||||
# Arrange
|
# Arrange
|
||||||
for bytesmode in (True, False):
|
expected = [
|
||||||
expected = [
|
"This is line 1\n",
|
||||||
"This is line 1\n",
|
"This is line 2\n",
|
||||||
"This is line 2\n",
|
"This is line 3\n",
|
||||||
"This is line 3\n",
|
"This is line 4\n",
|
||||||
"This is line 4\n",
|
"This is line 5\n",
|
||||||
"This is line 5\n",
|
"This is line 6\n",
|
||||||
"This is line 6\n",
|
"This is line 7\n",
|
||||||
"This is line 7\n",
|
"This is line 8\n",
|
||||||
"This is line 8\n",
|
]
|
||||||
]
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||||
container = ContainerIO.ContainerIO(fh, 0, 120)
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
data = container.readlines()
|
data = container.readlines()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
if bytesmode:
|
if bytesmode:
|
||||||
data = [line.decode() for line in data]
|
data = [line.decode() for line in data]
|
||||||
assert data == expected
|
assert data == expected
|
||||||
|
|
|
@ -146,6 +146,11 @@ def test_bytesio_object():
|
||||||
assert_image_similar(img, image1_scale1_compare, 5)
|
assert_image_similar(img, image1_scale1_compare, 5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_1_mode():
|
||||||
|
with Image.open("Tests/images/1.eps") as im:
|
||||||
|
assert im.mode == "1"
|
||||||
|
|
||||||
|
|
||||||
def test_image_mode_not_supported(tmp_path):
|
def test_image_mode_not_supported(tmp_path):
|
||||||
im = hopper("RGBA")
|
im = hopper("RGBA")
|
||||||
tmpfile = str(tmp_path / "temp.eps")
|
tmpfile = str(tmp_path / "temp.eps")
|
||||||
|
|
|
@ -78,15 +78,12 @@ def test_eoferror():
|
||||||
im.seek(n_frames - 1)
|
im.seek(n_frames - 1)
|
||||||
|
|
||||||
|
|
||||||
def test_roundtrip(tmp_path):
|
@pytest.mark.parametrize("mode", ("RGB", "P", "PA"))
|
||||||
def roundtrip(mode):
|
def test_roundtrip(mode, tmp_path):
|
||||||
out = str(tmp_path / "temp.im")
|
out = str(tmp_path / "temp.im")
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
im.save(out)
|
im.save(out)
|
||||||
assert_image_equal_tofile(im, out)
|
assert_image_equal_tofile(im, out)
|
||||||
|
|
||||||
for mode in ["RGB", "P", "PA"]:
|
|
||||||
roundtrip(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_save_unsupported_mode(tmp_path):
|
def test_save_unsupported_mode(tmp_path):
|
||||||
|
|
|
@ -135,50 +135,50 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
|
||||||
|
|
||||||
def test_write_metadata(self, tmp_path):
|
@pytest.mark.parametrize("legacy_api", (False, True))
|
||||||
|
def test_write_metadata(self, legacy_api, tmp_path):
|
||||||
"""Test metadata writing through libtiff"""
|
"""Test metadata writing through libtiff"""
|
||||||
for legacy_api in [False, True]:
|
f = str(tmp_path / "temp.tiff")
|
||||||
f = str(tmp_path / "temp.tiff")
|
with Image.open("Tests/images/hopper_g4.tif") as img:
|
||||||
with Image.open("Tests/images/hopper_g4.tif") as img:
|
img.save(f, tiffinfo=img.tag)
|
||||||
img.save(f, tiffinfo=img.tag)
|
|
||||||
|
|
||||||
if legacy_api:
|
if legacy_api:
|
||||||
original = img.tag.named()
|
original = img.tag.named()
|
||||||
else:
|
else:
|
||||||
original = img.tag_v2.named()
|
original = img.tag_v2.named()
|
||||||
|
|
||||||
# PhotometricInterpretation is set from SAVE_INFO,
|
# PhotometricInterpretation is set from SAVE_INFO,
|
||||||
# not the original image.
|
# not the original image.
|
||||||
ignored = [
|
ignored = [
|
||||||
"StripByteCounts",
|
"StripByteCounts",
|
||||||
"RowsPerStrip",
|
"RowsPerStrip",
|
||||||
"PageNumber",
|
"PageNumber",
|
||||||
"PhotometricInterpretation",
|
"PhotometricInterpretation",
|
||||||
]
|
]
|
||||||
|
|
||||||
with Image.open(f) as loaded:
|
with Image.open(f) as loaded:
|
||||||
if legacy_api:
|
if legacy_api:
|
||||||
reloaded = loaded.tag.named()
|
reloaded = loaded.tag.named()
|
||||||
else:
|
else:
|
||||||
reloaded = loaded.tag_v2.named()
|
reloaded = loaded.tag_v2.named()
|
||||||
|
|
||||||
for tag, value in itertools.chain(reloaded.items(), original.items()):
|
for tag, value in itertools.chain(reloaded.items(), original.items()):
|
||||||
if tag not in ignored:
|
if tag not in ignored:
|
||||||
val = original[tag]
|
val = original[tag]
|
||||||
if tag.endswith("Resolution"):
|
if tag.endswith("Resolution"):
|
||||||
if legacy_api:
|
if legacy_api:
|
||||||
assert val[0][0] / val[0][1] == (
|
assert val[0][0] / val[0][1] == (
|
||||||
4294967295 / 113653537
|
4294967295 / 113653537
|
||||||
), f"{tag} didn't roundtrip"
|
), f"{tag} didn't roundtrip"
|
||||||
else:
|
|
||||||
assert val == 37.79000115940079, f"{tag} didn't roundtrip"
|
|
||||||
else:
|
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
|
# https://github.com/python-pillow/Pillow/issues/1561
|
||||||
requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"]
|
requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"]
|
||||||
for field in requested_fields:
|
for field in requested_fields:
|
||||||
assert field in reloaded, f"{field} not in metadata"
|
assert field in reloaded, f"{field} not in metadata"
|
||||||
|
|
||||||
@pytest.mark.valgrind_known_error(reason="Known invalid metadata")
|
@pytest.mark.valgrind_known_error(reason="Known invalid metadata")
|
||||||
def test_additional_metadata(self, tmp_path):
|
def test_additional_metadata(self, tmp_path):
|
||||||
|
|
|
@ -27,13 +27,13 @@ def roundtrip(im, **options):
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
def test_sanity():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_sanity(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im.load()
|
im.load()
|
||||||
assert im.mode == "RGB"
|
assert im.mode == "RGB"
|
||||||
assert im.size == (640, 480)
|
assert im.size == (640, 480)
|
||||||
assert im.format == "MPO"
|
assert im.format == "MPO"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
|
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
|
||||||
|
@ -66,26 +66,25 @@ def test_context_manager():
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
def test_app():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_app(test_file):
|
||||||
# Test APP/COM reader (@PIL135)
|
# Test APP/COM reader (@PIL135)
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert im.applist[0][0] == "APP1"
|
assert im.applist[0][0] == "APP1"
|
||||||
assert im.applist[1][0] == "APP2"
|
assert im.applist[1][0] == "APP2"
|
||||||
assert (
|
assert (
|
||||||
im.applist[1][1][:16]
|
im.applist[1][1][:16] == b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00"
|
||||||
== b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00"
|
)
|
||||||
)
|
assert len(im.applist) == 2
|
||||||
assert len(im.applist) == 2
|
|
||||||
|
|
||||||
|
|
||||||
def test_exif():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_exif(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
info = im._getexif()
|
info = im._getexif()
|
||||||
assert info[272] == "Nintendo 3DS"
|
assert info[272] == "Nintendo 3DS"
|
||||||
assert info[296] == 2
|
assert info[296] == 2
|
||||||
assert info[34665] == 188
|
assert info[34665] == 188
|
||||||
|
|
||||||
|
|
||||||
def test_frame_size():
|
def test_frame_size():
|
||||||
|
@ -137,12 +136,12 @@ def test_reload_exif_after_seek():
|
||||||
assert 296 in exif
|
assert 296 in exif
|
||||||
|
|
||||||
|
|
||||||
def test_mp():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_mp(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
mpinfo = im._getmp()
|
mpinfo = im._getmp()
|
||||||
assert mpinfo[45056] == b"0100"
|
assert mpinfo[45056] == b"0100"
|
||||||
assert mpinfo[45057] == 2
|
assert mpinfo[45057] == 2
|
||||||
|
|
||||||
|
|
||||||
def test_mp_offset():
|
def test_mp_offset():
|
||||||
|
@ -162,48 +161,48 @@ def test_mp_no_data():
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
|
|
||||||
|
|
||||||
def test_mp_attribute():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_mp_attribute(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
mpinfo = im._getmp()
|
mpinfo = im._getmp()
|
||||||
frame_number = 0
|
frame_number = 0
|
||||||
for mpentry in mpinfo[0xB002]:
|
for mpentry in mpinfo[0xB002]:
|
||||||
mpattr = mpentry["Attribute"]
|
mpattr = mpentry["Attribute"]
|
||||||
if frame_number:
|
if frame_number:
|
||||||
assert not mpattr["RepresentativeImageFlag"]
|
assert not mpattr["RepresentativeImageFlag"]
|
||||||
else:
|
else:
|
||||||
assert mpattr["RepresentativeImageFlag"]
|
assert mpattr["RepresentativeImageFlag"]
|
||||||
assert not mpattr["DependentParentImageFlag"]
|
assert not mpattr["DependentParentImageFlag"]
|
||||||
assert not mpattr["DependentChildImageFlag"]
|
assert not mpattr["DependentChildImageFlag"]
|
||||||
assert mpattr["ImageDataFormat"] == "JPEG"
|
assert mpattr["ImageDataFormat"] == "JPEG"
|
||||||
assert mpattr["MPType"] == "Multi-Frame Image: (Disparity)"
|
assert mpattr["MPType"] == "Multi-Frame Image: (Disparity)"
|
||||||
assert mpattr["Reserved"] == 0
|
assert mpattr["Reserved"] == 0
|
||||||
frame_number += 1
|
frame_number += 1
|
||||||
|
|
||||||
|
|
||||||
def test_seek():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_seek(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert im.tell() == 0
|
assert im.tell() == 0
|
||||||
# prior to first image raises an error, both blatant and borderline
|
# prior to first image raises an error, both blatant and borderline
|
||||||
with pytest.raises(EOFError):
|
with pytest.raises(EOFError):
|
||||||
im.seek(-1)
|
im.seek(-1)
|
||||||
with pytest.raises(EOFError):
|
with pytest.raises(EOFError):
|
||||||
im.seek(-523)
|
im.seek(-523)
|
||||||
# after the final image raises an error,
|
# after the final image raises an error,
|
||||||
# both blatant and borderline
|
# both blatant and borderline
|
||||||
with pytest.raises(EOFError):
|
with pytest.raises(EOFError):
|
||||||
im.seek(2)
|
im.seek(2)
|
||||||
with pytest.raises(EOFError):
|
with pytest.raises(EOFError):
|
||||||
im.seek(523)
|
im.seek(523)
|
||||||
# bad calls shouldn't change the frame
|
# bad calls shouldn't change the frame
|
||||||
assert im.tell() == 0
|
assert im.tell() == 0
|
||||||
# this one will work
|
# this one will work
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.tell() == 1
|
assert im.tell() == 1
|
||||||
# and this one, too
|
# and this one, too
|
||||||
im.seek(0)
|
im.seek(0)
|
||||||
assert im.tell() == 0
|
assert im.tell() == 0
|
||||||
|
|
||||||
|
|
||||||
def test_n_frames():
|
def test_n_frames():
|
||||||
|
@ -225,31 +224,31 @@ def test_eoferror():
|
||||||
im.seek(n_frames - 1)
|
im.seek(n_frames - 1)
|
||||||
|
|
||||||
|
|
||||||
def test_image_grab():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_image_grab(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert im.tell() == 0
|
assert im.tell() == 0
|
||||||
im0 = im.tobytes()
|
im0 = im.tobytes()
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.tell() == 1
|
assert im.tell() == 1
|
||||||
im1 = im.tobytes()
|
im1 = im.tobytes()
|
||||||
im.seek(0)
|
im.seek(0)
|
||||||
assert im.tell() == 0
|
assert im.tell() == 0
|
||||||
im02 = im.tobytes()
|
im02 = im.tobytes()
|
||||||
assert im0 == im02
|
assert im0 == im02
|
||||||
assert im0 != im1
|
assert im0 != im1
|
||||||
|
|
||||||
|
|
||||||
def test_save():
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
for test_file in test_files:
|
def test_save(test_file):
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
assert im.tell() == 0
|
assert im.tell() == 0
|
||||||
jpg0 = roundtrip(im)
|
jpg0 = roundtrip(im)
|
||||||
assert_image_similar(im, jpg0, 30)
|
assert_image_similar(im, jpg0, 30)
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.tell() == 1
|
assert im.tell() == 1
|
||||||
jpg1 = roundtrip(im)
|
jpg1 = roundtrip(im)
|
||||||
assert_image_similar(im, jpg1, 30)
|
assert_image_similar(im, jpg1, 30)
|
||||||
|
|
||||||
|
|
||||||
def test_save_all():
|
def test_save_all():
|
||||||
|
|
|
@ -37,6 +37,7 @@ def helper_save_as_pdf(tmp_path, mode, **kwargs):
|
||||||
return outfile
|
return outfile
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.valgrind_known_error(reason="Temporary skip")
|
||||||
def test_monochrome(tmp_path):
|
def test_monochrome(tmp_path):
|
||||||
# Arrange
|
# Arrange
|
||||||
mode = "1"
|
mode = "1"
|
||||||
|
|
|
@ -18,51 +18,48 @@ _ORIGINS = ("tl", "bl")
|
||||||
_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
|
_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
|
||||||
|
|
||||||
|
|
||||||
def test_sanity(tmp_path):
|
@pytest.mark.parametrize("mode", _MODES)
|
||||||
for mode in _MODES:
|
def test_sanity(mode, tmp_path):
|
||||||
|
def roundtrip(original_im):
|
||||||
|
out = str(tmp_path / "temp.tga")
|
||||||
|
|
||||||
def roundtrip(original_im):
|
original_im.save(out, rle=rle)
|
||||||
out = str(tmp_path / "temp.tga")
|
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)
|
assert_image_equal(saved_im, original_im)
|
||||||
with Image.open(out) as saved_im:
|
|
||||||
if rle:
|
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 (
|
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":
|
||||||
if mode == "P":
|
assert original_im.getpalette() == reference_im.getpalette()
|
||||||
assert saved_im.getpalette() == original_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"))
|
roundtrip(original_im)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def test_palette_depth_16(tmp_path):
|
def test_palette_depth_16(tmp_path):
|
||||||
|
|
|
@ -185,6 +185,22 @@ def test_iptc(tmp_path):
|
||||||
im.save(out)
|
im.save(out)
|
||||||
|
|
||||||
|
|
||||||
|
def test_writing_bytes_to_ascii(tmp_path):
|
||||||
|
im = hopper()
|
||||||
|
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
|
|
||||||
|
tag = TiffTags.TAGS_V2[271]
|
||||||
|
assert tag.type == TiffTags.ASCII
|
||||||
|
|
||||||
|
info[271] = b"test"
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.tiff")
|
||||||
|
im.save(out, tiffinfo=info)
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.tag_v2[271] == "test"
|
||||||
|
|
||||||
|
|
||||||
def test_undefined_zero(tmp_path):
|
def test_undefined_zero(tmp_path):
|
||||||
# Check that the tag has not been changed since this test was created
|
# Check that the tag has not been changed since this test was created
|
||||||
tag = TiffTags.TAGS_V2[45059]
|
tag = TiffTags.TAGS_V2[45059]
|
||||||
|
|
|
@ -66,10 +66,10 @@ def test_load_set_dpi():
|
||||||
assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1)
|
assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1)
|
||||||
|
|
||||||
|
|
||||||
def test_save(tmp_path):
|
@pytest.mark.parametrize("ext", (".wmf", ".emf"))
|
||||||
|
def test_save(ext, tmp_path):
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
|
||||||
for ext in [".wmf", ".emf"]:
|
tmpfile = str(tmp_path / ("temp" + ext))
|
||||||
tmpfile = str(tmp_path / ("temp" + ext))
|
with pytest.raises(OSError):
|
||||||
with pytest.raises(OSError):
|
im.save(tmpfile)
|
||||||
im.save(tmpfile)
|
|
||||||
|
|
|
@ -22,8 +22,9 @@ from .helper import (
|
||||||
|
|
||||||
|
|
||||||
class TestImage:
|
class TestImage:
|
||||||
def test_image_modes_success(self):
|
@pytest.mark.parametrize(
|
||||||
for mode in [
|
"mode",
|
||||||
|
(
|
||||||
"1",
|
"1",
|
||||||
"P",
|
"P",
|
||||||
"PA",
|
"PA",
|
||||||
|
@ -44,22 +45,18 @@ class TestImage:
|
||||||
"YCbCr",
|
"YCbCr",
|
||||||
"LAB",
|
"LAB",
|
||||||
"HSV",
|
"HSV",
|
||||||
]:
|
),
|
||||||
Image.new(mode, (1, 1))
|
)
|
||||||
|
def test_image_modes_success(self, mode):
|
||||||
|
Image.new(mode, (1, 1))
|
||||||
|
|
||||||
def test_image_modes_fail(self):
|
@pytest.mark.parametrize(
|
||||||
for mode in [
|
"mode", ("", "bad", "very very long", "BGR;15", "BGR;16", "BGR;24", "BGR;32")
|
||||||
"",
|
)
|
||||||
"bad",
|
def test_image_modes_fail(self, mode):
|
||||||
"very very long",
|
with pytest.raises(ValueError) as e:
|
||||||
"BGR;15",
|
Image.new(mode, (1, 1))
|
||||||
"BGR;16",
|
assert str(e.value) == "unrecognized image mode"
|
||||||
"BGR;24",
|
|
||||||
"BGR;32",
|
|
||||||
]:
|
|
||||||
with pytest.raises(ValueError) as e:
|
|
||||||
Image.new(mode, (1, 1))
|
|
||||||
assert str(e.value) == "unrecognized image mode"
|
|
||||||
|
|
||||||
def test_exception_inheritance(self):
|
def test_exception_inheritance(self):
|
||||||
assert issubclass(UnidentifiedImageError, OSError)
|
assert issubclass(UnidentifiedImageError, OSError)
|
||||||
|
@ -539,23 +536,22 @@ class TestImage:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Image.linear_gradient(wrong_mode)
|
Image.linear_gradient(wrong_mode)
|
||||||
|
|
||||||
def test_linear_gradient(self):
|
@pytest.mark.parametrize("mode", ("L", "P", "I", "F"))
|
||||||
|
def test_linear_gradient(self, mode):
|
||||||
# Arrange
|
# Arrange
|
||||||
target_file = "Tests/images/linear_gradient.png"
|
target_file = "Tests/images/linear_gradient.png"
|
||||||
for mode in ["L", "P", "I", "F"]:
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
im = Image.linear_gradient(mode)
|
im = Image.linear_gradient(mode)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert im.size == (256, 256)
|
assert im.size == (256, 256)
|
||||||
assert im.mode == mode
|
assert im.mode == mode
|
||||||
assert im.getpixel((0, 0)) == 0
|
assert im.getpixel((0, 0)) == 0
|
||||||
assert im.getpixel((255, 255)) == 255
|
assert im.getpixel((255, 255)) == 255
|
||||||
with Image.open(target_file) as target:
|
with Image.open(target_file) as target:
|
||||||
target = target.convert(mode)
|
target = target.convert(mode)
|
||||||
assert_image_equal(im, target)
|
assert_image_equal(im, target)
|
||||||
|
|
||||||
def test_radial_gradient_wrong_mode(self):
|
def test_radial_gradient_wrong_mode(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -565,23 +561,22 @@ class TestImage:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
Image.radial_gradient(wrong_mode)
|
Image.radial_gradient(wrong_mode)
|
||||||
|
|
||||||
def test_radial_gradient(self):
|
@pytest.mark.parametrize("mode", ("L", "P", "I", "F"))
|
||||||
|
def test_radial_gradient(self, mode):
|
||||||
# Arrange
|
# Arrange
|
||||||
target_file = "Tests/images/radial_gradient.png"
|
target_file = "Tests/images/radial_gradient.png"
|
||||||
for mode in ["L", "P", "I", "F"]:
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
im = Image.radial_gradient(mode)
|
im = Image.radial_gradient(mode)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert im.size == (256, 256)
|
assert im.size == (256, 256)
|
||||||
assert im.mode == mode
|
assert im.mode == mode
|
||||||
assert im.getpixel((0, 0)) == 255
|
assert im.getpixel((0, 0)) == 255
|
||||||
assert im.getpixel((128, 128)) == 0
|
assert im.getpixel((128, 128)) == 0
|
||||||
with Image.open(target_file) as target:
|
with Image.open(target_file) as target:
|
||||||
target = target.convert(mode)
|
target = target.convert(mode)
|
||||||
assert_image_equal(im, target)
|
assert_image_equal(im, target)
|
||||||
|
|
||||||
def test_register_extensions(self):
|
def test_register_extensions(self):
|
||||||
test_format = "a"
|
test_format = "a"
|
||||||
|
|
|
@ -184,8 +184,9 @@ class TestImageGetPixel(AccessTest):
|
||||||
with pytest.raises(error):
|
with pytest.raises(error):
|
||||||
im.getpixel((-1, -1))
|
im.getpixel((-1, -1))
|
||||||
|
|
||||||
def test_basic(self):
|
@pytest.mark.parametrize(
|
||||||
for mode in (
|
"mode",
|
||||||
|
(
|
||||||
"1",
|
"1",
|
||||||
"L",
|
"L",
|
||||||
"LA",
|
"LA",
|
||||||
|
@ -200,26 +201,28 @@ class TestImageGetPixel(AccessTest):
|
||||||
"RGBX",
|
"RGBX",
|
||||||
"CMYK",
|
"CMYK",
|
||||||
"YCbCr",
|
"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
|
# see https://github.com/python-pillow/Pillow/issues/452
|
||||||
# pixelaccess is using signed int* instead of uint*
|
# pixelaccess is using signed int* instead of uint*
|
||||||
for mode in ("I;16", "I;16B"):
|
self.check(mode, 2**15 - 1)
|
||||||
self.check(mode, 2**15 - 1)
|
self.check(mode, 2**15)
|
||||||
self.check(mode, 2**15)
|
self.check(mode, 2**15 + 1)
|
||||||
self.check(mode, 2**15 + 1)
|
self.check(mode, 2**16 - 1)
|
||||||
self.check(mode, 2**16 - 1)
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||||
def test_p_putpixel_rgb_rgba(self, mode):
|
@pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))
|
||||||
for color in [(255, 0, 0), (255, 0, 0, 127)]:
|
def test_p_putpixel_rgb_rgba(self, mode, color):
|
||||||
im = Image.new(mode, (1, 1))
|
im = Image.new(mode, (1, 1))
|
||||||
im.putpixel((0, 0), color)
|
im.putpixel((0, 0), color)
|
||||||
|
|
||||||
alpha = color[3] if len(color) == 4 and mode == "PA" else 255
|
alpha = color[3] if len(color) == 4 and mode == "PA" else 255
|
||||||
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)
|
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||||
|
|
|
@ -268,36 +268,33 @@ def test_matrix_wrong_mode():
|
||||||
im.convert(mode="L", matrix=matrix)
|
im.convert(mode="L", matrix=matrix)
|
||||||
|
|
||||||
|
|
||||||
def test_matrix_xyz():
|
@pytest.mark.parametrize("mode", ("RGB", "L"))
|
||||||
def matrix_convert(mode):
|
def test_matrix_xyz(mode):
|
||||||
# Arrange
|
# Arrange
|
||||||
im = hopper("RGB")
|
im = hopper("RGB")
|
||||||
im.info["transparency"] = (255, 0, 0)
|
im.info["transparency"] = (255, 0, 0)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
matrix = (
|
matrix = (
|
||||||
0.412453, 0.357580, 0.180423, 0,
|
0.412453, 0.357580, 0.180423, 0,
|
||||||
0.212671, 0.715160, 0.072169, 0,
|
0.212671, 0.715160, 0.072169, 0,
|
||||||
0.019334, 0.119193, 0.950227, 0)
|
0.019334, 0.119193, 0.950227, 0)
|
||||||
# fmt: on
|
# fmt: on
|
||||||
assert im.mode == "RGB"
|
assert im.mode == "RGB"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
# Convert an RGB image to the CIE XYZ colour space
|
# Convert an RGB image to the CIE XYZ colour space
|
||||||
converted_im = im.convert(mode=mode, matrix=matrix)
|
converted_im = im.convert(mode=mode, matrix=matrix)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert converted_im.mode == mode
|
assert converted_im.mode == mode
|
||||||
assert converted_im.size == im.size
|
assert converted_im.size == im.size
|
||||||
with Image.open("Tests/images/hopper-XYZ.png") as target:
|
with Image.open("Tests/images/hopper-XYZ.png") as target:
|
||||||
if converted_im.mode == "RGB":
|
if converted_im.mode == "RGB":
|
||||||
assert_image_similar(converted_im, target, 3)
|
assert_image_similar(converted_im, target, 3)
|
||||||
assert converted_im.info["transparency"] == (105, 54, 4)
|
assert converted_im.info["transparency"] == (105, 54, 4)
|
||||||
else:
|
else:
|
||||||
assert_image_similar(converted_im, target.getchannel(0), 1)
|
assert_image_similar(converted_im, target.getchannel(0), 1)
|
||||||
assert converted_im.info["transparency"] == 105
|
assert converted_im.info["transparency"] == 105
|
||||||
|
|
||||||
matrix_convert("RGB")
|
|
||||||
matrix_convert("L")
|
|
||||||
|
|
||||||
|
|
||||||
def test_matrix_identity():
|
def test_matrix_identity():
|
||||||
|
|
|
@ -1,37 +1,40 @@
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import hopper
|
from .helper import hopper
|
||||||
|
|
||||||
|
|
||||||
def test_copy():
|
@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
|
||||||
|
def test_copy(mode):
|
||||||
cropped_coordinates = (10, 10, 20, 20)
|
cropped_coordinates = (10, 10, 20, 20)
|
||||||
cropped_size = (10, 10)
|
cropped_size = (10, 10)
|
||||||
for mode in "1", "P", "L", "RGB", "I", "F":
|
|
||||||
# Internal copy method
|
|
||||||
im = hopper(mode)
|
|
||||||
out = im.copy()
|
|
||||||
assert out.mode == im.mode
|
|
||||||
assert out.size == im.size
|
|
||||||
|
|
||||||
# Python's copy method
|
# Internal copy method
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
out = copy.copy(im)
|
out = im.copy()
|
||||||
assert out.mode == im.mode
|
assert out.mode == im.mode
|
||||||
assert out.size == im.size
|
assert out.size == im.size
|
||||||
|
|
||||||
# Internal copy method on a cropped image
|
# Python's copy method
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
out = im.crop(cropped_coordinates).copy()
|
out = copy.copy(im)
|
||||||
assert out.mode == im.mode
|
assert out.mode == im.mode
|
||||||
assert out.size == cropped_size
|
assert out.size == im.size
|
||||||
|
|
||||||
# Python's copy method on a cropped image
|
# Internal copy method on a cropped image
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
out = copy.copy(im.crop(cropped_coordinates))
|
out = im.crop(cropped_coordinates).copy()
|
||||||
assert out.mode == im.mode
|
assert out.mode == im.mode
|
||||||
assert out.size == cropped_size
|
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():
|
def test_copy_zero():
|
||||||
|
|
|
@ -5,17 +5,14 @@ from PIL import Image
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
|
|
||||||
def test_crop():
|
@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
|
||||||
def crop(mode):
|
def test_crop(mode):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
assert_image_equal(im.crop(), im)
|
assert_image_equal(im.crop(), im)
|
||||||
|
|
||||||
cropped = im.crop((50, 50, 100, 100))
|
cropped = im.crop((50, 50, 100, 100))
|
||||||
assert cropped.mode == mode
|
assert cropped.mode == mode
|
||||||
assert cropped.size == (50, 50)
|
assert cropped.size == (50, 50)
|
||||||
|
|
||||||
for mode in "1", "P", "L", "RGB", "I", "F":
|
|
||||||
crop(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_wide_crop():
|
def test_wide_crop():
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import CachedProperty, assert_image_equal
|
from .helper import CachedProperty, assert_image_equal
|
||||||
|
@ -101,226 +103,226 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_image_solid(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_image_solid(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "red")
|
im = Image.new(mode, (200, 200), "red")
|
||||||
im2 = getattr(self, "gradient_" + mode)
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
im.paste(im2, (12, 23))
|
im.paste(im2, (12, 23))
|
||||||
|
|
||||||
im = im.crop((12, 23, im2.width + 12, im2.height + 23))
|
im = im.crop((12, 23, im2.width + 12, im2.height + 23))
|
||||||
assert_image_equal(im, im2)
|
assert_image_equal(im, im2)
|
||||||
|
|
||||||
def test_image_mask_1(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_image_mask_1(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "white")
|
im = Image.new(mode, (200, 200), "white")
|
||||||
im2 = getattr(self, "gradient_" + mode)
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
self.assert_9points_paste(
|
self.assert_9points_paste(
|
||||||
im,
|
im,
|
||||||
im2,
|
im2,
|
||||||
self.mask_1,
|
self.mask_1,
|
||||||
[
|
[
|
||||||
(255, 255, 255, 255),
|
(255, 255, 255, 255),
|
||||||
(255, 255, 255, 255),
|
(255, 255, 255, 255),
|
||||||
(127, 254, 127, 0),
|
(127, 254, 127, 0),
|
||||||
(255, 255, 255, 255),
|
(255, 255, 255, 255),
|
||||||
(255, 255, 255, 255),
|
(255, 255, 255, 255),
|
||||||
(191, 190, 63, 64),
|
(191, 190, 63, 64),
|
||||||
(127, 0, 127, 254),
|
(127, 0, 127, 254),
|
||||||
(191, 64, 63, 190),
|
(191, 64, 63, 190),
|
||||||
(255, 255, 255, 255),
|
(255, 255, 255, 255),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_image_mask_L(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_image_mask_L(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "white")
|
im = Image.new(mode, (200, 200), "white")
|
||||||
im2 = getattr(self, "gradient_" + mode)
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
self.assert_9points_paste(
|
self.assert_9points_paste(
|
||||||
im,
|
im,
|
||||||
im2,
|
im2,
|
||||||
self.mask_L,
|
self.mask_L,
|
||||||
[
|
[
|
||||||
(128, 191, 255, 191),
|
(128, 191, 255, 191),
|
||||||
(208, 239, 239, 208),
|
(208, 239, 239, 208),
|
||||||
(255, 255, 255, 255),
|
(255, 255, 255, 255),
|
||||||
(112, 111, 206, 207),
|
(112, 111, 206, 207),
|
||||||
(192, 191, 191, 191),
|
(192, 191, 191, 191),
|
||||||
(239, 239, 207, 207),
|
(239, 239, 207, 207),
|
||||||
(128, 1, 128, 254),
|
(128, 1, 128, 254),
|
||||||
(207, 113, 112, 207),
|
(207, 113, 112, 207),
|
||||||
(255, 191, 128, 191),
|
(255, 191, 128, 191),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_image_mask_LA(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_image_mask_LA(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "white")
|
im = Image.new(mode, (200, 200), "white")
|
||||||
im2 = getattr(self, "gradient_" + mode)
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
self.assert_9points_paste(
|
self.assert_9points_paste(
|
||||||
im,
|
im,
|
||||||
im2,
|
im2,
|
||||||
self.gradient_LA,
|
self.gradient_LA,
|
||||||
[
|
[
|
||||||
(128, 191, 255, 191),
|
(128, 191, 255, 191),
|
||||||
(112, 207, 206, 111),
|
(112, 207, 206, 111),
|
||||||
(128, 254, 128, 1),
|
(128, 254, 128, 1),
|
||||||
(208, 208, 239, 239),
|
(208, 208, 239, 239),
|
||||||
(192, 191, 191, 191),
|
(192, 191, 191, 191),
|
||||||
(207, 207, 112, 113),
|
(207, 207, 112, 113),
|
||||||
(255, 255, 255, 255),
|
(255, 255, 255, 255),
|
||||||
(239, 207, 207, 239),
|
(239, 207, 207, 239),
|
||||||
(255, 191, 128, 191),
|
(255, 191, 128, 191),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_image_mask_RGBA(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_image_mask_RGBA(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "white")
|
im = Image.new(mode, (200, 200), "white")
|
||||||
im2 = getattr(self, "gradient_" + mode)
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
self.assert_9points_paste(
|
self.assert_9points_paste(
|
||||||
im,
|
im,
|
||||||
im2,
|
im2,
|
||||||
self.gradient_RGBA,
|
self.gradient_RGBA,
|
||||||
[
|
[
|
||||||
(128, 191, 255, 191),
|
(128, 191, 255, 191),
|
||||||
(208, 239, 239, 208),
|
(208, 239, 239, 208),
|
||||||
(255, 255, 255, 255),
|
(255, 255, 255, 255),
|
||||||
(112, 111, 206, 207),
|
(112, 111, 206, 207),
|
||||||
(192, 191, 191, 191),
|
(192, 191, 191, 191),
|
||||||
(239, 239, 207, 207),
|
(239, 239, 207, 207),
|
||||||
(128, 1, 128, 254),
|
(128, 1, 128, 254),
|
||||||
(207, 113, 112, 207),
|
(207, 113, 112, 207),
|
||||||
(255, 191, 128, 191),
|
(255, 191, 128, 191),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_image_mask_RGBa(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_image_mask_RGBa(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "white")
|
im = Image.new(mode, (200, 200), "white")
|
||||||
im2 = getattr(self, "gradient_" + mode)
|
im2 = getattr(self, "gradient_" + mode)
|
||||||
|
|
||||||
self.assert_9points_paste(
|
self.assert_9points_paste(
|
||||||
im,
|
im,
|
||||||
im2,
|
im2,
|
||||||
self.gradient_RGBa,
|
self.gradient_RGBa,
|
||||||
[
|
[
|
||||||
(128, 255, 126, 255),
|
(128, 255, 126, 255),
|
||||||
(0, 127, 126, 255),
|
(0, 127, 126, 255),
|
||||||
(126, 253, 126, 255),
|
(126, 253, 126, 255),
|
||||||
(128, 127, 254, 255),
|
(128, 127, 254, 255),
|
||||||
(0, 255, 254, 255),
|
(0, 255, 254, 255),
|
||||||
(126, 125, 254, 255),
|
(126, 125, 254, 255),
|
||||||
(128, 1, 128, 255),
|
(128, 1, 128, 255),
|
||||||
(0, 129, 128, 255),
|
(0, 129, 128, 255),
|
||||||
(126, 255, 128, 255),
|
(126, 255, 128, 255),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_color_solid(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_color_solid(self, mode):
|
||||||
im = Image.new(mode, (200, 200), "black")
|
im = Image.new(mode, (200, 200), "black")
|
||||||
|
|
||||||
rect = (12, 23, 128 + 12, 128 + 23)
|
rect = (12, 23, 128 + 12, 128 + 23)
|
||||||
im.paste("white", rect)
|
im.paste("white", rect)
|
||||||
|
|
||||||
hist = im.crop(rect).histogram()
|
hist = im.crop(rect).histogram()
|
||||||
while hist:
|
while hist:
|
||||||
head, hist = hist[:256], hist[256:]
|
head, hist = hist[:256], hist[256:]
|
||||||
assert head[255] == 128 * 128
|
assert head[255] == 128 * 128
|
||||||
assert sum(head[:255]) == 0
|
assert sum(head[:255]) == 0
|
||||||
|
|
||||||
def test_color_mask_1(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_color_mask_1(self, mode):
|
||||||
im = Image.new(mode, (200, 200), (50, 60, 70, 80)[: len(mode)])
|
im = Image.new(mode, (200, 200), (50, 60, 70, 80)[: len(mode)])
|
||||||
color = (10, 20, 30, 40)[: len(mode)]
|
color = (10, 20, 30, 40)[: len(mode)]
|
||||||
|
|
||||||
self.assert_9points_paste(
|
self.assert_9points_paste(
|
||||||
im,
|
im,
|
||||||
color,
|
color,
|
||||||
self.mask_1,
|
self.mask_1,
|
||||||
[
|
[
|
||||||
(50, 60, 70, 80),
|
(50, 60, 70, 80),
|
||||||
(50, 60, 70, 80),
|
(50, 60, 70, 80),
|
||||||
(10, 20, 30, 40),
|
(10, 20, 30, 40),
|
||||||
(50, 60, 70, 80),
|
(50, 60, 70, 80),
|
||||||
(50, 60, 70, 80),
|
(50, 60, 70, 80),
|
||||||
(10, 20, 30, 40),
|
(10, 20, 30, 40),
|
||||||
(10, 20, 30, 40),
|
(10, 20, 30, 40),
|
||||||
(10, 20, 30, 40),
|
(10, 20, 30, 40),
|
||||||
(50, 60, 70, 80),
|
(50, 60, 70, 80),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_color_mask_L(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_color_mask_L(self, mode):
|
||||||
im = getattr(self, "gradient_" + mode).copy()
|
im = getattr(self, "gradient_" + mode).copy()
|
||||||
color = "white"
|
color = "white"
|
||||||
|
|
||||||
self.assert_9points_paste(
|
self.assert_9points_paste(
|
||||||
im,
|
im,
|
||||||
color,
|
color,
|
||||||
self.mask_L,
|
self.mask_L,
|
||||||
[
|
[
|
||||||
(127, 191, 254, 191),
|
(127, 191, 254, 191),
|
||||||
(111, 207, 206, 110),
|
(111, 207, 206, 110),
|
||||||
(127, 254, 127, 0),
|
(127, 254, 127, 0),
|
||||||
(207, 207, 239, 239),
|
(207, 207, 239, 239),
|
||||||
(191, 191, 190, 191),
|
(191, 191, 190, 191),
|
||||||
(207, 206, 111, 112),
|
(207, 206, 111, 112),
|
||||||
(254, 254, 254, 255),
|
(254, 254, 254, 255),
|
||||||
(239, 206, 206, 238),
|
(239, 206, 206, 238),
|
||||||
(254, 191, 127, 191),
|
(254, 191, 127, 191),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_color_mask_RGBA(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_color_mask_RGBA(self, mode):
|
||||||
im = getattr(self, "gradient_" + mode).copy()
|
im = getattr(self, "gradient_" + mode).copy()
|
||||||
color = "white"
|
color = "white"
|
||||||
|
|
||||||
self.assert_9points_paste(
|
self.assert_9points_paste(
|
||||||
im,
|
im,
|
||||||
color,
|
color,
|
||||||
self.gradient_RGBA,
|
self.gradient_RGBA,
|
||||||
[
|
[
|
||||||
(127, 191, 254, 191),
|
(127, 191, 254, 191),
|
||||||
(111, 207, 206, 110),
|
(111, 207, 206, 110),
|
||||||
(127, 254, 127, 0),
|
(127, 254, 127, 0),
|
||||||
(207, 207, 239, 239),
|
(207, 207, 239, 239),
|
||||||
(191, 191, 190, 191),
|
(191, 191, 190, 191),
|
||||||
(207, 206, 111, 112),
|
(207, 206, 111, 112),
|
||||||
(254, 254, 254, 255),
|
(254, 254, 254, 255),
|
||||||
(239, 206, 206, 238),
|
(239, 206, 206, 238),
|
||||||
(254, 191, 127, 191),
|
(254, 191, 127, 191),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_color_mask_RGBa(self):
|
@pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
|
||||||
for mode in ("RGBA", "RGB", "L"):
|
def test_color_mask_RGBa(self, mode):
|
||||||
im = getattr(self, "gradient_" + mode).copy()
|
im = getattr(self, "gradient_" + mode).copy()
|
||||||
color = "white"
|
color = "white"
|
||||||
|
|
||||||
self.assert_9points_paste(
|
self.assert_9points_paste(
|
||||||
im,
|
im,
|
||||||
color,
|
color,
|
||||||
self.gradient_RGBa,
|
self.gradient_RGBa,
|
||||||
[
|
[
|
||||||
(255, 63, 126, 63),
|
(255, 63, 126, 63),
|
||||||
(47, 143, 142, 46),
|
(47, 143, 142, 46),
|
||||||
(126, 253, 126, 255),
|
(126, 253, 126, 255),
|
||||||
(15, 15, 47, 47),
|
(15, 15, 47, 47),
|
||||||
(63, 63, 62, 63),
|
(63, 63, 62, 63),
|
||||||
(142, 141, 46, 47),
|
(142, 141, 46, 47),
|
||||||
(255, 255, 255, 0),
|
(255, 255, 255, 0),
|
||||||
(48, 15, 15, 47),
|
(48, 15, 15, 47),
|
||||||
(126, 63, 255, 63),
|
(126, 63, 255, 63),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_different_sizes(self):
|
def test_different_sizes(self):
|
||||||
im = Image.new("RGB", (100, 100))
|
im = Image.new("RGB", (100, 100))
|
||||||
|
|
|
@ -100,40 +100,41 @@ class TestImagingCoreResampleAccuracy:
|
||||||
for y in range(image.size[1])
|
for y in range(image.size[1])
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_reduce_box(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_reduce_box(self, mode):
|
||||||
case = self.make_case(mode, (8, 8), 0xE1)
|
case = self.make_case(mode, (8, 8), 0xE1)
|
||||||
case = case.resize((4, 4), Image.Resampling.BOX)
|
case = case.resize((4, 4), Image.Resampling.BOX)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
data = ("e1 e1"
|
data = ("e1 e1"
|
||||||
"e1 e1")
|
"e1 e1")
|
||||||
# fmt: on
|
# fmt: on
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (4, 4)))
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
||||||
|
|
||||||
def test_reduce_bilinear(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_reduce_bilinear(self, mode):
|
||||||
case = self.make_case(mode, (8, 8), 0xE1)
|
case = self.make_case(mode, (8, 8), 0xE1)
|
||||||
case = case.resize((4, 4), Image.Resampling.BILINEAR)
|
case = case.resize((4, 4), Image.Resampling.BILINEAR)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
data = ("e1 c9"
|
data = ("e1 c9"
|
||||||
"c9 b7")
|
"c9 b7")
|
||||||
# fmt: on
|
# fmt: on
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (4, 4)))
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
||||||
|
|
||||||
def test_reduce_hamming(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_reduce_hamming(self, mode):
|
||||||
case = self.make_case(mode, (8, 8), 0xE1)
|
case = self.make_case(mode, (8, 8), 0xE1)
|
||||||
case = case.resize((4, 4), Image.Resampling.HAMMING)
|
case = case.resize((4, 4), Image.Resampling.HAMMING)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
data = ("e1 da"
|
data = ("e1 da"
|
||||||
"da d3")
|
"da d3")
|
||||||
# fmt: on
|
# fmt: on
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (4, 4)))
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
||||||
|
|
||||||
def test_reduce_bicubic(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
|
def test_reduce_bicubic(self, mode):
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
for mode in ["RGBX", "RGB", "La", "L"]:
|
||||||
case = self.make_case(mode, (12, 12), 0xE1)
|
case = self.make_case(mode, (12, 12), 0xE1)
|
||||||
case = case.resize((6, 6), Image.Resampling.BICUBIC)
|
case = case.resize((6, 6), Image.Resampling.BICUBIC)
|
||||||
|
@ -145,79 +146,79 @@ class TestImagingCoreResampleAccuracy:
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (6, 6)))
|
self.check_case(channel, self.make_sample(data, (6, 6)))
|
||||||
|
|
||||||
def test_reduce_lanczos(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_reduce_lanczos(self, mode):
|
||||||
case = self.make_case(mode, (16, 16), 0xE1)
|
case = self.make_case(mode, (16, 16), 0xE1)
|
||||||
case = case.resize((8, 8), Image.Resampling.LANCZOS)
|
case = case.resize((8, 8), Image.Resampling.LANCZOS)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
data = ("e1 e0 e4 d7"
|
data = ("e1 e0 e4 d7"
|
||||||
"e0 df e3 d6"
|
"e0 df e3 d6"
|
||||||
"e4 e3 e7 da"
|
"e4 e3 e7 da"
|
||||||
"d7 d6 d9 ce")
|
"d7 d6 d9 ce")
|
||||||
# fmt: on
|
# fmt: on
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (8, 8)))
|
self.check_case(channel, self.make_sample(data, (8, 8)))
|
||||||
|
|
||||||
def test_enlarge_box(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_enlarge_box(self, mode):
|
||||||
case = self.make_case(mode, (2, 2), 0xE1)
|
case = self.make_case(mode, (2, 2), 0xE1)
|
||||||
case = case.resize((4, 4), Image.Resampling.BOX)
|
case = case.resize((4, 4), Image.Resampling.BOX)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
data = ("e1 e1"
|
data = ("e1 e1"
|
||||||
"e1 e1")
|
"e1 e1")
|
||||||
# fmt: on
|
# fmt: on
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (4, 4)))
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
||||||
|
|
||||||
def test_enlarge_bilinear(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_enlarge_bilinear(self, mode):
|
||||||
case = self.make_case(mode, (2, 2), 0xE1)
|
case = self.make_case(mode, (2, 2), 0xE1)
|
||||||
case = case.resize((4, 4), Image.Resampling.BILINEAR)
|
case = case.resize((4, 4), Image.Resampling.BILINEAR)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
data = ("e1 b0"
|
data = ("e1 b0"
|
||||||
"b0 98")
|
"b0 98")
|
||||||
# fmt: on
|
# fmt: on
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (4, 4)))
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
||||||
|
|
||||||
def test_enlarge_hamming(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_enlarge_hamming(self, mode):
|
||||||
case = self.make_case(mode, (2, 2), 0xE1)
|
case = self.make_case(mode, (2, 2), 0xE1)
|
||||||
case = case.resize((4, 4), Image.Resampling.HAMMING)
|
case = case.resize((4, 4), Image.Resampling.HAMMING)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
data = ("e1 d2"
|
data = ("e1 d2"
|
||||||
"d2 c5")
|
"d2 c5")
|
||||||
# fmt: on
|
# fmt: on
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (4, 4)))
|
self.check_case(channel, self.make_sample(data, (4, 4)))
|
||||||
|
|
||||||
def test_enlarge_bicubic(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_enlarge_bicubic(self, mode):
|
||||||
case = self.make_case(mode, (4, 4), 0xE1)
|
case = self.make_case(mode, (4, 4), 0xE1)
|
||||||
case = case.resize((8, 8), Image.Resampling.BICUBIC)
|
case = case.resize((8, 8), Image.Resampling.BICUBIC)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
data = ("e1 e5 ee b9"
|
data = ("e1 e5 ee b9"
|
||||||
"e5 e9 f3 bc"
|
"e5 e9 f3 bc"
|
||||||
"ee f3 fd c1"
|
"ee f3 fd c1"
|
||||||
"b9 bc c1 a2")
|
"b9 bc c1 a2")
|
||||||
# fmt: on
|
# fmt: on
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (8, 8)))
|
self.check_case(channel, self.make_sample(data, (8, 8)))
|
||||||
|
|
||||||
def test_enlarge_lanczos(self):
|
@pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
|
||||||
for mode in ["RGBX", "RGB", "La", "L"]:
|
def test_enlarge_lanczos(self, mode):
|
||||||
case = self.make_case(mode, (6, 6), 0xE1)
|
case = self.make_case(mode, (6, 6), 0xE1)
|
||||||
case = case.resize((12, 12), Image.Resampling.LANCZOS)
|
case = case.resize((12, 12), Image.Resampling.LANCZOS)
|
||||||
data = (
|
data = (
|
||||||
"e1 e0 db ed f5 b8"
|
"e1 e0 db ed f5 b8"
|
||||||
"e0 df da ec f3 b7"
|
"e0 df da ec f3 b7"
|
||||||
"db db d6 e7 ee b5"
|
"db db d6 e7 ee b5"
|
||||||
"ed ec e6 fb ff bf"
|
"ed ec e6 fb ff bf"
|
||||||
"f5 f4 ee ff ff c4"
|
"f5 f4 ee ff ff c4"
|
||||||
"b8 b7 b4 bf c4 a0"
|
"b8 b7 b4 bf c4 a0"
|
||||||
)
|
)
|
||||||
for channel in case.split():
|
for channel in case.split():
|
||||||
self.check_case(channel, self.make_sample(data, (12, 12)))
|
self.check_case(channel, self.make_sample(data, (12, 12)))
|
||||||
|
|
||||||
def test_box_filter_correct_range(self):
|
def test_box_filter_correct_range(self):
|
||||||
im = Image.new("RGB", (8, 8), "#1688ff").resize(
|
im = Image.new("RGB", (8, 8), "#1688ff").resize(
|
||||||
|
@ -419,40 +420,43 @@ class TestCoreResampleCoefficients:
|
||||||
|
|
||||||
|
|
||||||
class TestCoreResampleBox:
|
class TestCoreResampleBox:
|
||||||
def test_wrong_arguments(self):
|
@pytest.mark.parametrize(
|
||||||
im = hopper()
|
"resample",
|
||||||
for resample in (
|
(
|
||||||
Image.Resampling.NEAREST,
|
Image.Resampling.NEAREST,
|
||||||
Image.Resampling.BOX,
|
Image.Resampling.BOX,
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
Image.Resampling.HAMMING,
|
Image.Resampling.HAMMING,
|
||||||
Image.Resampling.BICUBIC,
|
Image.Resampling.BICUBIC,
|
||||||
Image.Resampling.LANCZOS,
|
Image.Resampling.LANCZOS,
|
||||||
):
|
),
|
||||||
im.resize((32, 32), resample, (0, 0, im.width, im.height))
|
)
|
||||||
im.resize((32, 32), resample, (20, 20, im.width, im.height))
|
def test_wrong_arguments(self, resample):
|
||||||
im.resize((32, 32), resample, (20, 20, 20, 100))
|
im = hopper()
|
||||||
im.resize((32, 32), resample, (20, 20, 100, 20))
|
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"):
|
with pytest.raises(TypeError, match="must be sequence of length 4"):
|
||||||
im.resize((32, 32), resample, (im.width, im.height))
|
im.resize((32, 32), resample, (im.width, im.height))
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="can't be negative"):
|
with pytest.raises(ValueError, match="can't be negative"):
|
||||||
im.resize((32, 32), resample, (-20, 20, 100, 100))
|
im.resize((32, 32), resample, (-20, 20, 100, 100))
|
||||||
with pytest.raises(ValueError, match="can't be negative"):
|
with pytest.raises(ValueError, match="can't be negative"):
|
||||||
im.resize((32, 32), resample, (20, -20, 100, 100))
|
im.resize((32, 32), resample, (20, -20, 100, 100))
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="can't be empty"):
|
with pytest.raises(ValueError, match="can't be empty"):
|
||||||
im.resize((32, 32), resample, (20.1, 20, 20, 100))
|
im.resize((32, 32), resample, (20.1, 20, 20, 100))
|
||||||
with pytest.raises(ValueError, match="can't be empty"):
|
with pytest.raises(ValueError, match="can't be empty"):
|
||||||
im.resize((32, 32), resample, (20, 20.1, 100, 20))
|
im.resize((32, 32), resample, (20, 20.1, 100, 20))
|
||||||
with pytest.raises(ValueError, match="can't be empty"):
|
with pytest.raises(ValueError, match="can't be empty"):
|
||||||
im.resize((32, 32), resample, (20.1, 20.1, 20, 20))
|
im.resize((32, 32), resample, (20.1, 20.1, 20, 20))
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="can't exceed"):
|
with pytest.raises(ValueError, match="can't exceed"):
|
||||||
im.resize((32, 32), resample, (0, 0, im.width + 1, im.height))
|
im.resize((32, 32), resample, (0, 0, im.width + 1, im.height))
|
||||||
with pytest.raises(ValueError, match="can't exceed"):
|
with pytest.raises(ValueError, match="can't exceed"):
|
||||||
im.resize((32, 32), resample, (0, 0, im.width, im.height + 1))
|
im.resize((32, 32), resample, (0, 0, im.width, im.height + 1))
|
||||||
|
|
||||||
def resize_tiled(self, im, dst_size, xtiles, ytiles):
|
def resize_tiled(self, im, dst_size, xtiles, ytiles):
|
||||||
def split_range(size, tiles):
|
def split_range(size, tiles):
|
||||||
|
@ -509,14 +513,16 @@ class TestCoreResampleBox:
|
||||||
with pytest.raises(AssertionError, match=r"difference 29\."):
|
with pytest.raises(AssertionError, match=r"difference 29\."):
|
||||||
assert_image_similar(reference, without_box, 5)
|
assert_image_similar(reference, without_box, 5)
|
||||||
|
|
||||||
def test_formats(self):
|
@pytest.mark.parametrize("mode", ("RGB", "L", "RGBA", "LA", "I", ""))
|
||||||
for resample in [Image.Resampling.NEAREST, Image.Resampling.BILINEAR]:
|
@pytest.mark.parametrize(
|
||||||
for mode in ["RGB", "L", "RGBA", "LA", "I", ""]:
|
"resample", (Image.Resampling.NEAREST, Image.Resampling.BILINEAR)
|
||||||
im = hopper(mode)
|
)
|
||||||
box = (20, 20, im.size[0] - 20, im.size[1] - 20)
|
def test_formats(self, mode, resample):
|
||||||
with_box = im.resize((32, 32), resample, box)
|
im = hopper(mode)
|
||||||
cropped = im.crop(box).resize((32, 32), resample)
|
box = (20, 20, im.size[0] - 20, im.size[1] - 20)
|
||||||
assert_image_similar(cropped, with_box, 0.4)
|
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):
|
def test_passthrough(self):
|
||||||
# When no resize is required
|
# When no resize is required
|
||||||
|
|
|
@ -22,24 +22,15 @@ class TestImagingCoreResize:
|
||||||
im.load()
|
im.load()
|
||||||
return im._new(im.im.resize(size, f))
|
return im._new(im.im.resize(size, f))
|
||||||
|
|
||||||
def test_nearest_mode(self):
|
@pytest.mark.parametrize(
|
||||||
for mode in [
|
"mode", ("1", "P", "L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr", "I;16")
|
||||||
"1",
|
)
|
||||||
"P",
|
def test_nearest_mode(self, mode):
|
||||||
"L",
|
im = hopper(mode)
|
||||||
"I",
|
r = self.resize(im, (15, 12), Image.Resampling.NEAREST)
|
||||||
"F",
|
assert r.mode == mode
|
||||||
"RGB",
|
assert r.size == (15, 12)
|
||||||
"RGBA",
|
assert r.im.bands == im.im.bands
|
||||||
"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
|
|
||||||
|
|
||||||
def test_convolution_modes(self):
|
def test_convolution_modes(self):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
|
@ -55,33 +46,58 @@ class TestImagingCoreResize:
|
||||||
assert r.size == (15, 12)
|
assert r.size == (15, 12)
|
||||||
assert r.im.bands == im.im.bands
|
assert r.im.bands == im.im.bands
|
||||||
|
|
||||||
def test_reduce_filters(self):
|
@pytest.mark.parametrize(
|
||||||
for f in [
|
"resample",
|
||||||
|
(
|
||||||
Image.Resampling.NEAREST,
|
Image.Resampling.NEAREST,
|
||||||
Image.Resampling.BOX,
|
Image.Resampling.BOX,
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
Image.Resampling.HAMMING,
|
Image.Resampling.HAMMING,
|
||||||
Image.Resampling.BICUBIC,
|
Image.Resampling.BICUBIC,
|
||||||
Image.Resampling.LANCZOS,
|
Image.Resampling.LANCZOS,
|
||||||
]:
|
),
|
||||||
r = self.resize(hopper("RGB"), (15, 12), f)
|
)
|
||||||
assert r.mode == "RGB"
|
def test_reduce_filters(self, resample):
|
||||||
assert r.size == (15, 12)
|
r = self.resize(hopper("RGB"), (15, 12), resample)
|
||||||
|
assert r.mode == "RGB"
|
||||||
|
assert r.size == (15, 12)
|
||||||
|
|
||||||
def test_enlarge_filters(self):
|
@pytest.mark.parametrize(
|
||||||
for f in [
|
"resample",
|
||||||
|
(
|
||||||
Image.Resampling.NEAREST,
|
Image.Resampling.NEAREST,
|
||||||
Image.Resampling.BOX,
|
Image.Resampling.BOX,
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
Image.Resampling.HAMMING,
|
Image.Resampling.HAMMING,
|
||||||
Image.Resampling.BICUBIC,
|
Image.Resampling.BICUBIC,
|
||||||
Image.Resampling.LANCZOS,
|
Image.Resampling.LANCZOS,
|
||||||
]:
|
),
|
||||||
r = self.resize(hopper("RGB"), (212, 195), f)
|
)
|
||||||
assert r.mode == "RGB"
|
def test_enlarge_filters(self, resample):
|
||||||
assert r.size == (212, 195)
|
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.
|
# Make an image with one colored pixel, in one channel.
|
||||||
# When resized, that channel should be the same as a GS image.
|
# When resized, that channel should be the same as a GS image.
|
||||||
# Other channels should be unaffected.
|
# Other channels should be unaffected.
|
||||||
|
@ -95,47 +111,37 @@ class TestImagingCoreResize:
|
||||||
}
|
}
|
||||||
samples["dirty"].putpixel((1, 1), 128)
|
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.NEAREST,
|
||||||
Image.Resampling.BOX,
|
Image.Resampling.BOX,
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
Image.Resampling.HAMMING,
|
Image.Resampling.HAMMING,
|
||||||
Image.Resampling.BICUBIC,
|
Image.Resampling.BICUBIC,
|
||||||
Image.Resampling.LANCZOS,
|
Image.Resampling.LANCZOS,
|
||||||
]:
|
),
|
||||||
# samples resized with current filter
|
)
|
||||||
references = {
|
def test_enlarge_zero(self, resample):
|
||||||
name: self.resize(ch, (4, 4), f) for name, ch in samples.items()
|
r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), resample)
|
||||||
}
|
assert r.mode == "RGB"
|
||||||
|
assert r.size == (212, 195)
|
||||||
for mode, channels_set in [
|
assert r.getdata()[0] == (0, 0, 0)
|
||||||
("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_unknown_filter(self):
|
def test_unknown_filter(self):
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
|
@ -179,74 +185,71 @@ class TestReducingGapResize:
|
||||||
(52, 34), Image.Resampling.BICUBIC, reducing_gap=0.99
|
(52, 34), Image.Resampling.BICUBIC, reducing_gap=0.99
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_reducing_gap_1(self, gradients_image):
|
@pytest.mark.parametrize(
|
||||||
for box, epsilon in [
|
"box, epsilon",
|
||||||
(None, 4),
|
((None, 4), ((1.1, 2.2, 510.8, 510.9), 4), ((3, 10, 410, 256), 10)),
|
||||||
((1.1, 2.2, 510.8, 510.9), 4),
|
)
|
||||||
((3, 10, 410, 256), 10),
|
def test_reducing_gap_1(self, gradients_image, box, epsilon):
|
||||||
]:
|
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
|
||||||
ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
|
im = gradients_image.resize(
|
||||||
im = gradients_image.resize(
|
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
|
||||||
(52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
|
)
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
assert_image_equal(ref, im)
|
assert_image_equal(ref, im)
|
||||||
|
|
||||||
def test_box_filter(self, gradients_image):
|
assert_image_similar(ref, im, epsilon)
|
||||||
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)
|
@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:
|
class TestImageResize:
|
||||||
|
@ -273,15 +276,14 @@ class TestImageResize:
|
||||||
im = im.resize((64, 64))
|
im = im.resize((64, 64))
|
||||||
assert im.size == (64, 64)
|
assert im.size == (64, 64)
|
||||||
|
|
||||||
def test_default_filter(self):
|
@pytest.mark.parametrize("mode", ("L", "RGB", "I", "F"))
|
||||||
for mode in "L", "RGB", "I", "F":
|
def test_default_filter_bicubic(self, mode):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
|
assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
|
||||||
|
|
||||||
for mode in "1", "P":
|
@pytest.mark.parametrize(
|
||||||
im = hopper(mode)
|
"mode", ("1", "P", "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16")
|
||||||
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
|
)
|
||||||
|
def test_default_filter_nearest(self, mode):
|
||||||
for mode in "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16":
|
im = hopper(mode)
|
||||||
im = hopper(mode)
|
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
|
||||||
assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
|
@ -22,26 +24,26 @@ def rotate(im, mode, angle, center=None, translate=None):
|
||||||
assert out.size != im.size
|
assert out.size != im.size
|
||||||
|
|
||||||
|
|
||||||
def test_mode():
|
@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
|
||||||
for mode in ("1", "P", "L", "RGB", "I", "F"):
|
def test_mode(mode):
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
rotate(im, mode, 45)
|
rotate(im, mode, 45)
|
||||||
|
|
||||||
|
|
||||||
def test_angle():
|
@pytest.mark.parametrize("angle", (0, 90, 180, 270))
|
||||||
for angle in (0, 90, 180, 270):
|
def test_angle(angle):
|
||||||
with Image.open("Tests/images/test-card.png") as im:
|
with Image.open("Tests/images/test-card.png") as im:
|
||||||
rotate(im, im.mode, angle)
|
|
||||||
|
|
||||||
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))
|
|
||||||
rotate(im, im.mode, angle)
|
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():
|
def test_resample():
|
||||||
# Target image creation, inspected by eye.
|
# Target image creation, inspected by eye.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL.Image import Transpose
|
from PIL.Image import Transpose
|
||||||
|
|
||||||
from . import helper
|
from . import helper
|
||||||
|
@ -9,157 +11,136 @@ HOPPER = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_flip_left_right():
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def transpose(mode):
|
def test_flip_left_right(mode):
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.FLIP_LEFT_RIGHT)
|
out = im.transpose(Transpose.FLIP_LEFT_RIGHT)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
assert out.size == im.size
|
assert out.size == im.size
|
||||||
|
|
||||||
x, y = im.size
|
x, y = im.size
|
||||||
assert im.getpixel((1, 1)) == out.getpixel((x - 2, 1))
|
assert im.getpixel((1, 1)) == out.getpixel((x - 2, 1))
|
||||||
assert im.getpixel((x - 2, 1)) == out.getpixel((1, 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((1, y - 2)) == out.getpixel((x - 2, y - 2))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, y - 2))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, y - 2))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_flip_top_bottom():
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def transpose(mode):
|
def test_flip_top_bottom(mode):
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.FLIP_TOP_BOTTOM)
|
out = im.transpose(Transpose.FLIP_TOP_BOTTOM)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
assert out.size == im.size
|
assert out.size == im.size
|
||||||
|
|
||||||
x, y = im.size
|
x, y = im.size
|
||||||
assert im.getpixel((1, 1)) == out.getpixel((1, y - 2))
|
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((x - 2, 1)) == out.getpixel((x - 2, y - 2))
|
||||||
assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
|
assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((x - 2, 1))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((x - 2, 1))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_rotate_90():
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def transpose(mode):
|
def test_rotate_90(mode):
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.ROTATE_90)
|
out = im.transpose(Transpose.ROTATE_90)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
assert out.size == im.size[::-1]
|
assert out.size == im.size[::-1]
|
||||||
|
|
||||||
x, y = im.size
|
x, y = im.size
|
||||||
assert im.getpixel((1, 1)) == out.getpixel((1, x - 2))
|
assert im.getpixel((1, 1)) == out.getpixel((1, x - 2))
|
||||||
assert im.getpixel((x - 2, 1)) == out.getpixel((1, 1))
|
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((1, y - 2)) == out.getpixel((y - 2, x - 2))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, 1))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, 1))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_rotate_180():
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def transpose(mode):
|
def test_rotate_180(mode):
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.ROTATE_180)
|
out = im.transpose(Transpose.ROTATE_180)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
assert out.size == im.size
|
assert out.size == im.size
|
||||||
|
|
||||||
x, y = im.size
|
x, y = im.size
|
||||||
assert im.getpixel((1, 1)) == out.getpixel((x - 2, y - 2))
|
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((x - 2, 1)) == out.getpixel((1, y - 2))
|
||||||
assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, 1))
|
assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, 1))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_rotate_270():
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def transpose(mode):
|
def test_rotate_270(mode):
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.ROTATE_270)
|
out = im.transpose(Transpose.ROTATE_270)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
assert out.size == im.size[::-1]
|
assert out.size == im.size[::-1]
|
||||||
|
|
||||||
x, y = im.size
|
x, y = im.size
|
||||||
assert im.getpixel((1, 1)) == out.getpixel((y - 2, 1))
|
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((x - 2, 1)) == out.getpixel((y - 2, x - 2))
|
||||||
assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
|
assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, x - 2))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, x - 2))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_transpose():
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def transpose(mode):
|
def test_transpose(mode):
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.TRANSPOSE)
|
out = im.transpose(Transpose.TRANSPOSE)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
assert out.size == im.size[::-1]
|
assert out.size == im.size[::-1]
|
||||||
|
|
||||||
x, y = im.size
|
x, y = im.size
|
||||||
assert im.getpixel((1, 1)) == out.getpixel((1, 1))
|
assert im.getpixel((1, 1)) == out.getpixel((1, 1))
|
||||||
assert im.getpixel((x - 2, 1)) == out.getpixel((1, x - 2))
|
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((1, y - 2)) == out.getpixel((y - 2, 1))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, x - 2))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, x - 2))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_tranverse():
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
def transpose(mode):
|
def test_tranverse(mode):
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
out = im.transpose(Transpose.TRANSVERSE)
|
out = im.transpose(Transpose.TRANSVERSE)
|
||||||
assert out.mode == mode
|
assert out.mode == mode
|
||||||
assert out.size == im.size[::-1]
|
assert out.size == im.size[::-1]
|
||||||
|
|
||||||
x, y = im.size
|
x, y = im.size
|
||||||
assert im.getpixel((1, 1)) == out.getpixel((y - 2, x - 2))
|
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((x - 2, 1)) == out.getpixel((y - 2, 1))
|
||||||
assert im.getpixel((1, y - 2)) == out.getpixel((1, x - 2))
|
assert im.getpixel((1, y - 2)) == out.getpixel((1, x - 2))
|
||||||
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
|
assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
|
||||||
|
|
||||||
for mode in HOPPER:
|
|
||||||
transpose(mode)
|
|
||||||
|
|
||||||
|
|
||||||
def test_roundtrip():
|
@pytest.mark.parametrize("mode", HOPPER)
|
||||||
for mode in HOPPER:
|
def test_roundtrip(mode):
|
||||||
im = HOPPER[mode]
|
im = HOPPER[mode]
|
||||||
|
|
||||||
def transpose(first, second):
|
def transpose(first, second):
|
||||||
return im.transpose(first).transpose(second)
|
return im.transpose(first).transpose(second)
|
||||||
|
|
||||||
assert_image_equal(
|
assert_image_equal(
|
||||||
im, transpose(Transpose.FLIP_LEFT_RIGHT, Transpose.FLIP_LEFT_RIGHT)
|
im, transpose(Transpose.FLIP_LEFT_RIGHT, Transpose.FLIP_LEFT_RIGHT)
|
||||||
)
|
)
|
||||||
assert_image_equal(
|
assert_image_equal(
|
||||||
im, transpose(Transpose.FLIP_TOP_BOTTOM, Transpose.FLIP_TOP_BOTTOM)
|
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_90, Transpose.ROTATE_270))
|
||||||
assert_image_equal(im, transpose(Transpose.ROTATE_180, Transpose.ROTATE_180))
|
assert_image_equal(im, transpose(Transpose.ROTATE_180, Transpose.ROTATE_180))
|
||||||
assert_image_equal(
|
assert_image_equal(
|
||||||
im.transpose(Transpose.TRANSPOSE),
|
im.transpose(Transpose.TRANSPOSE),
|
||||||
transpose(Transpose.ROTATE_90, Transpose.FLIP_TOP_BOTTOM),
|
transpose(Transpose.ROTATE_90, Transpose.FLIP_TOP_BOTTOM),
|
||||||
)
|
)
|
||||||
assert_image_equal(
|
assert_image_equal(
|
||||||
im.transpose(Transpose.TRANSPOSE),
|
im.transpose(Transpose.TRANSPOSE),
|
||||||
transpose(Transpose.ROTATE_270, Transpose.FLIP_LEFT_RIGHT),
|
transpose(Transpose.ROTATE_270, Transpose.FLIP_LEFT_RIGHT),
|
||||||
)
|
)
|
||||||
assert_image_equal(
|
assert_image_equal(
|
||||||
im.transpose(Transpose.TRANSVERSE),
|
im.transpose(Transpose.TRANSVERSE),
|
||||||
transpose(Transpose.ROTATE_90, Transpose.FLIP_LEFT_RIGHT),
|
transpose(Transpose.ROTATE_90, Transpose.FLIP_LEFT_RIGHT),
|
||||||
)
|
)
|
||||||
assert_image_equal(
|
assert_image_equal(
|
||||||
im.transpose(Transpose.TRANSVERSE),
|
im.transpose(Transpose.TRANSVERSE),
|
||||||
transpose(Transpose.ROTATE_270, Transpose.FLIP_TOP_BOTTOM),
|
transpose(Transpose.ROTATE_270, Transpose.FLIP_TOP_BOTTOM),
|
||||||
)
|
)
|
||||||
assert_image_equal(
|
assert_image_equal(
|
||||||
im.transpose(Transpose.TRANSVERSE),
|
im.transpose(Transpose.TRANSVERSE),
|
||||||
transpose(Transpose.ROTATE_180, Transpose.TRANSPOSE),
|
transpose(Transpose.ROTATE_180, Transpose.TRANSPOSE),
|
||||||
)
|
)
|
||||||
|
|
|
@ -625,20 +625,20 @@ def test_polygon2():
|
||||||
helper_polygon(POINTS2)
|
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
|
# Test drawing lines of different gradients (dx>dy, dy>dx) and
|
||||||
# vertical (dx==0) and horizontal (dy==0) lines
|
# vertical (dx==0) and horizontal (dy==0) lines
|
||||||
for mode in ["RGB", "L"]:
|
# Arrange
|
||||||
# Arrange
|
im = Image.new(mode, (W, H))
|
||||||
im = Image.new(mode, (W, H))
|
draw = ImageDraw.Draw(im)
|
||||||
draw = ImageDraw.Draw(im)
|
expected = f"Tests/images/imagedraw_polygon_kite_{mode}.png"
|
||||||
expected = f"Tests/images/imagedraw_polygon_kite_{mode}.png"
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.polygon(KITE_POINTS, fill="blue", outline="yellow")
|
draw.polygon(KITE_POINTS, fill="blue", outline="yellow")
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im, expected)
|
assert_image_equal_tofile(im, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_polygon_1px_high():
|
def test_polygon_1px_high():
|
||||||
|
@ -1314,6 +1314,23 @@ def test_stroke_multiline():
|
||||||
assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_setting_default_font():
|
||||||
|
# Arrange
|
||||||
|
im = Image.new("RGB", (100, 250))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
ImageDraw.ImageDraw.font = font
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
try:
|
||||||
|
assert draw.getfont() == font
|
||||||
|
finally:
|
||||||
|
ImageDraw.ImageDraw.font = None
|
||||||
|
assert isinstance(draw.getfont(), ImageFont.ImageFont)
|
||||||
|
|
||||||
|
|
||||||
def test_same_color_outline():
|
def test_same_color_outline():
|
||||||
# Prepare shape
|
# Prepare shape
|
||||||
x0, y0 = 5, 5
|
x0, y0 = 5, 5
|
||||||
|
|
|
@ -16,32 +16,32 @@ if ImageQt.qt_is_installed:
|
||||||
from PIL.ImageQt import QImage
|
from PIL.ImageQt import QImage
|
||||||
|
|
||||||
|
|
||||||
def test_sanity(tmp_path):
|
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "L", "P", "1"))
|
||||||
for mode in ("RGB", "RGBA", "L", "P", "1"):
|
def test_sanity(mode, tmp_path):
|
||||||
src = hopper(mode)
|
src = hopper(mode)
|
||||||
data = ImageQt.toqimage(src)
|
data = ImageQt.toqimage(src)
|
||||||
|
|
||||||
assert isinstance(data, QImage)
|
assert isinstance(data, QImage)
|
||||||
assert not data.isNull()
|
assert not data.isNull()
|
||||||
|
|
||||||
# reload directly from the qimage
|
# reload directly from the qimage
|
||||||
rt = ImageQt.fromqimage(data)
|
rt = ImageQt.fromqimage(data)
|
||||||
if mode in ("L", "P", "1"):
|
if mode in ("L", "P", "1"):
|
||||||
assert_image_equal(rt, src.convert("RGB"))
|
assert_image_equal(rt, src.convert("RGB"))
|
||||||
else:
|
else:
|
||||||
assert_image_equal(rt, src)
|
assert_image_equal(rt, src)
|
||||||
|
|
||||||
if mode == "1":
|
if mode == "1":
|
||||||
# BW appears to not save correctly on QT4 and QT5
|
# BW appears to not save correctly on QT5
|
||||||
# kicks out errors on console:
|
# kicks out errors on console:
|
||||||
# libpng warning: Invalid color type/bit depth combination
|
# libpng warning: Invalid color type/bit depth combination
|
||||||
# in IHDR
|
# in IHDR
|
||||||
# libpng error: Invalid IHDR data
|
# libpng error: Invalid IHDR data
|
||||||
continue
|
return
|
||||||
|
|
||||||
# Test saving the file
|
# Test saving the file
|
||||||
tempfile = str(tmp_path / f"temp_{mode}.png")
|
tempfile = str(tmp_path / f"temp_{mode}.png")
|
||||||
data.save(tempfile)
|
data.save(tempfile)
|
||||||
|
|
||||||
# Check that it actually worked.
|
# Check that it actually worked.
|
||||||
assert_image_equal_tofile(src, tempfile)
|
assert_image_equal_tofile(src, tempfile)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install libimagequant
|
# install libimagequant
|
||||||
|
|
||||||
archive=libimagequant-4.0.1
|
archive=libimagequant-4.0.4
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||||
|
|
||||||
|
|
|
@ -968,7 +968,7 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
|
||||||
methods are: :data:`None`, ``"group3"``, ``"group4"``, ``"jpeg"``, ``"lzma"``,
|
methods are: :data:`None`, ``"group3"``, ``"group4"``, ``"jpeg"``, ``"lzma"``,
|
||||||
``"packbits"``, ``"tiff_adobe_deflate"``, ``"tiff_ccitt"``, ``"tiff_lzw"``,
|
``"packbits"``, ``"tiff_adobe_deflate"``, ``"tiff_ccitt"``, ``"tiff_lzw"``,
|
||||||
``"tiff_raw_16"``, ``"tiff_sgilog"``, ``"tiff_sgilog24"``, ``"tiff_thunderscan"``,
|
``"tiff_raw_16"``, ``"tiff_sgilog"``, ``"tiff_sgilog24"``, ``"tiff_thunderscan"``,
|
||||||
``"webp"`, ``"zstd"``
|
``"webp"``, ``"zstd"``
|
||||||
|
|
||||||
**quality**
|
**quality**
|
||||||
The image quality for JPEG compression, on a scale from 0 (worst) to 100
|
The image quality for JPEG compression, on a scale from 0 (worst) to 100
|
||||||
|
|
|
@ -166,7 +166,7 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
* **libimagequant** provides improved color quantization
|
* **libimagequant** provides improved color quantization
|
||||||
|
|
||||||
* Pillow has been tested with libimagequant **2.6-4.0.1**
|
* Pillow has been tested with libimagequant **2.6-4.0.4**
|
||||||
* Libimagequant is licensed GPLv3, which is more restrictive than
|
* Libimagequant is licensed GPLv3, which is more restrictive than
|
||||||
the Pillow license, therefore we will not be distributing binaries
|
the Pillow license, therefore we will not be distributing binaries
|
||||||
with libimagequant support enabled.
|
with libimagequant support enabled.
|
||||||
|
@ -367,7 +367,7 @@ In Alpine, the command is::
|
||||||
|
|
||||||
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
|
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
|
||||||
|
|
||||||
Prerequisites for **Ubuntu 16.04 LTS - 20.04 LTS** are installed with::
|
Prerequisites for **Ubuntu 16.04 LTS - 22.04 LTS** are installed with::
|
||||||
|
|
||||||
sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \
|
sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \
|
||||||
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
|
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
|
||||||
|
|
|
@ -64,7 +64,7 @@ Fonts
|
||||||
|
|
||||||
PIL can use bitmap fonts or OpenType/TrueType fonts.
|
PIL can use bitmap fonts or OpenType/TrueType fonts.
|
||||||
|
|
||||||
Bitmap fonts are stored in PIL’s own format, where each font typically consists
|
Bitmap fonts are stored in PIL's own format, where each font typically consists
|
||||||
of two files, one named .pil and the other usually named .pbm. The former
|
of two files, one named .pil and the other usually named .pbm. The former
|
||||||
contains font metrics, the latter raster data.
|
contains font metrics, the latter raster data.
|
||||||
|
|
||||||
|
@ -146,6 +146,11 @@ Methods
|
||||||
|
|
||||||
Get the current default font.
|
Get the current default font.
|
||||||
|
|
||||||
|
To set the default font for all future ImageDraw instances::
|
||||||
|
|
||||||
|
from PIL import ImageDraw, ImageFont
|
||||||
|
ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")
|
||||||
|
|
||||||
:returns: An image font.
|
:returns: An image font.
|
||||||
|
|
||||||
.. py:method:: ImageDraw.arc(xy, start, end, fill=None, width=0)
|
.. py:method:: ImageDraw.arc(xy, start, end, fill=None, width=0)
|
||||||
|
|
|
@ -26,6 +26,16 @@ TODO
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
Allow default ImageDraw font to be set
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Rather than specifying a font when calling text-related ImageDraw methods, or
|
||||||
|
setting a font on each ImageDraw instance, the default font can now be set for
|
||||||
|
all future ImageDraw operations::
|
||||||
|
|
||||||
|
from PIL import ImageDraw, ImageFont
|
||||||
|
ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")
|
||||||
|
|
||||||
Saving multiple MPO frames
|
Saving multiple MPO frames
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -288,11 +288,14 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
# Encoded bitmapped image.
|
# Encoded bitmapped image.
|
||||||
x, y, bi, mo = s[11:].split(None, 7)[:4]
|
x, y, bi, mo = s[11:].split(None, 7)[:4]
|
||||||
|
|
||||||
if int(bi) != 8:
|
if int(bi) == 1:
|
||||||
break
|
self.mode = "1"
|
||||||
try:
|
elif int(bi) == 8:
|
||||||
self.mode = self.mode_map[int(mo)]
|
try:
|
||||||
except ValueError:
|
self.mode = self.mode_map[int(mo)]
|
||||||
|
except ValueError:
|
||||||
|
break
|
||||||
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
self._size = int(x), int(y)
|
self._size = int(x), int(y)
|
||||||
|
|
|
@ -46,6 +46,8 @@ directly.
|
||||||
|
|
||||||
|
|
||||||
class ImageDraw:
|
class ImageDraw:
|
||||||
|
font = None
|
||||||
|
|
||||||
def __init__(self, im, mode=None):
|
def __init__(self, im, mode=None):
|
||||||
"""
|
"""
|
||||||
Create a drawing instance.
|
Create a drawing instance.
|
||||||
|
@ -86,12 +88,16 @@ class ImageDraw:
|
||||||
else:
|
else:
|
||||||
self.fontmode = "L" # aliasing is okay for other modes
|
self.fontmode = "L" # aliasing is okay for other modes
|
||||||
self.fill = 0
|
self.fill = 0
|
||||||
self.font = None
|
|
||||||
|
|
||||||
def getfont(self):
|
def getfont(self):
|
||||||
"""
|
"""
|
||||||
Get the current default font.
|
Get the current default font.
|
||||||
|
|
||||||
|
To set the default font for all future ImageDraw instances::
|
||||||
|
|
||||||
|
from PIL import ImageDraw, ImageFont
|
||||||
|
ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")
|
||||||
|
|
||||||
:returns: An image font."""
|
:returns: An image font."""
|
||||||
if not self.font:
|
if not self.font:
|
||||||
# FIXME: should add a font repository
|
# FIXME: should add a font repository
|
||||||
|
|
|
@ -192,6 +192,9 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
with open(self.filename) as fp:
|
with open(self.filename) as fp:
|
||||||
self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
|
self.map = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
|
||||||
|
if offset + self.size[1] * args[1] > self.map.size():
|
||||||
|
# buffer is not large enough
|
||||||
|
raise OSError
|
||||||
self.im = Image.core.map_buffer(
|
self.im = Image.core.map_buffer(
|
||||||
self.map, self.size, decoder_name, offset, args
|
self.map, self.size, decoder_name, offset, args
|
||||||
)
|
)
|
||||||
|
|
|
@ -68,21 +68,18 @@ def _pyimagingtkcall(command, photo, id):
|
||||||
# may raise an error if it cannot attach to Tkinter
|
# may raise an error if it cannot attach to Tkinter
|
||||||
from . import _imagingtk
|
from . import _imagingtk
|
||||||
|
|
||||||
try:
|
if hasattr(tk, "interp"):
|
||||||
if hasattr(tk, "interp"):
|
# Required for PyPy, which always has CFFI installed
|
||||||
# Required for PyPy, which always has CFFI installed
|
from cffi import FFI
|
||||||
from cffi import FFI
|
|
||||||
|
|
||||||
ffi = FFI()
|
ffi = FFI()
|
||||||
|
|
||||||
# PyPy is using an FFI CDATA element
|
# PyPy is using an FFI CDATA element
|
||||||
# (Pdb) self.tk.interp
|
# (Pdb) self.tk.interp
|
||||||
# <cdata 'Tcl_Interp *' 0x3061b50>
|
# <cdata 'Tcl_Interp *' 0x3061b50>
|
||||||
_imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)), 1)
|
_imagingtk.tkinit(int(ffi.cast("uintptr_t", tk.interp)))
|
||||||
else:
|
else:
|
||||||
_imagingtk.tkinit(tk.interpaddr(), 1)
|
_imagingtk.tkinit(tk.interpaddr())
|
||||||
except AttributeError:
|
|
||||||
_imagingtk.tkinit(id(tk), 0)
|
|
||||||
tk.call(command, photo, id)
|
tk.call(command, photo, id)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -727,7 +727,9 @@ class ImageFileDirectory_v2(MutableMapping):
|
||||||
@_register_writer(2)
|
@_register_writer(2)
|
||||||
def write_string(self, value):
|
def write_string(self, value):
|
||||||
# remerge of https://github.com/python-pillow/Pillow/pull/1416
|
# remerge of https://github.com/python-pillow/Pillow/pull/1416
|
||||||
return b"" + value.encode("ascii", "replace") + b"\0"
|
if not isinstance(value, bytes):
|
||||||
|
value = value.encode("ascii", "replace")
|
||||||
|
return value + b"\0"
|
||||||
|
|
||||||
@_register_loader(5, 8)
|
@_register_loader(5, 8)
|
||||||
def load_rational(self, data, legacy_api=True):
|
def load_rational(self, data, legacy_api=True):
|
||||||
|
|
|
@ -23,33 +23,16 @@ TkImaging_Init(Tcl_Interp *interp);
|
||||||
extern int
|
extern int
|
||||||
load_tkinter_funcs(void);
|
load_tkinter_funcs(void);
|
||||||
|
|
||||||
/* copied from _tkinter.c (this isn't as bad as it may seem: for new
|
|
||||||
versions, we use _tkinter's interpaddr hook instead, and all older
|
|
||||||
versions use this structure layout) */
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PyObject_HEAD Tcl_Interp *interp;
|
|
||||||
} TkappObject;
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_tkinit(PyObject *self, PyObject *args) {
|
_tkinit(PyObject *self, PyObject *args) {
|
||||||
Tcl_Interp *interp;
|
Tcl_Interp *interp;
|
||||||
|
|
||||||
PyObject *arg;
|
PyObject *arg;
|
||||||
int is_interp;
|
if (!PyArg_ParseTuple(args, "O", &arg)) {
|
||||||
if (!PyArg_ParseTuple(args, "Oi", &arg, &is_interp)) {
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_interp) {
|
interp = (Tcl_Interp *)PyLong_AsVoidPtr(arg);
|
||||||
interp = (Tcl_Interp *)PyLong_AsVoidPtr(arg);
|
|
||||||
} else {
|
|
||||||
TkappObject *app;
|
|
||||||
/* Do it the hard way. This will break if the TkappObject
|
|
||||||
layout changes */
|
|
||||||
app = (TkappObject *)PyLong_AsVoidPtr(arg);
|
|
||||||
interp = app->interp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This will bomb if interp is invalid... */
|
/* This will bomb if interp is invalid... */
|
||||||
TkImaging_Init(interp);
|
TkImaging_Init(interp);
|
||||||
|
|
|
@ -1026,6 +1026,14 @@ pa2l(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pa2p(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
|
||||||
|
int x;
|
||||||
|
for (x = 0; x < xsize; x++, in += 4) {
|
||||||
|
*out++ = in[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
|
p2pa(UINT8 *out, const UINT8 *in, int xsize, ImagingPalette palette) {
|
||||||
int x;
|
int x;
|
||||||
|
@ -1209,6 +1217,8 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
|
||||||
convert = alpha ? pa2l : p2l;
|
convert = alpha ? pa2l : p2l;
|
||||||
} else if (strcmp(mode, "LA") == 0) {
|
} else if (strcmp(mode, "LA") == 0) {
|
||||||
convert = alpha ? pa2la : p2la;
|
convert = alpha ? pa2la : p2la;
|
||||||
|
} else if (strcmp(mode, "P") == 0) {
|
||||||
|
convert = pa2p;
|
||||||
} else if (strcmp(mode, "PA") == 0) {
|
} else if (strcmp(mode, "PA") == 0) {
|
||||||
convert = p2pa;
|
convert = p2pa;
|
||||||
} else if (strcmp(mode, "I") == 0) {
|
} else if (strcmp(mode, "I") == 0) {
|
||||||
|
@ -1233,6 +1243,10 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
|
||||||
if (!imOut) {
|
if (!imOut) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
if (strcmp(mode, "P") == 0) {
|
||||||
|
ImagingPaletteDelete(imOut->palette);
|
||||||
|
imOut->palette = ImagingPaletteDuplicate(imIn->palette);
|
||||||
|
}
|
||||||
|
|
||||||
ImagingSectionEnter(&cookie);
|
ImagingSectionEnter(&cookie);
|
||||||
for (y = 0; y < imIn->ysize; y++) {
|
for (y = 0; y < imIn->ysize; y++) {
|
||||||
|
|
|
@ -916,7 +916,7 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
|
||||||
dump_state(clientstate);
|
dump_state(clientstate);
|
||||||
|
|
||||||
if (state->state == 0) {
|
if (state->state == 0) {
|
||||||
TRACE(("Encoding line bt line"));
|
TRACE(("Encoding line by line"));
|
||||||
while (state->y < state->ysize) {
|
while (state->y < state->ysize) {
|
||||||
state->shuffle(
|
state->shuffle(
|
||||||
state->buffer,
|
state->buffer,
|
||||||
|
|
|
@ -11,8 +11,8 @@ For more extensive info, see the [Windows build instructions](build.rst).
|
||||||
* Requires Microsoft Visual Studio 2017 or newer with C++ component.
|
* Requires Microsoft Visual Studio 2017 or newer with C++ component.
|
||||||
* Requires NASM for libjpeg-turbo, a required dependency when using this script.
|
* Requires NASM for libjpeg-turbo, a required dependency when using this script.
|
||||||
* Requires CMake 3.12 or newer (available as Visual Studio component).
|
* Requires CMake 3.12 or newer (available as Visual Studio component).
|
||||||
* Tested on Windows Server 2016 with Visual Studio 2017 Community (AppVeyor).
|
* Tested on Windows Server 2016 with Visual Studio 2017 Community, and Windows Server 2019 with Visual Studio 2022 Community (AppVeyor).
|
||||||
* Tested on Windows Server 2019 with Visual Studio 2019 Enterprise (GitHub Actions).
|
* Tested on Windows Server 2022 with Visual Studio 2022 Enterprise (GitHub Actions).
|
||||||
|
|
||||||
The following is a simplified version of the script used on AppVeyor:
|
The following is a simplified version of the script used on AppVeyor:
|
||||||
```
|
```
|
||||||
|
|
|
@ -226,21 +226,21 @@ deps = {
|
||||||
"filename": "lcms2-2.13.1.tar.gz",
|
"filename": "lcms2-2.13.1.tar.gz",
|
||||||
"dir": "lcms2-2.13.1",
|
"dir": "lcms2-2.13.1",
|
||||||
"patch": {
|
"patch": {
|
||||||
r"Projects\VC2019\lcms2_static\lcms2_static.vcxproj": {
|
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
|
||||||
# default is /MD for x86 and /MT for x64, we need /MD always
|
# default is /MD for x86 and /MT for x64, we need /MD always
|
||||||
"<RuntimeLibrary>MultiThreaded</RuntimeLibrary>": "<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>", # noqa: E501
|
"<RuntimeLibrary>MultiThreaded</RuntimeLibrary>": "<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>", # noqa: E501
|
||||||
# retarget to default toolset (selected by vcvarsall.bat)
|
# retarget to default toolset (selected by vcvarsall.bat)
|
||||||
"<PlatformToolset>v142</PlatformToolset>": "<PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>", # noqa: E501
|
"<PlatformToolset>v143</PlatformToolset>": "<PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>", # noqa: E501
|
||||||
# retarget to latest (selected by vcvarsall.bat)
|
# retarget to latest (selected by vcvarsall.bat)
|
||||||
"<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>": "<WindowsTargetPlatformVersion>$(WindowsSDKVersion)</WindowsTargetPlatformVersion>", # noqa: E501
|
"<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>": "<WindowsTargetPlatformVersion>$(WindowsSDKVersion)</WindowsTargetPlatformVersion>", # noqa: E501
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"build": [
|
"build": [
|
||||||
cmd_rmdir("Lib"),
|
cmd_rmdir("Lib"),
|
||||||
cmd_rmdir(r"Projects\VC2019\Release"),
|
cmd_rmdir(r"Projects\VC2022\Release"),
|
||||||
cmd_msbuild(r"Projects\VC2019\lcms2.sln", "Release", "Clean"),
|
cmd_msbuild(r"Projects\VC2022\lcms2.sln", "Release", "Clean"),
|
||||||
cmd_msbuild(
|
cmd_msbuild(
|
||||||
r"Projects\VC2019\lcms2.sln", "Release", "lcms2_static:Rebuild"
|
r"Projects\VC2022\lcms2.sln", "Release", "lcms2_static:Rebuild"
|
||||||
),
|
),
|
||||||
cmd_xcopy("include", "{inc_dir}"),
|
cmd_xcopy("include", "{inc_dir}"),
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue
Block a user