Merge branch 'main' into pyaccess_pa

This commit is contained in:
Andrew Murray 2022-08-31 20:56:38 +10:00
commit 2eca2989a2
45 changed files with 1147 additions and 1082 deletions

View File

@ -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

View File

@ -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

View File

@ -12,11 +12,9 @@ python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov python3 -m pip install -U pytest-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

View File

@ -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

View File

@ -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

Binary file not shown.

BIN
Tests/images/mmap_error.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -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):

View File

@ -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()

View File

@ -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

View File

@ -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")

View File

@ -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):

View File

@ -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):

View File

@ -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():

View File

@ -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"

View File

@ -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):

View File

@ -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]

View File

@ -66,10 +66,10 @@ def test_load_set_dpi():
assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1) 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)

View File

@ -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"

View File

@ -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")

View File

@ -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():

View File

@ -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():

View File

@ -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():

View File

@ -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))

View File

@ -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

View File

@ -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))

View File

@ -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.

View File

@ -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),
) )

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 \

View File

@ -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 PILs 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)

View File

@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -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)

View File

@ -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

View File

@ -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
) )

View File

@ -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)

View File

@ -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):

View File

@ -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);

View File

@ -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++) {

View File

@ -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,

View File

@ -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:
``` ```

View File

@ -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}"),
], ],