From 60e911ed7b9348af7327ea0197890dacab89c55f Mon Sep 17 00:00:00 2001 From: Anton Soloviev Date: Wed, 14 Oct 2026 13:33:37 -0700 Subject: [PATCH 1/3] Honor reducing_gap when resizing RGBA and LA images --- Tests/test_image_resize.py | 23 +++++++++++++++++++++++ Tests/test_image_thumbnail.py | 11 +++++++++++ docs/releasenotes/13.0.0.rst | 8 ++++++++ src/PIL/Image.py | 2 +- 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index f37fd4b04e7..04fc5333db3 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -200,6 +200,29 @@ def gradients_image() -> Generator[ImageFile.ImageFile]: class TestReducingGapResize: + @pytest.mark.parametrize( + "mode, opaque, transparent, expected", + ( + ("RGBA", (255, 255, 255, 255), (255, 0, 0, 0), (255, 255, 255, 64)), + ("LA", (255, 255), (255, 0), (255, 64)), + ), + ) + def test_reducing_gap_alpha( + self, + mode: str, + opaque: tuple[int, ...], + transparent: tuple[int, ...], + expected: tuple[int, ...], + ) -> None: + im = Image.new(mode, (16, 16), transparent) + for x in range(0, 16, 4): + im.paste(opaque, (x, 0, x + 1, 16)) + + # Each 4x4 block has four opaque white pixels and twelve transparent + # pixels. Integer reduction averages alpha to round(255 / 4) = 64. + resized = im.resize((4, 4), Image.Resampling.LANCZOS, reducing_gap=1.0) + assert_image_equal(resized, Image.new(mode, (4, 4), expected)) + def test_reducing_gap_values(self, gradients_image: ImageFile.ImageFile) -> None: ref = gradients_image.resize( (52, 34), Image.Resampling.BICUBIC, reducing_gap=None diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 00b47a2a70a..2bf50dc185d 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -154,6 +154,17 @@ def test_reducing_gap_values() -> None: assert_image_similar(ref, im, 3.5) +@pytest.mark.parametrize("mode", ("RGBA", "LA")) +def test_reducing_gap_alpha(mode: str) -> None: + im = Image.open("Tests/images/pil123rgba.png").convert(mode) + premultiplied = im.convert({"RGBA": "RGBa", "LA": "La"}[mode]) + premultiplied.thumbnail((16, 16), Image.Resampling.LANCZOS, reducing_gap=2.0) + expected = premultiplied.convert(mode) + + im.thumbnail((16, 16), Image.Resampling.LANCZOS) + assert_image_equal(im, expected) + + def test_reducing_gap_for_DCT_scaling() -> None: with Image.open("Tests/images/hopper.jpg") as ref: # thumbnail should call draft with reducing_gap scale diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index 0763dccf8fb..8085963afeb 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -99,6 +99,14 @@ intended for internal use, so there is no replacement. API changes =========== +Resizing images with alpha +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``reducing_gap`` argument to :py:meth:`~PIL.Image.Image.resize` is now +applied to RGBA and LA images. Previously, these modes ignored the argument, +including when called by :py:meth:`~PIL.Image.Image.thumbnail`, which uses +``reducing_gap=2.0`` by default. + TODO ^^^^ diff --git a/src/PIL/Image.py b/src/PIL/Image.py index a55cdccd710..6dec284d086 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2420,7 +2420,7 @@ def resize( if self.mode in ["LA", "RGBA"] and resample != Resampling.NEAREST: im = self.convert({"LA": "La", "RGBA": "RGBa"}[self.mode]) - im = im.resize(size, resample, box) + im = im.resize(size, resample, box, reducing_gap) return im.convert(self.mode) self.load() From 100530e6401a1b0d22dbe90ab31beac82fe610c8 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Thu, 15 Oct 2026 11:28:46 +1100 Subject: [PATCH 2/3] Fix title underline width --- docs/releasenotes/13.0.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index 8085963afeb..351efd79417 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -100,7 +100,7 @@ API changes =========== Resizing images with alpha -^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``reducing_gap`` argument to :py:meth:`~PIL.Image.Image.resize` is now applied to RGBA and LA images. Previously, these modes ignored the argument, From f7340f40fa8525ee0f1417546258203ef1692f91 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 15 Oct 2026 14:23:43 +1100 Subject: [PATCH 3/3] Simplify tests --- Tests/test_image_resize.py | 35 ++++++++++++----------------------- Tests/test_image_thumbnail.py | 11 ----------- docs/releasenotes/13.0.0.rst | 16 ++++++++-------- 3 files changed, 20 insertions(+), 42 deletions(-) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 04fc5333db3..9dc24f47b68 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -200,29 +200,6 @@ def gradients_image() -> Generator[ImageFile.ImageFile]: class TestReducingGapResize: - @pytest.mark.parametrize( - "mode, opaque, transparent, expected", - ( - ("RGBA", (255, 255, 255, 255), (255, 0, 0, 0), (255, 255, 255, 64)), - ("LA", (255, 255), (255, 0), (255, 64)), - ), - ) - def test_reducing_gap_alpha( - self, - mode: str, - opaque: tuple[int, ...], - transparent: tuple[int, ...], - expected: tuple[int, ...], - ) -> None: - im = Image.new(mode, (16, 16), transparent) - for x in range(0, 16, 4): - im.paste(opaque, (x, 0, x + 1, 16)) - - # Each 4x4 block has four opaque white pixels and twelve transparent - # pixels. Integer reduction averages alpha to round(255 / 4) = 64. - resized = im.resize((4, 4), Image.Resampling.LANCZOS, reducing_gap=1.0) - assert_image_equal(resized, Image.new(mode, (4, 4), expected)) - def test_reducing_gap_values(self, gradients_image: ImageFile.ImageFile) -> None: ref = gradients_image.resize( (52, 34), Image.Resampling.BICUBIC, reducing_gap=None @@ -305,6 +282,18 @@ def test_reducing_gap_8( assert_image_equal(ref, im) + @pytest.mark.parametrize("mode", ("RGBA", "LA")) + def test_reducing_gap_alpha(self, mode: str) -> None: + with Image.open("Tests/images/transparent.png") as im: + im_converted = im.convert(mode) + + # Premultiply the alpha manually + premultiplied = im_converted.convert({"RGBA": "RGBa", "LA": "La"}[mode]) + expected = premultiplied.resize((16, 16), reducing_gap=2).convert(mode) + + # Test that resizing the image applies the alpha in the same way + assert_image_equal(im_converted.resize((16, 16), reducing_gap=2), expected) + @pytest.mark.parametrize( "box, epsilon", (((0, 0, 512, 512), 5.5), ((0.9, 1.7, 128, 128), 9.5)), diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 2bf50dc185d..00b47a2a70a 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -154,17 +154,6 @@ def test_reducing_gap_values() -> None: assert_image_similar(ref, im, 3.5) -@pytest.mark.parametrize("mode", ("RGBA", "LA")) -def test_reducing_gap_alpha(mode: str) -> None: - im = Image.open("Tests/images/pil123rgba.png").convert(mode) - premultiplied = im.convert({"RGBA": "RGBa", "LA": "La"}[mode]) - premultiplied.thumbnail((16, 16), Image.Resampling.LANCZOS, reducing_gap=2.0) - expected = premultiplied.convert(mode) - - im.thumbnail((16, 16), Image.Resampling.LANCZOS) - assert_image_equal(im, expected) - - def test_reducing_gap_for_DCT_scaling() -> None: with Image.open("Tests/images/hopper.jpg") as ref: # thumbnail should call draft with reducing_gap scale diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index 351efd79417..a9fcc0dc510 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -99,14 +99,6 @@ intended for internal use, so there is no replacement. API changes =========== -Resizing images with alpha -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The ``reducing_gap`` argument to :py:meth:`~PIL.Image.Image.resize` is now -applied to RGBA and LA images. Previously, these modes ignored the argument, -including when called by :py:meth:`~PIL.Image.Image.thumbnail`, which uses -``reducing_gap=2.0`` by default. - TODO ^^^^ @@ -191,6 +183,14 @@ installed on the system with the expected name (e.g. ``libharfbuzz.so.0``). All wheels are now linked against the HarfBuzz that Pillow ships, which may slightly change how text is rendered in scripts that rely on the auto-hinter. +Resizing images with alpha +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``reducing_gap`` argument to :py:meth:`~PIL.Image.Image.resize` is now +applied to RGBA and LA images. Previously, these modes ignored the argument, +including when called by :py:meth:`~PIL.Image.Image.thumbnail`, which uses +``reducing_gap=2.0`` by default. + Preserved transparency when padding or expanding images in ImageOps ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^