From 8ecfeaadff429e483dc481089a89bf1ab94d3f39 Mon Sep 17 00:00:00 2001 From: ROTl24 <119035617+ROTl24@users.noreply.github.com> Date: Sun, 13 Sep 2026 22:31:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(imageops):=20=E9=81=BF=E5=85=8D=E7=AA=84?= =?UTF-8?q?=E5=9B=BE=E7=BC=A9=E6=94=BE=E5=90=8E=E7=9A=84=E5=B0=BA=E5=AF=B8?= =?UTF-8?q?=E8=A2=AB=E8=88=8D=E5=85=A5=E4=B8=BA=E9=9B=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit contain 在正数目标尺寸下可能把短边舍入为零,导致 resize 抛出异常,并连带影响 pad。将计算出的短边限制为至少一个像素,保留已有正常尺寸计算。 新增横向、纵向及半像素边界四组回归用例,检查 contain 尺寸和 pad 内容,并补充 13.0.0 发布说明。修复前四组失败,修复后 ImageOps 文件 53 项测试通过。验证采用当前 ImageOps 源码与已安装 Pillow 12.3.0 核心,未构建当前主分支 C 扩展或运行完整测试套件。已安装核心 selftest 59 项通过,Black、Ruff、sphinx-lint 和差异检查通过;Black 提示运行时低于目标 Python 版本。 --- Tests/test_imageops.py | 14 ++++++++++++++ docs/releasenotes/13.0.0.rst | 8 ++++++++ src/PIL/ImageOps.py | 4 ++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index c7e63fbe548..5fce20ff1ba 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -132,6 +132,20 @@ def test_contain(new_size: tuple[int, int]) -> None: assert new_im.size == (256, 256) +@pytest.mark.parametrize("source_size", ((100, 1), (1, 100), (20, 1), (1, 20))) +def test_contain_minimum_dimension(source_size: tuple[int, int]) -> None: + im = Image.new("RGB", source_size, "red") + result = ImageOps.contain(im, (10, 10)) + expected_size = (10, 1) if source_size[0] > source_size[1] else (1, 10) + assert result.size == expected_size + assert result.getpixel((0, 0)) == (255, 0, 0) + + padded = ImageOps.pad(im, (10, 10), color="blue") + assert padded.size == (10, 10) + assert padded.getpixel((4, 4)) == (255, 0, 0) + assert padded.getpixel((0, 0)) == (0, 0, 255) + + def test_contain_round() -> None: im = Image.new("1", (43, 63), 1) new_im = ImageOps.contain(im, (5, 7)) diff --git a/docs/releasenotes/13.0.0.rst b/docs/releasenotes/13.0.0.rst index 46dbed3b58b..6e032ae65b0 100644 --- a/docs/releasenotes/13.0.0.rst +++ b/docs/releasenotes/13.0.0.rst @@ -126,6 +126,14 @@ Two new filters are available for :py:meth:`~PIL.Image.Image.resize` and Other changes ============= +Keep resized dimensions positive in ImageOps.contain() +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:py:func:`~PIL.ImageOps.contain` now keeps the calculated width or height at a +minimum of one pixel. Previously, resizing a very narrow image could round one +dimension to zero and raise ``ValueError``. This also fixes +:py:func:`~PIL.ImageOps.pad` for these images. + Python 3.15 ^^^^^^^^^^^ diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 94964e12156..b04cd65423c 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -300,11 +300,11 @@ def contain( if im_ratio != dest_ratio: if im_ratio > dest_ratio: - new_height = round(image.height / image.width * size[0]) + new_height = max(1, round(image.height / image.width * size[0])) if new_height != size[1]: size = (size[0], new_height) else: - new_width = round(image.width / image.height * size[1]) + new_width = max(1, round(image.width / image.height * size[1])) if new_width != size[0]: size = (new_width, size[1]) return image.resize(size, resample=method)