diff --git a/.ci/install.sh b/.ci/install.sh
index 16a056dd5..7ead209be 100755
--- a/.ci/install.sh
+++ b/.ci/install.sh
@@ -35,7 +35,6 @@ python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
-python3 -m pip install test-image-results
if [[ $(uname) != CYGWIN* ]]; then
# TODO Remove condition when NumPy supports 3.11
diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh
index 06b829645..bb0bcd680 100755
--- a/.github/workflows/macos-install.sh
+++ b/.github/workflows/macos-install.sh
@@ -12,7 +12,6 @@ python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
-python3 -m pip install test-image-results
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
# TODO Remove condition when NumPy supports 3.11
diff --git a/CHANGES.rst b/CHANGES.rst
index 5f99d9d25..fb634eaba 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -5,6 +5,9 @@ Changelog (Pillow)
9.3.0 (unreleased)
------------------
+- Allow default ImageDraw font to be set #6484
+ [radarhere, hugovk]
+
- Save 1 mode PDF using CCITTFaxDecode filter #6470
[radarhere]
diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py
index ad61a07cc..0ff05f608 100644
--- a/Tests/test_file_apng.py
+++ b/Tests/test_file_apng.py
@@ -325,8 +325,9 @@ def test_apng_syntax_errors():
pytest.warns(UserWarning, open)
-def test_apng_sequence_errors():
- test_files = [
+@pytest.mark.parametrize(
+ "test_file",
+ (
"sequence_start.png",
"sequence_gap.png",
"sequence_repeat.png",
@@ -334,12 +335,13 @@ def test_apng_sequence_errors():
"sequence_reorder.png",
"sequence_reorder_chunk.png",
"sequence_fdat_fctl.png",
- ]
- for f in test_files:
- with pytest.raises(SyntaxError):
- with Image.open(f"Tests/images/apng/{f}") as im:
- im.seek(im.n_frames - 1)
- im.load()
+ ),
+)
+def test_apng_sequence_errors(test_file):
+ with pytest.raises(SyntaxError):
+ with Image.open(f"Tests/images/apng/{test_file}") as im:
+ im.seek(im.n_frames - 1)
+ im.load()
def test_apng_save(tmp_path):
diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py
index b752e217f..65cf6a75e 100644
--- a/Tests/test_file_container.py
+++ b/Tests/test_file_container.py
@@ -1,3 +1,5 @@
+import pytest
+
from PIL import ContainerIO, Image
from .helper import hopper
@@ -59,89 +61,89 @@ def test_seek_mode_2():
assert container.tell() == 100
-def test_read_n0():
+@pytest.mark.parametrize("bytesmode", (True, False))
+def test_read_n0(bytesmode):
# Arrange
- for bytesmode in (True, False):
- with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
- container = ContainerIO.ContainerIO(fh, 22, 100)
+ with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
+ container = ContainerIO.ContainerIO(fh, 22, 100)
- # Act
- container.seek(81)
- data = container.read()
+ # Act
+ container.seek(81)
+ data = container.read()
- # Assert
- if bytesmode:
- data = data.decode()
- assert data == "7\nThis is line 8\n"
+ # Assert
+ if bytesmode:
+ data = data.decode()
+ assert data == "7\nThis is line 8\n"
-def test_read_n():
+@pytest.mark.parametrize("bytesmode", (True, False))
+def test_read_n(bytesmode):
# Arrange
- for bytesmode in (True, False):
- with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
- container = ContainerIO.ContainerIO(fh, 22, 100)
+ with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
+ container = ContainerIO.ContainerIO(fh, 22, 100)
- # Act
- container.seek(81)
- data = container.read(3)
+ # Act
+ container.seek(81)
+ data = container.read(3)
- # Assert
- if bytesmode:
- data = data.decode()
- assert data == "7\nT"
+ # Assert
+ if bytesmode:
+ data = data.decode()
+ assert data == "7\nT"
-def test_read_eof():
+@pytest.mark.parametrize("bytesmode", (True, False))
+def test_read_eof(bytesmode):
# Arrange
- for bytesmode in (True, False):
- with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
- container = ContainerIO.ContainerIO(fh, 22, 100)
+ with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
+ container = ContainerIO.ContainerIO(fh, 22, 100)
- # Act
- container.seek(100)
- data = container.read()
+ # Act
+ container.seek(100)
+ data = container.read()
- # Assert
- if bytesmode:
- data = data.decode()
- assert data == ""
+ # Assert
+ if bytesmode:
+ data = data.decode()
+ assert data == ""
-def test_readline():
+@pytest.mark.parametrize("bytesmode", (True, False))
+def test_readline(bytesmode):
# Arrange
- for bytesmode in (True, False):
- with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
- container = ContainerIO.ContainerIO(fh, 0, 120)
+ with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
+ container = ContainerIO.ContainerIO(fh, 0, 120)
- # Act
- data = container.readline()
+ # Act
+ data = container.readline()
- # Assert
- if bytesmode:
- data = data.decode()
- assert data == "This is line 1\n"
+ # Assert
+ if bytesmode:
+ data = data.decode()
+ assert data == "This is line 1\n"
-def test_readlines():
+@pytest.mark.parametrize("bytesmode", (True, False))
+def test_readlines(bytesmode):
# Arrange
- for bytesmode in (True, False):
- expected = [
- "This is line 1\n",
- "This is line 2\n",
- "This is line 3\n",
- "This is line 4\n",
- "This is line 5\n",
- "This is line 6\n",
- "This is line 7\n",
- "This is line 8\n",
- ]
- with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
- container = ContainerIO.ContainerIO(fh, 0, 120)
+ expected = [
+ "This is line 1\n",
+ "This is line 2\n",
+ "This is line 3\n",
+ "This is line 4\n",
+ "This is line 5\n",
+ "This is line 6\n",
+ "This is line 7\n",
+ "This is line 8\n",
+ ]
+ with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
+ container = ContainerIO.ContainerIO(fh, 0, 120)
- # Act
- data = container.readlines()
+ # Act
+ data = container.readlines()
- # Assert
- if bytesmode:
- data = [line.decode() for line in data]
- assert data == expected
+ # Assert
+ if bytesmode:
+ data = [line.decode() for line in data]
+ assert data == expected
diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py
index 675210c30..e458a197c 100644
--- a/Tests/test_file_im.py
+++ b/Tests/test_file_im.py
@@ -78,15 +78,12 @@ def test_eoferror():
im.seek(n_frames - 1)
-def test_roundtrip(tmp_path):
- def roundtrip(mode):
- out = str(tmp_path / "temp.im")
- im = hopper(mode)
- im.save(out)
- assert_image_equal_tofile(im, out)
-
- for mode in ["RGB", "P", "PA"]:
- roundtrip(mode)
+@pytest.mark.parametrize("mode", ("RGB", "P", "PA"))
+def test_roundtrip(mode, tmp_path):
+ out = str(tmp_path / "temp.im")
+ im = hopper(mode)
+ im.save(out)
+ assert_image_equal_tofile(im, out)
def test_save_unsupported_mode(tmp_path):
diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
index f9d8e2826..86a0fda04 100644
--- a/Tests/test_file_libtiff.py
+++ b/Tests/test_file_libtiff.py
@@ -135,50 +135,50 @@ class TestFileLibTiff(LibTiffTestCase):
assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")
- def test_write_metadata(self, tmp_path):
+ @pytest.mark.parametrize("legacy_api", (False, True))
+ def test_write_metadata(self, legacy_api, tmp_path):
"""Test metadata writing through libtiff"""
- for legacy_api in [False, True]:
- f = str(tmp_path / "temp.tiff")
- with Image.open("Tests/images/hopper_g4.tif") as img:
- img.save(f, tiffinfo=img.tag)
+ f = str(tmp_path / "temp.tiff")
+ with Image.open("Tests/images/hopper_g4.tif") as img:
+ img.save(f, tiffinfo=img.tag)
- if legacy_api:
- original = img.tag.named()
- else:
- original = img.tag_v2.named()
+ if legacy_api:
+ original = img.tag.named()
+ else:
+ original = img.tag_v2.named()
- # PhotometricInterpretation is set from SAVE_INFO,
- # not the original image.
- ignored = [
- "StripByteCounts",
- "RowsPerStrip",
- "PageNumber",
- "PhotometricInterpretation",
- ]
+ # PhotometricInterpretation is set from SAVE_INFO,
+ # not the original image.
+ ignored = [
+ "StripByteCounts",
+ "RowsPerStrip",
+ "PageNumber",
+ "PhotometricInterpretation",
+ ]
- with Image.open(f) as loaded:
- if legacy_api:
- reloaded = loaded.tag.named()
- else:
- reloaded = loaded.tag_v2.named()
+ with Image.open(f) as loaded:
+ if legacy_api:
+ reloaded = loaded.tag.named()
+ else:
+ reloaded = loaded.tag_v2.named()
- for tag, value in itertools.chain(reloaded.items(), original.items()):
- if tag not in ignored:
- val = original[tag]
- if tag.endswith("Resolution"):
- if legacy_api:
- assert val[0][0] / val[0][1] == (
- 4294967295 / 113653537
- ), f"{tag} didn't roundtrip"
- else:
- assert val == 37.79000115940079, f"{tag} didn't roundtrip"
+ for tag, value in itertools.chain(reloaded.items(), original.items()):
+ if tag not in ignored:
+ val = original[tag]
+ if tag.endswith("Resolution"):
+ if legacy_api:
+ assert val[0][0] / val[0][1] == (
+ 4294967295 / 113653537
+ ), f"{tag} didn't roundtrip"
else:
- assert val == value, f"{tag} didn't roundtrip"
+ assert val == 37.79000115940079, f"{tag} didn't roundtrip"
+ else:
+ assert val == value, f"{tag} didn't roundtrip"
- # https://github.com/python-pillow/Pillow/issues/1561
- requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"]
- for field in requested_fields:
- assert field in reloaded, f"{field} not in metadata"
+ # https://github.com/python-pillow/Pillow/issues/1561
+ requested_fields = ["StripByteCounts", "RowsPerStrip", "StripOffsets"]
+ for field in requested_fields:
+ assert field in reloaded, f"{field} not in metadata"
@pytest.mark.valgrind_known_error(reason="Known invalid metadata")
def test_additional_metadata(self, tmp_path):
diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py
index 849857d31..d94bdaa96 100644
--- a/Tests/test_file_mpo.py
+++ b/Tests/test_file_mpo.py
@@ -27,13 +27,13 @@ def roundtrip(im, **options):
return im
-def test_sanity():
- for test_file in test_files:
- with Image.open(test_file) as im:
- im.load()
- assert im.mode == "RGB"
- assert im.size == (640, 480)
- assert im.format == "MPO"
+@pytest.mark.parametrize("test_file", test_files)
+def test_sanity(test_file):
+ with Image.open(test_file) as im:
+ im.load()
+ assert im.mode == "RGB"
+ assert im.size == (640, 480)
+ assert im.format == "MPO"
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
@@ -66,26 +66,25 @@ def test_context_manager():
im.load()
-def test_app():
- for test_file in test_files:
- # Test APP/COM reader (@PIL135)
- with Image.open(test_file) as im:
- assert im.applist[0][0] == "APP1"
- assert im.applist[1][0] == "APP2"
- assert (
- im.applist[1][1][:16]
- == b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00"
- )
- assert len(im.applist) == 2
+@pytest.mark.parametrize("test_file", test_files)
+def test_app(test_file):
+ # Test APP/COM reader (@PIL135)
+ with Image.open(test_file) as im:
+ assert im.applist[0][0] == "APP1"
+ assert im.applist[1][0] == "APP2"
+ assert (
+ im.applist[1][1][:16] == b"MPF\x00MM\x00*\x00\x00\x00\x08\x00\x03\xb0\x00"
+ )
+ assert len(im.applist) == 2
-def test_exif():
- for test_file in test_files:
- with Image.open(test_file) as im:
- info = im._getexif()
- assert info[272] == "Nintendo 3DS"
- assert info[296] == 2
- assert info[34665] == 188
+@pytest.mark.parametrize("test_file", test_files)
+def test_exif(test_file):
+ with Image.open(test_file) as im:
+ info = im._getexif()
+ assert info[272] == "Nintendo 3DS"
+ assert info[296] == 2
+ assert info[34665] == 188
def test_frame_size():
@@ -137,12 +136,12 @@ def test_reload_exif_after_seek():
assert 296 in exif
-def test_mp():
- for test_file in test_files:
- with Image.open(test_file) as im:
- mpinfo = im._getmp()
- assert mpinfo[45056] == b"0100"
- assert mpinfo[45057] == 2
+@pytest.mark.parametrize("test_file", test_files)
+def test_mp(test_file):
+ with Image.open(test_file) as im:
+ mpinfo = im._getmp()
+ assert mpinfo[45056] == b"0100"
+ assert mpinfo[45057] == 2
def test_mp_offset():
@@ -162,48 +161,48 @@ def test_mp_no_data():
im.seek(1)
-def test_mp_attribute():
- for test_file in test_files:
- with Image.open(test_file) as im:
- mpinfo = im._getmp()
- frame_number = 0
- for mpentry in mpinfo[0xB002]:
- mpattr = mpentry["Attribute"]
- if frame_number:
- assert not mpattr["RepresentativeImageFlag"]
- else:
- assert mpattr["RepresentativeImageFlag"]
- assert not mpattr["DependentParentImageFlag"]
- assert not mpattr["DependentChildImageFlag"]
- assert mpattr["ImageDataFormat"] == "JPEG"
- assert mpattr["MPType"] == "Multi-Frame Image: (Disparity)"
- assert mpattr["Reserved"] == 0
- frame_number += 1
+@pytest.mark.parametrize("test_file", test_files)
+def test_mp_attribute(test_file):
+ with Image.open(test_file) as im:
+ mpinfo = im._getmp()
+ frame_number = 0
+ for mpentry in mpinfo[0xB002]:
+ mpattr = mpentry["Attribute"]
+ if frame_number:
+ assert not mpattr["RepresentativeImageFlag"]
+ else:
+ assert mpattr["RepresentativeImageFlag"]
+ assert not mpattr["DependentParentImageFlag"]
+ assert not mpattr["DependentChildImageFlag"]
+ assert mpattr["ImageDataFormat"] == "JPEG"
+ assert mpattr["MPType"] == "Multi-Frame Image: (Disparity)"
+ assert mpattr["Reserved"] == 0
+ frame_number += 1
-def test_seek():
- for test_file in test_files:
- with Image.open(test_file) as im:
- assert im.tell() == 0
- # prior to first image raises an error, both blatant and borderline
- with pytest.raises(EOFError):
- im.seek(-1)
- with pytest.raises(EOFError):
- im.seek(-523)
- # after the final image raises an error,
- # both blatant and borderline
- with pytest.raises(EOFError):
- im.seek(2)
- with pytest.raises(EOFError):
- im.seek(523)
- # bad calls shouldn't change the frame
- assert im.tell() == 0
- # this one will work
- im.seek(1)
- assert im.tell() == 1
- # and this one, too
- im.seek(0)
- assert im.tell() == 0
+@pytest.mark.parametrize("test_file", test_files)
+def test_seek(test_file):
+ with Image.open(test_file) as im:
+ assert im.tell() == 0
+ # prior to first image raises an error, both blatant and borderline
+ with pytest.raises(EOFError):
+ im.seek(-1)
+ with pytest.raises(EOFError):
+ im.seek(-523)
+ # after the final image raises an error,
+ # both blatant and borderline
+ with pytest.raises(EOFError):
+ im.seek(2)
+ with pytest.raises(EOFError):
+ im.seek(523)
+ # bad calls shouldn't change the frame
+ assert im.tell() == 0
+ # this one will work
+ im.seek(1)
+ assert im.tell() == 1
+ # and this one, too
+ im.seek(0)
+ assert im.tell() == 0
def test_n_frames():
@@ -225,31 +224,31 @@ def test_eoferror():
im.seek(n_frames - 1)
-def test_image_grab():
- for test_file in test_files:
- with Image.open(test_file) as im:
- assert im.tell() == 0
- im0 = im.tobytes()
- im.seek(1)
- assert im.tell() == 1
- im1 = im.tobytes()
- im.seek(0)
- assert im.tell() == 0
- im02 = im.tobytes()
- assert im0 == im02
- assert im0 != im1
+@pytest.mark.parametrize("test_file", test_files)
+def test_image_grab(test_file):
+ with Image.open(test_file) as im:
+ assert im.tell() == 0
+ im0 = im.tobytes()
+ im.seek(1)
+ assert im.tell() == 1
+ im1 = im.tobytes()
+ im.seek(0)
+ assert im.tell() == 0
+ im02 = im.tobytes()
+ assert im0 == im02
+ assert im0 != im1
-def test_save():
- for test_file in test_files:
- with Image.open(test_file) as im:
- assert im.tell() == 0
- jpg0 = roundtrip(im)
- assert_image_similar(im, jpg0, 30)
- im.seek(1)
- assert im.tell() == 1
- jpg1 = roundtrip(im)
- assert_image_similar(im, jpg1, 30)
+@pytest.mark.parametrize("test_file", test_files)
+def test_save(test_file):
+ with Image.open(test_file) as im:
+ assert im.tell() == 0
+ jpg0 = roundtrip(im)
+ assert_image_similar(im, jpg0, 30)
+ im.seek(1)
+ assert im.tell() == 1
+ jpg1 = roundtrip(im)
+ assert_image_similar(im, jpg1, 30)
def test_save_all():
diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py
index 310619fb2..df0b7abe6 100644
--- a/Tests/test_file_pdf.py
+++ b/Tests/test_file_pdf.py
@@ -37,6 +37,7 @@ def helper_save_as_pdf(tmp_path, mode, **kwargs):
return outfile
+@pytest.mark.valgrind_known_error(reason="Temporary skip")
def test_monochrome(tmp_path):
# Arrange
mode = "1"
diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py
index 0c8c9f304..cbbb7df1d 100644
--- a/Tests/test_file_tga.py
+++ b/Tests/test_file_tga.py
@@ -18,51 +18,48 @@ _ORIGINS = ("tl", "bl")
_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
-def test_sanity(tmp_path):
- for mode in _MODES:
+@pytest.mark.parametrize("mode", _MODES)
+def test_sanity(mode, tmp_path):
+ def roundtrip(original_im):
+ out = str(tmp_path / "temp.tga")
- def roundtrip(original_im):
- out = str(tmp_path / "temp.tga")
+ original_im.save(out, rle=rle)
+ with Image.open(out) as saved_im:
+ if rle:
+ assert saved_im.info["compression"] == original_im.info["compression"]
+ assert saved_im.info["orientation"] == original_im.info["orientation"]
+ if mode == "P":
+ assert saved_im.getpalette() == original_im.getpalette()
- original_im.save(out, rle=rle)
- with Image.open(out) as saved_im:
- if rle:
+ assert_image_equal(saved_im, original_im)
+
+ png_paths = glob(os.path.join(_TGA_DIR_COMMON, f"*x*_{mode.lower()}.png"))
+
+ for png_path in png_paths:
+ with Image.open(png_path) as reference_im:
+ assert reference_im.mode == mode
+
+ path_no_ext = os.path.splitext(png_path)[0]
+ for origin, rle in product(_ORIGINS, (True, False)):
+ tga_path = "{}_{}_{}.tga".format(
+ path_no_ext, origin, "rle" if rle else "raw"
+ )
+
+ with Image.open(tga_path) as original_im:
+ assert original_im.format == "TGA"
+ assert original_im.get_format_mimetype() == "image/x-tga"
+ if rle:
+ assert original_im.info["compression"] == "tga_rle"
assert (
- saved_im.info["compression"] == original_im.info["compression"]
+ original_im.info["orientation"]
+ == _ORIGIN_TO_ORIENTATION[origin]
)
- assert saved_im.info["orientation"] == original_im.info["orientation"]
- if mode == "P":
- assert saved_im.getpalette() == original_im.getpalette()
+ if mode == "P":
+ assert original_im.getpalette() == reference_im.getpalette()
- assert_image_equal(saved_im, original_im)
+ assert_image_equal(original_im, reference_im)
- png_paths = glob(os.path.join(_TGA_DIR_COMMON, f"*x*_{mode.lower()}.png"))
-
- for png_path in png_paths:
- with Image.open(png_path) as reference_im:
- assert reference_im.mode == mode
-
- path_no_ext = os.path.splitext(png_path)[0]
- for origin, rle in product(_ORIGINS, (True, False)):
- tga_path = "{}_{}_{}.tga".format(
- path_no_ext, origin, "rle" if rle else "raw"
- )
-
- with Image.open(tga_path) as original_im:
- assert original_im.format == "TGA"
- assert original_im.get_format_mimetype() == "image/x-tga"
- if rle:
- assert original_im.info["compression"] == "tga_rle"
- assert (
- original_im.info["orientation"]
- == _ORIGIN_TO_ORIENTATION[origin]
- )
- if mode == "P":
- assert original_im.getpalette() == reference_im.getpalette()
-
- assert_image_equal(original_im, reference_im)
-
- roundtrip(original_im)
+ roundtrip(original_im)
def test_palette_depth_16(tmp_path):
diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py
index d6769a24b..439cb15bc 100644
--- a/Tests/test_file_wmf.py
+++ b/Tests/test_file_wmf.py
@@ -66,10 +66,10 @@ def test_load_set_dpi():
assert_image_similar_tofile(im, "Tests/images/drawing_wmf_ref_144.png", 2.1)
-def test_save(tmp_path):
+@pytest.mark.parametrize("ext", (".wmf", ".emf"))
+def test_save(ext, tmp_path):
im = hopper()
- for ext in [".wmf", ".emf"]:
- tmpfile = str(tmp_path / ("temp" + ext))
- with pytest.raises(OSError):
- im.save(tmpfile)
+ tmpfile = str(tmp_path / ("temp" + ext))
+ with pytest.raises(OSError):
+ im.save(tmpfile)
diff --git a/Tests/test_image.py b/Tests/test_image.py
index 571ee30cc..245ca861e 100644
--- a/Tests/test_image.py
+++ b/Tests/test_image.py
@@ -22,8 +22,9 @@ from .helper import (
class TestImage:
- def test_image_modes_success(self):
- for mode in [
+ @pytest.mark.parametrize(
+ "mode",
+ (
"1",
"P",
"PA",
@@ -44,19 +45,15 @@ class TestImage:
"YCbCr",
"LAB",
"HSV",
- ]:
+ ),
+ )
+ def test_image_modes_success(self, mode):
Image.new(mode, (1, 1))
- def test_image_modes_fail(self):
- for mode in [
- "",
- "bad",
- "very very long",
- "BGR;15",
- "BGR;16",
- "BGR;24",
- "BGR;32",
- ]:
+ @pytest.mark.parametrize(
+ "mode", ("", "bad", "very very long", "BGR;15", "BGR;16", "BGR;24", "BGR;32")
+ )
+ def test_image_modes_fail(self, mode):
with pytest.raises(ValueError) as e:
Image.new(mode, (1, 1))
assert str(e.value) == "unrecognized image mode"
diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py
index 617274a57..bb75eb0b5 100644
--- a/Tests/test_image_access.py
+++ b/Tests/test_image_access.py
@@ -184,8 +184,9 @@ class TestImageGetPixel(AccessTest):
with pytest.raises(error):
im.getpixel((-1, -1))
- def test_basic(self):
- for mode in (
+ @pytest.mark.parametrize(
+ "mode",
+ (
"1",
"L",
"LA",
@@ -200,23 +201,25 @@ class TestImageGetPixel(AccessTest):
"RGBX",
"CMYK",
"YCbCr",
- ):
- self.check(mode)
+ ),
+ )
+ def test_basic(self, mode):
+ self.check(mode)
- def test_signedness(self):
+ @pytest.mark.parametrize("mode", ("I;16", "I;16B"))
+ def test_signedness(self, mode):
# see https://github.com/python-pillow/Pillow/issues/452
# pixelaccess is using signed int* instead of uint*
- for mode in ("I;16", "I;16B"):
- self.check(mode, 2**15 - 1)
- self.check(mode, 2**15)
- self.check(mode, 2**15 + 1)
- self.check(mode, 2**16 - 1)
+ self.check(mode, 2**15 - 1)
+ self.check(mode, 2**15)
+ self.check(mode, 2**15 + 1)
+ self.check(mode, 2**16 - 1)
- def test_p_putpixel_rgb_rgba(self):
- for color in [(255, 0, 0), (255, 0, 0, 255)]:
- im = Image.new("P", (1, 1), 0)
- im.putpixel((0, 0), color)
- assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
+ @pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255)))
+ def test_p_putpixel_rgb_rgba(self, color):
+ im = Image.new("P", (1, 1), 0)
+ im.putpixel((0, 0), color)
+ assert im.convert("RGB").getpixel((0, 0)) == (255, 0, 0)
@pytest.mark.skipif(cffi is None, reason="No CFFI")
diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py
index e5639e105..8f4b8b43c 100644
--- a/Tests/test_image_convert.py
+++ b/Tests/test_image_convert.py
@@ -268,36 +268,33 @@ def test_matrix_wrong_mode():
im.convert(mode="L", matrix=matrix)
-def test_matrix_xyz():
- def matrix_convert(mode):
- # Arrange
- im = hopper("RGB")
- im.info["transparency"] = (255, 0, 0)
- # fmt: off
- matrix = (
- 0.412453, 0.357580, 0.180423, 0,
- 0.212671, 0.715160, 0.072169, 0,
- 0.019334, 0.119193, 0.950227, 0)
- # fmt: on
- assert im.mode == "RGB"
+@pytest.mark.parametrize("mode", ("RGB", "L"))
+def test_matrix_xyz(mode):
+ # Arrange
+ im = hopper("RGB")
+ im.info["transparency"] = (255, 0, 0)
+ # fmt: off
+ matrix = (
+ 0.412453, 0.357580, 0.180423, 0,
+ 0.212671, 0.715160, 0.072169, 0,
+ 0.019334, 0.119193, 0.950227, 0)
+ # fmt: on
+ assert im.mode == "RGB"
- # Act
- # Convert an RGB image to the CIE XYZ colour space
- converted_im = im.convert(mode=mode, matrix=matrix)
+ # Act
+ # Convert an RGB image to the CIE XYZ colour space
+ converted_im = im.convert(mode=mode, matrix=matrix)
- # Assert
- assert converted_im.mode == mode
- assert converted_im.size == im.size
- with Image.open("Tests/images/hopper-XYZ.png") as target:
- if converted_im.mode == "RGB":
- assert_image_similar(converted_im, target, 3)
- assert converted_im.info["transparency"] == (105, 54, 4)
- else:
- assert_image_similar(converted_im, target.getchannel(0), 1)
- assert converted_im.info["transparency"] == 105
-
- matrix_convert("RGB")
- matrix_convert("L")
+ # Assert
+ assert converted_im.mode == mode
+ assert converted_im.size == im.size
+ with Image.open("Tests/images/hopper-XYZ.png") as target:
+ if converted_im.mode == "RGB":
+ assert_image_similar(converted_im, target, 3)
+ assert converted_im.info["transparency"] == (105, 54, 4)
+ else:
+ assert_image_similar(converted_im, target.getchannel(0), 1)
+ assert converted_im.info["transparency"] == 105
def test_matrix_identity():
diff --git a/Tests/test_image_copy.py b/Tests/test_image_copy.py
index 21e438654..591832147 100644
--- a/Tests/test_image_copy.py
+++ b/Tests/test_image_copy.py
@@ -1,37 +1,40 @@
import copy
+import pytest
+
from PIL import Image
from .helper import hopper
-def test_copy():
+@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
+def test_copy(mode):
cropped_coordinates = (10, 10, 20, 20)
cropped_size = (10, 10)
- for mode in "1", "P", "L", "RGB", "I", "F":
- # Internal copy method
- im = hopper(mode)
- out = im.copy()
- assert out.mode == im.mode
- assert out.size == im.size
- # Python's copy method
- im = hopper(mode)
- out = copy.copy(im)
- assert out.mode == im.mode
- assert out.size == im.size
+ # Internal copy method
+ im = hopper(mode)
+ out = im.copy()
+ assert out.mode == im.mode
+ assert out.size == im.size
- # Internal copy method on a cropped image
- im = hopper(mode)
- out = im.crop(cropped_coordinates).copy()
- assert out.mode == im.mode
- assert out.size == cropped_size
+ # Python's copy method
+ im = hopper(mode)
+ out = copy.copy(im)
+ assert out.mode == im.mode
+ assert out.size == im.size
- # Python's copy method on a cropped image
- im = hopper(mode)
- out = copy.copy(im.crop(cropped_coordinates))
- assert out.mode == im.mode
- assert out.size == cropped_size
+ # Internal copy method on a cropped image
+ im = hopper(mode)
+ out = im.crop(cropped_coordinates).copy()
+ assert out.mode == im.mode
+ assert out.size == cropped_size
+
+ # Python's copy method on a cropped image
+ im = hopper(mode)
+ out = copy.copy(im.crop(cropped_coordinates))
+ assert out.mode == im.mode
+ assert out.size == cropped_size
def test_copy_zero():
diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py
index 6574e6efd..4aa41de27 100644
--- a/Tests/test_image_crop.py
+++ b/Tests/test_image_crop.py
@@ -5,17 +5,14 @@ from PIL import Image
from .helper import assert_image_equal, hopper
-def test_crop():
- def crop(mode):
- im = hopper(mode)
- assert_image_equal(im.crop(), im)
+@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
+def test_crop(mode):
+ im = hopper(mode)
+ assert_image_equal(im.crop(), im)
- cropped = im.crop((50, 50, 100, 100))
- assert cropped.mode == mode
- assert cropped.size == (50, 50)
-
- for mode in "1", "P", "L", "RGB", "I", "F":
- crop(mode)
+ cropped = im.crop((50, 50, 100, 100))
+ assert cropped.mode == mode
+ assert cropped.size == (50, 50)
def test_wide_crop():
diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py
index 4ea1d73ce..1ab02017d 100644
--- a/Tests/test_image_paste.py
+++ b/Tests/test_image_paste.py
@@ -1,3 +1,5 @@
+import pytest
+
from PIL import Image
from .helper import CachedProperty, assert_image_equal
@@ -101,226 +103,226 @@ class TestImagingPaste:
],
)
- def test_image_solid(self):
- for mode in ("RGBA", "RGB", "L"):
- im = Image.new(mode, (200, 200), "red")
- im2 = getattr(self, "gradient_" + mode)
+ @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
+ def test_image_solid(self, mode):
+ im = Image.new(mode, (200, 200), "red")
+ im2 = getattr(self, "gradient_" + mode)
- im.paste(im2, (12, 23))
+ im.paste(im2, (12, 23))
- im = im.crop((12, 23, im2.width + 12, im2.height + 23))
- assert_image_equal(im, im2)
+ im = im.crop((12, 23, im2.width + 12, im2.height + 23))
+ assert_image_equal(im, im2)
- def test_image_mask_1(self):
- for mode in ("RGBA", "RGB", "L"):
- im = Image.new(mode, (200, 200), "white")
- im2 = getattr(self, "gradient_" + mode)
+ @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
+ def test_image_mask_1(self, mode):
+ im = Image.new(mode, (200, 200), "white")
+ im2 = getattr(self, "gradient_" + mode)
- self.assert_9points_paste(
- im,
- im2,
- self.mask_1,
- [
- (255, 255, 255, 255),
- (255, 255, 255, 255),
- (127, 254, 127, 0),
- (255, 255, 255, 255),
- (255, 255, 255, 255),
- (191, 190, 63, 64),
- (127, 0, 127, 254),
- (191, 64, 63, 190),
- (255, 255, 255, 255),
- ],
- )
+ self.assert_9points_paste(
+ im,
+ im2,
+ self.mask_1,
+ [
+ (255, 255, 255, 255),
+ (255, 255, 255, 255),
+ (127, 254, 127, 0),
+ (255, 255, 255, 255),
+ (255, 255, 255, 255),
+ (191, 190, 63, 64),
+ (127, 0, 127, 254),
+ (191, 64, 63, 190),
+ (255, 255, 255, 255),
+ ],
+ )
- def test_image_mask_L(self):
- for mode in ("RGBA", "RGB", "L"):
- im = Image.new(mode, (200, 200), "white")
- im2 = getattr(self, "gradient_" + mode)
+ @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
+ def test_image_mask_L(self, mode):
+ im = Image.new(mode, (200, 200), "white")
+ im2 = getattr(self, "gradient_" + mode)
- self.assert_9points_paste(
- im,
- im2,
- self.mask_L,
- [
- (128, 191, 255, 191),
- (208, 239, 239, 208),
- (255, 255, 255, 255),
- (112, 111, 206, 207),
- (192, 191, 191, 191),
- (239, 239, 207, 207),
- (128, 1, 128, 254),
- (207, 113, 112, 207),
- (255, 191, 128, 191),
- ],
- )
+ self.assert_9points_paste(
+ im,
+ im2,
+ self.mask_L,
+ [
+ (128, 191, 255, 191),
+ (208, 239, 239, 208),
+ (255, 255, 255, 255),
+ (112, 111, 206, 207),
+ (192, 191, 191, 191),
+ (239, 239, 207, 207),
+ (128, 1, 128, 254),
+ (207, 113, 112, 207),
+ (255, 191, 128, 191),
+ ],
+ )
- def test_image_mask_LA(self):
- for mode in ("RGBA", "RGB", "L"):
- im = Image.new(mode, (200, 200), "white")
- im2 = getattr(self, "gradient_" + mode)
+ @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
+ def test_image_mask_LA(self, mode):
+ im = Image.new(mode, (200, 200), "white")
+ im2 = getattr(self, "gradient_" + mode)
- self.assert_9points_paste(
- im,
- im2,
- self.gradient_LA,
- [
- (128, 191, 255, 191),
- (112, 207, 206, 111),
- (128, 254, 128, 1),
- (208, 208, 239, 239),
- (192, 191, 191, 191),
- (207, 207, 112, 113),
- (255, 255, 255, 255),
- (239, 207, 207, 239),
- (255, 191, 128, 191),
- ],
- )
+ self.assert_9points_paste(
+ im,
+ im2,
+ self.gradient_LA,
+ [
+ (128, 191, 255, 191),
+ (112, 207, 206, 111),
+ (128, 254, 128, 1),
+ (208, 208, 239, 239),
+ (192, 191, 191, 191),
+ (207, 207, 112, 113),
+ (255, 255, 255, 255),
+ (239, 207, 207, 239),
+ (255, 191, 128, 191),
+ ],
+ )
- def test_image_mask_RGBA(self):
- for mode in ("RGBA", "RGB", "L"):
- im = Image.new(mode, (200, 200), "white")
- im2 = getattr(self, "gradient_" + mode)
+ @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
+ def test_image_mask_RGBA(self, mode):
+ im = Image.new(mode, (200, 200), "white")
+ im2 = getattr(self, "gradient_" + mode)
- self.assert_9points_paste(
- im,
- im2,
- self.gradient_RGBA,
- [
- (128, 191, 255, 191),
- (208, 239, 239, 208),
- (255, 255, 255, 255),
- (112, 111, 206, 207),
- (192, 191, 191, 191),
- (239, 239, 207, 207),
- (128, 1, 128, 254),
- (207, 113, 112, 207),
- (255, 191, 128, 191),
- ],
- )
+ self.assert_9points_paste(
+ im,
+ im2,
+ self.gradient_RGBA,
+ [
+ (128, 191, 255, 191),
+ (208, 239, 239, 208),
+ (255, 255, 255, 255),
+ (112, 111, 206, 207),
+ (192, 191, 191, 191),
+ (239, 239, 207, 207),
+ (128, 1, 128, 254),
+ (207, 113, 112, 207),
+ (255, 191, 128, 191),
+ ],
+ )
- def test_image_mask_RGBa(self):
- for mode in ("RGBA", "RGB", "L"):
- im = Image.new(mode, (200, 200), "white")
- im2 = getattr(self, "gradient_" + mode)
+ @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
+ def test_image_mask_RGBa(self, mode):
+ im = Image.new(mode, (200, 200), "white")
+ im2 = getattr(self, "gradient_" + mode)
- self.assert_9points_paste(
- im,
- im2,
- self.gradient_RGBa,
- [
- (128, 255, 126, 255),
- (0, 127, 126, 255),
- (126, 253, 126, 255),
- (128, 127, 254, 255),
- (0, 255, 254, 255),
- (126, 125, 254, 255),
- (128, 1, 128, 255),
- (0, 129, 128, 255),
- (126, 255, 128, 255),
- ],
- )
+ self.assert_9points_paste(
+ im,
+ im2,
+ self.gradient_RGBa,
+ [
+ (128, 255, 126, 255),
+ (0, 127, 126, 255),
+ (126, 253, 126, 255),
+ (128, 127, 254, 255),
+ (0, 255, 254, 255),
+ (126, 125, 254, 255),
+ (128, 1, 128, 255),
+ (0, 129, 128, 255),
+ (126, 255, 128, 255),
+ ],
+ )
- def test_color_solid(self):
- for mode in ("RGBA", "RGB", "L"):
- im = Image.new(mode, (200, 200), "black")
+ @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
+ def test_color_solid(self, mode):
+ im = Image.new(mode, (200, 200), "black")
- rect = (12, 23, 128 + 12, 128 + 23)
- im.paste("white", rect)
+ rect = (12, 23, 128 + 12, 128 + 23)
+ im.paste("white", rect)
- hist = im.crop(rect).histogram()
- while hist:
- head, hist = hist[:256], hist[256:]
- assert head[255] == 128 * 128
- assert sum(head[:255]) == 0
+ hist = im.crop(rect).histogram()
+ while hist:
+ head, hist = hist[:256], hist[256:]
+ assert head[255] == 128 * 128
+ assert sum(head[:255]) == 0
- def test_color_mask_1(self):
- for mode in ("RGBA", "RGB", "L"):
- im = Image.new(mode, (200, 200), (50, 60, 70, 80)[: len(mode)])
- color = (10, 20, 30, 40)[: len(mode)]
+ @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
+ def test_color_mask_1(self, mode):
+ im = Image.new(mode, (200, 200), (50, 60, 70, 80)[: len(mode)])
+ color = (10, 20, 30, 40)[: len(mode)]
- self.assert_9points_paste(
- im,
- color,
- self.mask_1,
- [
- (50, 60, 70, 80),
- (50, 60, 70, 80),
- (10, 20, 30, 40),
- (50, 60, 70, 80),
- (50, 60, 70, 80),
- (10, 20, 30, 40),
- (10, 20, 30, 40),
- (10, 20, 30, 40),
- (50, 60, 70, 80),
- ],
- )
+ self.assert_9points_paste(
+ im,
+ color,
+ self.mask_1,
+ [
+ (50, 60, 70, 80),
+ (50, 60, 70, 80),
+ (10, 20, 30, 40),
+ (50, 60, 70, 80),
+ (50, 60, 70, 80),
+ (10, 20, 30, 40),
+ (10, 20, 30, 40),
+ (10, 20, 30, 40),
+ (50, 60, 70, 80),
+ ],
+ )
- def test_color_mask_L(self):
- for mode in ("RGBA", "RGB", "L"):
- im = getattr(self, "gradient_" + mode).copy()
- color = "white"
+ @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
+ def test_color_mask_L(self, mode):
+ im = getattr(self, "gradient_" + mode).copy()
+ color = "white"
- self.assert_9points_paste(
- im,
- color,
- self.mask_L,
- [
- (127, 191, 254, 191),
- (111, 207, 206, 110),
- (127, 254, 127, 0),
- (207, 207, 239, 239),
- (191, 191, 190, 191),
- (207, 206, 111, 112),
- (254, 254, 254, 255),
- (239, 206, 206, 238),
- (254, 191, 127, 191),
- ],
- )
+ self.assert_9points_paste(
+ im,
+ color,
+ self.mask_L,
+ [
+ (127, 191, 254, 191),
+ (111, 207, 206, 110),
+ (127, 254, 127, 0),
+ (207, 207, 239, 239),
+ (191, 191, 190, 191),
+ (207, 206, 111, 112),
+ (254, 254, 254, 255),
+ (239, 206, 206, 238),
+ (254, 191, 127, 191),
+ ],
+ )
- def test_color_mask_RGBA(self):
- for mode in ("RGBA", "RGB", "L"):
- im = getattr(self, "gradient_" + mode).copy()
- color = "white"
+ @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
+ def test_color_mask_RGBA(self, mode):
+ im = getattr(self, "gradient_" + mode).copy()
+ color = "white"
- self.assert_9points_paste(
- im,
- color,
- self.gradient_RGBA,
- [
- (127, 191, 254, 191),
- (111, 207, 206, 110),
- (127, 254, 127, 0),
- (207, 207, 239, 239),
- (191, 191, 190, 191),
- (207, 206, 111, 112),
- (254, 254, 254, 255),
- (239, 206, 206, 238),
- (254, 191, 127, 191),
- ],
- )
+ self.assert_9points_paste(
+ im,
+ color,
+ self.gradient_RGBA,
+ [
+ (127, 191, 254, 191),
+ (111, 207, 206, 110),
+ (127, 254, 127, 0),
+ (207, 207, 239, 239),
+ (191, 191, 190, 191),
+ (207, 206, 111, 112),
+ (254, 254, 254, 255),
+ (239, 206, 206, 238),
+ (254, 191, 127, 191),
+ ],
+ )
- def test_color_mask_RGBa(self):
- for mode in ("RGBA", "RGB", "L"):
- im = getattr(self, "gradient_" + mode).copy()
- color = "white"
+ @pytest.mark.parametrize("mode", ["RGBA", "RGB", "L"])
+ def test_color_mask_RGBa(self, mode):
+ im = getattr(self, "gradient_" + mode).copy()
+ color = "white"
- self.assert_9points_paste(
- im,
- color,
- self.gradient_RGBa,
- [
- (255, 63, 126, 63),
- (47, 143, 142, 46),
- (126, 253, 126, 255),
- (15, 15, 47, 47),
- (63, 63, 62, 63),
- (142, 141, 46, 47),
- (255, 255, 255, 0),
- (48, 15, 15, 47),
- (126, 63, 255, 63),
- ],
- )
+ self.assert_9points_paste(
+ im,
+ color,
+ self.gradient_RGBa,
+ [
+ (255, 63, 126, 63),
+ (47, 143, 142, 46),
+ (126, 253, 126, 255),
+ (15, 15, 47, 47),
+ (63, 63, 62, 63),
+ (142, 141, 46, 47),
+ (255, 255, 255, 0),
+ (48, 15, 15, 47),
+ (126, 63, 255, 63),
+ ],
+ )
def test_different_sizes(self):
im = Image.new("RGB", (100, 100))
diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py
index 6d050efcc..5ce98a235 100644
--- a/Tests/test_image_resample.py
+++ b/Tests/test_image_resample.py
@@ -100,40 +100,41 @@ class TestImagingCoreResampleAccuracy:
for y in range(image.size[1])
)
- def test_reduce_box(self):
- for mode in ["RGBX", "RGB", "La", "L"]:
- case = self.make_case(mode, (8, 8), 0xE1)
- case = case.resize((4, 4), Image.Resampling.BOX)
- # fmt: off
- data = ("e1 e1"
- "e1 e1")
- # fmt: on
- for channel in case.split():
- self.check_case(channel, self.make_sample(data, (4, 4)))
+ @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
+ def test_reduce_box(self, mode):
+ case = self.make_case(mode, (8, 8), 0xE1)
+ case = case.resize((4, 4), Image.Resampling.BOX)
+ # fmt: off
+ data = ("e1 e1"
+ "e1 e1")
+ # fmt: on
+ for channel in case.split():
+ self.check_case(channel, self.make_sample(data, (4, 4)))
- def test_reduce_bilinear(self):
- for mode in ["RGBX", "RGB", "La", "L"]:
- case = self.make_case(mode, (8, 8), 0xE1)
- case = case.resize((4, 4), Image.Resampling.BILINEAR)
- # fmt: off
- data = ("e1 c9"
- "c9 b7")
- # fmt: on
- for channel in case.split():
- self.check_case(channel, self.make_sample(data, (4, 4)))
+ @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
+ def test_reduce_bilinear(self, mode):
+ case = self.make_case(mode, (8, 8), 0xE1)
+ case = case.resize((4, 4), Image.Resampling.BILINEAR)
+ # fmt: off
+ data = ("e1 c9"
+ "c9 b7")
+ # fmt: on
+ for channel in case.split():
+ self.check_case(channel, self.make_sample(data, (4, 4)))
- def test_reduce_hamming(self):
- for mode in ["RGBX", "RGB", "La", "L"]:
- case = self.make_case(mode, (8, 8), 0xE1)
- case = case.resize((4, 4), Image.Resampling.HAMMING)
- # fmt: off
- data = ("e1 da"
- "da d3")
- # fmt: on
- for channel in case.split():
- self.check_case(channel, self.make_sample(data, (4, 4)))
+ @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
+ def test_reduce_hamming(self, mode):
+ case = self.make_case(mode, (8, 8), 0xE1)
+ case = case.resize((4, 4), Image.Resampling.HAMMING)
+ # fmt: off
+ data = ("e1 da"
+ "da d3")
+ # fmt: on
+ for channel in case.split():
+ self.check_case(channel, self.make_sample(data, (4, 4)))
- def test_reduce_bicubic(self):
+ @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
+ def test_reduce_bicubic(self, mode):
for mode in ["RGBX", "RGB", "La", "L"]:
case = self.make_case(mode, (12, 12), 0xE1)
case = case.resize((6, 6), Image.Resampling.BICUBIC)
@@ -145,79 +146,79 @@ class TestImagingCoreResampleAccuracy:
for channel in case.split():
self.check_case(channel, self.make_sample(data, (6, 6)))
- def test_reduce_lanczos(self):
- for mode in ["RGBX", "RGB", "La", "L"]:
- case = self.make_case(mode, (16, 16), 0xE1)
- case = case.resize((8, 8), Image.Resampling.LANCZOS)
- # fmt: off
- data = ("e1 e0 e4 d7"
- "e0 df e3 d6"
- "e4 e3 e7 da"
- "d7 d6 d9 ce")
- # fmt: on
- for channel in case.split():
- self.check_case(channel, self.make_sample(data, (8, 8)))
+ @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
+ def test_reduce_lanczos(self, mode):
+ case = self.make_case(mode, (16, 16), 0xE1)
+ case = case.resize((8, 8), Image.Resampling.LANCZOS)
+ # fmt: off
+ data = ("e1 e0 e4 d7"
+ "e0 df e3 d6"
+ "e4 e3 e7 da"
+ "d7 d6 d9 ce")
+ # fmt: on
+ for channel in case.split():
+ self.check_case(channel, self.make_sample(data, (8, 8)))
- def test_enlarge_box(self):
- for mode in ["RGBX", "RGB", "La", "L"]:
- case = self.make_case(mode, (2, 2), 0xE1)
- case = case.resize((4, 4), Image.Resampling.BOX)
- # fmt: off
- data = ("e1 e1"
- "e1 e1")
- # fmt: on
- for channel in case.split():
- self.check_case(channel, self.make_sample(data, (4, 4)))
+ @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
+ def test_enlarge_box(self, mode):
+ case = self.make_case(mode, (2, 2), 0xE1)
+ case = case.resize((4, 4), Image.Resampling.BOX)
+ # fmt: off
+ data = ("e1 e1"
+ "e1 e1")
+ # fmt: on
+ for channel in case.split():
+ self.check_case(channel, self.make_sample(data, (4, 4)))
- def test_enlarge_bilinear(self):
- for mode in ["RGBX", "RGB", "La", "L"]:
- case = self.make_case(mode, (2, 2), 0xE1)
- case = case.resize((4, 4), Image.Resampling.BILINEAR)
- # fmt: off
- data = ("e1 b0"
- "b0 98")
- # fmt: on
- for channel in case.split():
- self.check_case(channel, self.make_sample(data, (4, 4)))
+ @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
+ def test_enlarge_bilinear(self, mode):
+ case = self.make_case(mode, (2, 2), 0xE1)
+ case = case.resize((4, 4), Image.Resampling.BILINEAR)
+ # fmt: off
+ data = ("e1 b0"
+ "b0 98")
+ # fmt: on
+ for channel in case.split():
+ self.check_case(channel, self.make_sample(data, (4, 4)))
- def test_enlarge_hamming(self):
- for mode in ["RGBX", "RGB", "La", "L"]:
- case = self.make_case(mode, (2, 2), 0xE1)
- case = case.resize((4, 4), Image.Resampling.HAMMING)
- # fmt: off
- data = ("e1 d2"
- "d2 c5")
- # fmt: on
- for channel in case.split():
- self.check_case(channel, self.make_sample(data, (4, 4)))
+ @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
+ def test_enlarge_hamming(self, mode):
+ case = self.make_case(mode, (2, 2), 0xE1)
+ case = case.resize((4, 4), Image.Resampling.HAMMING)
+ # fmt: off
+ data = ("e1 d2"
+ "d2 c5")
+ # fmt: on
+ for channel in case.split():
+ self.check_case(channel, self.make_sample(data, (4, 4)))
- def test_enlarge_bicubic(self):
- for mode in ["RGBX", "RGB", "La", "L"]:
- case = self.make_case(mode, (4, 4), 0xE1)
- case = case.resize((8, 8), Image.Resampling.BICUBIC)
- # fmt: off
- data = ("e1 e5 ee b9"
- "e5 e9 f3 bc"
- "ee f3 fd c1"
- "b9 bc c1 a2")
- # fmt: on
- for channel in case.split():
- self.check_case(channel, self.make_sample(data, (8, 8)))
+ @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
+ def test_enlarge_bicubic(self, mode):
+ case = self.make_case(mode, (4, 4), 0xE1)
+ case = case.resize((8, 8), Image.Resampling.BICUBIC)
+ # fmt: off
+ data = ("e1 e5 ee b9"
+ "e5 e9 f3 bc"
+ "ee f3 fd c1"
+ "b9 bc c1 a2")
+ # fmt: on
+ for channel in case.split():
+ self.check_case(channel, self.make_sample(data, (8, 8)))
- def test_enlarge_lanczos(self):
- for mode in ["RGBX", "RGB", "La", "L"]:
- case = self.make_case(mode, (6, 6), 0xE1)
- case = case.resize((12, 12), Image.Resampling.LANCZOS)
- data = (
- "e1 e0 db ed f5 b8"
- "e0 df da ec f3 b7"
- "db db d6 e7 ee b5"
- "ed ec e6 fb ff bf"
- "f5 f4 ee ff ff c4"
- "b8 b7 b4 bf c4 a0"
- )
- for channel in case.split():
- self.check_case(channel, self.make_sample(data, (12, 12)))
+ @pytest.mark.parametrize("mode", ("RGBX", "RGB", "La", "L"))
+ def test_enlarge_lanczos(self, mode):
+ case = self.make_case(mode, (6, 6), 0xE1)
+ case = case.resize((12, 12), Image.Resampling.LANCZOS)
+ data = (
+ "e1 e0 db ed f5 b8"
+ "e0 df da ec f3 b7"
+ "db db d6 e7 ee b5"
+ "ed ec e6 fb ff bf"
+ "f5 f4 ee ff ff c4"
+ "b8 b7 b4 bf c4 a0"
+ )
+ for channel in case.split():
+ self.check_case(channel, self.make_sample(data, (12, 12)))
def test_box_filter_correct_range(self):
im = Image.new("RGB", (8, 8), "#1688ff").resize(
@@ -419,40 +420,43 @@ class TestCoreResampleCoefficients:
class TestCoreResampleBox:
- def test_wrong_arguments(self):
- im = hopper()
- for resample in (
+ @pytest.mark.parametrize(
+ "resample",
+ (
Image.Resampling.NEAREST,
Image.Resampling.BOX,
Image.Resampling.BILINEAR,
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
- ):
- im.resize((32, 32), resample, (0, 0, im.width, im.height))
- im.resize((32, 32), resample, (20, 20, im.width, im.height))
- im.resize((32, 32), resample, (20, 20, 20, 100))
- im.resize((32, 32), resample, (20, 20, 100, 20))
+ ),
+ )
+ def test_wrong_arguments(self, resample):
+ im = hopper()
+ im.resize((32, 32), resample, (0, 0, im.width, im.height))
+ im.resize((32, 32), resample, (20, 20, im.width, im.height))
+ im.resize((32, 32), resample, (20, 20, 20, 100))
+ im.resize((32, 32), resample, (20, 20, 100, 20))
- with pytest.raises(TypeError, match="must be sequence of length 4"):
- im.resize((32, 32), resample, (im.width, im.height))
+ with pytest.raises(TypeError, match="must be sequence of length 4"):
+ im.resize((32, 32), resample, (im.width, im.height))
- with pytest.raises(ValueError, match="can't be negative"):
- im.resize((32, 32), resample, (-20, 20, 100, 100))
- with pytest.raises(ValueError, match="can't be negative"):
- im.resize((32, 32), resample, (20, -20, 100, 100))
+ with pytest.raises(ValueError, match="can't be negative"):
+ im.resize((32, 32), resample, (-20, 20, 100, 100))
+ with pytest.raises(ValueError, match="can't be negative"):
+ im.resize((32, 32), resample, (20, -20, 100, 100))
- with pytest.raises(ValueError, match="can't be empty"):
- im.resize((32, 32), resample, (20.1, 20, 20, 100))
- with pytest.raises(ValueError, match="can't be empty"):
- im.resize((32, 32), resample, (20, 20.1, 100, 20))
- with pytest.raises(ValueError, match="can't be empty"):
- im.resize((32, 32), resample, (20.1, 20.1, 20, 20))
+ with pytest.raises(ValueError, match="can't be empty"):
+ im.resize((32, 32), resample, (20.1, 20, 20, 100))
+ with pytest.raises(ValueError, match="can't be empty"):
+ im.resize((32, 32), resample, (20, 20.1, 100, 20))
+ with pytest.raises(ValueError, match="can't be empty"):
+ im.resize((32, 32), resample, (20.1, 20.1, 20, 20))
- with pytest.raises(ValueError, match="can't exceed"):
- im.resize((32, 32), resample, (0, 0, im.width + 1, im.height))
- with pytest.raises(ValueError, match="can't exceed"):
- im.resize((32, 32), resample, (0, 0, im.width, im.height + 1))
+ with pytest.raises(ValueError, match="can't exceed"):
+ im.resize((32, 32), resample, (0, 0, im.width + 1, im.height))
+ with pytest.raises(ValueError, match="can't exceed"):
+ im.resize((32, 32), resample, (0, 0, im.width, im.height + 1))
def resize_tiled(self, im, dst_size, xtiles, ytiles):
def split_range(size, tiles):
@@ -509,14 +513,16 @@ class TestCoreResampleBox:
with pytest.raises(AssertionError, match=r"difference 29\."):
assert_image_similar(reference, without_box, 5)
- def test_formats(self):
- for resample in [Image.Resampling.NEAREST, Image.Resampling.BILINEAR]:
- for mode in ["RGB", "L", "RGBA", "LA", "I", ""]:
- im = hopper(mode)
- box = (20, 20, im.size[0] - 20, im.size[1] - 20)
- with_box = im.resize((32, 32), resample, box)
- cropped = im.crop(box).resize((32, 32), resample)
- assert_image_similar(cropped, with_box, 0.4)
+ @pytest.mark.parametrize("mode", ("RGB", "L", "RGBA", "LA", "I", ""))
+ @pytest.mark.parametrize(
+ "resample", (Image.Resampling.NEAREST, Image.Resampling.BILINEAR)
+ )
+ def test_formats(self, mode, resample):
+ im = hopper(mode)
+ box = (20, 20, im.size[0] - 20, im.size[1] - 20)
+ with_box = im.resize((32, 32), resample, box)
+ cropped = im.crop(box).resize((32, 32), resample)
+ assert_image_similar(cropped, with_box, 0.4)
def test_passthrough(self):
# When no resize is required
diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py
index 8347fabb9..83c54cf62 100644
--- a/Tests/test_image_resize.py
+++ b/Tests/test_image_resize.py
@@ -22,24 +22,15 @@ class TestImagingCoreResize:
im.load()
return im._new(im.im.resize(size, f))
- def test_nearest_mode(self):
- for mode in [
- "1",
- "P",
- "L",
- "I",
- "F",
- "RGB",
- "RGBA",
- "CMYK",
- "YCbCr",
- "I;16",
- ]: # exotic mode
- im = hopper(mode)
- r = self.resize(im, (15, 12), Image.Resampling.NEAREST)
- assert r.mode == mode
- assert r.size == (15, 12)
- assert r.im.bands == im.im.bands
+ @pytest.mark.parametrize(
+ "mode", ("1", "P", "L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr", "I;16")
+ )
+ def test_nearest_mode(self, mode):
+ im = hopper(mode)
+ r = self.resize(im, (15, 12), Image.Resampling.NEAREST)
+ assert r.mode == mode
+ assert r.size == (15, 12)
+ assert r.im.bands == im.im.bands
def test_convolution_modes(self):
with pytest.raises(ValueError):
@@ -55,33 +46,58 @@ class TestImagingCoreResize:
assert r.size == (15, 12)
assert r.im.bands == im.im.bands
- def test_reduce_filters(self):
- for f in [
+ @pytest.mark.parametrize(
+ "resample",
+ (
Image.Resampling.NEAREST,
Image.Resampling.BOX,
Image.Resampling.BILINEAR,
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
- ]:
- r = self.resize(hopper("RGB"), (15, 12), f)
- assert r.mode == "RGB"
- assert r.size == (15, 12)
+ ),
+ )
+ def test_reduce_filters(self, resample):
+ r = self.resize(hopper("RGB"), (15, 12), resample)
+ assert r.mode == "RGB"
+ assert r.size == (15, 12)
- def test_enlarge_filters(self):
- for f in [
+ @pytest.mark.parametrize(
+ "resample",
+ (
Image.Resampling.NEAREST,
Image.Resampling.BOX,
Image.Resampling.BILINEAR,
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
- ]:
- r = self.resize(hopper("RGB"), (212, 195), f)
- assert r.mode == "RGB"
- assert r.size == (212, 195)
+ ),
+ )
+ def test_enlarge_filters(self, resample):
+ r = self.resize(hopper("RGB"), (212, 195), resample)
+ assert r.mode == "RGB"
+ assert r.size == (212, 195)
- def test_endianness(self):
+ @pytest.mark.parametrize(
+ "resample",
+ (
+ Image.Resampling.NEAREST,
+ Image.Resampling.BOX,
+ Image.Resampling.BILINEAR,
+ Image.Resampling.HAMMING,
+ Image.Resampling.BICUBIC,
+ Image.Resampling.LANCZOS,
+ ),
+ )
+ @pytest.mark.parametrize(
+ "mode, channels_set",
+ (
+ ("RGB", ("blank", "filled", "dirty")),
+ ("RGBA", ("blank", "blank", "filled", "dirty")),
+ ("LA", ("filled", "dirty")),
+ ),
+ )
+ def test_endianness(self, resample, mode, channels_set):
# Make an image with one colored pixel, in one channel.
# When resized, that channel should be the same as a GS image.
# Other channels should be unaffected.
@@ -95,47 +111,37 @@ class TestImagingCoreResize:
}
samples["dirty"].putpixel((1, 1), 128)
- for f in [
+ # samples resized with current filter
+ references = {
+ name: self.resize(ch, (4, 4), resample) for name, ch in samples.items()
+ }
+
+ for channels in set(permutations(channels_set)):
+ # compile image from different channels permutations
+ im = Image.merge(mode, [samples[ch] for ch in channels])
+ resized = self.resize(im, (4, 4), resample)
+
+ for i, ch in enumerate(resized.split()):
+ # check what resized channel in image is the same
+ # as separately resized channel
+ assert_image_equal(ch, references[channels[i]])
+
+ @pytest.mark.parametrize(
+ "resample",
+ (
Image.Resampling.NEAREST,
Image.Resampling.BOX,
Image.Resampling.BILINEAR,
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
- ]:
- # samples resized with current filter
- references = {
- name: self.resize(ch, (4, 4), f) for name, ch in samples.items()
- }
-
- for mode, channels_set in [
- ("RGB", ("blank", "filled", "dirty")),
- ("RGBA", ("blank", "blank", "filled", "dirty")),
- ("LA", ("filled", "dirty")),
- ]:
- for channels in set(permutations(channels_set)):
- # compile image from different channels permutations
- im = Image.merge(mode, [samples[ch] for ch in channels])
- resized = self.resize(im, (4, 4), f)
-
- for i, ch in enumerate(resized.split()):
- # check what resized channel in image is the same
- # as separately resized channel
- assert_image_equal(ch, references[channels[i]])
-
- def test_enlarge_zero(self):
- for f in [
- Image.Resampling.NEAREST,
- Image.Resampling.BOX,
- Image.Resampling.BILINEAR,
- Image.Resampling.HAMMING,
- Image.Resampling.BICUBIC,
- Image.Resampling.LANCZOS,
- ]:
- r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), f)
- assert r.mode == "RGB"
- assert r.size == (212, 195)
- assert r.getdata()[0] == (0, 0, 0)
+ ),
+ )
+ def test_enlarge_zero(self, resample):
+ r = self.resize(Image.new("RGB", (0, 0), "white"), (212, 195), resample)
+ assert r.mode == "RGB"
+ assert r.size == (212, 195)
+ assert r.getdata()[0] == (0, 0, 0)
def test_unknown_filter(self):
with pytest.raises(ValueError):
@@ -179,74 +185,71 @@ class TestReducingGapResize:
(52, 34), Image.Resampling.BICUBIC, reducing_gap=0.99
)
- def test_reducing_gap_1(self, gradients_image):
- for box, epsilon in [
- (None, 4),
- ((1.1, 2.2, 510.8, 510.9), 4),
- ((3, 10, 410, 256), 10),
- ]:
- ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
- im = gradients_image.resize(
- (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
- )
-
- with pytest.raises(AssertionError):
- assert_image_equal(ref, im)
-
- assert_image_similar(ref, im, epsilon)
-
- def test_reducing_gap_2(self, gradients_image):
- for box, epsilon in [
- (None, 1.5),
- ((1.1, 2.2, 510.8, 510.9), 1.5),
- ((3, 10, 410, 256), 1),
- ]:
- ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
- im = gradients_image.resize(
- (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0
- )
-
- with pytest.raises(AssertionError):
- assert_image_equal(ref, im)
-
- assert_image_similar(ref, im, epsilon)
-
- def test_reducing_gap_3(self, gradients_image):
- for box, epsilon in [
- (None, 1),
- ((1.1, 2.2, 510.8, 510.9), 1),
- ((3, 10, 410, 256), 0.5),
- ]:
- ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
- im = gradients_image.resize(
- (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0
- )
-
- with pytest.raises(AssertionError):
- assert_image_equal(ref, im)
-
- assert_image_similar(ref, im, epsilon)
-
- def test_reducing_gap_8(self, gradients_image):
- for box in [None, (1.1, 2.2, 510.8, 510.9), (3, 10, 410, 256)]:
- ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
- im = gradients_image.resize(
- (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=8.0
- )
+ @pytest.mark.parametrize(
+ "box, epsilon",
+ ((None, 4), ((1.1, 2.2, 510.8, 510.9), 4), ((3, 10, 410, 256), 10)),
+ )
+ def test_reducing_gap_1(self, gradients_image, box, epsilon):
+ ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
+ im = gradients_image.resize(
+ (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0
+ )
+ with pytest.raises(AssertionError):
assert_image_equal(ref, im)
- def test_box_filter(self, gradients_image):
- for box, epsilon in [
- ((0, 0, 512, 512), 5.5),
- ((0.9, 1.7, 128, 128), 9.5),
- ]:
- ref = gradients_image.resize((52, 34), Image.Resampling.BOX, box=box)
- im = gradients_image.resize(
- (52, 34), Image.Resampling.BOX, box=box, reducing_gap=1.0
- )
+ assert_image_similar(ref, im, epsilon)
- assert_image_similar(ref, im, epsilon)
+ @pytest.mark.parametrize(
+ "box, epsilon",
+ ((None, 1.5), ((1.1, 2.2, 510.8, 510.9), 1.5), ((3, 10, 410, 256), 1)),
+ )
+ def test_reducing_gap_2(self, gradients_image, box, epsilon):
+ ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
+ im = gradients_image.resize(
+ (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0
+ )
+
+ with pytest.raises(AssertionError):
+ assert_image_equal(ref, im)
+
+ assert_image_similar(ref, im, epsilon)
+
+ @pytest.mark.parametrize(
+ "box, epsilon",
+ ((None, 1), ((1.1, 2.2, 510.8, 510.9), 1), ((3, 10, 410, 256), 0.5)),
+ )
+ def test_reducing_gap_3(self, gradients_image, box, epsilon):
+ ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
+ im = gradients_image.resize(
+ (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0
+ )
+
+ with pytest.raises(AssertionError):
+ assert_image_equal(ref, im)
+
+ assert_image_similar(ref, im, epsilon)
+
+ @pytest.mark.parametrize("box", (None, (1.1, 2.2, 510.8, 510.9), (3, 10, 410, 256)))
+ def test_reducing_gap_8(self, gradients_image, box):
+ ref = gradients_image.resize((52, 34), Image.Resampling.BICUBIC, box=box)
+ im = gradients_image.resize(
+ (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=8.0
+ )
+
+ assert_image_equal(ref, im)
+
+ @pytest.mark.parametrize(
+ "box, epsilon",
+ (((0, 0, 512, 512), 5.5), ((0.9, 1.7, 128, 128), 9.5)),
+ )
+ def test_box_filter(self, gradients_image, box, epsilon):
+ ref = gradients_image.resize((52, 34), Image.Resampling.BOX, box=box)
+ im = gradients_image.resize(
+ (52, 34), Image.Resampling.BOX, box=box, reducing_gap=1.0
+ )
+
+ assert_image_similar(ref, im, epsilon)
class TestImageResize:
@@ -273,15 +276,14 @@ class TestImageResize:
im = im.resize((64, 64))
assert im.size == (64, 64)
- def test_default_filter(self):
- for mode in "L", "RGB", "I", "F":
- im = hopper(mode)
- assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
+ @pytest.mark.parametrize("mode", ("L", "RGB", "I", "F"))
+ def test_default_filter_bicubic(self, mode):
+ im = hopper(mode)
+ assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
- for mode in "1", "P":
- im = hopper(mode)
- assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
-
- for mode in "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16":
- im = hopper(mode)
- assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
+ @pytest.mark.parametrize(
+ "mode", ("1", "P", "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16")
+ )
+ def test_default_filter_nearest(self, mode):
+ im = hopper(mode)
+ assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))
diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py
index f96864c53..a19f19831 100644
--- a/Tests/test_image_rotate.py
+++ b/Tests/test_image_rotate.py
@@ -1,3 +1,5 @@
+import pytest
+
from PIL import Image
from .helper import (
@@ -22,26 +24,26 @@ def rotate(im, mode, angle, center=None, translate=None):
assert out.size != im.size
-def test_mode():
- for mode in ("1", "P", "L", "RGB", "I", "F"):
- im = hopper(mode)
- rotate(im, mode, 45)
+@pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F"))
+def test_mode(mode):
+ im = hopper(mode)
+ rotate(im, mode, 45)
-def test_angle():
- for angle in (0, 90, 180, 270):
- with Image.open("Tests/images/test-card.png") as im:
- rotate(im, im.mode, angle)
-
- im = hopper()
- assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1))
-
-
-def test_zero():
- for angle in (0, 45, 90, 180, 270):
- im = Image.new("RGB", (0, 0))
+@pytest.mark.parametrize("angle", (0, 90, 180, 270))
+def test_angle(angle):
+ with Image.open("Tests/images/test-card.png") as im:
rotate(im, im.mode, angle)
+ im = hopper()
+ assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1))
+
+
+@pytest.mark.parametrize("angle", (0, 45, 90, 180, 270))
+def test_zero(angle):
+ im = Image.new("RGB", (0, 0))
+ rotate(im, im.mode, angle)
+
def test_resample():
# Target image creation, inspected by eye.
diff --git a/Tests/test_image_transpose.py b/Tests/test_image_transpose.py
index 6408e1564..877f439ca 100644
--- a/Tests/test_image_transpose.py
+++ b/Tests/test_image_transpose.py
@@ -1,3 +1,5 @@
+import pytest
+
from PIL.Image import Transpose
from . import helper
@@ -9,157 +11,136 @@ HOPPER = {
}
-def test_flip_left_right():
- def transpose(mode):
- im = HOPPER[mode]
- out = im.transpose(Transpose.FLIP_LEFT_RIGHT)
- assert out.mode == mode
- assert out.size == im.size
+@pytest.mark.parametrize("mode", HOPPER)
+def test_flip_left_right(mode):
+ im = HOPPER[mode]
+ out = im.transpose(Transpose.FLIP_LEFT_RIGHT)
+ assert out.mode == mode
+ assert out.size == im.size
- x, y = im.size
- assert im.getpixel((1, 1)) == out.getpixel((x - 2, 1))
- assert im.getpixel((x - 2, 1)) == out.getpixel((1, 1))
- assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, y - 2))
- assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, y - 2))
-
- for mode in HOPPER:
- transpose(mode)
+ x, y = im.size
+ assert im.getpixel((1, 1)) == out.getpixel((x - 2, 1))
+ assert im.getpixel((x - 2, 1)) == out.getpixel((1, 1))
+ assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, y - 2))
+ assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, y - 2))
-def test_flip_top_bottom():
- def transpose(mode):
- im = HOPPER[mode]
- out = im.transpose(Transpose.FLIP_TOP_BOTTOM)
- assert out.mode == mode
- assert out.size == im.size
+@pytest.mark.parametrize("mode", HOPPER)
+def test_flip_top_bottom(mode):
+ im = HOPPER[mode]
+ out = im.transpose(Transpose.FLIP_TOP_BOTTOM)
+ assert out.mode == mode
+ assert out.size == im.size
- x, y = im.size
- assert im.getpixel((1, 1)) == out.getpixel((1, y - 2))
- assert im.getpixel((x - 2, 1)) == out.getpixel((x - 2, y - 2))
- assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
- assert im.getpixel((x - 2, y - 2)) == out.getpixel((x - 2, 1))
-
- for mode in HOPPER:
- transpose(mode)
+ x, y = im.size
+ assert im.getpixel((1, 1)) == out.getpixel((1, y - 2))
+ assert im.getpixel((x - 2, 1)) == out.getpixel((x - 2, y - 2))
+ assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
+ assert im.getpixel((x - 2, y - 2)) == out.getpixel((x - 2, 1))
-def test_rotate_90():
- def transpose(mode):
- im = HOPPER[mode]
- out = im.transpose(Transpose.ROTATE_90)
- assert out.mode == mode
- assert out.size == im.size[::-1]
+@pytest.mark.parametrize("mode", HOPPER)
+def test_rotate_90(mode):
+ im = HOPPER[mode]
+ out = im.transpose(Transpose.ROTATE_90)
+ assert out.mode == mode
+ assert out.size == im.size[::-1]
- x, y = im.size
- assert im.getpixel((1, 1)) == out.getpixel((1, x - 2))
- assert im.getpixel((x - 2, 1)) == out.getpixel((1, 1))
- assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, x - 2))
- assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, 1))
-
- for mode in HOPPER:
- transpose(mode)
+ x, y = im.size
+ assert im.getpixel((1, 1)) == out.getpixel((1, x - 2))
+ assert im.getpixel((x - 2, 1)) == out.getpixel((1, 1))
+ assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, x - 2))
+ assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, 1))
-def test_rotate_180():
- def transpose(mode):
- im = HOPPER[mode]
- out = im.transpose(Transpose.ROTATE_180)
- assert out.mode == mode
- assert out.size == im.size
+@pytest.mark.parametrize("mode", HOPPER)
+def test_rotate_180(mode):
+ im = HOPPER[mode]
+ out = im.transpose(Transpose.ROTATE_180)
+ assert out.mode == mode
+ assert out.size == im.size
- x, y = im.size
- assert im.getpixel((1, 1)) == out.getpixel((x - 2, y - 2))
- assert im.getpixel((x - 2, 1)) == out.getpixel((1, y - 2))
- assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, 1))
- assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
-
- for mode in HOPPER:
- transpose(mode)
+ x, y = im.size
+ assert im.getpixel((1, 1)) == out.getpixel((x - 2, y - 2))
+ assert im.getpixel((x - 2, 1)) == out.getpixel((1, y - 2))
+ assert im.getpixel((1, y - 2)) == out.getpixel((x - 2, 1))
+ assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
-def test_rotate_270():
- def transpose(mode):
- im = HOPPER[mode]
- out = im.transpose(Transpose.ROTATE_270)
- assert out.mode == mode
- assert out.size == im.size[::-1]
+@pytest.mark.parametrize("mode", HOPPER)
+def test_rotate_270(mode):
+ im = HOPPER[mode]
+ out = im.transpose(Transpose.ROTATE_270)
+ assert out.mode == mode
+ assert out.size == im.size[::-1]
- x, y = im.size
- assert im.getpixel((1, 1)) == out.getpixel((y - 2, 1))
- assert im.getpixel((x - 2, 1)) == out.getpixel((y - 2, x - 2))
- assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
- assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, x - 2))
-
- for mode in HOPPER:
- transpose(mode)
+ x, y = im.size
+ assert im.getpixel((1, 1)) == out.getpixel((y - 2, 1))
+ assert im.getpixel((x - 2, 1)) == out.getpixel((y - 2, x - 2))
+ assert im.getpixel((1, y - 2)) == out.getpixel((1, 1))
+ assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, x - 2))
-def test_transpose():
- def transpose(mode):
- im = HOPPER[mode]
- out = im.transpose(Transpose.TRANSPOSE)
- assert out.mode == mode
- assert out.size == im.size[::-1]
+@pytest.mark.parametrize("mode", HOPPER)
+def test_transpose(mode):
+ im = HOPPER[mode]
+ out = im.transpose(Transpose.TRANSPOSE)
+ assert out.mode == mode
+ assert out.size == im.size[::-1]
- x, y = im.size
- assert im.getpixel((1, 1)) == out.getpixel((1, 1))
- assert im.getpixel((x - 2, 1)) == out.getpixel((1, x - 2))
- assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, 1))
- assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, x - 2))
-
- for mode in HOPPER:
- transpose(mode)
+ x, y = im.size
+ assert im.getpixel((1, 1)) == out.getpixel((1, 1))
+ assert im.getpixel((x - 2, 1)) == out.getpixel((1, x - 2))
+ assert im.getpixel((1, y - 2)) == out.getpixel((y - 2, 1))
+ assert im.getpixel((x - 2, y - 2)) == out.getpixel((y - 2, x - 2))
-def test_tranverse():
- def transpose(mode):
- im = HOPPER[mode]
- out = im.transpose(Transpose.TRANSVERSE)
- assert out.mode == mode
- assert out.size == im.size[::-1]
+@pytest.mark.parametrize("mode", HOPPER)
+def test_tranverse(mode):
+ im = HOPPER[mode]
+ out = im.transpose(Transpose.TRANSVERSE)
+ assert out.mode == mode
+ assert out.size == im.size[::-1]
- x, y = im.size
- assert im.getpixel((1, 1)) == out.getpixel((y - 2, x - 2))
- assert im.getpixel((x - 2, 1)) == out.getpixel((y - 2, 1))
- assert im.getpixel((1, y - 2)) == out.getpixel((1, x - 2))
- assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
-
- for mode in HOPPER:
- transpose(mode)
+ x, y = im.size
+ assert im.getpixel((1, 1)) == out.getpixel((y - 2, x - 2))
+ assert im.getpixel((x - 2, 1)) == out.getpixel((y - 2, 1))
+ assert im.getpixel((1, y - 2)) == out.getpixel((1, x - 2))
+ assert im.getpixel((x - 2, y - 2)) == out.getpixel((1, 1))
-def test_roundtrip():
- for mode in HOPPER:
- im = HOPPER[mode]
+@pytest.mark.parametrize("mode", HOPPER)
+def test_roundtrip(mode):
+ im = HOPPER[mode]
- def transpose(first, second):
- return im.transpose(first).transpose(second)
+ def transpose(first, second):
+ return im.transpose(first).transpose(second)
- assert_image_equal(
- im, transpose(Transpose.FLIP_LEFT_RIGHT, Transpose.FLIP_LEFT_RIGHT)
- )
- assert_image_equal(
- im, transpose(Transpose.FLIP_TOP_BOTTOM, Transpose.FLIP_TOP_BOTTOM)
- )
- assert_image_equal(im, transpose(Transpose.ROTATE_90, Transpose.ROTATE_270))
- assert_image_equal(im, transpose(Transpose.ROTATE_180, Transpose.ROTATE_180))
- assert_image_equal(
- im.transpose(Transpose.TRANSPOSE),
- transpose(Transpose.ROTATE_90, Transpose.FLIP_TOP_BOTTOM),
- )
- assert_image_equal(
- im.transpose(Transpose.TRANSPOSE),
- transpose(Transpose.ROTATE_270, Transpose.FLIP_LEFT_RIGHT),
- )
- assert_image_equal(
- im.transpose(Transpose.TRANSVERSE),
- transpose(Transpose.ROTATE_90, Transpose.FLIP_LEFT_RIGHT),
- )
- assert_image_equal(
- im.transpose(Transpose.TRANSVERSE),
- transpose(Transpose.ROTATE_270, Transpose.FLIP_TOP_BOTTOM),
- )
- assert_image_equal(
- im.transpose(Transpose.TRANSVERSE),
- transpose(Transpose.ROTATE_180, Transpose.TRANSPOSE),
- )
+ assert_image_equal(
+ im, transpose(Transpose.FLIP_LEFT_RIGHT, Transpose.FLIP_LEFT_RIGHT)
+ )
+ assert_image_equal(
+ im, transpose(Transpose.FLIP_TOP_BOTTOM, Transpose.FLIP_TOP_BOTTOM)
+ )
+ assert_image_equal(im, transpose(Transpose.ROTATE_90, Transpose.ROTATE_270))
+ assert_image_equal(im, transpose(Transpose.ROTATE_180, Transpose.ROTATE_180))
+ assert_image_equal(
+ im.transpose(Transpose.TRANSPOSE),
+ transpose(Transpose.ROTATE_90, Transpose.FLIP_TOP_BOTTOM),
+ )
+ assert_image_equal(
+ im.transpose(Transpose.TRANSPOSE),
+ transpose(Transpose.ROTATE_270, Transpose.FLIP_LEFT_RIGHT),
+ )
+ assert_image_equal(
+ im.transpose(Transpose.TRANSVERSE),
+ transpose(Transpose.ROTATE_90, Transpose.FLIP_LEFT_RIGHT),
+ )
+ assert_image_equal(
+ im.transpose(Transpose.TRANSVERSE),
+ transpose(Transpose.ROTATE_270, Transpose.FLIP_TOP_BOTTOM),
+ )
+ assert_image_equal(
+ im.transpose(Transpose.TRANSVERSE),
+ transpose(Transpose.ROTATE_180, Transpose.TRANSPOSE),
+ )
diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py
index 23bc756bb..d1dd1e47c 100644
--- a/Tests/test_imagedraw.py
+++ b/Tests/test_imagedraw.py
@@ -625,20 +625,20 @@ def test_polygon2():
helper_polygon(POINTS2)
-def test_polygon_kite():
+@pytest.mark.parametrize("mode", ("RGB", "L"))
+def test_polygon_kite(mode):
# Test drawing lines of different gradients (dx>dy, dy>dx) and
# vertical (dx==0) and horizontal (dy==0) lines
- for mode in ["RGB", "L"]:
- # Arrange
- im = Image.new(mode, (W, H))
- draw = ImageDraw.Draw(im)
- expected = f"Tests/images/imagedraw_polygon_kite_{mode}.png"
+ # Arrange
+ im = Image.new(mode, (W, H))
+ draw = ImageDraw.Draw(im)
+ expected = f"Tests/images/imagedraw_polygon_kite_{mode}.png"
- # Act
- draw.polygon(KITE_POINTS, fill="blue", outline="yellow")
+ # Act
+ draw.polygon(KITE_POINTS, fill="blue", outline="yellow")
- # Assert
- assert_image_equal_tofile(im, expected)
+ # Assert
+ assert_image_equal_tofile(im, expected)
def test_polygon_1px_high():
@@ -1314,6 +1314,23 @@ def test_stroke_multiline():
assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3)
+def test_setting_default_font():
+ # Arrange
+ im = Image.new("RGB", (100, 250))
+ draw = ImageDraw.Draw(im)
+ font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 120)
+
+ # Act
+ ImageDraw.ImageDraw.font = font
+
+ # Assert
+ try:
+ assert draw.getfont() == font
+ finally:
+ ImageDraw.ImageDraw.font = None
+ assert isinstance(draw.getfont(), ImageFont.ImageFont)
+
+
def test_same_color_outline():
# Prepare shape
x0, y0 = 5, 5
diff --git a/Tests/test_qt_image_toqimage.py b/Tests/test_qt_image_toqimage.py
index 60bfaeb9b..c1983031a 100644
--- a/Tests/test_qt_image_toqimage.py
+++ b/Tests/test_qt_image_toqimage.py
@@ -16,32 +16,32 @@ if ImageQt.qt_is_installed:
from PIL.ImageQt import QImage
-def test_sanity(tmp_path):
- for mode in ("RGB", "RGBA", "L", "P", "1"):
- src = hopper(mode)
- data = ImageQt.toqimage(src)
+@pytest.mark.parametrize("mode", ("RGB", "RGBA", "L", "P", "1"))
+def test_sanity(mode, tmp_path):
+ src = hopper(mode)
+ data = ImageQt.toqimage(src)
- assert isinstance(data, QImage)
- assert not data.isNull()
+ assert isinstance(data, QImage)
+ assert not data.isNull()
- # reload directly from the qimage
- rt = ImageQt.fromqimage(data)
- if mode in ("L", "P", "1"):
- assert_image_equal(rt, src.convert("RGB"))
- else:
- assert_image_equal(rt, src)
+ # reload directly from the qimage
+ rt = ImageQt.fromqimage(data)
+ if mode in ("L", "P", "1"):
+ assert_image_equal(rt, src.convert("RGB"))
+ else:
+ assert_image_equal(rt, src)
- if mode == "1":
- # BW appears to not save correctly on QT4 and QT5
- # kicks out errors on console:
- # libpng warning: Invalid color type/bit depth combination
- # in IHDR
- # libpng error: Invalid IHDR data
- continue
+ if mode == "1":
+ # BW appears to not save correctly on QT5
+ # kicks out errors on console:
+ # libpng warning: Invalid color type/bit depth combination
+ # in IHDR
+ # libpng error: Invalid IHDR data
+ return
- # Test saving the file
- tempfile = str(tmp_path / f"temp_{mode}.png")
- data.save(tempfile)
+ # Test saving the file
+ tempfile = str(tmp_path / f"temp_{mode}.png")
+ data.save(tempfile)
- # Check that it actually worked.
- assert_image_equal_tofile(src, tempfile)
+ # Check that it actually worked.
+ assert_image_equal_tofile(src, tempfile)
diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh
index 9b3088b94..64dd024bd 100755
--- a/depends/install_imagequant.sh
+++ b/depends/install_imagequant.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# 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
diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst
index 1728c8e05..7db7b117a 100644
--- a/docs/handbook/image-file-formats.rst
+++ b/docs/handbook/image-file-formats.rst
@@ -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"``,
``"packbits"``, ``"tiff_adobe_deflate"``, ``"tiff_ccitt"``, ``"tiff_lzw"``,
``"tiff_raw_16"``, ``"tiff_sgilog"``, ``"tiff_sgilog24"``, ``"tiff_thunderscan"``,
- ``"webp"`, ``"zstd"``
+ ``"webp"``, ``"zstd"``
**quality**
The image quality for JPEG compression, on a scale from 0 (worst) to 100
diff --git a/docs/installation.rst b/docs/installation.rst
index f147fa6a7..bb547c1ad 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -166,7 +166,7 @@ Many of Pillow's features require external libraries:
* **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
the Pillow license, therefore we will not be distributing binaries
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.
-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 \
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst
index c2d72c804..1ef9079fb 100644
--- a/docs/reference/ImageDraw.rst
+++ b/docs/reference/ImageDraw.rst
@@ -64,7 +64,7 @@ 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
contains font metrics, the latter raster data.
@@ -146,6 +146,11 @@ Methods
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.
.. py:method:: ImageDraw.arc(xy, start, end, fill=None, width=0)
diff --git a/docs/releasenotes/9.3.0.rst b/docs/releasenotes/9.3.0.rst
index c64423b01..7109a09f2 100644
--- a/docs/releasenotes/9.3.0.rst
+++ b/docs/releasenotes/9.3.0.rst
@@ -26,6 +26,16 @@ TODO
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
^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py
index 8970471d3..e84dafb12 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -46,6 +46,8 @@ directly.
class ImageDraw:
+ font = None
+
def __init__(self, im, mode=None):
"""
Create a drawing instance.
@@ -86,12 +88,16 @@ class ImageDraw:
else:
self.fontmode = "L" # aliasing is okay for other modes
self.fill = 0
- self.font = None
def getfont(self):
"""
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."""
if not self.font:
# FIXME: should add a font repository
diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c
index 777c5fdd7..098eb583a 100644
--- a/src/libImaging/Convert.c
+++ b/src/libImaging/Convert.c
@@ -1310,6 +1310,10 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) {
if (!imOut) {
return NULL;
}
+ if (strcmp(mode, "P") == 0) {
+ ImagingPaletteDelete(imOut->palette);
+ imOut->palette = ImagingPaletteDuplicate(imIn->palette);
+ }
ImagingSectionEnter(&cookie);
for (y = 0; y < imIn->ysize; y++) {
diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py
index a381d636d..94e5dd871 100644
--- a/winbuild/build_prepare.py
+++ b/winbuild/build_prepare.py
@@ -226,21 +226,21 @@ deps = {
"filename": "lcms2-2.13.1.tar.gz",
"dir": "lcms2-2.13.1",
"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
"MultiThreaded": "MultiThreadedDLL", # noqa: E501
# retarget to default toolset (selected by vcvarsall.bat)
- "v142": "$(DefaultPlatformToolset)", # noqa: E501
+ "v143": "$(DefaultPlatformToolset)", # noqa: E501
# retarget to latest (selected by vcvarsall.bat)
"10.0": "$(WindowsSDKVersion)", # noqa: E501
}
},
"build": [
cmd_rmdir("Lib"),
- cmd_rmdir(r"Projects\VC2019\Release"),
- cmd_msbuild(r"Projects\VC2019\lcms2.sln", "Release", "Clean"),
+ cmd_rmdir(r"Projects\VC2022\Release"),
+ cmd_msbuild(r"Projects\VC2022\lcms2.sln", "Release", "Clean"),
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}"),
],