From b430b02a31e5452c61c11ccfdb8a14941ee2f405 Mon Sep 17 00:00:00 2001
From: drhead
Date: Tue, 11 Mar 2025 15:27:14 -0400
Subject: [PATCH 1/7] Add support for Magic Kernel Sharp 2021 resampling
---
src/PIL/Image.py | 7 ++++++-
src/libImaging/Imaging.h | 1 +
src/libImaging/Resample.c | 22 ++++++++++++++++++++++
3 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index 884659882..1a43e723d 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -169,6 +169,7 @@ class Resampling(IntEnum):
HAMMING = 5
BICUBIC = 3
LANCZOS = 1
+ MKS2021 = 6
_filters_support = {
@@ -177,6 +178,7 @@ _filters_support = {
Resampling.HAMMING: 1.0,
Resampling.BICUBIC: 2.0,
Resampling.LANCZOS: 3.0,
+ Resampling.MKS2021: 4.5,
}
@@ -2242,6 +2244,7 @@ class Image:
Resampling.LANCZOS,
Resampling.BOX,
Resampling.HAMMING,
+ Resampling.MKS2021,
):
msg = f"Unknown resampling filter ({resample})."
@@ -2254,6 +2257,7 @@ class Image:
(Resampling.BICUBIC, "Image.Resampling.BICUBIC"),
(Resampling.BOX, "Image.Resampling.BOX"),
(Resampling.HAMMING, "Image.Resampling.HAMMING"),
+ (Resampling.MKS2021, "Image.Resampling.MKS2021"),
)
]
msg += f" Use {', '.join(filters[:-1])} or {filters[-1]}"
@@ -2904,11 +2908,12 @@ class Image:
Resampling.BILINEAR,
Resampling.BICUBIC,
):
- if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS):
+ if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS, Resampling.MKS2021):
unusable: dict[int, str] = {
Resampling.BOX: "Image.Resampling.BOX",
Resampling.HAMMING: "Image.Resampling.HAMMING",
Resampling.LANCZOS: "Image.Resampling.LANCZOS",
+ Resampling.MKS2021: "Image.Resampling.MKS2021",
}
msg = unusable[resample] + f" ({resample}) cannot be used."
else:
diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h
index 0c2d3fc2e..573a08f0a 100644
--- a/src/libImaging/Imaging.h
+++ b/src/libImaging/Imaging.h
@@ -269,6 +269,7 @@ ImagingError_Clear(void);
#define IMAGING_TRANSFORM_HAMMING 5
#define IMAGING_TRANSFORM_BICUBIC 3
#define IMAGING_TRANSFORM_LANCZOS 1
+#define IMAGING_TRANSFORM_MKS2021 6
typedef int (*ImagingTransformMap)(double *X, double *Y, int x, int y, void *data);
typedef int (*ImagingTransformFilter)(void *out, Imaging im, double x, double y);
diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c
index f5e386dc2..b723f6f7b 100644
--- a/src/libImaging/Resample.c
+++ b/src/libImaging/Resample.c
@@ -79,11 +79,30 @@ lanczos_filter(double x) {
return 0.0;
}
+static inline double
+mks_2021_filter(double x) {
+ /* https://johncostella.com/magic/ */
+ if (x < 0.0)
+ x = -x;
+ if (x < 0.5)
+ return 577.0/576.0 - 239.0/144.0 * pow(x, 2.0);
+ if (x < 1.5)
+ return 35.0/36.0 * (x - 1.0) * (x - 239.0/140.0);
+ if (x < 2.5)
+ return 1.0/6.0 * (x - 2.0) * (65.0/24.0 - x);
+ if (x < 3.5)
+ return 1.0/36.0 * (x - 3.0) * (x - 15.0/4.0);
+ if (x < 4.5)
+ return -1.0/288.0 * pow(x - 9.0/2.0, 2.0);
+ return(0.0);
+}
+
static struct filter BOX = {box_filter, 0.5};
static struct filter BILINEAR = {bilinear_filter, 1.0};
static struct filter HAMMING = {hamming_filter, 1.0};
static struct filter BICUBIC = {bicubic_filter, 2.0};
static struct filter LANCZOS = {lanczos_filter, 3.0};
+static struct filter MKS2021 = {mks_2021_filter, 4.5};
/* 8 bits for result. Filter can have negative areas.
In one cases the sum of the coefficients will be negative,
@@ -693,6 +712,9 @@ ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) {
case IMAGING_TRANSFORM_LANCZOS:
filterp = &LANCZOS;
break;
+ case IMAGING_TRANSFORM_MKS2021:
+ filterp = &MKS2021;
+ break;
default:
return (Imaging)ImagingError_ValueError("unsupported resampling filter");
}
From acde784811428263cef38564486bfaed9cd6b690 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 11 Mar 2025 19:31:18 +0000
Subject: [PATCH 2/7] [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---
src/PIL/Image.py | 7 ++++++-
src/libImaging/Resample.c | 12 ++++++------
2 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index 1a43e723d..f94d307f6 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -2908,7 +2908,12 @@ class Image:
Resampling.BILINEAR,
Resampling.BICUBIC,
):
- if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS, Resampling.MKS2021):
+ if resample in (
+ Resampling.BOX,
+ Resampling.HAMMING,
+ Resampling.LANCZOS,
+ Resampling.MKS2021,
+ ):
unusable: dict[int, str] = {
Resampling.BOX: "Image.Resampling.BOX",
Resampling.HAMMING: "Image.Resampling.HAMMING",
diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c
index b723f6f7b..7b8e289bd 100644
--- a/src/libImaging/Resample.c
+++ b/src/libImaging/Resample.c
@@ -85,16 +85,16 @@ mks_2021_filter(double x) {
if (x < 0.0)
x = -x;
if (x < 0.5)
- return 577.0/576.0 - 239.0/144.0 * pow(x, 2.0);
+ return 577.0 / 576.0 - 239.0 / 144.0 * pow(x, 2.0);
if (x < 1.5)
- return 35.0/36.0 * (x - 1.0) * (x - 239.0/140.0);
+ return 35.0 / 36.0 * (x - 1.0) * (x - 239.0 / 140.0);
if (x < 2.5)
- return 1.0/6.0 * (x - 2.0) * (65.0/24.0 - x);
+ return 1.0 / 6.0 * (x - 2.0) * (65.0 / 24.0 - x);
if (x < 3.5)
- return 1.0/36.0 * (x - 3.0) * (x - 15.0/4.0);
+ return 1.0 / 36.0 * (x - 3.0) * (x - 15.0 / 4.0);
if (x < 4.5)
- return -1.0/288.0 * pow(x - 9.0/2.0, 2.0);
- return(0.0);
+ return -1.0 / 288.0 * pow(x - 9.0 / 2.0, 2.0);
+ return (0.0);
}
static struct filter BOX = {box_filter, 0.5};
From 81c115825b10c12dd95643cf587531ad893f475d Mon Sep 17 00:00:00 2001
From: drhead
Date: Fri, 4 Apr 2025 16:11:20 -0400
Subject: [PATCH 3/7] update tests and docs
---
Tests/test_image_resample.py | 14 ++++++++++++++
Tests/test_image_resize.py | 8 ++++++++
docs/handbook/concepts.rst | 21 +++++++++++++++++++++
src/PIL/Image.py | 14 +++++++++++---
src/libImaging/Imaging.h | 3 ++-
src/libImaging/Resample.c | 18 ++++++++++++++++++
6 files changed, 74 insertions(+), 4 deletions(-)
diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py
index ce6209c0d..6acb9de23 100644
--- a/Tests/test_image_resample.py
+++ b/Tests/test_image_resample.py
@@ -309,6 +309,8 @@ class TestCoreResampleAlphaCorrect:
self.run_levels_case(case.resize((512, 32), Image.Resampling.HAMMING))
self.run_levels_case(case.resize((512, 32), Image.Resampling.BICUBIC))
self.run_levels_case(case.resize((512, 32), Image.Resampling.LANCZOS))
+ self.run_levels_case(case.resize((512, 32), Image.Resampling.MKS2013))
+ self.run_levels_case(case.resize((512, 32), Image.Resampling.MKS2021))
@pytest.mark.xfail(reason="Current implementation isn't precise enough")
def test_levels_la(self) -> None:
@@ -318,6 +320,8 @@ class TestCoreResampleAlphaCorrect:
self.run_levels_case(case.resize((512, 32), Image.Resampling.HAMMING))
self.run_levels_case(case.resize((512, 32), Image.Resampling.BICUBIC))
self.run_levels_case(case.resize((512, 32), Image.Resampling.LANCZOS))
+ self.run_levels_case(case.resize((512, 32), Image.Resampling.MKS2013))
+ self.run_levels_case(case.resize((512, 32), Image.Resampling.MKS2021))
def make_dirty_case(
self, mode: str, clean_pixel: tuple[int, ...], dirty_pixel: tuple[int, ...]
@@ -360,6 +364,12 @@ class TestCoreResampleAlphaCorrect:
self.run_dirty_case(
case.resize((20, 20), Image.Resampling.LANCZOS), (255, 255, 0)
)
+ self.run_dirty_case(
+ case.resize((20, 20), Image.Resampling.MKS2013), (255, 255, 0)
+ )
+ self.run_dirty_case(
+ case.resize((20, 20), Image.Resampling.MKS2021), (255, 255, 0)
+ )
def test_dirty_pixels_la(self) -> None:
case = self.make_dirty_case("LA", (255, 128), (0, 0))
@@ -368,6 +378,8 @@ class TestCoreResampleAlphaCorrect:
self.run_dirty_case(case.resize((20, 20), Image.Resampling.HAMMING), (255,))
self.run_dirty_case(case.resize((20, 20), Image.Resampling.BICUBIC), (255,))
self.run_dirty_case(case.resize((20, 20), Image.Resampling.LANCZOS), (255,))
+ self.run_dirty_case(case.resize((20, 20), Image.Resampling.MKS2013), (255,))
+ self.run_dirty_case(case.resize((20, 20), Image.Resampling.MKS2021), (255,))
class TestCoreResamplePasses:
@@ -453,6 +465,8 @@ class TestCoreResampleBox:
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
+ Image.Resampling.MKS2013,
+ Image.Resampling.MKS2021,
),
)
def test_wrong_arguments(self, resample: Image.Resampling) -> None:
diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py
index 1166371b8..59393552d 100644
--- a/Tests/test_image_resize.py
+++ b/Tests/test_image_resize.py
@@ -72,6 +72,8 @@ class TestImagingCoreResize:
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
+ Image.Resampling.MKS2013,
+ Image.Resampling.MKS2021,
),
)
def test_reduce_filters(self, resample: Image.Resampling) -> None:
@@ -88,6 +90,8 @@ class TestImagingCoreResize:
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
+ Image.Resampling.MKS2013,
+ Image.Resampling.MKS2021,
),
)
def test_enlarge_filters(self, resample: Image.Resampling) -> None:
@@ -104,6 +108,8 @@ class TestImagingCoreResize:
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
+ Image.Resampling.MKS2013,
+ Image.Resampling.MKS2021,
),
)
@pytest.mark.parametrize(
@@ -154,6 +160,8 @@ class TestImagingCoreResize:
Image.Resampling.HAMMING,
Image.Resampling.BICUBIC,
Image.Resampling.LANCZOS,
+ Image.Resampling.MKS2013,
+ Image.Resampling.MKS2021,
),
)
def test_enlarge_zero(self, resample: Image.Resampling) -> None:
diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst
index 7da1078c1..a9cef3847 100644
--- a/docs/handbook/concepts.rst
+++ b/docs/handbook/concepts.rst
@@ -195,6 +195,23 @@ pixel, the Python Imaging Library provides different resampling *filters*.
.. versionadded:: 1.1.3
+.. data:: Resampling.MKS2013
+ :noindex:
+
+ Calculate the output pixel value using the Magic Kernel Sharp 2013 filter
+ (a quadratic B-spline composed with a sharpening kernel) on all pixels that
+ may contribute to the output value. This filter can only be used with the
+ :py:meth:`~PIL.Image.Image.resize` and :py:meth:`~PIL.Image.Image.thumbnail`
+ methods.
+
+.. data:: Resampling.MKS2021
+ :noindex:
+
+ Calculate the output pixel value using the Magic Kernel Sharp 2021 filter
+ (a quadratic B-spline composed with a sharpening kernel) on all pixels that
+ may contribute to the output value. This filter can only be used with the
+ :py:meth:`~PIL.Image.Image.resize` and :py:meth:`~PIL.Image.Image.thumbnail`
+ methods.
Filters comparison table
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -215,3 +232,7 @@ Filters comparison table
+---------------------------+-------------+-----------+-------------+
|:data:`Resampling.LANCZOS` | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐ |
+---------------------------+-------------+-----------+-------------+
+|:data:`Resampling.MKS2013` | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐ |
++---------------------------+-------------+-----------+-------------+
+|:data:`Resampling.MKS2021` | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐ |
++---------------------------+-------------+-----------+-------------+
\ No newline at end of file
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index f94d307f6..85adf954e 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -169,7 +169,8 @@ class Resampling(IntEnum):
HAMMING = 5
BICUBIC = 3
LANCZOS = 1
- MKS2021 = 6
+ MKS2013 = 6
+ MKS2021 = 7
_filters_support = {
@@ -178,6 +179,7 @@ _filters_support = {
Resampling.HAMMING: 1.0,
Resampling.BICUBIC: 2.0,
Resampling.LANCZOS: 3.0,
+ Resampling.MKS2013: 2.5,
Resampling.MKS2021: 4.5,
}
@@ -2209,7 +2211,8 @@ class Image:
:param resample: An optional resampling filter. This can be
one of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`,
:py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`,
- :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`.
+ :py:data:`Resampling.BICUBIC`, :py:data:`Resampling.LANCZOS`,
+ :py:data:`Resampling.MKS2013`, or :py:data:`Resampling.MKS2021`.
If the image has mode "1" or "P", it is always set to
:py:data:`Resampling.NEAREST`. If the image mode is "BGR;15",
"BGR;16" or "BGR;24", then the default filter is
@@ -2244,6 +2247,7 @@ class Image:
Resampling.LANCZOS,
Resampling.BOX,
Resampling.HAMMING,
+ Resampling.MKS2013,
Resampling.MKS2021,
):
msg = f"Unknown resampling filter ({resample})."
@@ -2257,6 +2261,7 @@ class Image:
(Resampling.BICUBIC, "Image.Resampling.BICUBIC"),
(Resampling.BOX, "Image.Resampling.BOX"),
(Resampling.HAMMING, "Image.Resampling.HAMMING"),
+ (Resampling.MKS2013, "Image.Resampling.MKS2013"),
(Resampling.MKS2021, "Image.Resampling.MKS2021"),
)
]
@@ -2694,7 +2699,8 @@ class Image:
:param resample: Optional resampling filter. This can be one
of :py:data:`Resampling.NEAREST`, :py:data:`Resampling.BOX`,
:py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`,
- :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`.
+ :py:data:`Resampling.BICUBIC`, :py:data:`Resampling.LANCZOS`,
+ :py:data:`Resampling.MKS2013`, or :py:data:`Resampling.MKS2021`.
If omitted, it defaults to :py:data:`Resampling.BICUBIC`.
(was :py:data:`Resampling.NEAREST` prior to version 2.5.0).
See: :ref:`concept-filters`.
@@ -2912,12 +2918,14 @@ class Image:
Resampling.BOX,
Resampling.HAMMING,
Resampling.LANCZOS,
+ Resampling.MKS2013,
Resampling.MKS2021,
):
unusable: dict[int, str] = {
Resampling.BOX: "Image.Resampling.BOX",
Resampling.HAMMING: "Image.Resampling.HAMMING",
Resampling.LANCZOS: "Image.Resampling.LANCZOS",
+ Resampling.MKS2013: "Image.Resampling.MKS2013",
Resampling.MKS2021: "Image.Resampling.MKS2021",
}
msg = unusable[resample] + f" ({resample}) cannot be used."
diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h
index 573a08f0a..5c9d94e44 100644
--- a/src/libImaging/Imaging.h
+++ b/src/libImaging/Imaging.h
@@ -269,7 +269,8 @@ ImagingError_Clear(void);
#define IMAGING_TRANSFORM_HAMMING 5
#define IMAGING_TRANSFORM_BICUBIC 3
#define IMAGING_TRANSFORM_LANCZOS 1
-#define IMAGING_TRANSFORM_MKS2021 6
+#define IMAGING_TRANSFORM_MKS2013 6
+#define IMAGING_TRANSFORM_MKS2021 7
typedef int (*ImagingTransformMap)(double *X, double *Y, int x, int y, void *data);
typedef int (*ImagingTransformFilter)(void *out, Imaging im, double x, double y);
diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c
index 7b8e289bd..aab377888 100644
--- a/src/libImaging/Resample.c
+++ b/src/libImaging/Resample.c
@@ -79,6 +79,20 @@ lanczos_filter(double x) {
return 0.0;
}
+static inline double
+mks_2013_filter(double x) {
+ /* https://johncostella.com/magic/ */
+ if (x < 0.0)
+ x = -x;
+ if (x < 0.5)
+ return 17.0 / 16.0 - 7.0 / 4.0 * pow(x, 2.0);
+ if (x < 1.5)
+ return (1.0 - x) * (7.0/4.0 - x);
+ if (x < 2.5)
+ return -1.0 / 8.0 * pow(x - 5.0 / 2.0, 2.0);
+ return (0.0);
+}
+
static inline double
mks_2021_filter(double x) {
/* https://johncostella.com/magic/ */
@@ -102,6 +116,7 @@ static struct filter BILINEAR = {bilinear_filter, 1.0};
static struct filter HAMMING = {hamming_filter, 1.0};
static struct filter BICUBIC = {bicubic_filter, 2.0};
static struct filter LANCZOS = {lanczos_filter, 3.0};
+static struct filter MKS2013 = {mks_2013_filter, 2.5};
static struct filter MKS2021 = {mks_2021_filter, 4.5};
/* 8 bits for result. Filter can have negative areas.
@@ -712,6 +727,9 @@ ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) {
case IMAGING_TRANSFORM_LANCZOS:
filterp = &LANCZOS;
break;
+ case IMAGING_TRANSFORM_MKS2013:
+ filterp = &MKS2013;
+ break;
case IMAGING_TRANSFORM_MKS2021:
filterp = &MKS2021;
break;
From 76b4dfb50001b870aaedabf85eba8cf1c99d3edc Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Fri, 4 Apr 2025 20:11:49 +0000
Subject: [PATCH 4/7] [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---
docs/handbook/concepts.rst | 2 +-
src/libImaging/Resample.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst
index a9cef3847..b0183a34a 100644
--- a/docs/handbook/concepts.rst
+++ b/docs/handbook/concepts.rst
@@ -235,4 +235,4 @@ Filters comparison table
|:data:`Resampling.MKS2013` | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐ |
+---------------------------+-------------+-----------+-------------+
|:data:`Resampling.MKS2021` | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐ |
-+---------------------------+-------------+-----------+-------------+
\ No newline at end of file
++---------------------------+-------------+-----------+-------------+
diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c
index aab377888..89deef5ec 100644
--- a/src/libImaging/Resample.c
+++ b/src/libImaging/Resample.c
@@ -87,7 +87,7 @@ mks_2013_filter(double x) {
if (x < 0.5)
return 17.0 / 16.0 - 7.0 / 4.0 * pow(x, 2.0);
if (x < 1.5)
- return (1.0 - x) * (7.0/4.0 - x);
+ return (1.0 - x) * (7.0 / 4.0 - x);
if (x < 2.5)
return -1.0 / 8.0 * pow(x - 5.0 / 2.0, 2.0);
return (0.0);
From 2f0458258f48e0108f5d45475425a4a477f18250 Mon Sep 17 00:00:00 2001
From: Andrew Murray
Date: Sat, 5 Apr 2025 14:50:35 +1100
Subject: [PATCH 5/7] Corrected table syntax
---
docs/handbook/concepts.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/handbook/concepts.rst b/docs/handbook/concepts.rst
index b0183a34a..070913e62 100644
--- a/docs/handbook/concepts.rst
+++ b/docs/handbook/concepts.rst
@@ -234,5 +234,5 @@ Filters comparison table
+---------------------------+-------------+-----------+-------------+
|:data:`Resampling.MKS2013` | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐ |
+---------------------------+-------------+-----------+-------------+
-|:data:`Resampling.MKS2021` | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐ |
+|:data:`Resampling.MKS2021` | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐| ⭐ |
+---------------------------+-------------+-----------+-------------+
From 91c19be6c8f78a3879104a8e788985b05f550ff0 Mon Sep 17 00:00:00 2001
From: drhead <1313496+drhead@users.noreply.github.com>
Date: Fri, 4 Apr 2025 23:12:00 -0400
Subject: [PATCH 6/7] add braces
Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com>
---
src/libImaging/Resample.c | 42 ++++++++++++++++++++++++---------------
1 file changed, 26 insertions(+), 16 deletions(-)
diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c
index 89deef5ec..0f399cdc6 100644
--- a/src/libImaging/Resample.c
+++ b/src/libImaging/Resample.c
@@ -82,33 +82,43 @@ lanczos_filter(double x) {
static inline double
mks_2013_filter(double x) {
/* https://johncostella.com/magic/ */
- if (x < 0.0)
+ if (x < 0) {
x = -x;
- if (x < 0.5)
- return 17.0 / 16.0 - 7.0 / 4.0 * pow(x, 2.0);
- if (x < 1.5)
+ }
+ if (x < 0.5) {
+ return 17.0 / 16.0 - 7.0 / 4.0 * pow(x, 2);
+ }
+ if (x < 1.5) {
return (1.0 - x) * (7.0 / 4.0 - x);
- if (x < 2.5)
- return -1.0 / 8.0 * pow(x - 5.0 / 2.0, 2.0);
- return (0.0);
+ }
+ if (x < 2.5) {
+ return -1.0 / 8.0 * pow(x - 5.0 / 2.0, 2);
+ }
+ return 0;
}
static inline double
mks_2021_filter(double x) {
/* https://johncostella.com/magic/ */
- if (x < 0.0)
+ if (x < 0) {
x = -x;
- if (x < 0.5)
- return 577.0 / 576.0 - 239.0 / 144.0 * pow(x, 2.0);
- if (x < 1.5)
+ }
+ if (x < 0.5) {
+ return 577.0 / 576.0 - 239.0 / 144.0 * pow(x, 2);
+ }
+ if (x < 1.5) {
return 35.0 / 36.0 * (x - 1.0) * (x - 239.0 / 140.0);
- if (x < 2.5)
+ }
+ if (x < 2.5) {
return 1.0 / 6.0 * (x - 2.0) * (65.0 / 24.0 - x);
- if (x < 3.5)
+ }
+ if (x < 3.5) {
return 1.0 / 36.0 * (x - 3.0) * (x - 15.0 / 4.0);
- if (x < 4.5)
- return -1.0 / 288.0 * pow(x - 9.0 / 2.0, 2.0);
- return (0.0);
+ }
+ if (x < 4.5) {
+ return -1.0 / 288.0 * pow(x - 9.0 / 2.0, 2);
+ }
+ return 0;
}
static struct filter BOX = {box_filter, 0.5};
From 6530c9c05537200d0948a35d40a5eded7c920ac7 Mon Sep 17 00:00:00 2001
From: Andrew Murray
Date: Mon, 14 Apr 2025 19:20:06 +1000
Subject: [PATCH 7/7] Added additional tests
---
Tests/test_image_resample.py | 56 ++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py
index 6acb9de23..fc22991f7 100644
--- a/Tests/test_image_resample.py
+++ b/Tests/test_image_resample.py
@@ -165,6 +165,32 @@ class TestImagingCoreResampleAccuracy:
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_mks2013(self, mode: str) -> None:
+ case = self.make_case(mode, (16, 16), 0xE1)
+ case = case.resize((8, 8), Image.Resampling.MKS2013)
+ # fmt: off
+ data = ("e1 e1 e9 dc"
+ "e1 e1 e9 dc"
+ "e9 e9 f1 e3"
+ "dc dc e4 d8")
+ # 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_mks2021(self, mode: str) -> None:
+ case = self.make_case(mode, (16, 16), 0xE1)
+ case = case.resize((8, 8), Image.Resampling.MKS2021)
+ # fmt: off
+ data = ("e1 e1 e3 d7"
+ "e1 e1 e3 d7"
+ "e3 e3 e5 d9"
+ "d7 d7 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_enlarge_box(self, mode: str) -> None:
case = self.make_case(mode, (2, 2), 0xE1)
@@ -226,6 +252,36 @@ class TestImagingCoreResampleAccuracy:
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_mks2013(self, mode: str) -> None:
+ case = self.make_case(mode, (6, 6), 0xE1)
+ case = case.resize((12, 12), Image.Resampling.MKS2013)
+ data = (
+ "e1 e1 e2 ef fb be"
+ "e1 e1 e2 ef fb be"
+ "e2 e2 e3 f1 fd bf"
+ "ef ef f0 ff ff c7"
+ "fb fb fc ff ff cf"
+ "be be bf c7 cf a8"
+ )
+ 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_mks2021(self, mode: str) -> None:
+ case = self.make_case(mode, (6, 6), 0xE1)
+ case = case.resize((12, 12), Image.Resampling.MKS2021)
+ data = (
+ "e3 e1 df e9 f5 bb"
+ "e1 df dd e7 f3 b9"
+ "df dd db e5 f1 b8"
+ "e9 e7 e5 ef fc be"
+ "f5 f3 f0 fc ff c5"
+ "bb ba b8 bf c6 a3"
+ )
+ for channel in case.split():
+ self.check_case(channel, self.make_sample(data, (12, 12)))
+
def test_box_filter_correct_range(self) -> None:
im = Image.new("RGB", (8, 8), "#1688ff").resize(
(100, 100), Image.Resampling.BOX