diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 00000000000..d1d82433553 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ], + "labels": [ + "Dependency" + ], + "packageRules": [ + { + "groupName": "github-actions", + "matchManagers": ["github-actions"], + "separateMajorMinor": "false" + } + ], + "schedule": ["on the 3rd day of the month"] +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eeb4b391ee3..f81bcb956fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,6 +40,7 @@ repos: rev: v4.3.0 hooks: - id: check-merge-conflict + - id: check-json - id: check-yaml - repo: https://github.com/sphinx-contrib/sphinx-lint diff --git a/MANIFEST.in b/MANIFEST.in index 26f9401f2d9..08f6dfc0877 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -25,6 +25,7 @@ exclude .coveragerc exclude .editorconfig exclude .readthedocs.yml exclude codecov.yml +exclude renovate.json global-exclude .git* global-exclude *.pyc global-exclude *.so diff --git a/README.md b/README.md index 5e9adaf7e95..e7c0ebc5aa5 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,9 @@ As of 2019, Pillow development is Number of PyPI downloads + OpenSSF Best Practices diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 68cb8a36e8d..4e967faec91 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1087,6 +1087,19 @@ def test_palette_save_P(tmp_path): assert_image_equal(reloaded, im) +def test_palette_save_duplicate_entries(tmp_path): + im = Image.new("P", (1, 2)) + im.putpixel((0, 1), 1) + + im.putpalette((0, 0, 0, 0, 0, 0)) + + out = str(tmp_path / "temp.gif") + im.save(out, palette=[0, 0, 0, 0, 0, 0, 1, 1, 1]) + + with Image.open(out) as reloaded: + assert reloaded.convert("RGB").getpixel((0, 1)) == (0, 0, 0) + + def test_palette_save_all_P(tmp_path): frames = [] colors = ((255, 0, 0), (0, 255, 0)) diff --git a/Tests/test_image.py b/Tests/test_image.py index 7cebed127d9..ab945e946f0 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -620,6 +620,7 @@ def test_remap_palette_transparency(self): im_remapped = im.remap_palette([1, 0]) assert im_remapped.info["transparency"] == 1 + assert len(im_remapped.getpalette()) == 6 # Test unused transparency im.info["transparency"] = 2 diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 1051d9843cf..c9b2fd865b8 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -110,6 +110,16 @@ def test_contain(new_size): assert new_im.size == (256, 256) +def test_contain_round(): + im = Image.new("1", (43, 63), 1) + new_im = ImageOps.contain(im, (5, 7)) + assert new_im.width == 5 + + im = Image.new("1", (63, 43), 1) + new_im = ImageOps.contain(im, (7, 5)) + assert new_im.height == 5 + + def test_pad(): # Same ratio im = hopper() @@ -130,6 +140,15 @@ def test_pad(): ) +def test_pad_round(): + im = Image.new("1", (1, 1), 1) + new_im = ImageOps.pad(im, (4, 1)) + assert new_im.load()[2, 0] == 1 + + new_im = ImageOps.pad(im, (1, 4)) + assert new_im.load()[0, 2] == 1 + + @pytest.mark.parametrize("mode", ("P", "PA")) def test_palette(mode): im = hopper(mode) diff --git a/docs/index.rst b/docs/index.rst index c731e274600..45af4c5714c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -69,6 +69,10 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more dest_ratio: - new_height = int(image.height / image.width * size[0]) + new_height = round(image.height / image.width * size[0]) if new_height != size[1]: size = (size[0], new_height) else: - new_width = int(image.width / image.height * size[1]) + new_width = round(image.width / image.height * size[1]) if new_width != size[0]: size = (new_width, size[1]) return image.resize(size, resample=method) @@ -294,10 +294,10 @@ def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5 if resized.palette: out.putpalette(resized.getpalette()) if resized.width != size[0]: - x = int((size[0] - resized.width) * max(0, min(centering[0], 1))) + x = round((size[0] - resized.width) * max(0, min(centering[0], 1))) out.paste(resized, (x, 0)) else: - y = int((size[1] - resized.height) * max(0, min(centering[1], 1))) + y = round((size[1] - resized.height) * max(0, min(centering[1], 1))) out.paste(resized, (0, y)) return out