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
+
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