mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 01:16:16 +03:00
Merge branch 'main' into winbuild-update
This commit is contained in:
commit
8053772a2b
17
.github/renovate.json
vendored
Normal file
17
.github/renovate.json
vendored
Normal file
|
@ -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"]
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ repos:
|
||||||
rev: v4.3.0
|
rev: v4.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
|
- id: check-json
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
|
|
||||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||||
|
|
|
@ -5,6 +5,9 @@ Changelog (Pillow)
|
||||||
9.3.0 (unreleased)
|
9.3.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Corrected BMP and TGA palette size when saving #6500
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Do not call load() before draft() in Image.thumbnail #6539
|
- Do not call load() before draft() in Image.thumbnail #6539
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ exclude .coveragerc
|
||||||
exclude .editorconfig
|
exclude .editorconfig
|
||||||
exclude .readthedocs.yml
|
exclude .readthedocs.yml
|
||||||
exclude codecov.yml
|
exclude codecov.yml
|
||||||
|
exclude renovate.json
|
||||||
global-exclude .git*
|
global-exclude .git*
|
||||||
global-exclude *.pyc
|
global-exclude *.pyc
|
||||||
global-exclude *.so
|
global-exclude *.so
|
||||||
|
|
|
@ -74,6 +74,9 @@ As of 2019, Pillow development is
|
||||||
<a href="https://pypi.org/project/Pillow/"><img
|
<a href="https://pypi.org/project/Pillow/"><img
|
||||||
alt="Number of PyPI downloads"
|
alt="Number of PyPI downloads"
|
||||||
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
||||||
|
<a href="https://bestpractices.coreinfrastructure.org/projects/6331"><img
|
||||||
|
alt="OpenSSF Best Practices"
|
||||||
|
src="https://bestpractices.coreinfrastructure.org/projects/6331/badge"></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -58,6 +58,18 @@ def test_save_to_bytes():
|
||||||
assert reloaded.format == "BMP"
|
assert reloaded.format == "BMP"
|
||||||
|
|
||||||
|
|
||||||
|
def test_small_palette(tmp_path):
|
||||||
|
im = Image.new("P", (1, 1))
|
||||||
|
colors = [0, 0, 0, 125, 125, 125, 255, 255, 255]
|
||||||
|
im.putpalette(colors)
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.bmp")
|
||||||
|
im.save(out)
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.getpalette() == colors
|
||||||
|
|
||||||
|
|
||||||
def test_save_too_large(tmp_path):
|
def test_save_too_large(tmp_path):
|
||||||
outfile = str(tmp_path / "temp.bmp")
|
outfile = str(tmp_path / "temp.bmp")
|
||||||
with Image.new("RGB", (1, 1)) as im:
|
with Image.new("RGB", (1, 1)) as im:
|
||||||
|
|
|
@ -1087,6 +1087,19 @@ def test_palette_save_P(tmp_path):
|
||||||
assert_image_equal(reloaded, im)
|
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):
|
def test_palette_save_all_P(tmp_path):
|
||||||
frames = []
|
frames = []
|
||||||
colors = ((255, 0, 0), (0, 255, 0))
|
colors = ((255, 0, 0), (0, 255, 0))
|
||||||
|
|
|
@ -120,6 +120,18 @@ def test_save(tmp_path):
|
||||||
assert test_im.size == (100, 100)
|
assert test_im.size == (100, 100)
|
||||||
|
|
||||||
|
|
||||||
|
def test_small_palette(tmp_path):
|
||||||
|
im = Image.new("P", (1, 1))
|
||||||
|
colors = [0, 0, 0]
|
||||||
|
im.putpalette(colors)
|
||||||
|
|
||||||
|
out = str(tmp_path / "temp.tga")
|
||||||
|
im.save(out)
|
||||||
|
|
||||||
|
with Image.open(out) as reloaded:
|
||||||
|
assert reloaded.getpalette() == colors
|
||||||
|
|
||||||
|
|
||||||
def test_save_wrong_mode(tmp_path):
|
def test_save_wrong_mode(tmp_path):
|
||||||
im = hopper("PA")
|
im = hopper("PA")
|
||||||
out = str(tmp_path / "temp.tga")
|
out = str(tmp_path / "temp.tga")
|
||||||
|
|
|
@ -620,6 +620,7 @@ class TestImage:
|
||||||
|
|
||||||
im_remapped = im.remap_palette([1, 0])
|
im_remapped = im.remap_palette([1, 0])
|
||||||
assert im_remapped.info["transparency"] == 1
|
assert im_remapped.info["transparency"] == 1
|
||||||
|
assert len(im_remapped.getpalette()) == 6
|
||||||
|
|
||||||
# Test unused transparency
|
# Test unused transparency
|
||||||
im.info["transparency"] = 2
|
im.info["transparency"] = 2
|
||||||
|
|
|
@ -110,6 +110,16 @@ def test_contain(new_size):
|
||||||
assert new_im.size == (256, 256)
|
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():
|
def test_pad():
|
||||||
# Same ratio
|
# Same ratio
|
||||||
im = hopper()
|
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
|
||||||
|
|
||||||
|
|
||||||
def test_pil163():
|
def test_pil163():
|
||||||
# Division by zero in equalize if < 255 pixels in image (@PIL163)
|
# Division by zero in equalize if < 255 pixels in image (@PIL163)
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,10 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
|
||||||
:target: https://pypi.org/project/Pillow/
|
:target: https://pypi.org/project/Pillow/
|
||||||
:alt: Number of PyPI downloads
|
:alt: Number of PyPI downloads
|
||||||
|
|
||||||
|
.. image:: https://bestpractices.coreinfrastructure.org/projects/6331/badge
|
||||||
|
:target: https://bestpractices.coreinfrastructure.org/projects/6331
|
||||||
|
:alt: OpenSSF Best Practices
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
========
|
========
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ provide constants and clear-text names for various well-known EXIF tags.
|
||||||
.. py:data:: TAGS
|
.. py:data:: TAGS
|
||||||
:type: dict
|
:type: dict
|
||||||
|
|
||||||
The TAG dictionary maps 16-bit integer EXIF tag enumerations to
|
The TAGS dictionary maps 16-bit integer EXIF tag enumerations to
|
||||||
descriptive string names. For instance:
|
descriptive string names. For instance:
|
||||||
|
|
||||||
>>> from PIL.ExifTags import TAGS
|
>>> from PIL.ExifTags import TAGS
|
||||||
|
|
|
@ -375,6 +375,16 @@ def _save(im, fp, filename, bitmap_header=True):
|
||||||
header = 40 # or 64 for OS/2 version 2
|
header = 40 # or 64 for OS/2 version 2
|
||||||
image = stride * im.size[1]
|
image = stride * im.size[1]
|
||||||
|
|
||||||
|
if im.mode == "1":
|
||||||
|
palette = b"".join(o8(i) * 4 for i in (0, 255))
|
||||||
|
elif im.mode == "L":
|
||||||
|
palette = b"".join(o8(i) * 4 for i in range(256))
|
||||||
|
elif im.mode == "P":
|
||||||
|
palette = im.im.getpalette("RGB", "BGRX")
|
||||||
|
colors = len(palette) // 4
|
||||||
|
else:
|
||||||
|
palette = None
|
||||||
|
|
||||||
# bitmap header
|
# bitmap header
|
||||||
if bitmap_header:
|
if bitmap_header:
|
||||||
offset = 14 + header + colors * 4
|
offset = 14 + header + colors * 4
|
||||||
|
@ -405,14 +415,8 @@ def _save(im, fp, filename, bitmap_header=True):
|
||||||
|
|
||||||
fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
|
fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
|
||||||
|
|
||||||
if im.mode == "1":
|
if palette:
|
||||||
for i in (0, 255):
|
fp.write(palette)
|
||||||
fp.write(o8(i) * 4)
|
|
||||||
elif im.mode == "L":
|
|
||||||
for i in range(256):
|
|
||||||
fp.write(o8(i) * 4)
|
|
||||||
elif im.mode == "P":
|
|
||||||
fp.write(im.im.getpalette("RGB", "BGRX"))
|
|
||||||
|
|
||||||
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))])
|
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, stride, -1))])
|
||||||
|
|
||||||
|
|
|
@ -519,9 +519,8 @@ def _normalize_palette(im, palette, info):
|
||||||
used_palette_colors = []
|
used_palette_colors = []
|
||||||
for i in range(0, len(source_palette), 3):
|
for i in range(0, len(source_palette), 3):
|
||||||
source_color = tuple(source_palette[i : i + 3])
|
source_color = tuple(source_palette[i : i + 3])
|
||||||
try:
|
index = im.palette.colors.get(source_color)
|
||||||
index = im.palette.colors[source_color]
|
if index in used_palette_colors:
|
||||||
except KeyError:
|
|
||||||
index = None
|
index = None
|
||||||
used_palette_colors.append(index)
|
used_palette_colors.append(index)
|
||||||
for i, index in enumerate(used_palette_colors):
|
for i, index in enumerate(used_palette_colors):
|
||||||
|
|
|
@ -1949,11 +1949,7 @@ class Image:
|
||||||
|
|
||||||
m_im = m_im.convert("L")
|
m_im = m_im.convert("L")
|
||||||
|
|
||||||
# Internally, we require 256 palette entries.
|
m_im.putpalette(palette_bytes, palette_mode)
|
||||||
new_palette_bytes = (
|
|
||||||
palette_bytes + ((256 * bands) - len(palette_bytes)) * b"\x00"
|
|
||||||
)
|
|
||||||
m_im.putpalette(new_palette_bytes, palette_mode)
|
|
||||||
m_im.palette = ImagePalette.ImagePalette(palette_mode, palette=palette_bytes)
|
m_im.palette = ImagePalette.ImagePalette(palette_mode, palette=palette_bytes)
|
||||||
|
|
||||||
if "transparency" in self.info:
|
if "transparency" in self.info:
|
||||||
|
|
|
@ -255,11 +255,11 @@ def contain(image, size, method=Image.Resampling.BICUBIC):
|
||||||
|
|
||||||
if im_ratio != dest_ratio:
|
if im_ratio != dest_ratio:
|
||||||
if im_ratio > dest_ratio:
|
if im_ratio > 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]:
|
if new_height != size[1]:
|
||||||
size = (size[0], new_height)
|
size = (size[0], new_height)
|
||||||
else:
|
else:
|
||||||
new_width = int(image.width / image.height * size[1])
|
new_width = round(image.width / image.height * size[1])
|
||||||
if new_width != size[0]:
|
if new_width != size[0]:
|
||||||
size = (new_width, size[1])
|
size = (new_width, size[1])
|
||||||
return image.resize(size, resample=method)
|
return image.resize(size, resample=method)
|
||||||
|
@ -292,10 +292,10 @@ def pad(image, size, method=Image.Resampling.BICUBIC, color=None, centering=(0.5
|
||||||
else:
|
else:
|
||||||
out = Image.new(image.mode, size, color)
|
out = Image.new(image.mode, size, color)
|
||||||
if resized.width != size[0]:
|
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))
|
out.paste(resized, (x, 0))
|
||||||
else:
|
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))
|
out.paste(resized, (0, y))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
|
@ -193,9 +193,10 @@ def _save(im, fp, filename):
|
||||||
warnings.warn("id_section has been trimmed to 255 characters")
|
warnings.warn("id_section has been trimmed to 255 characters")
|
||||||
|
|
||||||
if colormaptype:
|
if colormaptype:
|
||||||
colormapfirst, colormaplength, colormapentry = 0, 256, 24
|
palette = im.im.getpalette("RGB", "BGR")
|
||||||
|
colormaplength, colormapentry = len(palette) // 3, 24
|
||||||
else:
|
else:
|
||||||
colormapfirst, colormaplength, colormapentry = 0, 0, 0
|
colormaplength, colormapentry = 0, 0
|
||||||
|
|
||||||
if im.mode in ("LA", "RGBA"):
|
if im.mode in ("LA", "RGBA"):
|
||||||
flags = 8
|
flags = 8
|
||||||
|
@ -210,7 +211,7 @@ def _save(im, fp, filename):
|
||||||
o8(id_len)
|
o8(id_len)
|
||||||
+ o8(colormaptype)
|
+ o8(colormaptype)
|
||||||
+ o8(imagetype)
|
+ o8(imagetype)
|
||||||
+ o16(colormapfirst)
|
+ o16(0) # colormapfirst
|
||||||
+ o16(colormaplength)
|
+ o16(colormaplength)
|
||||||
+ o8(colormapentry)
|
+ o8(colormapentry)
|
||||||
+ o16(0)
|
+ o16(0)
|
||||||
|
@ -225,7 +226,7 @@ def _save(im, fp, filename):
|
||||||
fp.write(id_section)
|
fp.write(id_section)
|
||||||
|
|
||||||
if colormaptype:
|
if colormaptype:
|
||||||
fp.write(im.im.getpalette("RGB", "BGR"))
|
fp.write(palette)
|
||||||
|
|
||||||
if rle:
|
if rle:
|
||||||
ImageFile._save(
|
ImageFile._save(
|
||||||
|
|
|
@ -355,9 +355,9 @@ deps = {
|
||||||
"libs": [r"imagequant.lib"],
|
"libs": [r"imagequant.lib"],
|
||||||
},
|
},
|
||||||
"harfbuzz": {
|
"harfbuzz": {
|
||||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/5.1.0.zip",
|
"url": "https://github.com/harfbuzz/harfbuzz/archive/5.2.0.zip",
|
||||||
"filename": "harfbuzz-5.1.0.zip",
|
"filename": "harfbuzz-5.2.0.zip",
|
||||||
"dir": "harfbuzz-5.1.0",
|
"dir": "harfbuzz-5.2.0",
|
||||||
"license": "COPYING",
|
"license": "COPYING",
|
||||||
"build": [
|
"build": [
|
||||||
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
|
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user