From 8f05310ac16810c79837d6bcdbe3f95eab4db23f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 10 Sep 2026 19:39:12 +1000 Subject: [PATCH 1/2] Add _FilterArgs NamedTuple to ImageFilter --- Tests/test_image_filter.py | 2 +- src/PIL/ImageFilter.py | 53 +++++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 24 deletions(-) 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..5ee756e4fc1 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,16 @@ class MultibandFilter(Filter): pass +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 +87,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 +272,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): From 749c66e9056a69bef8fa1d193fa106540ba6fcd4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 7 Sep 2026 20:46:33 +1000 Subject: [PATCH 2/2] Add comment --- src/PIL/ImageFilter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 5ee756e4fc1..2a0fcea93e0 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -40,6 +40,7 @@ class MultibandFilter(Filter): pass +# Arguments passed to the core filter method class _FilterArgs(NamedTuple): size: tuple[int, int] scale: float