diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 0a4abdae1b6..84104d23f14 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -76,7 +76,7 @@ def test_sanity_error(mode: str) -> None: def test_noop_on_small_images() -> None: # If image is smaller than the kernel size, return it as-is. - kernel_size: tuple[int, int] = ImageFilter.SMOOTH_MORE.filterargs[0] + kernel_size: tuple[int, int] = ImageFilter.SMOOTH_MORE.filterargs.size kernel_w, kernel_h = kernel_size for w in range(1, kernel_w): for h in range(1, kernel_h): diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 5d6a1d5ec6c..2a0fcea93e0 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -18,7 +18,7 @@ import abc import math -from typing import cast +from typing import NamedTuple, cast TYPE_CHECKING = False if TYPE_CHECKING: @@ -40,9 +40,17 @@ class MultibandFilter(Filter): pass +# Arguments passed to the core filter method +class _FilterArgs(NamedTuple): + size: tuple[int, int] + scale: float + offset: float + kernel: Sequence[float] + + class BuiltinFilter(MultibandFilter): name: str - filterargs: tuple[Any, ...] + filterargs: _FilterArgs def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: if image.mode == "P": @@ -80,7 +88,7 @@ def __init__( if size[0] * size[1] != len(kernel): msg = "not enough coefficients in kernel" raise ValueError(msg) - self.filterargs = size, scale, offset, kernel + self.filterargs = _FilterArgs(size, scale, offset, kernel) class RankFilter(Filter): @@ -265,96 +273,96 @@ def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: class BLUR(BuiltinFilter): name = "Blur" - filterargs = (5, 5), 16, 0, ( + filterargs = _FilterArgs(size=(5, 5), scale=16, offset=0, kernel=( 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, - ) # fmt: skip + )) # fmt: skip class CONTOUR(BuiltinFilter): name = "Contour" - filterargs = (3, 3), 1, 255, ( + filterargs = _FilterArgs(size=(3, 3), scale=1, offset=255, kernel=( -1, -1, -1, -1, 8, -1, -1, -1, -1, - ) # fmt: skip + )) # fmt: skip class DETAIL(BuiltinFilter): name = "Detail" - filterargs = (3, 3), 6, 0, ( + filterargs = _FilterArgs(size=(3, 3), scale=6, offset=0, kernel=( 0, -1, 0, -1, 10, -1, 0, -1, 0, - ) # fmt: skip + )) # fmt: skip class EDGE_ENHANCE(BuiltinFilter): name = "Edge-enhance" - filterargs = (3, 3), 2, 0, ( + filterargs = _FilterArgs(size=(3, 3), scale=2, offset=0, kernel=( -1, -1, -1, -1, 10, -1, -1, -1, -1, - ) # fmt: skip + )) # fmt: skip class EDGE_ENHANCE_MORE(BuiltinFilter): name = "Edge-enhance More" - filterargs = (3, 3), 1, 0, ( + filterargs = _FilterArgs(size=(3, 3), scale=1, offset=0, kernel=( -1, -1, -1, -1, 9, -1, -1, -1, -1, - ) # fmt: skip + )) # fmt: skip class EMBOSS(BuiltinFilter): name = "Emboss" - filterargs = (3, 3), 1, 128, ( + filterargs = _FilterArgs(size=(3, 3), scale=1, offset=128, kernel=( -1, 0, 0, 0, 1, 0, 0, 0, 0, - ) # fmt: skip + )) # fmt: skip class FIND_EDGES(BuiltinFilter): name = "Find Edges" - filterargs = (3, 3), 1, 0, ( + filterargs = _FilterArgs(size=(3, 3), scale=1, offset=0, kernel=( -1, -1, -1, -1, 8, -1, -1, -1, -1, - ) # fmt: skip + )) # fmt: skip class SHARPEN(BuiltinFilter): name = "Sharpen" - filterargs = (3, 3), 16, 0, ( + filterargs = _FilterArgs(size=(3, 3), scale=16, offset=0, kernel=( -2, -2, -2, -2, 32, -2, -2, -2, -2, - ) # fmt: skip + )) # fmt: skip class SMOOTH(BuiltinFilter): name = "Smooth" - filterargs = (3, 3), 13, 0, ( + filterargs = _FilterArgs(size=(3, 3), scale=13, offset=0, kernel=( 1, 1, 1, 1, 5, 1, 1, 1, 1, - ) # fmt: skip + )) # fmt: skip class SMOOTH_MORE(BuiltinFilter): name = "Smooth More" - filterargs = (5, 5), 100, 0, ( + filterargs = _FilterArgs(size=(5, 5), scale=100, offset=0, kernel=( 1, 1, 1, 1, 1, 1, 5, 5, 5, 1, 1, 5, 44, 5, 1, 1, 5, 5, 5, 1, 1, 1, 1, 1, 1, - ) # fmt: skip + )) # fmt: skip class Color3DLUT(MultibandFilter):