mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-14 13:46:57 +03:00
Merge branch 'main' into add-cygwin-to-ci
This commit is contained in:
commit
853a95d56b
|
@ -40,12 +40,10 @@ if [[ $(uname) != CYGWIN* ]]; then
|
||||||
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
||||||
python3 -m pip install numpy
|
python3 -m pip install numpy
|
||||||
|
|
||||||
# PyQt5 doesn't support PyPy3
|
# PyQt6 doesn't support PyPy3
|
||||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||||
# arm64, ppc64le, s390x CPUs:
|
sudo apt-get -qq install libegl1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxkbcommon-x11-0
|
||||||
# "ERROR: Could not find a version that satisfies the requirement pyqt5"
|
python3 -m pip install pyqt6
|
||||||
sudo apt-get -qq install libxcb-xinerama0 pyqt5-dev-tools
|
|
||||||
python3 -m pip install pyqt5
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# webp
|
# webp
|
||||||
|
|
1
.github/workflows/test-docker.yml
vendored
1
.github/workflows/test-docker.yml
vendored
|
@ -27,6 +27,7 @@ jobs:
|
||||||
gentoo,
|
gentoo,
|
||||||
ubuntu-18.04-bionic-amd64,
|
ubuntu-18.04-bionic-amd64,
|
||||||
ubuntu-20.04-focal-amd64,
|
ubuntu-20.04-focal-amd64,
|
||||||
|
ubuntu-22.04-jammy-amd64,
|
||||||
]
|
]
|
||||||
dockerTag: [main]
|
dockerTag: [main]
|
||||||
include:
|
include:
|
||||||
|
|
2
.github/workflows/tidelift.yml
vendored
2
.github/workflows/tidelift.yml
vendored
|
@ -4,9 +4,11 @@ on:
|
||||||
- cron: "30 2 * * *" # daily at 02:30 UTC
|
- cron: "30 2 * * *" # daily at 02:30 UTC
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
|
- "Pipfile*"
|
||||||
- ".github/workflows/tidelift.yml"
|
- ".github/workflows/tidelift.yml"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
|
- "Pipfile*"
|
||||||
- ".github/workflows/tidelift.yml"
|
- ".github/workflows/tidelift.yml"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,12 @@ Changelog (Pillow)
|
||||||
9.2.0 (unreleased)
|
9.2.0 (unreleased)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Increase wait time of temporary file deletion on Windows #6224
|
||||||
|
[AlexTedeschi]
|
||||||
|
|
||||||
|
- Deprecate FreeTypeFont.getmask2 fill parameter #6220
|
||||||
|
[nulano, radarhere, hugovk]
|
||||||
|
|
||||||
- Round lut values where necessary #6188
|
- Round lut values where necessary #6188
|
||||||
[radarhere]
|
[radarhere]
|
||||||
|
|
||||||
|
|
|
@ -324,7 +324,7 @@ def is_mingw():
|
||||||
return sysconfig.get_platform() == "mingw"
|
return sysconfig.get_platform() == "mingw"
|
||||||
|
|
||||||
|
|
||||||
class cached_property:
|
class CachedProperty:
|
||||||
def __init__(self, func):
|
def __init__(self, func):
|
||||||
self.func = func
|
self.func = func
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ def box_blur(image, radius=1, n=1):
|
||||||
return image._new(image.im.box_blur(radius, n))
|
return image._new(image.im.box_blur(radius, n))
|
||||||
|
|
||||||
|
|
||||||
def assertImage(im, data, delta=0):
|
def assert_image(im, data, delta=0):
|
||||||
it = iter(im.getdata())
|
it = iter(im.getdata())
|
||||||
for data_row in data:
|
for data_row in data:
|
||||||
im_row = [next(it) for _ in range(im.size[0])]
|
im_row = [next(it) for _ in range(im.size[0])]
|
||||||
|
@ -35,12 +35,12 @@ def assertImage(im, data, delta=0):
|
||||||
next(it)
|
next(it)
|
||||||
|
|
||||||
|
|
||||||
def assertBlur(im, radius, data, passes=1, delta=0):
|
def assert_blur(im, radius, data, passes=1, delta=0):
|
||||||
# check grayscale image
|
# check grayscale image
|
||||||
assertImage(box_blur(im, radius, passes), data, delta)
|
assert_image(box_blur(im, radius, passes), data, delta)
|
||||||
rgba = Image.merge("RGBA", (im, im, im, im))
|
rgba = Image.merge("RGBA", (im, im, im, im))
|
||||||
for band in box_blur(rgba, radius, passes).split():
|
for band in box_blur(rgba, radius, passes).split():
|
||||||
assertImage(band, data, delta)
|
assert_image(band, data, delta)
|
||||||
|
|
||||||
|
|
||||||
def test_color_modes():
|
def test_color_modes():
|
||||||
|
@ -64,7 +64,7 @@ def test_color_modes():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0():
|
def test_radius_0():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0,
|
0,
|
||||||
[
|
[
|
||||||
|
@ -80,7 +80,7 @@ def test_radius_0():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0_02():
|
def test_radius_0_02():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0.02,
|
0.02,
|
||||||
[
|
[
|
||||||
|
@ -97,7 +97,7 @@ def test_radius_0_02():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0_05():
|
def test_radius_0_05():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0.05,
|
0.05,
|
||||||
[
|
[
|
||||||
|
@ -114,7 +114,7 @@ def test_radius_0_05():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0_1():
|
def test_radius_0_1():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0.1,
|
0.1,
|
||||||
[
|
[
|
||||||
|
@ -131,7 +131,7 @@ def test_radius_0_1():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_0_5():
|
def test_radius_0_5():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
0.5,
|
0.5,
|
||||||
[
|
[
|
||||||
|
@ -148,7 +148,7 @@ def test_radius_0_5():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_1():
|
def test_radius_1():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
1,
|
1,
|
||||||
[
|
[
|
||||||
|
@ -165,7 +165,7 @@ def test_radius_1():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_1_5():
|
def test_radius_1_5():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
1.5,
|
1.5,
|
||||||
[
|
[
|
||||||
|
@ -182,7 +182,7 @@ def test_radius_1_5():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_bigger_then_half():
|
def test_radius_bigger_then_half():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
3,
|
3,
|
||||||
[
|
[
|
||||||
|
@ -199,7 +199,7 @@ def test_radius_bigger_then_half():
|
||||||
|
|
||||||
|
|
||||||
def test_radius_bigger_then_width():
|
def test_radius_bigger_then_width():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
10,
|
10,
|
||||||
[
|
[
|
||||||
|
@ -214,7 +214,7 @@ def test_radius_bigger_then_width():
|
||||||
|
|
||||||
|
|
||||||
def test_extreme_large_radius():
|
def test_extreme_large_radius():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
600,
|
600,
|
||||||
[
|
[
|
||||||
|
@ -229,7 +229,7 @@ def test_extreme_large_radius():
|
||||||
|
|
||||||
|
|
||||||
def test_two_passes():
|
def test_two_passes():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
1,
|
1,
|
||||||
[
|
[
|
||||||
|
@ -247,7 +247,7 @@ def test_two_passes():
|
||||||
|
|
||||||
|
|
||||||
def test_three_passes():
|
def test_three_passes():
|
||||||
assertBlur(
|
assert_blur(
|
||||||
sample,
|
sample,
|
||||||
1,
|
1,
|
||||||
[
|
[
|
||||||
|
|
|
@ -15,27 +15,27 @@ except ImportError:
|
||||||
class TestColorLut3DCoreAPI:
|
class TestColorLut3DCoreAPI:
|
||||||
def generate_identity_table(self, channels, size):
|
def generate_identity_table(self, channels, size):
|
||||||
if isinstance(size, tuple):
|
if isinstance(size, tuple):
|
||||||
size1D, size2D, size3D = size
|
size_1d, size_2d, size_3d = size
|
||||||
else:
|
else:
|
||||||
size1D, size2D, size3D = (size, size, size)
|
size_1d, size_2d, size_3d = (size, size, size)
|
||||||
|
|
||||||
table = [
|
table = [
|
||||||
[
|
[
|
||||||
r / (size1D - 1) if size1D != 1 else 0,
|
r / (size_1d - 1) if size_1d != 1 else 0,
|
||||||
g / (size2D - 1) if size2D != 1 else 0,
|
g / (size_2d - 1) if size_2d != 1 else 0,
|
||||||
b / (size3D - 1) if size3D != 1 else 0,
|
b / (size_3d - 1) if size_3d != 1 else 0,
|
||||||
r / (size1D - 1) if size1D != 1 else 0,
|
r / (size_1d - 1) if size_1d != 1 else 0,
|
||||||
g / (size2D - 1) if size2D != 1 else 0,
|
g / (size_2d - 1) if size_2d != 1 else 0,
|
||||||
][:channels]
|
][:channels]
|
||||||
for b in range(size3D)
|
for b in range(size_3d)
|
||||||
for g in range(size2D)
|
for g in range(size_2d)
|
||||||
for r in range(size1D)
|
for r in range(size_1d)
|
||||||
]
|
]
|
||||||
return (
|
return (
|
||||||
channels,
|
channels,
|
||||||
size1D,
|
size_1d,
|
||||||
size2D,
|
size_2d,
|
||||||
size3D,
|
size_3d,
|
||||||
[item for sublist in table for item in sublist],
|
[item for sublist in table for item in sublist],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ class TestDecompressionCrop:
|
||||||
def teardown_class(self):
|
def teardown_class(self):
|
||||||
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
|
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
|
||||||
|
|
||||||
def testEnlargeCrop(self):
|
def test_enlarge_crop(self):
|
||||||
# Crops can extend the extents, therefore we should have the
|
# Crops can extend the extents, therefore we should have the
|
||||||
# same decompression bomb warnings on them.
|
# same decompression bomb warnings on them.
|
||||||
with hopper() as src:
|
with hopper() as src:
|
||||||
|
|
91
Tests/test_deprecate.py
Normal file
91
Tests/test_deprecate.py
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PIL import _deprecate
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"version, expected",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
10,
|
||||||
|
"Old thing is deprecated and will be removed in Pillow 10 "
|
||||||
|
r"\(2023-07-01\)\. Use new thing instead\.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
None,
|
||||||
|
r"Old thing is deprecated and will be removed in a future version\. "
|
||||||
|
r"Use new thing instead\.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_version(version, expected):
|
||||||
|
with pytest.warns(DeprecationWarning, match=expected):
|
||||||
|
_deprecate.deprecate("Old thing", version, "new thing")
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_version():
|
||||||
|
expected = r"Unknown removal version, update PIL\._deprecate\?"
|
||||||
|
with pytest.raises(ValueError, match=expected):
|
||||||
|
_deprecate.deprecate("Old thing", 12345, "new thing")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"deprecated, plural, expected",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"Old thing",
|
||||||
|
False,
|
||||||
|
r"Old thing is deprecated and should be removed\.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Old things",
|
||||||
|
True,
|
||||||
|
r"Old things are deprecated and should be removed\.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_old_version(deprecated, plural, expected):
|
||||||
|
expected = r""
|
||||||
|
with pytest.raises(RuntimeError, match=expected):
|
||||||
|
_deprecate.deprecate(deprecated, 1, plural=plural)
|
||||||
|
|
||||||
|
|
||||||
|
def test_plural():
|
||||||
|
expected = (
|
||||||
|
r"Old things are deprecated and will be removed in Pillow 10 \(2023-07-01\)\. "
|
||||||
|
r"Use new thing instead\."
|
||||||
|
)
|
||||||
|
with pytest.warns(DeprecationWarning, match=expected):
|
||||||
|
_deprecate.deprecate("Old things", 10, "new thing", plural=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_replacement_and_action():
|
||||||
|
expected = "Use only one of 'replacement' and 'action'"
|
||||||
|
with pytest.raises(ValueError, match=expected):
|
||||||
|
_deprecate.deprecate(
|
||||||
|
"Old thing", 10, replacement="new thing", action="Upgrade to new thing"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"action",
|
||||||
|
[
|
||||||
|
"Upgrade to new thing",
|
||||||
|
"Upgrade to new thing.",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_action(action):
|
||||||
|
expected = (
|
||||||
|
r"Old thing is deprecated and will be removed in Pillow 10 \(2023-07-01\)\. "
|
||||||
|
r"Upgrade to new thing\."
|
||||||
|
)
|
||||||
|
with pytest.warns(DeprecationWarning, match=expected):
|
||||||
|
_deprecate.deprecate("Old thing", 10, action=action)
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_replacement_or_action():
|
||||||
|
expected = (
|
||||||
|
r"Old thing is deprecated and will be removed in Pillow 10 \(2023-07-01\)"
|
||||||
|
)
|
||||||
|
with pytest.warns(DeprecationWarning, match=expected):
|
||||||
|
_deprecate.deprecate("Old thing", 10)
|
18
Tests/test_deprecated_imageqt.py
Normal file
18
Tests/test_deprecated_imageqt.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
# Arrange: cause all warnings to always be triggered
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
|
||||||
|
# Act: trigger a warning with Qt5
|
||||||
|
from PIL import ImageQt
|
||||||
|
|
||||||
|
|
||||||
|
def test_deprecated():
|
||||||
|
# Assert
|
||||||
|
if ImageQt.qt_version in ("5", "side2"):
|
||||||
|
assert len(w) == 1
|
||||||
|
assert issubclass(w[0].category, DeprecationWarning)
|
||||||
|
assert "deprecated" in str(w[0].message)
|
||||||
|
else:
|
||||||
|
assert len(w) == 0
|
|
@ -799,31 +799,31 @@ def test_zero_comment_subblocks():
|
||||||
def test_version(tmp_path):
|
def test_version(tmp_path):
|
||||||
out = str(tmp_path / "temp.gif")
|
out = str(tmp_path / "temp.gif")
|
||||||
|
|
||||||
def assertVersionAfterSave(im, version):
|
def assert_version_after_save(im, version):
|
||||||
im.save(out)
|
im.save(out)
|
||||||
with Image.open(out) as reread:
|
with Image.open(out) as reread:
|
||||||
assert reread.info["version"] == version
|
assert reread.info["version"] == version
|
||||||
|
|
||||||
# Test that GIF87a is used by default
|
# Test that GIF87a is used by default
|
||||||
im = Image.new("L", (100, 100), "#000")
|
im = Image.new("L", (100, 100), "#000")
|
||||||
assertVersionAfterSave(im, b"GIF87a")
|
assert_version_after_save(im, b"GIF87a")
|
||||||
|
|
||||||
# Test setting the version to 89a
|
# Test setting the version to 89a
|
||||||
im = Image.new("L", (100, 100), "#000")
|
im = Image.new("L", (100, 100), "#000")
|
||||||
im.info["version"] = b"89a"
|
im.info["version"] = b"89a"
|
||||||
assertVersionAfterSave(im, b"GIF89a")
|
assert_version_after_save(im, b"GIF89a")
|
||||||
|
|
||||||
# Test that adding a GIF89a feature changes the version
|
# Test that adding a GIF89a feature changes the version
|
||||||
im.info["transparency"] = 1
|
im.info["transparency"] = 1
|
||||||
assertVersionAfterSave(im, b"GIF89a")
|
assert_version_after_save(im, b"GIF89a")
|
||||||
|
|
||||||
# Test that a GIF87a image is also saved in that format
|
# Test that a GIF87a image is also saved in that format
|
||||||
with Image.open("Tests/images/test.colors.gif") as im:
|
with Image.open("Tests/images/test.colors.gif") as im:
|
||||||
assertVersionAfterSave(im, b"GIF87a")
|
assert_version_after_save(im, b"GIF87a")
|
||||||
|
|
||||||
# Test that a GIF89a image is also saved in that format
|
# Test that a GIF89a image is also saved in that format
|
||||||
im.info["version"] = b"GIF89a"
|
im.info["version"] = b"GIF89a"
|
||||||
assertVersionAfterSave(im, b"GIF87a")
|
assert_version_after_save(im, b"GIF87a")
|
||||||
|
|
||||||
|
|
||||||
def test_append_images(tmp_path):
|
def test_append_images(tmp_path):
|
||||||
|
@ -838,10 +838,10 @@ def test_append_images(tmp_path):
|
||||||
assert reread.n_frames == 3
|
assert reread.n_frames == 3
|
||||||
|
|
||||||
# Tests appending using a generator
|
# Tests appending using a generator
|
||||||
def imGenerator(ims):
|
def im_generator(ims):
|
||||||
yield from ims
|
yield from ims
|
||||||
|
|
||||||
im.save(out, save_all=True, append_images=imGenerator(ims))
|
im.save(out, save_all=True, append_images=im_generator(ims))
|
||||||
|
|
||||||
with Image.open(out) as reread:
|
with Image.open(out) as reread:
|
||||||
assert reread.n_frames == 3
|
assert reread.n_frames == 3
|
||||||
|
|
|
@ -145,10 +145,10 @@ def test_mp_attribute():
|
||||||
for test_file in test_files:
|
for test_file in test_files:
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
mpinfo = im._getmp()
|
mpinfo = im._getmp()
|
||||||
frameNumber = 0
|
frame_number = 0
|
||||||
for mpentry in mpinfo[0xB002]:
|
for mpentry in mpinfo[0xB002]:
|
||||||
mpattr = mpentry["Attribute"]
|
mpattr = mpentry["Attribute"]
|
||||||
if frameNumber:
|
if frame_number:
|
||||||
assert not mpattr["RepresentativeImageFlag"]
|
assert not mpattr["RepresentativeImageFlag"]
|
||||||
else:
|
else:
|
||||||
assert mpattr["RepresentativeImageFlag"]
|
assert mpattr["RepresentativeImageFlag"]
|
||||||
|
@ -157,7 +157,7 @@ def test_mp_attribute():
|
||||||
assert mpattr["ImageDataFormat"] == "JPEG"
|
assert mpattr["ImageDataFormat"] == "JPEG"
|
||||||
assert mpattr["MPType"] == "Multi-Frame Image: (Disparity)"
|
assert mpattr["MPType"] == "Multi-Frame Image: (Disparity)"
|
||||||
assert mpattr["Reserved"] == 0
|
assert mpattr["Reserved"] == 0
|
||||||
frameNumber += 1
|
frame_number += 1
|
||||||
|
|
||||||
|
|
||||||
def test_seek():
|
def test_seek():
|
||||||
|
|
|
@ -131,10 +131,10 @@ def test_save_all(tmp_path):
|
||||||
assert os.path.getsize(outfile) > 0
|
assert os.path.getsize(outfile) > 0
|
||||||
|
|
||||||
# Test appending using a generator
|
# Test appending using a generator
|
||||||
def imGenerator(ims):
|
def im_generator(ims):
|
||||||
yield from ims
|
yield from ims
|
||||||
|
|
||||||
im.save(outfile, save_all=True, append_images=imGenerator(ims))
|
im.save(outfile, save_all=True, append_images=im_generator(ims))
|
||||||
|
|
||||||
assert os.path.isfile(outfile)
|
assert os.path.isfile(outfile)
|
||||||
assert os.path.getsize(outfile) > 0
|
assert os.path.getsize(outfile) > 0
|
||||||
|
@ -253,9 +253,9 @@ def test_pdf_append(tmp_path):
|
||||||
check_pdf_pages_consistency(pdf)
|
check_pdf_pages_consistency(pdf)
|
||||||
|
|
||||||
# append two images
|
# append two images
|
||||||
mode_CMYK = hopper("CMYK")
|
mode_cmyk = hopper("CMYK")
|
||||||
mode_P = hopper("P")
|
mode_p = hopper("P")
|
||||||
mode_CMYK.save(pdf_filename, append=True, save_all=True, append_images=[mode_P])
|
mode_cmyk.save(pdf_filename, append=True, save_all=True, append_images=[mode_p])
|
||||||
|
|
||||||
# open the PDF again, check pages and info again
|
# open the PDF again, check pages and info again
|
||||||
with PdfParser.PdfParser(pdf_filename) as pdf:
|
with PdfParser.PdfParser(pdf_filename) as pdf:
|
||||||
|
|
|
@ -151,14 +151,14 @@ class TestFileTiff:
|
||||||
assert im.info["dpi"] == (71.0, 71.0)
|
assert im.info["dpi"] == (71.0, 71.0)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"resolutionUnit, dpi",
|
"resolution_unit, dpi",
|
||||||
[(None, 72.8), (2, 72.8), (3, 184.912)],
|
[(None, 72.8), (2, 72.8), (3, 184.912)],
|
||||||
)
|
)
|
||||||
def test_load_float_dpi(self, resolutionUnit, dpi):
|
def test_load_float_dpi(self, resolution_unit, dpi):
|
||||||
with Image.open(
|
with Image.open(
|
||||||
"Tests/images/hopper_float_dpi_" + str(resolutionUnit) + ".tif"
|
"Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif"
|
||||||
) as im:
|
) as im:
|
||||||
assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit
|
assert im.tag_v2.get(RESOLUTION_UNIT) == resolution_unit
|
||||||
assert im.info["dpi"] == (dpi, dpi)
|
assert im.info["dpi"] == (dpi, dpi)
|
||||||
|
|
||||||
def test_save_float_dpi(self, tmp_path):
|
def test_save_float_dpi(self, tmp_path):
|
||||||
|
@ -655,11 +655,11 @@ class TestFileTiff:
|
||||||
assert reread.n_frames == 3
|
assert reread.n_frames == 3
|
||||||
|
|
||||||
# Test appending using a generator
|
# Test appending using a generator
|
||||||
def imGenerator(ims):
|
def im_generator(ims):
|
||||||
yield from ims
|
yield from ims
|
||||||
|
|
||||||
mp = BytesIO()
|
mp = BytesIO()
|
||||||
im.save(mp, format="TIFF", save_all=True, append_images=imGenerator(ims))
|
im.save(mp, format="TIFF", save_all=True, append_images=im_generator(ims))
|
||||||
|
|
||||||
mp.seek(0, os.SEEK_SET)
|
mp.seek(0, os.SEEK_SET)
|
||||||
with Image.open(mp) as reread:
|
with Image.open(mp) as reread:
|
||||||
|
|
|
@ -28,26 +28,26 @@ def test_rt_metadata(tmp_path):
|
||||||
# For text items, we still have to decode('ascii','replace') because
|
# For text items, we still have to decode('ascii','replace') because
|
||||||
# the tiff file format can't take 8 bit bytes in that field.
|
# the tiff file format can't take 8 bit bytes in that field.
|
||||||
|
|
||||||
basetextdata = "This is some arbitrary metadata for a text field"
|
base_text_data = "This is some arbitrary metadata for a text field"
|
||||||
bindata = basetextdata.encode("ascii") + b" \xff"
|
bin_data = base_text_data.encode("ascii") + b" \xff"
|
||||||
textdata = basetextdata + " " + chr(255)
|
text_data = base_text_data + " " + chr(255)
|
||||||
reloaded_textdata = basetextdata + " ?"
|
reloaded_text_data = base_text_data + " ?"
|
||||||
floatdata = 12.345
|
float_data = 12.345
|
||||||
doubledata = 67.89
|
double_data = 67.89
|
||||||
info = TiffImagePlugin.ImageFileDirectory()
|
info = TiffImagePlugin.ImageFileDirectory()
|
||||||
|
|
||||||
ImageJMetaData = TAG_IDS["ImageJMetaData"]
|
ImageJMetaData = TAG_IDS["ImageJMetaData"]
|
||||||
ImageJMetaDataByteCounts = TAG_IDS["ImageJMetaDataByteCounts"]
|
ImageJMetaDataByteCounts = TAG_IDS["ImageJMetaDataByteCounts"]
|
||||||
ImageDescription = TAG_IDS["ImageDescription"]
|
ImageDescription = TAG_IDS["ImageDescription"]
|
||||||
|
|
||||||
info[ImageJMetaDataByteCounts] = len(bindata)
|
info[ImageJMetaDataByteCounts] = len(bin_data)
|
||||||
info[ImageJMetaData] = bindata
|
info[ImageJMetaData] = bin_data
|
||||||
info[TAG_IDS["RollAngle"]] = floatdata
|
info[TAG_IDS["RollAngle"]] = float_data
|
||||||
info.tagtype[TAG_IDS["RollAngle"]] = 11
|
info.tagtype[TAG_IDS["RollAngle"]] = 11
|
||||||
info[TAG_IDS["YawAngle"]] = doubledata
|
info[TAG_IDS["YawAngle"]] = double_data
|
||||||
info.tagtype[TAG_IDS["YawAngle"]] = 12
|
info.tagtype[TAG_IDS["YawAngle"]] = 12
|
||||||
|
|
||||||
info[ImageDescription] = textdata
|
info[ImageDescription] = text_data
|
||||||
|
|
||||||
f = str(tmp_path / "temp.tif")
|
f = str(tmp_path / "temp.tif")
|
||||||
|
|
||||||
|
@ -55,28 +55,28 @@ def test_rt_metadata(tmp_path):
|
||||||
|
|
||||||
with Image.open(f) as loaded:
|
with Image.open(f) as loaded:
|
||||||
|
|
||||||
assert loaded.tag[ImageJMetaDataByteCounts] == (len(bindata),)
|
assert loaded.tag[ImageJMetaDataByteCounts] == (len(bin_data),)
|
||||||
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bindata),)
|
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (len(bin_data),)
|
||||||
|
|
||||||
assert loaded.tag[ImageJMetaData] == bindata
|
assert loaded.tag[ImageJMetaData] == bin_data
|
||||||
assert loaded.tag_v2[ImageJMetaData] == bindata
|
assert loaded.tag_v2[ImageJMetaData] == bin_data
|
||||||
|
|
||||||
assert loaded.tag[ImageDescription] == (reloaded_textdata,)
|
assert loaded.tag[ImageDescription] == (reloaded_text_data,)
|
||||||
assert loaded.tag_v2[ImageDescription] == reloaded_textdata
|
assert loaded.tag_v2[ImageDescription] == reloaded_text_data
|
||||||
|
|
||||||
loaded_float = loaded.tag[TAG_IDS["RollAngle"]][0]
|
loaded_float = loaded.tag[TAG_IDS["RollAngle"]][0]
|
||||||
assert round(abs(loaded_float - floatdata), 5) == 0
|
assert round(abs(loaded_float - float_data), 5) == 0
|
||||||
loaded_double = loaded.tag[TAG_IDS["YawAngle"]][0]
|
loaded_double = loaded.tag[TAG_IDS["YawAngle"]][0]
|
||||||
assert round(abs(loaded_double - doubledata), 7) == 0
|
assert round(abs(loaded_double - double_data), 7) == 0
|
||||||
|
|
||||||
# check with 2 element ImageJMetaDataByteCounts, issue #2006
|
# check with 2 element ImageJMetaDataByteCounts, issue #2006
|
||||||
|
|
||||||
info[ImageJMetaDataByteCounts] = (8, len(bindata) - 8)
|
info[ImageJMetaDataByteCounts] = (8, len(bin_data) - 8)
|
||||||
img.save(f, tiffinfo=info)
|
img.save(f, tiffinfo=info)
|
||||||
with Image.open(f) as loaded:
|
with Image.open(f) as loaded:
|
||||||
|
|
||||||
assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bindata) - 8)
|
assert loaded.tag[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
|
||||||
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bindata) - 8)
|
assert loaded.tag_v2[ImageJMetaDataByteCounts] == (8, len(bin_data) - 8)
|
||||||
|
|
||||||
|
|
||||||
def test_read_metadata():
|
def test_read_metadata():
|
||||||
|
@ -356,7 +356,7 @@ def test_empty_values():
|
||||||
assert 33432 in info
|
assert 33432 in info
|
||||||
|
|
||||||
|
|
||||||
def test_PhotoshopInfo(tmp_path):
|
def test_photoshop_info(tmp_path):
|
||||||
with Image.open("Tests/images/issue_2278.tif") as im:
|
with Image.open("Tests/images/issue_2278.tif") as im:
|
||||||
assert len(im.tag_v2[34377]) == 70
|
assert len(im.tag_v2[34377]) == 70
|
||||||
assert isinstance(im.tag_v2[34377], bytes)
|
assert isinstance(im.tag_v2[34377], bytes)
|
||||||
|
|
|
@ -90,14 +90,14 @@ def test_write_animation_RGB(tmp_path):
|
||||||
check(temp_file1)
|
check(temp_file1)
|
||||||
|
|
||||||
# Tests appending using a generator
|
# Tests appending using a generator
|
||||||
def imGenerator(ims):
|
def im_generator(ims):
|
||||||
yield from ims
|
yield from ims
|
||||||
|
|
||||||
temp_file2 = str(tmp_path / "temp_generator.webp")
|
temp_file2 = str(tmp_path / "temp_generator.webp")
|
||||||
frame1.copy().save(
|
frame1.copy().save(
|
||||||
temp_file2,
|
temp_file2,
|
||||||
save_all=True,
|
save_all=True,
|
||||||
append_images=imGenerator([frame2]),
|
append_images=im_generator([frame2]),
|
||||||
lossless=True,
|
lossless=True,
|
||||||
)
|
)
|
||||||
check(temp_file2)
|
check(temp_file2)
|
||||||
|
|
|
@ -6,8 +6,8 @@ from .helper import hopper
|
||||||
|
|
||||||
|
|
||||||
def test_copy():
|
def test_copy():
|
||||||
croppedCoordinates = (10, 10, 20, 20)
|
cropped_coordinates = (10, 10, 20, 20)
|
||||||
croppedSize = (10, 10)
|
cropped_size = (10, 10)
|
||||||
for mode in "1", "P", "L", "RGB", "I", "F":
|
for mode in "1", "P", "L", "RGB", "I", "F":
|
||||||
# Internal copy method
|
# Internal copy method
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
|
@ -23,15 +23,15 @@ def test_copy():
|
||||||
|
|
||||||
# Internal copy method on a cropped image
|
# Internal copy method on a cropped image
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
out = im.crop(croppedCoordinates).copy()
|
out = im.crop(cropped_coordinates).copy()
|
||||||
assert out.mode == im.mode
|
assert out.mode == im.mode
|
||||||
assert out.size == croppedSize
|
assert out.size == cropped_size
|
||||||
|
|
||||||
# Python's copy method on a cropped image
|
# Python's copy method on a cropped image
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
out = copy.copy(im.crop(croppedCoordinates))
|
out = copy.copy(im.crop(cropped_coordinates))
|
||||||
assert out.mode == im.mode
|
assert out.mode == im.mode
|
||||||
assert out.size == croppedSize
|
assert out.size == cropped_size
|
||||||
|
|
||||||
|
|
||||||
def test_copy_zero():
|
def test_copy_zero():
|
||||||
|
|
|
@ -99,10 +99,10 @@ def test_rankfilter_properties():
|
||||||
|
|
||||||
|
|
||||||
def test_builtinfilter_p():
|
def test_builtinfilter_p():
|
||||||
builtinFilter = ImageFilter.BuiltinFilter()
|
builtin_filter = ImageFilter.BuiltinFilter()
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
builtinFilter.filter(hopper("P"))
|
builtin_filter.filter(hopper("P"))
|
||||||
|
|
||||||
|
|
||||||
def test_kernel_not_enough_coefficients():
|
def test_kernel_not_enough_coefficients():
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageQt
|
from PIL import Image
|
||||||
|
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore", category=DeprecationWarning)
|
||||||
|
from PIL import ImageQt
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import assert_image_equal, cached_property
|
from .helper import CachedProperty, assert_image_equal
|
||||||
|
|
||||||
|
|
||||||
class TestImagingPaste:
|
class TestImagingPaste:
|
||||||
|
@ -34,7 +34,7 @@ class TestImagingPaste:
|
||||||
im.paste(im2, mask)
|
im.paste(im2, mask)
|
||||||
self.assert_9points_image(im, expected)
|
self.assert_9points_image(im, expected)
|
||||||
|
|
||||||
@cached_property
|
@CachedProperty
|
||||||
def mask_1(self):
|
def mask_1(self):
|
||||||
mask = Image.new("1", (self.size, self.size))
|
mask = Image.new("1", (self.size, self.size))
|
||||||
px = mask.load()
|
px = mask.load()
|
||||||
|
@ -43,11 +43,11 @@ class TestImagingPaste:
|
||||||
px[y, x] = (x + y) % 2
|
px[y, x] = (x + y) % 2
|
||||||
return mask
|
return mask
|
||||||
|
|
||||||
@cached_property
|
@CachedProperty
|
||||||
def mask_L(self):
|
def mask_L(self):
|
||||||
return self.gradient_L.transpose(Image.Transpose.ROTATE_270)
|
return self.gradient_L.transpose(Image.Transpose.ROTATE_270)
|
||||||
|
|
||||||
@cached_property
|
@CachedProperty
|
||||||
def gradient_L(self):
|
def gradient_L(self):
|
||||||
gradient = Image.new("L", (self.size, self.size))
|
gradient = Image.new("L", (self.size, self.size))
|
||||||
px = gradient.load()
|
px = gradient.load()
|
||||||
|
@ -56,7 +56,7 @@ class TestImagingPaste:
|
||||||
px[y, x] = (x + y) % 255
|
px[y, x] = (x + y) % 255
|
||||||
return gradient
|
return gradient
|
||||||
|
|
||||||
@cached_property
|
@CachedProperty
|
||||||
def gradient_RGB(self):
|
def gradient_RGB(self):
|
||||||
return Image.merge(
|
return Image.merge(
|
||||||
"RGB",
|
"RGB",
|
||||||
|
@ -67,7 +67,7 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@cached_property
|
@CachedProperty
|
||||||
def gradient_LA(self):
|
def gradient_LA(self):
|
||||||
return Image.merge(
|
return Image.merge(
|
||||||
"LA",
|
"LA",
|
||||||
|
@ -77,7 +77,7 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@cached_property
|
@CachedProperty
|
||||||
def gradient_RGBA(self):
|
def gradient_RGBA(self):
|
||||||
return Image.merge(
|
return Image.merge(
|
||||||
"RGBA",
|
"RGBA",
|
||||||
|
@ -89,7 +89,7 @@ class TestImagingPaste:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@cached_property
|
@CachedProperty
|
||||||
def gradient_RGBa(self):
|
def gradient_RGBa(self):
|
||||||
return Image.merge(
|
return Image.merge(
|
||||||
"RGBa",
|
"RGBa",
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .helper import (
|
||||||
assert_image_equal_tofile,
|
assert_image_equal_tofile,
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
hopper,
|
hopper,
|
||||||
|
skip_unless_feature,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -264,6 +265,7 @@ class TestImageResize:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.resize((10, 10), "unknown")
|
im.resize((10, 10), "unknown")
|
||||||
|
|
||||||
|
@skip_unless_feature("libtiff")
|
||||||
def test_load_first(self):
|
def test_load_first(self):
|
||||||
# load() may change the size of the image
|
# load() may change the size of the image
|
||||||
# Test that resize() is calling it before getting the size
|
# Test that resize() is calling it before getting the size
|
||||||
|
|
|
@ -7,6 +7,7 @@ from .helper import (
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
fromstring,
|
fromstring,
|
||||||
hopper,
|
hopper,
|
||||||
|
skip_unless_feature,
|
||||||
tostring,
|
tostring,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -88,6 +89,7 @@ def test_no_resize():
|
||||||
assert im.size == (64, 64)
|
assert im.size == (64, 64)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature("libtiff")
|
||||||
def test_load_first():
|
def test_load_first():
|
||||||
# load() may change the size of the image
|
# load() may change the size of the image
|
||||||
# Test that thumbnail() is calling it before performing size calculations
|
# Test that thumbnail() is calling it before performing size calculations
|
||||||
|
|
|
@ -35,9 +35,9 @@ class TestImageFile:
|
||||||
|
|
||||||
parser = ImageFile.Parser()
|
parser = ImageFile.Parser()
|
||||||
parser.feed(data)
|
parser.feed(data)
|
||||||
imOut = parser.close()
|
im_out = parser.close()
|
||||||
|
|
||||||
return im, imOut
|
return im, im_out
|
||||||
|
|
||||||
assert_image_equal(*roundtrip("BMP"))
|
assert_image_equal(*roundtrip("BMP"))
|
||||||
im1, im2 = roundtrip("GIF")
|
im1, im2 = roundtrip("GIF")
|
||||||
|
|
|
@ -977,6 +977,14 @@ class TestImageFont:
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22)
|
assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22)
|
||||||
|
|
||||||
|
def test_fill_deprecation(self):
|
||||||
|
font = self.get_font()
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
font.getmask2("Hello world", fill=Image.core.fill)
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
font.getmask2("Hello world", fill=None)
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("raqm")
|
@skip_unless_feature("raqm")
|
||||||
class TestImageFont_RaqmLayout(TestImageFont):
|
class TestImageFont_RaqmLayout(TestImageFont):
|
||||||
|
|
|
@ -48,8 +48,8 @@ def img_string_normalize(im):
|
||||||
return img_to_string(string_to_img(im))
|
return img_to_string(string_to_img(im))
|
||||||
|
|
||||||
|
|
||||||
def assert_img_equal_img_string(A, Bstring):
|
def assert_img_equal_img_string(a, b_string):
|
||||||
assert img_to_string(A) == img_string_normalize(Bstring)
|
assert img_to_string(a) == img_string_normalize(b_string)
|
||||||
|
|
||||||
|
|
||||||
def test_str_to_img():
|
def test_str_to_img():
|
||||||
|
|
|
@ -174,7 +174,7 @@ def test_overflow_segfault():
|
||||||
# through to the sequence. Seeing this on 32-bit Windows.
|
# through to the sequence. Seeing this on 32-bit Windows.
|
||||||
with pytest.raises((TypeError, MemoryError)):
|
with pytest.raises((TypeError, MemoryError)):
|
||||||
# post patch, this fails with a memory error
|
# post patch, this fails with a memory error
|
||||||
x = evil()
|
x = Evil()
|
||||||
|
|
||||||
# This fails due to the invalid malloc above,
|
# This fails due to the invalid malloc above,
|
||||||
# and segfaults
|
# and segfaults
|
||||||
|
@ -182,7 +182,7 @@ def test_overflow_segfault():
|
||||||
x[i] = b"0" * 16
|
x[i] = b"0" * 16
|
||||||
|
|
||||||
|
|
||||||
class evil:
|
class Evil:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.corrupt = Image.core.path(0x4000000000000000)
|
self.corrupt = Image.core.path(0x4000000000000000)
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,12 @@ import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from .helper import assert_image_similar, hopper
|
||||||
|
|
||||||
|
with warnings.catch_warnings() as w:
|
||||||
|
warnings.simplefilter("ignore", category=DeprecationWarning)
|
||||||
from PIL import ImageQt
|
from PIL import ImageQt
|
||||||
|
|
||||||
from .helper import assert_image_similar, hopper
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.skipif(
|
pytestmark = pytest.mark.skipif(
|
||||||
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
|
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
|
||||||
|
|
|
@ -65,12 +65,12 @@ def test_libtiff():
|
||||||
|
|
||||||
def test_consecutive():
|
def test_consecutive():
|
||||||
with Image.open("Tests/images/multipage.tiff") as im:
|
with Image.open("Tests/images/multipage.tiff") as im:
|
||||||
firstFrame = None
|
first_frame = None
|
||||||
for frame in ImageSequence.Iterator(im):
|
for frame in ImageSequence.Iterator(im):
|
||||||
if firstFrame is None:
|
if first_frame is None:
|
||||||
firstFrame = frame.copy()
|
first_frame = frame.copy()
|
||||||
for frame in ImageSequence.Iterator(im):
|
for frame in ImageSequence.Iterator(im):
|
||||||
assert_image_equal(frame, firstFrame)
|
assert_image_equal(frame, first_frame)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,51 +26,51 @@ def test_basic(tmp_path):
|
||||||
|
|
||||||
def basic(mode):
|
def basic(mode):
|
||||||
|
|
||||||
imIn = original.convert(mode)
|
im_in = original.convert(mode)
|
||||||
verify(imIn)
|
verify(im_in)
|
||||||
|
|
||||||
w, h = imIn.size
|
w, h = im_in.size
|
||||||
|
|
||||||
imOut = imIn.copy()
|
im_out = im_in.copy()
|
||||||
verify(imOut) # copy
|
verify(im_out) # copy
|
||||||
|
|
||||||
imOut = imIn.transform((w, h), Image.Transform.EXTENT, (0, 0, w, h))
|
im_out = im_in.transform((w, h), Image.Transform.EXTENT, (0, 0, w, h))
|
||||||
verify(imOut) # transform
|
verify(im_out) # transform
|
||||||
|
|
||||||
filename = str(tmp_path / "temp.im")
|
filename = str(tmp_path / "temp.im")
|
||||||
imIn.save(filename)
|
im_in.save(filename)
|
||||||
|
|
||||||
with Image.open(filename) as imOut:
|
with Image.open(filename) as im_out:
|
||||||
|
|
||||||
verify(imIn)
|
verify(im_in)
|
||||||
verify(imOut)
|
verify(im_out)
|
||||||
|
|
||||||
imOut = imIn.crop((0, 0, w, h))
|
im_out = im_in.crop((0, 0, w, h))
|
||||||
verify(imOut)
|
verify(im_out)
|
||||||
|
|
||||||
imOut = Image.new(mode, (w, h), None)
|
im_out = Image.new(mode, (w, h), None)
|
||||||
imOut.paste(imIn.crop((0, 0, w // 2, h)), (0, 0))
|
im_out.paste(im_in.crop((0, 0, w // 2, h)), (0, 0))
|
||||||
imOut.paste(imIn.crop((w // 2, 0, w, h)), (w // 2, 0))
|
im_out.paste(im_in.crop((w // 2, 0, w, h)), (w // 2, 0))
|
||||||
|
|
||||||
verify(imIn)
|
verify(im_in)
|
||||||
verify(imOut)
|
verify(im_out)
|
||||||
|
|
||||||
imIn = Image.new(mode, (1, 1), 1)
|
im_in = Image.new(mode, (1, 1), 1)
|
||||||
assert imIn.getpixel((0, 0)) == 1
|
assert im_in.getpixel((0, 0)) == 1
|
||||||
|
|
||||||
imIn.putpixel((0, 0), 2)
|
im_in.putpixel((0, 0), 2)
|
||||||
assert imIn.getpixel((0, 0)) == 2
|
assert im_in.getpixel((0, 0)) == 2
|
||||||
|
|
||||||
if mode == "L":
|
if mode == "L":
|
||||||
maximum = 255
|
maximum = 255
|
||||||
else:
|
else:
|
||||||
maximum = 32767
|
maximum = 32767
|
||||||
|
|
||||||
imIn = Image.new(mode, (1, 1), 256)
|
im_in = Image.new(mode, (1, 1), 256)
|
||||||
assert imIn.getpixel((0, 0)) == min(256, maximum)
|
assert im_in.getpixel((0, 0)) == min(256, maximum)
|
||||||
|
|
||||||
imIn.putpixel((0, 0), 512)
|
im_in.putpixel((0, 0), 512)
|
||||||
assert imIn.getpixel((0, 0)) == min(512, maximum)
|
assert im_in.getpixel((0, 0)) == min(512, maximum)
|
||||||
|
|
||||||
basic("L")
|
basic("L")
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore", category=DeprecationWarning)
|
||||||
from PIL import ImageQt
|
from PIL import ImageQt
|
||||||
|
|
||||||
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore", category=DeprecationWarning)
|
||||||
from PIL import ImageQt
|
from PIL import ImageQt
|
||||||
|
|
||||||
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
||||||
|
|
|
@ -8,7 +8,7 @@ def test_is_path():
|
||||||
fp = "filename.ext"
|
fp = "filename.ext"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
it_is = _util.isPath(fp)
|
it_is = _util.is_path(fp)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert it_is
|
assert it_is
|
||||||
|
@ -21,7 +21,7 @@ def test_path_obj_is_path():
|
||||||
test_path = Path("filename.ext")
|
test_path = Path("filename.ext")
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
it_is = _util.isPath(test_path)
|
it_is = _util.is_path(test_path)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert it_is
|
assert it_is
|
||||||
|
@ -33,7 +33,7 @@ def test_is_not_path(tmp_path):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
it_is_not = _util.isPath(fp)
|
it_is_not = _util.is_path(fp)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert not it_is_not
|
assert not it_is_not
|
||||||
|
@ -44,7 +44,7 @@ def test_is_directory():
|
||||||
directory = "Tests"
|
directory = "Tests"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
it_is = _util.isDirectory(directory)
|
it_is = _util.is_directory(directory)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert it_is
|
assert it_is
|
||||||
|
@ -55,7 +55,7 @@ def test_is_not_directory():
|
||||||
text = "abc"
|
text = "abc"
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
it_is_not = _util.isDirectory(text)
|
it_is_not = _util.is_directory(text)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert not it_is_not
|
assert not it_is_not
|
||||||
|
@ -65,7 +65,7 @@ def test_deferred_error():
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
thing = _util.deferred_error(ValueError("Some error text"))
|
thing = _util.DeferredError(ValueError("Some error text"))
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
|
|
|
@ -142,6 +142,14 @@ The stub image plugin ``FitsStubImagePlugin`` has been deprecated and will be re
|
||||||
Pillow 10.0.0 (2023-07-01). FITS images can be read without a handler through
|
Pillow 10.0.0 (2023-07-01). FITS images can be read without a handler through
|
||||||
:mod:`~PIL.FitsImagePlugin` instead.
|
:mod:`~PIL.FitsImagePlugin` instead.
|
||||||
|
|
||||||
|
FreeTypeFont.getmask2 fill parameter
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. deprecated:: 9.2.0
|
||||||
|
|
||||||
|
The undocumented ``fill`` parameter of :py:meth:`.FreeTypeFont.getmask2` has been
|
||||||
|
deprecated and will be removed in Pillow 10 (2023-07-01).
|
||||||
|
|
||||||
PhotoImage.paste box parameter
|
PhotoImage.paste box parameter
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -149,6 +157,19 @@ PhotoImage.paste box parameter
|
||||||
|
|
||||||
The ``box`` parameter is unused. It will be removed in Pillow 10.0.0 (2023-07-01).
|
The ``box`` parameter is unused. It will be removed in Pillow 10.0.0 (2023-07-01).
|
||||||
|
|
||||||
|
PyQt5 and PySide2
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. deprecated:: 9.2.0
|
||||||
|
|
||||||
|
`Qt 5 reached end-of-life <https://www.qt.io/blog/qt-5.15-released>`_ on 2020-12-08 for
|
||||||
|
open-source users (and will reach EOL on 2023-12-08 for commercial licence holders).
|
||||||
|
|
||||||
|
Support for PyQt5 and PySide2 has been deprecated from ``ImageQt`` and will be removed
|
||||||
|
in Pillow 10 (2023-07-01). Upgrade to
|
||||||
|
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
|
||||||
|
`PySide6 <https://doc.qt.io/qtforpython/>`_ instead.
|
||||||
|
|
||||||
Removed features
|
Removed features
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ GIF
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
Pillow reads GIF87a and GIF89a versions of the GIF file format. The library
|
Pillow reads GIF87a and GIF89a versions of the GIF file format. The library
|
||||||
writes run-length encoded files in GIF87a by default, unless GIF89a features
|
writes LZW encoded files in GIF87a by default, unless GIF89a features
|
||||||
are used or GIF89a is already in use.
|
are used or GIF89a is already in use.
|
||||||
|
|
||||||
GIF files are initially read as grayscale (``L``) or palette mode (``P``)
|
GIF files are initially read as grayscale (``L``) or palette mode (``P``)
|
||||||
|
|
|
@ -504,6 +504,17 @@ image header. In addition, seek will also be used when the image data is read
|
||||||
tar file, you can use the :py:class:`~PIL.ContainerIO` or
|
tar file, you can use the :py:class:`~PIL.ContainerIO` or
|
||||||
:py:class:`~PIL.TarIO` modules to access it.
|
:py:class:`~PIL.TarIO` modules to access it.
|
||||||
|
|
||||||
|
Reading from URL
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from urllib.request import urlopen
|
||||||
|
url = "https://python-pillow.org/images/pillow-logo.png"
|
||||||
|
img = Image.open(urlopen(url))
|
||||||
|
|
||||||
|
|
||||||
Reading from a tar archive
|
Reading from a tar archive
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -474,6 +474,8 @@ These platforms are built and tested for every change.
|
||||||
| | 3.8 | arm64v8, ppc64le, |
|
| | 3.8 | arm64v8, ppc64le, |
|
||||||
| | | s390x |
|
| | | s390x |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
|
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
||||||
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Windows Server 2016 | 3.7 | x86-64 |
|
| Windows Server 2016 | 3.7 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Windows Server 2019 | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86, x86-64 |
|
| Windows Server 2019 | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86, x86-64 |
|
||||||
|
|
|
@ -7,6 +7,14 @@
|
||||||
The :py:mod:`~PIL.ImageQt` module contains support for creating PyQt6, PySide6, PyQt5
|
The :py:mod:`~PIL.ImageQt` module contains support for creating PyQt6, PySide6, PyQt5
|
||||||
or PySide2 QImage objects from PIL images.
|
or PySide2 QImage objects from PIL images.
|
||||||
|
|
||||||
|
`Qt 5 reached end-of-life <https://www.qt.io/blog/qt-5.15-released>`_ on 2020-12-08 for
|
||||||
|
open-source users (and will reach EOL on 2023-12-08 for commercial licence holders).
|
||||||
|
|
||||||
|
Support for PyQt5 and PySide2 has been deprecated from ``ImageQt`` and will be removed
|
||||||
|
in Pillow 10 (2023-07-01). Upgrade to
|
||||||
|
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
|
||||||
|
`PySide6 <https://doc.qt.io/qtforpython/>`_ instead.
|
||||||
|
|
||||||
.. versionadded:: 1.1.6
|
.. versionadded:: 1.1.6
|
||||||
|
|
||||||
.. py:class:: ImageQt(image)
|
.. py:class:: ImageQt(image)
|
||||||
|
|
|
@ -9,6 +9,14 @@ Internal Modules
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
:mod:`~PIL._deprecate` Module
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
.. automodule:: PIL._deprecate
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
:mod:`~PIL._tkinter_finder` Module
|
:mod:`~PIL._tkinter_finder` Module
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
|
|
64
docs/releasenotes/9.2.0.rst
Normal file
64
docs/releasenotes/9.2.0.rst
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
9.2.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
Backwards Incompatible Changes
|
||||||
|
==============================
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
============
|
||||||
|
|
||||||
|
PyQt5 and PySide2
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 9.2.0
|
||||||
|
|
||||||
|
`Qt 5 reached end-of-life <https://www.qt.io/blog/qt-5.15-released>`_ on 2020-12-08 for
|
||||||
|
open-source users (and will reach EOL on 2023-12-08 for commercial licence holders).
|
||||||
|
|
||||||
|
Support for PyQt5 and PySide2 has been deprecated from ``ImageQt`` and will be removed
|
||||||
|
in Pillow 10 (2023-07-01). Upgrade to
|
||||||
|
`PyQt6 <https://www.riverbankcomputing.com/static/Docs/PyQt6/>`_ or
|
||||||
|
`PySide6 <https://doc.qt.io/qtforpython/>`_ instead.
|
||||||
|
|
||||||
|
FreeTypeFont.getmask2 fill parameter
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 9.2.0
|
||||||
|
|
||||||
|
The undocumented ``fill`` parameter of :py:meth:`.FreeTypeFont.getmask2`
|
||||||
|
has been deprecated and will be removed in Pillow 10 (2023-07-01).
|
||||||
|
|
||||||
|
API Changes
|
||||||
|
===========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
API Additions
|
||||||
|
=============
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
|
@ -14,6 +14,7 @@ expected to be backported to earlier versions.
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
9.2.0
|
||||||
9.1.0
|
9.1.0
|
||||||
9.0.1
|
9.0.1
|
||||||
9.0.0
|
9.0.0
|
||||||
|
|
|
@ -31,11 +31,11 @@ BLP files come in many different flavours:
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
import warnings
|
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
from ._deprecate import deprecate
|
||||||
|
|
||||||
|
|
||||||
class Format(IntEnum):
|
class Format(IntEnum):
|
||||||
|
@ -55,7 +55,6 @@ class AlphaEncoding(IntEnum):
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
def __getattr__(name):
|
||||||
deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
|
|
||||||
for enum, prefix in {
|
for enum, prefix in {
|
||||||
Format: "BLP_FORMAT_",
|
Format: "BLP_FORMAT_",
|
||||||
Encoding: "BLP_ENCODING_",
|
Encoding: "BLP_ENCODING_",
|
||||||
|
@ -64,19 +63,7 @@ def __getattr__(name):
|
||||||
if name.startswith(prefix):
|
if name.startswith(prefix):
|
||||||
name = name[len(prefix) :]
|
name = name[len(prefix) :]
|
||||||
if name in enum.__members__:
|
if name in enum.__members__:
|
||||||
warnings.warn(
|
deprecate(f"{prefix}{name}", 10, f"{enum.__name__}.{name}")
|
||||||
prefix
|
|
||||||
+ name
|
|
||||||
+ " is "
|
|
||||||
+ deprecated
|
|
||||||
+ "Use "
|
|
||||||
+ enum.__name__
|
|
||||||
+ "."
|
|
||||||
+ name
|
|
||||||
+ " instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return enum[name]
|
return enum[name]
|
||||||
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,8 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from . import FitsImagePlugin, Image, ImageFile
|
from . import FitsImagePlugin, Image, ImageFile
|
||||||
|
from ._deprecate import deprecate
|
||||||
|
|
||||||
_handler = None
|
_handler = None
|
||||||
|
|
||||||
|
@ -25,11 +24,11 @@ def register_handler(handler):
|
||||||
global _handler
|
global _handler
|
||||||
_handler = handler
|
_handler = handler
|
||||||
|
|
||||||
warnings.warn(
|
deprecate(
|
||||||
"FitsStubImagePlugin is deprecated and will be removed in Pillow "
|
"FitsStubImagePlugin",
|
||||||
"10 (2023-07-01). FITS images can now be read without a handler through "
|
10,
|
||||||
"FitsImagePlugin instead.",
|
action="FITS images can now be read without "
|
||||||
DeprecationWarning,
|
"a handler through FitsImagePlugin instead",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Override FitsImagePlugin with this handler
|
# Override FitsImagePlugin with this handler
|
||||||
|
|
|
@ -52,11 +52,11 @@ Note: All data is stored in little-Endian (Intel) byte order.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import struct
|
import struct
|
||||||
import warnings
|
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
from ._deprecate import deprecate
|
||||||
|
|
||||||
MAGIC = b"FTEX"
|
MAGIC = b"FTEX"
|
||||||
|
|
||||||
|
@ -67,24 +67,11 @@ class Format(IntEnum):
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
def __getattr__(name):
|
||||||
deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
|
|
||||||
for enum, prefix in {Format: "FORMAT_"}.items():
|
for enum, prefix in {Format: "FORMAT_"}.items():
|
||||||
if name.startswith(prefix):
|
if name.startswith(prefix):
|
||||||
name = name[len(prefix) :]
|
name = name[len(prefix) :]
|
||||||
if name in enum.__members__:
|
if name in enum.__members__:
|
||||||
warnings.warn(
|
deprecate(f"{prefix}{name}", 10, f"{enum.__name__}.{name}")
|
||||||
prefix
|
|
||||||
+ name
|
|
||||||
+ " is "
|
|
||||||
+ deprecated
|
|
||||||
+ "Use "
|
|
||||||
+ enum.__name__
|
|
||||||
+ "."
|
|
||||||
+ name
|
|
||||||
+ " instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return enum[name]
|
return enum[name]
|
||||||
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||||
|
|
||||||
|
|
|
@ -54,20 +54,25 @@ class GdImageFile(ImageFile.ImageFile):
|
||||||
self.mode = "L" # FIXME: "P"
|
self.mode = "L" # FIXME: "P"
|
||||||
self._size = i16(s, 2), i16(s, 4)
|
self._size = i16(s, 2), i16(s, 4)
|
||||||
|
|
||||||
trueColor = s[6]
|
true_color = s[6]
|
||||||
trueColorOffset = 2 if trueColor else 0
|
true_color_offset = 2 if true_color else 0
|
||||||
|
|
||||||
# transparency index
|
# transparency index
|
||||||
tindex = i32(s, 7 + trueColorOffset)
|
tindex = i32(s, 7 + true_color_offset)
|
||||||
if tindex < 256:
|
if tindex < 256:
|
||||||
self.info["transparency"] = tindex
|
self.info["transparency"] = tindex
|
||||||
|
|
||||||
self.palette = ImagePalette.raw(
|
self.palette = ImagePalette.raw(
|
||||||
"XBGR", s[7 + trueColorOffset + 4 : 7 + trueColorOffset + 4 + 256 * 4]
|
"XBGR", s[7 + true_color_offset + 4 : 7 + true_color_offset + 4 + 256 * 4]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.tile = [
|
self.tile = [
|
||||||
("raw", (0, 0) + self.size, 7 + trueColorOffset + 4 + 256 * 4, ("L", 0, 1))
|
(
|
||||||
|
"raw",
|
||||||
|
(0, 0) + self.size,
|
||||||
|
7 + true_color_offset + 4 + 256 * 4,
|
||||||
|
("L", 0, 1),
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -883,10 +883,10 @@ def _get_palette_bytes(im):
|
||||||
return im.palette.palette
|
return im.palette.palette
|
||||||
|
|
||||||
|
|
||||||
def _get_background(im, infoBackground):
|
def _get_background(im, info_background):
|
||||||
background = 0
|
background = 0
|
||||||
if infoBackground:
|
if info_background:
|
||||||
background = infoBackground
|
background = info_background
|
||||||
if isinstance(background, tuple):
|
if isinstance(background, tuple):
|
||||||
# WebPImagePlugin stores an RGBA value in info["background"]
|
# WebPImagePlugin stores an RGBA value in info["background"]
|
||||||
# So it must be converted to the same format as GifImagePlugin's
|
# So it must be converted to the same format as GifImagePlugin's
|
||||||
|
|
|
@ -50,28 +50,17 @@ except ImportError:
|
||||||
# Use __version__ instead.
|
# Use __version__ instead.
|
||||||
from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins
|
from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins
|
||||||
from ._binary import i32le, o32be, o32le
|
from ._binary import i32le, o32be, o32le
|
||||||
from ._util import deferred_error, isPath
|
from ._deprecate import deprecate
|
||||||
|
from ._util import DeferredError, is_path
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
def __getattr__(name):
|
||||||
deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
|
|
||||||
categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2}
|
categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2}
|
||||||
if name in categories:
|
if name in categories:
|
||||||
warnings.warn(
|
deprecate("Image categories", 10, "is_animated", plural=True)
|
||||||
"Image categories are " + deprecated + "Use is_animated instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return categories[name]
|
return categories[name]
|
||||||
elif name in ("NEAREST", "NONE"):
|
elif name in ("NEAREST", "NONE"):
|
||||||
warnings.warn(
|
deprecate(name, 10, "Resampling.NEAREST or Dither.NONE")
|
||||||
name
|
|
||||||
+ " is "
|
|
||||||
+ deprecated
|
|
||||||
+ "Use Resampling.NEAREST or Dither.NONE instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return 0
|
return 0
|
||||||
old_resampling = {
|
old_resampling = {
|
||||||
"LINEAR": "BILINEAR",
|
"LINEAR": "BILINEAR",
|
||||||
|
@ -79,31 +68,11 @@ def __getattr__(name):
|
||||||
"ANTIALIAS": "LANCZOS",
|
"ANTIALIAS": "LANCZOS",
|
||||||
}
|
}
|
||||||
if name in old_resampling:
|
if name in old_resampling:
|
||||||
warnings.warn(
|
deprecate(name, 10, f"Resampling.{old_resampling[name]}")
|
||||||
name
|
|
||||||
+ " is "
|
|
||||||
+ deprecated
|
|
||||||
+ "Use Resampling."
|
|
||||||
+ old_resampling[name]
|
|
||||||
+ " instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return Resampling[old_resampling[name]]
|
return Resampling[old_resampling[name]]
|
||||||
for enum in (Transpose, Transform, Resampling, Dither, Palette, Quantize):
|
for enum in (Transpose, Transform, Resampling, Dither, Palette, Quantize):
|
||||||
if name in enum.__members__:
|
if name in enum.__members__:
|
||||||
warnings.warn(
|
deprecate(name, 10, f"{enum.__name__}.{name}")
|
||||||
name
|
|
||||||
+ " is "
|
|
||||||
+ deprecated
|
|
||||||
+ "Use "
|
|
||||||
+ enum.__name__
|
|
||||||
+ "."
|
|
||||||
+ name
|
|
||||||
+ " instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return enum[name]
|
return enum[name]
|
||||||
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||||
|
|
||||||
|
@ -139,7 +108,7 @@ try:
|
||||||
)
|
)
|
||||||
|
|
||||||
except ImportError as v:
|
except ImportError as v:
|
||||||
core = deferred_error(ImportError("The _imaging C module is not installed."))
|
core = DeferredError(ImportError("The _imaging C module is not installed."))
|
||||||
# Explanations for ways that we know we might have an import error
|
# Explanations for ways that we know we might have an import error
|
||||||
if str(v).startswith("Module use of python"):
|
if str(v).startswith("Module use of python"):
|
||||||
# The _imaging C module is present, but not compiled for
|
# The _imaging C module is present, but not compiled for
|
||||||
|
@ -538,12 +507,7 @@ class Image:
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if name == "category":
|
if name == "category":
|
||||||
warnings.warn(
|
deprecate("Image categories", 10, "is_animated", plural=True)
|
||||||
"Image categories are deprecated and will be removed in Pillow 10 "
|
|
||||||
"(2023-07-01). Use is_animated instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return self._category
|
return self._category
|
||||||
raise AttributeError(name)
|
raise AttributeError(name)
|
||||||
|
|
||||||
|
@ -613,7 +577,7 @@ class Image:
|
||||||
# Instead of simply setting to None, we're setting up a
|
# Instead of simply setting to None, we're setting up a
|
||||||
# deferred error that will better explain that the core image
|
# deferred error that will better explain that the core image
|
||||||
# object is gone.
|
# object is gone.
|
||||||
self.im = deferred_error(ValueError("Operation on closed image"))
|
self.im = DeferredError(ValueError("Operation on closed image"))
|
||||||
|
|
||||||
def _copy(self):
|
def _copy(self):
|
||||||
self.load()
|
self.load()
|
||||||
|
@ -2251,7 +2215,7 @@ class Image:
|
||||||
if isinstance(fp, Path):
|
if isinstance(fp, Path):
|
||||||
filename = str(fp)
|
filename = str(fp)
|
||||||
open_fp = True
|
open_fp = True
|
||||||
elif isPath(fp):
|
elif is_path(fp):
|
||||||
filename = fp
|
filename = fp
|
||||||
open_fp = True
|
open_fp = True
|
||||||
elif fp == sys.stdout:
|
elif fp == sys.stdout:
|
||||||
|
@ -2259,7 +2223,7 @@ class Image:
|
||||||
fp = sys.stdout.buffer
|
fp = sys.stdout.buffer
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
if not filename and hasattr(fp, "name") and isPath(fp.name):
|
if not filename and hasattr(fp, "name") and is_path(fp.name):
|
||||||
# only set the name for metadata purposes
|
# only set the name for metadata purposes
|
||||||
filename = fp.name
|
filename = fp.name
|
||||||
|
|
||||||
|
@ -3065,7 +3029,7 @@ def open(fp, mode="r", formats=None):
|
||||||
filename = ""
|
filename = ""
|
||||||
if isinstance(fp, Path):
|
if isinstance(fp, Path):
|
||||||
filename = str(fp.resolve())
|
filename = str(fp.resolve())
|
||||||
elif isPath(fp):
|
elif is_path(fp):
|
||||||
filename = fp
|
filename = fp
|
||||||
|
|
||||||
if filename:
|
if filename:
|
||||||
|
@ -3363,7 +3327,7 @@ def effect_mandelbrot(size, extent, quality):
|
||||||
:param size: The requested size in pixels, as a 2-tuple:
|
:param size: The requested size in pixels, as a 2-tuple:
|
||||||
(width, height).
|
(width, height).
|
||||||
:param extent: The extent to cover, as a 4-tuple:
|
:param extent: The extent to cover, as a 4-tuple:
|
||||||
(x0, y0, x1, y2).
|
(x0, y0, x1, y1).
|
||||||
:param quality: Quality.
|
:param quality: Quality.
|
||||||
"""
|
"""
|
||||||
return Image()._new(core.effect_mandelbrot(size, extent, quality))
|
return Image()._new(core.effect_mandelbrot(size, extent, quality))
|
||||||
|
|
|
@ -16,19 +16,20 @@
|
||||||
# below for the original description.
|
# below for the original description.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
from ._deprecate import deprecate
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import _imagingcms
|
from PIL import _imagingcms
|
||||||
except ImportError as ex:
|
except ImportError as ex:
|
||||||
# Allow error import for doc purposes, but error out when accessing
|
# Allow error import for doc purposes, but error out when accessing
|
||||||
# anything in core.
|
# anything in core.
|
||||||
from ._util import deferred_error
|
from ._util import DeferredError
|
||||||
|
|
||||||
_imagingcms = deferred_error(ex)
|
_imagingcms = DeferredError(ex)
|
||||||
|
|
||||||
DESCRIPTION = """
|
DESCRIPTION = """
|
||||||
pyCMS
|
pyCMS
|
||||||
|
@ -117,24 +118,11 @@ class Direction(IntEnum):
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
def __getattr__(name):
|
||||||
deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
|
|
||||||
for enum, prefix in {Intent: "INTENT_", Direction: "DIRECTION_"}.items():
|
for enum, prefix in {Intent: "INTENT_", Direction: "DIRECTION_"}.items():
|
||||||
if name.startswith(prefix):
|
if name.startswith(prefix):
|
||||||
name = name[len(prefix) :]
|
name = name[len(prefix) :]
|
||||||
if name in enum.__members__:
|
if name in enum.__members__:
|
||||||
warnings.warn(
|
deprecate(f"{prefix}{name}", 10, f"{enum.__name__}.{name}")
|
||||||
prefix
|
|
||||||
+ name
|
|
||||||
+ " is "
|
|
||||||
+ deprecated
|
|
||||||
+ "Use "
|
|
||||||
+ enum.__name__
|
|
||||||
+ "."
|
|
||||||
+ name
|
|
||||||
+ " instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return enum[name]
|
return enum[name]
|
||||||
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||||
|
|
||||||
|
|
|
@ -197,18 +197,18 @@ class ImageDraw:
|
||||||
if width > 8:
|
if width > 8:
|
||||||
# Cover potential gaps between the line and the joint
|
# Cover potential gaps between the line and the joint
|
||||||
if flipped:
|
if flipped:
|
||||||
gapCoords = [
|
gap_coords = [
|
||||||
coord_at_angle(point, angles[0] + 90),
|
coord_at_angle(point, angles[0] + 90),
|
||||||
point,
|
point,
|
||||||
coord_at_angle(point, angles[1] + 90),
|
coord_at_angle(point, angles[1] + 90),
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
gapCoords = [
|
gap_coords = [
|
||||||
coord_at_angle(point, angles[0] - 90),
|
coord_at_angle(point, angles[0] - 90),
|
||||||
point,
|
point,
|
||||||
coord_at_angle(point, angles[1] - 90),
|
coord_at_angle(point, angles[1] - 90),
|
||||||
]
|
]
|
||||||
self.line(gapCoords, fill, width=3)
|
self.line(gap_coords, fill, width=3)
|
||||||
|
|
||||||
def shape(self, shape, fill=None, outline=None):
|
def shape(self, shape, fill=None, outline=None):
|
||||||
"""(Experimental) Draw a shape."""
|
"""(Experimental) Draw a shape."""
|
||||||
|
|
|
@ -33,7 +33,7 @@ import struct
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._util import isPath
|
from ._util import is_path
|
||||||
|
|
||||||
MAXBLOCK = 65536
|
MAXBLOCK = 65536
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ class ImageFile(Image.Image):
|
||||||
self.decoderconfig = ()
|
self.decoderconfig = ()
|
||||||
self.decodermaxblock = MAXBLOCK
|
self.decodermaxblock = MAXBLOCK
|
||||||
|
|
||||||
if isPath(fp):
|
if is_path(fp):
|
||||||
# filename
|
# filename
|
||||||
self.fp = open(fp, "rb")
|
self.fp = open(fp, "rb")
|
||||||
self.filename = fp
|
self.filename = fp
|
||||||
|
|
|
@ -421,8 +421,8 @@ class Color3DLUT(MultibandFilter):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
size = (size, size, size)
|
size = (size, size, size)
|
||||||
size = [int(x) for x in size]
|
size = [int(x) for x in size]
|
||||||
for size1D in size:
|
for size_1d in size:
|
||||||
if not 2 <= size1D <= 65:
|
if not 2 <= size_1d <= 65:
|
||||||
raise ValueError("Size should be in [2, 65] range.")
|
raise ValueError("Size should be in [2, 65] range.")
|
||||||
return size
|
return size
|
||||||
|
|
||||||
|
@ -439,22 +439,22 @@ class Color3DLUT(MultibandFilter):
|
||||||
:param target_mode: Passed to the constructor of the resulting
|
:param target_mode: Passed to the constructor of the resulting
|
||||||
lookup table.
|
lookup table.
|
||||||
"""
|
"""
|
||||||
size1D, size2D, size3D = cls._check_size(size)
|
size_1d, size_2d, size_3d = cls._check_size(size)
|
||||||
if channels not in (3, 4):
|
if channels not in (3, 4):
|
||||||
raise ValueError("Only 3 or 4 output channels are supported")
|
raise ValueError("Only 3 or 4 output channels are supported")
|
||||||
|
|
||||||
table = [0] * (size1D * size2D * size3D * channels)
|
table = [0] * (size_1d * size_2d * size_3d * channels)
|
||||||
idx_out = 0
|
idx_out = 0
|
||||||
for b in range(size3D):
|
for b in range(size_3d):
|
||||||
for g in range(size2D):
|
for g in range(size_2d):
|
||||||
for r in range(size1D):
|
for r in range(size_1d):
|
||||||
table[idx_out : idx_out + channels] = callback(
|
table[idx_out : idx_out + channels] = callback(
|
||||||
r / (size1D - 1), g / (size2D - 1), b / (size3D - 1)
|
r / (size_1d - 1), g / (size_2d - 1), b / (size_3d - 1)
|
||||||
)
|
)
|
||||||
idx_out += channels
|
idx_out += channels
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
(size1D, size2D, size3D),
|
(size_1d, size_2d, size_3d),
|
||||||
table,
|
table,
|
||||||
channels=channels,
|
channels=channels,
|
||||||
target_mode=target_mode,
|
target_mode=target_mode,
|
||||||
|
@ -484,20 +484,20 @@ class Color3DLUT(MultibandFilter):
|
||||||
raise ValueError("Only 3 or 4 output channels are supported")
|
raise ValueError("Only 3 or 4 output channels are supported")
|
||||||
ch_in = self.channels
|
ch_in = self.channels
|
||||||
ch_out = channels or ch_in
|
ch_out = channels or ch_in
|
||||||
size1D, size2D, size3D = self.size
|
size_1d, size_2d, size_3d = self.size
|
||||||
|
|
||||||
table = [0] * (size1D * size2D * size3D * ch_out)
|
table = [0] * (size_1d * size_2d * size_3d * ch_out)
|
||||||
idx_in = 0
|
idx_in = 0
|
||||||
idx_out = 0
|
idx_out = 0
|
||||||
for b in range(size3D):
|
for b in range(size_3d):
|
||||||
for g in range(size2D):
|
for g in range(size_2d):
|
||||||
for r in range(size1D):
|
for r in range(size_1d):
|
||||||
values = self.table[idx_in : idx_in + ch_in]
|
values = self.table[idx_in : idx_in + ch_in]
|
||||||
if with_normals:
|
if with_normals:
|
||||||
values = callback(
|
values = callback(
|
||||||
r / (size1D - 1),
|
r / (size_1d - 1),
|
||||||
g / (size2D - 1),
|
g / (size_2d - 1),
|
||||||
b / (size3D - 1),
|
b / (size_3d - 1),
|
||||||
*values,
|
*values,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -33,7 +33,8 @@ from enum import IntEnum
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._util import isDirectory, isPath
|
from ._deprecate import deprecate
|
||||||
|
from ._util import is_directory, is_path
|
||||||
|
|
||||||
|
|
||||||
class Layout(IntEnum):
|
class Layout(IntEnum):
|
||||||
|
@ -42,29 +43,16 @@ class Layout(IntEnum):
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
def __getattr__(name):
|
||||||
deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
|
|
||||||
for enum, prefix in {Layout: "LAYOUT_"}.items():
|
for enum, prefix in {Layout: "LAYOUT_"}.items():
|
||||||
if name.startswith(prefix):
|
if name.startswith(prefix):
|
||||||
name = name[len(prefix) :]
|
name = name[len(prefix) :]
|
||||||
if name in enum.__members__:
|
if name in enum.__members__:
|
||||||
warnings.warn(
|
deprecate(f"{prefix}{name}", 10, f"{enum.__name__}.{name}")
|
||||||
prefix
|
|
||||||
+ name
|
|
||||||
+ " is "
|
|
||||||
+ deprecated
|
|
||||||
+ "Use "
|
|
||||||
+ enum.__name__
|
|
||||||
+ "."
|
|
||||||
+ name
|
|
||||||
+ " instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return enum[name]
|
return enum[name]
|
||||||
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||||
|
|
||||||
|
|
||||||
class _imagingft_not_installed:
|
class _ImagingFtNotInstalled:
|
||||||
# module placeholder
|
# module placeholder
|
||||||
def __getattr__(self, id):
|
def __getattr__(self, id):
|
||||||
raise ImportError("The _imagingft C module is not installed")
|
raise ImportError("The _imagingft C module is not installed")
|
||||||
|
@ -73,7 +61,10 @@ class _imagingft_not_installed:
|
||||||
try:
|
try:
|
||||||
from . import _imagingft as core
|
from . import _imagingft as core
|
||||||
except ImportError:
|
except ImportError:
|
||||||
core = _imagingft_not_installed()
|
core = _ImagingFtNotInstalled()
|
||||||
|
|
||||||
|
|
||||||
|
_UNSPECIFIED = object()
|
||||||
|
|
||||||
|
|
||||||
# FIXME: add support for pilfont2 format (see FontFile.py)
|
# FIXME: add support for pilfont2 format (see FontFile.py)
|
||||||
|
@ -196,8 +187,6 @@ class FreeTypeFont:
|
||||||
if core.HAVE_RAQM:
|
if core.HAVE_RAQM:
|
||||||
layout_engine = Layout.RAQM
|
layout_engine = Layout.RAQM
|
||||||
elif layout_engine == Layout.RAQM and not core.HAVE_RAQM:
|
elif layout_engine == Layout.RAQM and not core.HAVE_RAQM:
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"Raqm layout was requested, but Raqm is not available. "
|
"Raqm layout was requested, but Raqm is not available. "
|
||||||
"Falling back to basic layout."
|
"Falling back to basic layout."
|
||||||
|
@ -212,7 +201,7 @@ class FreeTypeFont:
|
||||||
"", size, index, encoding, self.font_bytes, layout_engine
|
"", size, index, encoding, self.font_bytes, layout_engine
|
||||||
)
|
)
|
||||||
|
|
||||||
if isPath(font):
|
if is_path(font):
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
font_bytes_path = font if isinstance(font, bytes) else font.encode()
|
font_bytes_path = font if isinstance(font, bytes) else font.encode()
|
||||||
try:
|
try:
|
||||||
|
@ -616,7 +605,7 @@ class FreeTypeFont:
|
||||||
self,
|
self,
|
||||||
text,
|
text,
|
||||||
mode="",
|
mode="",
|
||||||
fill=Image.core.fill,
|
fill=_UNSPECIFIED,
|
||||||
direction=None,
|
direction=None,
|
||||||
features=None,
|
features=None,
|
||||||
language=None,
|
language=None,
|
||||||
|
@ -641,6 +630,12 @@ class FreeTypeFont:
|
||||||
|
|
||||||
.. versionadded:: 1.1.5
|
.. versionadded:: 1.1.5
|
||||||
|
|
||||||
|
:param fill: Optional fill function. By default, an internal Pillow function
|
||||||
|
will be used.
|
||||||
|
|
||||||
|
Deprecated. This parameter will be removed in Pillow 10
|
||||||
|
(2023-07-01).
|
||||||
|
|
||||||
:param direction: Direction of the text. It can be 'rtl' (right to
|
:param direction: Direction of the text. It can be 'rtl' (right to
|
||||||
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
left), 'ltr' (left to right) or 'ttb' (top to bottom).
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
|
@ -688,6 +683,10 @@ class FreeTypeFont:
|
||||||
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
||||||
gap between the starting coordinate and the first marking
|
gap between the starting coordinate and the first marking
|
||||||
"""
|
"""
|
||||||
|
if fill is _UNSPECIFIED:
|
||||||
|
fill = Image.core.fill
|
||||||
|
else:
|
||||||
|
deprecate("fill", 10)
|
||||||
size, offset = self.font.getsize(
|
size, offset = self.font.getsize(
|
||||||
text, mode, direction, features, language, anchor
|
text, mode, direction, features, language, anchor
|
||||||
)
|
)
|
||||||
|
@ -877,7 +876,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
|
||||||
try:
|
try:
|
||||||
return freetype(font)
|
return freetype(font)
|
||||||
except OSError:
|
except OSError:
|
||||||
if not isPath(font):
|
if not is_path(font):
|
||||||
raise
|
raise
|
||||||
ttf_filename = os.path.basename(font)
|
ttf_filename = os.path.basename(font)
|
||||||
|
|
||||||
|
@ -931,7 +930,7 @@ def load_path(filename):
|
||||||
:exception OSError: If the file could not be read.
|
:exception OSError: If the file could not be read.
|
||||||
"""
|
"""
|
||||||
for directory in sys.path:
|
for directory in sys.path:
|
||||||
if isDirectory(directory):
|
if is_directory(directory):
|
||||||
if not isinstance(filename, str):
|
if not isinstance(filename, str):
|
||||||
filename = filename.decode("utf-8")
|
filename = filename.decode("utf-8")
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -17,9 +17,9 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import array
|
import array
|
||||||
import warnings
|
|
||||||
|
|
||||||
from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
|
from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile
|
||||||
|
from ._deprecate import deprecate
|
||||||
|
|
||||||
|
|
||||||
class ImagePalette:
|
class ImagePalette:
|
||||||
|
@ -40,11 +40,7 @@ class ImagePalette:
|
||||||
self.palette = palette or bytearray()
|
self.palette = palette or bytearray()
|
||||||
self.dirty = None
|
self.dirty = None
|
||||||
if size != 0:
|
if size != 0:
|
||||||
warnings.warn(
|
deprecate("The size parameter", 10, None)
|
||||||
"The size parameter is deprecated and will be removed in Pillow 10 "
|
|
||||||
"(2023-07-01).",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
if size != len(self.palette):
|
if size != len(self.palette):
|
||||||
raise ValueError("wrong palette size")
|
raise ValueError("wrong palette size")
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,8 @@ import sys
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._util import isPath
|
from ._deprecate import deprecate
|
||||||
|
from ._util import is_path
|
||||||
|
|
||||||
qt_versions = [
|
qt_versions = [
|
||||||
["6", "PyQt6"],
|
["6", "PyQt6"],
|
||||||
|
@ -42,9 +43,13 @@ for qt_version, qt_module in qt_versions:
|
||||||
elif qt_module == "PyQt5":
|
elif qt_module == "PyQt5":
|
||||||
from PyQt5.QtCore import QBuffer, QIODevice
|
from PyQt5.QtCore import QBuffer, QIODevice
|
||||||
from PyQt5.QtGui import QImage, QPixmap, qRgba
|
from PyQt5.QtGui import QImage, QPixmap, qRgba
|
||||||
|
|
||||||
|
deprecate("Support for PyQt5", 10, "PyQt6 or PySide6")
|
||||||
elif qt_module == "PySide2":
|
elif qt_module == "PySide2":
|
||||||
from PySide2.QtCore import QBuffer, QIODevice
|
from PySide2.QtCore import QBuffer, QIODevice
|
||||||
from PySide2.QtGui import QImage, QPixmap, qRgba
|
from PySide2.QtGui import QImage, QPixmap, qRgba
|
||||||
|
|
||||||
|
deprecate("Support for PySide2", 10, "PyQt6 or PySide6")
|
||||||
except (ImportError, RuntimeError):
|
except (ImportError, RuntimeError):
|
||||||
continue
|
continue
|
||||||
qt_is_installed = True
|
qt_is_installed = True
|
||||||
|
@ -140,7 +145,7 @@ def _toqclass_helper(im):
|
||||||
if hasattr(im, "toUtf8"):
|
if hasattr(im, "toUtf8"):
|
||||||
# FIXME - is this really the best way to do this?
|
# FIXME - is this really the best way to do this?
|
||||||
im = str(im.toUtf8(), "utf-8")
|
im = str(im.toUtf8(), "utf-8")
|
||||||
if isPath(im):
|
if is_path(im):
|
||||||
im = Image.open(im)
|
im = Image.open(im)
|
||||||
exclusive_fp = True
|
exclusive_fp = True
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,12 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
|
||||||
from shlex import quote
|
from shlex import quote
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
from ._deprecate import deprecate
|
||||||
|
|
||||||
_viewers = []
|
_viewers = []
|
||||||
|
|
||||||
|
|
||||||
|
@ -120,11 +121,7 @@ class Viewer:
|
||||||
"""
|
"""
|
||||||
if path is None:
|
if path is None:
|
||||||
if "file" in options:
|
if "file" in options:
|
||||||
warnings.warn(
|
deprecate("The 'file' argument", 10, "'path'")
|
||||||
"The 'file' argument is deprecated and will be removed in Pillow "
|
|
||||||
"10 (2023-07-01). Use 'path' instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
path = options.pop("file")
|
path = options.pop("file")
|
||||||
else:
|
else:
|
||||||
raise TypeError("Missing required argument: 'path'")
|
raise TypeError("Missing required argument: 'path'")
|
||||||
|
@ -144,7 +141,7 @@ class WindowsViewer(Viewer):
|
||||||
def get_command(self, file, **options):
|
def get_command(self, file, **options):
|
||||||
return (
|
return (
|
||||||
f'start "Pillow" /WAIT "{file}" '
|
f'start "Pillow" /WAIT "{file}" '
|
||||||
"&& ping -n 2 127.0.0.1 >NUL "
|
"&& ping -n 4 127.0.0.1 >NUL "
|
||||||
f'&& del /f "{file}"'
|
f'&& del /f "{file}"'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -176,11 +173,7 @@ class MacViewer(Viewer):
|
||||||
"""
|
"""
|
||||||
if path is None:
|
if path is None:
|
||||||
if "file" in options:
|
if "file" in options:
|
||||||
warnings.warn(
|
deprecate("The 'file' argument", 10, "'path'")
|
||||||
"The 'file' argument is deprecated and will be removed in Pillow "
|
|
||||||
"10 (2023-07-01). Use 'path' instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
path = options.pop("file")
|
path = options.pop("file")
|
||||||
else:
|
else:
|
||||||
raise TypeError("Missing required argument: 'path'")
|
raise TypeError("Missing required argument: 'path'")
|
||||||
|
@ -228,11 +221,7 @@ class XDGViewer(UnixViewer):
|
||||||
"""
|
"""
|
||||||
if path is None:
|
if path is None:
|
||||||
if "file" in options:
|
if "file" in options:
|
||||||
warnings.warn(
|
deprecate("The 'file' argument", 10, "'path'")
|
||||||
"The 'file' argument is deprecated and will be removed in Pillow "
|
|
||||||
"10 (2023-07-01). Use 'path' instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
path = options.pop("file")
|
path = options.pop("file")
|
||||||
else:
|
else:
|
||||||
raise TypeError("Missing required argument: 'path'")
|
raise TypeError("Missing required argument: 'path'")
|
||||||
|
@ -261,11 +250,7 @@ class DisplayViewer(UnixViewer):
|
||||||
"""
|
"""
|
||||||
if path is None:
|
if path is None:
|
||||||
if "file" in options:
|
if "file" in options:
|
||||||
warnings.warn(
|
deprecate("The 'file' argument", 10, "'path'")
|
||||||
"The 'file' argument is deprecated and will be removed in Pillow "
|
|
||||||
"10 (2023-07-01). Use 'path' instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
path = options.pop("file")
|
path = options.pop("file")
|
||||||
else:
|
else:
|
||||||
raise TypeError("Missing required argument: 'path'")
|
raise TypeError("Missing required argument: 'path'")
|
||||||
|
@ -296,11 +281,7 @@ class GmDisplayViewer(UnixViewer):
|
||||||
"""
|
"""
|
||||||
if path is None:
|
if path is None:
|
||||||
if "file" in options:
|
if "file" in options:
|
||||||
warnings.warn(
|
deprecate("The 'file' argument", 10, "'path'")
|
||||||
"The 'file' argument is deprecated and will be removed in Pillow "
|
|
||||||
"10 (2023-07-01). Use 'path' instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
path = options.pop("file")
|
path = options.pop("file")
|
||||||
else:
|
else:
|
||||||
raise TypeError("Missing required argument: 'path'")
|
raise TypeError("Missing required argument: 'path'")
|
||||||
|
@ -325,11 +306,7 @@ class EogViewer(UnixViewer):
|
||||||
"""
|
"""
|
||||||
if path is None:
|
if path is None:
|
||||||
if "file" in options:
|
if "file" in options:
|
||||||
warnings.warn(
|
deprecate("The 'file' argument", 10, "'path'")
|
||||||
"The 'file' argument is deprecated and will be removed in Pillow "
|
|
||||||
"10 (2023-07-01). Use 'path' instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
path = options.pop("file")
|
path = options.pop("file")
|
||||||
else:
|
else:
|
||||||
raise TypeError("Missing required argument: 'path'")
|
raise TypeError("Missing required argument: 'path'")
|
||||||
|
@ -360,11 +337,7 @@ class XVViewer(UnixViewer):
|
||||||
"""
|
"""
|
||||||
if path is None:
|
if path is None:
|
||||||
if "file" in options:
|
if "file" in options:
|
||||||
warnings.warn(
|
deprecate("The 'file' argument", 10, "'path'")
|
||||||
"The 'file' argument is deprecated and will be removed in Pillow "
|
|
||||||
"10 (2023-07-01). Use 'path' instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
path = options.pop("file")
|
path = options.pop("file")
|
||||||
else:
|
else:
|
||||||
raise TypeError("Missing required argument: 'path'")
|
raise TypeError("Missing required argument: 'path'")
|
||||||
|
|
|
@ -78,10 +78,10 @@ class Stat:
|
||||||
|
|
||||||
v = []
|
v = []
|
||||||
for i in range(0, len(self.h), 256):
|
for i in range(0, len(self.h), 256):
|
||||||
layerSum = 0.0
|
layer_sum = 0.0
|
||||||
for j in range(256):
|
for j in range(256):
|
||||||
layerSum += j * self.h[i + j]
|
layer_sum += j * self.h[i + j]
|
||||||
v.append(layerSum)
|
v.append(layer_sum)
|
||||||
return v
|
return v
|
||||||
|
|
||||||
def _getsum2(self):
|
def _getsum2(self):
|
||||||
|
|
|
@ -26,10 +26,10 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import tkinter
|
import tkinter
|
||||||
import warnings
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
|
from ._deprecate import deprecate
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Check for Tkinter interface hooks
|
# Check for Tkinter interface hooks
|
||||||
|
@ -187,11 +187,7 @@ class PhotoImage:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if box is not None:
|
if box is not None:
|
||||||
warnings.warn(
|
deprecate("The box parameter", 10, None)
|
||||||
"The box parameter is deprecated and will be removed in Pillow 10 "
|
|
||||||
"(2023-07-01).",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
|
|
||||||
# convert to blittable
|
# convert to blittable
|
||||||
im.load()
|
im.load()
|
||||||
|
|
|
@ -45,6 +45,7 @@ from . import Image, ImageFile, TiffImagePlugin
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
from ._binary import i32be as i32
|
from ._binary import i32be as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
|
from ._deprecate import deprecate
|
||||||
from .JpegPresets import presets
|
from .JpegPresets import presets
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -603,11 +604,7 @@ samplings = {
|
||||||
|
|
||||||
|
|
||||||
def convert_dict_qtables(qtables):
|
def convert_dict_qtables(qtables):
|
||||||
warnings.warn(
|
deprecate("convert_dict_qtables", 10, action="Conversion is no longer needed")
|
||||||
"convert_dict_qtables is deprecated and will be removed in Pillow 10"
|
|
||||||
"(2023-07-01). Conversion is no longer needed.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
|
||||||
return qtables
|
return qtables
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -193,15 +193,15 @@ class PcfFontFile(FontFile.FontFile):
|
||||||
for i in range(nbitmaps):
|
for i in range(nbitmaps):
|
||||||
offsets.append(i32(fp.read(4)))
|
offsets.append(i32(fp.read(4)))
|
||||||
|
|
||||||
bitmapSizes = []
|
bitmap_sizes = []
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
bitmapSizes.append(i32(fp.read(4)))
|
bitmap_sizes.append(i32(fp.read(4)))
|
||||||
|
|
||||||
# byteorder = format & 4 # non-zero => MSB
|
# byteorder = format & 4 # non-zero => MSB
|
||||||
bitorder = format & 8 # non-zero => MSB
|
bitorder = format & 8 # non-zero => MSB
|
||||||
padindex = format & 3
|
padindex = format & 3
|
||||||
|
|
||||||
bitmapsize = bitmapSizes[padindex]
|
bitmapsize = bitmap_sizes[padindex]
|
||||||
offsets.append(bitmapsize)
|
offsets.append(bitmapsize)
|
||||||
|
|
||||||
data = fp.read(bitmapsize)
|
data = fp.read(bitmapsize)
|
||||||
|
@ -225,22 +225,22 @@ class PcfFontFile(FontFile.FontFile):
|
||||||
|
|
||||||
fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS)
|
fp, format, i16, i32 = self._getformat(PCF_BDF_ENCODINGS)
|
||||||
|
|
||||||
firstCol, lastCol = i16(fp.read(2)), i16(fp.read(2))
|
first_col, last_col = i16(fp.read(2)), i16(fp.read(2))
|
||||||
firstRow, lastRow = i16(fp.read(2)), i16(fp.read(2))
|
first_row, last_row = i16(fp.read(2)), i16(fp.read(2))
|
||||||
|
|
||||||
i16(fp.read(2)) # default
|
i16(fp.read(2)) # default
|
||||||
|
|
||||||
nencoding = (lastCol - firstCol + 1) * (lastRow - firstRow + 1)
|
nencoding = (last_col - first_col + 1) * (last_row - first_row + 1)
|
||||||
|
|
||||||
encodingOffsets = [i16(fp.read(2)) for _ in range(nencoding)]
|
encoding_offsets = [i16(fp.read(2)) for _ in range(nencoding)]
|
||||||
|
|
||||||
for i in range(firstCol, len(encoding)):
|
for i in range(first_col, len(encoding)):
|
||||||
try:
|
try:
|
||||||
encodingOffset = encodingOffsets[
|
encoding_offset = encoding_offsets[
|
||||||
ord(bytearray([i]).decode(self.charset_encoding))
|
ord(bytearray([i]).decode(self.charset_encoding))
|
||||||
]
|
]
|
||||||
if encodingOffset != 0xFFFF:
|
if encoding_offset != 0xFFFF:
|
||||||
encoding[i] = encodingOffset
|
encoding[i] = encoding_offset
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
# character is not supported in selected encoding
|
# character is not supported in selected encoding
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -87,21 +87,21 @@ def _save(im, fp, filename, save_all=False):
|
||||||
for append_im in append_images:
|
for append_im in append_images:
|
||||||
append_im.encoderinfo = im.encoderinfo.copy()
|
append_im.encoderinfo = im.encoderinfo.copy()
|
||||||
ims.append(append_im)
|
ims.append(append_im)
|
||||||
numberOfPages = 0
|
number_of_pages = 0
|
||||||
image_refs = []
|
image_refs = []
|
||||||
page_refs = []
|
page_refs = []
|
||||||
contents_refs = []
|
contents_refs = []
|
||||||
for im in ims:
|
for im in ims:
|
||||||
im_numberOfPages = 1
|
im_number_of_pages = 1
|
||||||
if save_all:
|
if save_all:
|
||||||
try:
|
try:
|
||||||
im_numberOfPages = im.n_frames
|
im_number_of_pages = im.n_frames
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Image format does not have n_frames.
|
# Image format does not have n_frames.
|
||||||
# It is a single frame image
|
# It is a single frame image
|
||||||
pass
|
pass
|
||||||
numberOfPages += im_numberOfPages
|
number_of_pages += im_number_of_pages
|
||||||
for i in range(im_numberOfPages):
|
for i in range(im_number_of_pages):
|
||||||
image_refs.append(existing_pdf.next_object_id(0))
|
image_refs.append(existing_pdf.next_object_id(0))
|
||||||
page_refs.append(existing_pdf.next_object_id(0))
|
page_refs.append(existing_pdf.next_object_id(0))
|
||||||
contents_refs.append(existing_pdf.next_object_id(0))
|
contents_refs.append(existing_pdf.next_object_id(0))
|
||||||
|
@ -111,9 +111,9 @@ def _save(im, fp, filename, save_all=False):
|
||||||
# catalog and list of pages
|
# catalog and list of pages
|
||||||
existing_pdf.write_catalog()
|
existing_pdf.write_catalog()
|
||||||
|
|
||||||
pageNumber = 0
|
page_number = 0
|
||||||
for imSequence in ims:
|
for im_sequence in ims:
|
||||||
im_pages = ImageSequence.Iterator(imSequence) if save_all else [imSequence]
|
im_pages = ImageSequence.Iterator(im_sequence) if save_all else [im_sequence]
|
||||||
for im in im_pages:
|
for im in im_pages:
|
||||||
# FIXME: Should replace ASCIIHexDecode with RunLengthDecode
|
# FIXME: Should replace ASCIIHexDecode with RunLengthDecode
|
||||||
# (packbits) or LZWDecode (tiff/lzw compression). Note that
|
# (packbits) or LZWDecode (tiff/lzw compression). Note that
|
||||||
|
@ -176,7 +176,7 @@ def _save(im, fp, filename, save_all=False):
|
||||||
width, height = im.size
|
width, height = im.size
|
||||||
|
|
||||||
existing_pdf.write_obj(
|
existing_pdf.write_obj(
|
||||||
image_refs[pageNumber],
|
image_refs[page_number],
|
||||||
stream=op.getvalue(),
|
stream=op.getvalue(),
|
||||||
Type=PdfParser.PdfName("XObject"),
|
Type=PdfParser.PdfName("XObject"),
|
||||||
Subtype=PdfParser.PdfName("Image"),
|
Subtype=PdfParser.PdfName("Image"),
|
||||||
|
@ -193,10 +193,10 @@ def _save(im, fp, filename, save_all=False):
|
||||||
# page
|
# page
|
||||||
|
|
||||||
existing_pdf.write_page(
|
existing_pdf.write_page(
|
||||||
page_refs[pageNumber],
|
page_refs[page_number],
|
||||||
Resources=PdfParser.PdfDict(
|
Resources=PdfParser.PdfDict(
|
||||||
ProcSet=[PdfParser.PdfName("PDF"), PdfParser.PdfName(procset)],
|
ProcSet=[PdfParser.PdfName("PDF"), PdfParser.PdfName(procset)],
|
||||||
XObject=PdfParser.PdfDict(image=image_refs[pageNumber]),
|
XObject=PdfParser.PdfDict(image=image_refs[page_number]),
|
||||||
),
|
),
|
||||||
MediaBox=[
|
MediaBox=[
|
||||||
0,
|
0,
|
||||||
|
@ -204,7 +204,7 @@ def _save(im, fp, filename, save_all=False):
|
||||||
width * 72.0 / resolution,
|
width * 72.0 / resolution,
|
||||||
height * 72.0 / resolution,
|
height * 72.0 / resolution,
|
||||||
],
|
],
|
||||||
Contents=contents_refs[pageNumber],
|
Contents=contents_refs[page_number],
|
||||||
)
|
)
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -215,9 +215,9 @@ def _save(im, fp, filename, save_all=False):
|
||||||
height * 72.0 / resolution,
|
height * 72.0 / resolution,
|
||||||
)
|
)
|
||||||
|
|
||||||
existing_pdf.write_obj(contents_refs[pageNumber], stream=page_contents)
|
existing_pdf.write_obj(contents_refs[page_number], stream=page_contents)
|
||||||
|
|
||||||
pageNumber += 1
|
page_number += 1
|
||||||
|
|
||||||
#
|
#
|
||||||
# trailer
|
# trailer
|
||||||
|
|
|
@ -45,6 +45,7 @@ from ._binary import i32be as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
from ._binary import o16be as o16
|
from ._binary import o16be as o16
|
||||||
from ._binary import o32be as o32
|
from ._binary import o32be as o32
|
||||||
|
from ._deprecate import deprecate
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -131,24 +132,11 @@ class Blend(IntEnum):
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name):
|
def __getattr__(name):
|
||||||
deprecated = "deprecated and will be removed in Pillow 10 (2023-07-01). "
|
|
||||||
for enum, prefix in {Disposal: "APNG_DISPOSE_", Blend: "APNG_BLEND_"}.items():
|
for enum, prefix in {Disposal: "APNG_DISPOSE_", Blend: "APNG_BLEND_"}.items():
|
||||||
if name.startswith(prefix):
|
if name.startswith(prefix):
|
||||||
name = name[len(prefix) :]
|
name = name[len(prefix) :]
|
||||||
if name in enum.__members__:
|
if name in enum.__members__:
|
||||||
warnings.warn(
|
deprecate(f"{prefix}{name}", 10, f"{enum.__name__}.{name}")
|
||||||
prefix
|
|
||||||
+ name
|
|
||||||
+ " is "
|
|
||||||
+ deprecated
|
|
||||||
+ "Use "
|
|
||||||
+ enum.__name__
|
|
||||||
+ "."
|
|
||||||
+ name
|
|
||||||
+ " instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return enum[name]
|
return enum[name]
|
||||||
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,9 @@ try:
|
||||||
except ImportError as ex:
|
except ImportError as ex:
|
||||||
# Allow error import for doc purposes, but error out when accessing
|
# Allow error import for doc purposes, but error out when accessing
|
||||||
# anything in core.
|
# anything in core.
|
||||||
from ._util import deferred_error
|
from ._util import DeferredError
|
||||||
|
|
||||||
FFI = ffi = deferred_error(ex)
|
FFI = ffi = DeferredError(ex)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ def _save(im, fp, filename):
|
||||||
# Flip the image, since the origin of SGI file is the bottom-left corner
|
# Flip the image, since the origin of SGI file is the bottom-left corner
|
||||||
orientation = -1
|
orientation = -1
|
||||||
# Define the file as SGI File Format
|
# Define the file as SGI File Format
|
||||||
magicNumber = 474
|
magic_number = 474
|
||||||
# Run-Length Encoding Compression - Unsupported at this time
|
# Run-Length Encoding Compression - Unsupported at this time
|
||||||
rle = 0
|
rle = 0
|
||||||
|
|
||||||
|
@ -167,11 +167,11 @@ def _save(im, fp, filename):
|
||||||
# Maximum Byte value (255 = 8bits per pixel)
|
# Maximum Byte value (255 = 8bits per pixel)
|
||||||
pinmax = 255
|
pinmax = 255
|
||||||
# Image name (79 characters max, truncated below in write)
|
# Image name (79 characters max, truncated below in write)
|
||||||
imgName = os.path.splitext(os.path.basename(filename))[0]
|
img_name = os.path.splitext(os.path.basename(filename))[0]
|
||||||
imgName = imgName.encode("ascii", "ignore")
|
img_name = img_name.encode("ascii", "ignore")
|
||||||
# Standard representation of pixel in the file
|
# Standard representation of pixel in the file
|
||||||
colormap = 0
|
colormap = 0
|
||||||
fp.write(struct.pack(">h", magicNumber))
|
fp.write(struct.pack(">h", magic_number))
|
||||||
fp.write(o8(rle))
|
fp.write(o8(rle))
|
||||||
fp.write(o8(bpc))
|
fp.write(o8(bpc))
|
||||||
fp.write(struct.pack(">H", dim))
|
fp.write(struct.pack(">H", dim))
|
||||||
|
@ -181,8 +181,8 @@ def _save(im, fp, filename):
|
||||||
fp.write(struct.pack(">l", pinmin))
|
fp.write(struct.pack(">l", pinmin))
|
||||||
fp.write(struct.pack(">l", pinmax))
|
fp.write(struct.pack(">l", pinmax))
|
||||||
fp.write(struct.pack("4s", b"")) # dummy
|
fp.write(struct.pack("4s", b"")) # dummy
|
||||||
fp.write(struct.pack("79s", imgName)) # truncates to 79 chars
|
fp.write(struct.pack("79s", img_name)) # truncates to 79 chars
|
||||||
fp.write(struct.pack("s", b"")) # force null byte after imgname
|
fp.write(struct.pack("s", b"")) # force null byte after img_name
|
||||||
fp.write(struct.pack(">l", colormap))
|
fp.write(struct.pack(">l", colormap))
|
||||||
fp.write(struct.pack("404s", b"")) # dummy
|
fp.write(struct.pack("404s", b"")) # dummy
|
||||||
|
|
||||||
|
|
|
@ -1336,14 +1336,14 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
logger.debug(f"- size: {self.size}")
|
logger.debug(f"- size: {self.size}")
|
||||||
|
|
||||||
sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))
|
sample_format = self.tag_v2.get(SAMPLEFORMAT, (1,))
|
||||||
if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1:
|
if len(sample_format) > 1 and max(sample_format) == min(sample_format) == 1:
|
||||||
# SAMPLEFORMAT is properly per band, so an RGB image will
|
# SAMPLEFORMAT is properly per band, so an RGB image will
|
||||||
# be (1,1,1). But, we don't support per band pixel types,
|
# be (1,1,1). But, we don't support per band pixel types,
|
||||||
# and anything more than one band is a uint8. So, just
|
# and anything more than one band is a uint8. So, just
|
||||||
# take the first element. Revisit this if adding support
|
# take the first element. Revisit this if adding support
|
||||||
# for more exotic images.
|
# for more exotic images.
|
||||||
sampleFormat = (1,)
|
sample_format = (1,)
|
||||||
|
|
||||||
bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))
|
bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))
|
||||||
extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())
|
extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())
|
||||||
|
@ -1364,18 +1364,18 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# presume it is the same number of bits for all of the samples.
|
# presume it is the same number of bits for all of the samples.
|
||||||
bps_tuple = bps_tuple * bps_count
|
bps_tuple = bps_tuple * bps_count
|
||||||
|
|
||||||
samplesPerPixel = self.tag_v2.get(
|
samples_per_pixel = self.tag_v2.get(
|
||||||
SAMPLESPERPIXEL,
|
SAMPLESPERPIXEL,
|
||||||
3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1,
|
3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1,
|
||||||
)
|
)
|
||||||
if len(bps_tuple) != samplesPerPixel:
|
if len(bps_tuple) != samples_per_pixel:
|
||||||
raise SyntaxError("unknown data organization")
|
raise SyntaxError("unknown data organization")
|
||||||
|
|
||||||
# mode: check photometric interpretation and bits per pixel
|
# mode: check photometric interpretation and bits per pixel
|
||||||
key = (
|
key = (
|
||||||
self.tag_v2.prefix,
|
self.tag_v2.prefix,
|
||||||
photo,
|
photo,
|
||||||
sampleFormat,
|
sample_format,
|
||||||
fillorder,
|
fillorder,
|
||||||
bps_tuple,
|
bps_tuple,
|
||||||
extra_tuple,
|
extra_tuple,
|
||||||
|
@ -1880,16 +1880,16 @@ class AppendingTiffWriter:
|
||||||
self.whereToWriteNewIFDOffset = None
|
self.whereToWriteNewIFDOffset = None
|
||||||
self.offsetOfNewPage = 0
|
self.offsetOfNewPage = 0
|
||||||
|
|
||||||
self.IIMM = IIMM = self.f.read(4)
|
self.IIMM = iimm = self.f.read(4)
|
||||||
if not IIMM:
|
if not iimm:
|
||||||
# empty file - first page
|
# empty file - first page
|
||||||
self.isFirst = True
|
self.isFirst = True
|
||||||
return
|
return
|
||||||
|
|
||||||
self.isFirst = False
|
self.isFirst = False
|
||||||
if IIMM == b"II\x2a\x00":
|
if iimm == b"II\x2a\x00":
|
||||||
self.setEndian("<")
|
self.setEndian("<")
|
||||||
elif IIMM == b"MM\x00\x2a":
|
elif iimm == b"MM\x00\x2a":
|
||||||
self.setEndian(">")
|
self.setEndian(">")
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("Invalid TIFF file header")
|
raise RuntimeError("Invalid TIFF file header")
|
||||||
|
@ -1904,20 +1904,20 @@ class AppendingTiffWriter:
|
||||||
# fix offsets
|
# fix offsets
|
||||||
self.f.seek(self.offsetOfNewPage)
|
self.f.seek(self.offsetOfNewPage)
|
||||||
|
|
||||||
IIMM = self.f.read(4)
|
iimm = self.f.read(4)
|
||||||
if not IIMM:
|
if not iimm:
|
||||||
# raise RuntimeError("nothing written into new page")
|
# raise RuntimeError("nothing written into new page")
|
||||||
# Make it easy to finish a frame without committing to a new one.
|
# Make it easy to finish a frame without committing to a new one.
|
||||||
return
|
return
|
||||||
|
|
||||||
if IIMM != self.IIMM:
|
if iimm != self.IIMM:
|
||||||
raise RuntimeError("IIMM of new page doesn't match IIMM of first page")
|
raise RuntimeError("IIMM of new page doesn't match IIMM of first page")
|
||||||
|
|
||||||
IFDoffset = self.readLong()
|
ifd_offset = self.readLong()
|
||||||
IFDoffset += self.offsetOfNewPage
|
ifd_offset += self.offsetOfNewPage
|
||||||
self.f.seek(self.whereToWriteNewIFDOffset)
|
self.f.seek(self.whereToWriteNewIFDOffset)
|
||||||
self.writeLong(IFDoffset)
|
self.writeLong(ifd_offset)
|
||||||
self.f.seek(IFDoffset)
|
self.f.seek(ifd_offset)
|
||||||
self.fixIFD()
|
self.fixIFD()
|
||||||
|
|
||||||
def newFrame(self):
|
def newFrame(self):
|
||||||
|
@ -1948,9 +1948,9 @@ class AppendingTiffWriter:
|
||||||
pos = self.f.tell()
|
pos = self.f.tell()
|
||||||
|
|
||||||
# pad to 16 byte boundary
|
# pad to 16 byte boundary
|
||||||
padBytes = 16 - pos % 16
|
pad_bytes = 16 - pos % 16
|
||||||
if 0 < padBytes < 16:
|
if 0 < pad_bytes < 16:
|
||||||
self.f.write(bytes(padBytes))
|
self.f.write(bytes(pad_bytes))
|
||||||
self.offsetOfNewPage = self.f.tell()
|
self.offsetOfNewPage = self.f.tell()
|
||||||
|
|
||||||
def setEndian(self, endian):
|
def setEndian(self, endian):
|
||||||
|
@ -1961,14 +1961,14 @@ class AppendingTiffWriter:
|
||||||
|
|
||||||
def skipIFDs(self):
|
def skipIFDs(self):
|
||||||
while True:
|
while True:
|
||||||
IFDoffset = self.readLong()
|
ifd_offset = self.readLong()
|
||||||
if IFDoffset == 0:
|
if ifd_offset == 0:
|
||||||
self.whereToWriteNewIFDOffset = self.f.tell() - 4
|
self.whereToWriteNewIFDOffset = self.f.tell() - 4
|
||||||
break
|
break
|
||||||
|
|
||||||
self.f.seek(IFDoffset)
|
self.f.seek(ifd_offset)
|
||||||
numTags = self.readShort()
|
num_tags = self.readShort()
|
||||||
self.f.seek(numTags * 12, os.SEEK_CUR)
|
self.f.seek(num_tags * 12, os.SEEK_CUR)
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
return self.f.write(data)
|
return self.f.write(data)
|
||||||
|
@ -1983,68 +1983,68 @@ class AppendingTiffWriter:
|
||||||
|
|
||||||
def rewriteLastShortToLong(self, value):
|
def rewriteLastShortToLong(self, value):
|
||||||
self.f.seek(-2, os.SEEK_CUR)
|
self.f.seek(-2, os.SEEK_CUR)
|
||||||
bytesWritten = self.f.write(struct.pack(self.longFmt, value))
|
bytes_written = self.f.write(struct.pack(self.longFmt, value))
|
||||||
if bytesWritten is not None and bytesWritten != 4:
|
if bytes_written is not None and bytes_written != 4:
|
||||||
raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4")
|
raise RuntimeError(f"wrote only {bytes_written} bytes but wanted 4")
|
||||||
|
|
||||||
def rewriteLastShort(self, value):
|
def rewriteLastShort(self, value):
|
||||||
self.f.seek(-2, os.SEEK_CUR)
|
self.f.seek(-2, os.SEEK_CUR)
|
||||||
bytesWritten = self.f.write(struct.pack(self.shortFmt, value))
|
bytes_written = self.f.write(struct.pack(self.shortFmt, value))
|
||||||
if bytesWritten is not None and bytesWritten != 2:
|
if bytes_written is not None and bytes_written != 2:
|
||||||
raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 2")
|
raise RuntimeError(f"wrote only {bytes_written} bytes but wanted 2")
|
||||||
|
|
||||||
def rewriteLastLong(self, value):
|
def rewriteLastLong(self, value):
|
||||||
self.f.seek(-4, os.SEEK_CUR)
|
self.f.seek(-4, os.SEEK_CUR)
|
||||||
bytesWritten = self.f.write(struct.pack(self.longFmt, value))
|
bytes_written = self.f.write(struct.pack(self.longFmt, value))
|
||||||
if bytesWritten is not None and bytesWritten != 4:
|
if bytes_written is not None and bytes_written != 4:
|
||||||
raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4")
|
raise RuntimeError(f"wrote only {bytes_written} bytes but wanted 4")
|
||||||
|
|
||||||
def writeShort(self, value):
|
def writeShort(self, value):
|
||||||
bytesWritten = self.f.write(struct.pack(self.shortFmt, value))
|
bytes_written = self.f.write(struct.pack(self.shortFmt, value))
|
||||||
if bytesWritten is not None and bytesWritten != 2:
|
if bytes_written is not None and bytes_written != 2:
|
||||||
raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 2")
|
raise RuntimeError(f"wrote only {bytes_written} bytes but wanted 2")
|
||||||
|
|
||||||
def writeLong(self, value):
|
def writeLong(self, value):
|
||||||
bytesWritten = self.f.write(struct.pack(self.longFmt, value))
|
bytes_written = self.f.write(struct.pack(self.longFmt, value))
|
||||||
if bytesWritten is not None and bytesWritten != 4:
|
if bytes_written is not None and bytes_written != 4:
|
||||||
raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4")
|
raise RuntimeError(f"wrote only {bytes_written} bytes but wanted 4")
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.finalize()
|
self.finalize()
|
||||||
self.f.close()
|
self.f.close()
|
||||||
|
|
||||||
def fixIFD(self):
|
def fixIFD(self):
|
||||||
numTags = self.readShort()
|
num_tags = self.readShort()
|
||||||
|
|
||||||
for i in range(numTags):
|
for i in range(num_tags):
|
||||||
tag, fieldType, count = struct.unpack(self.tagFormat, self.f.read(8))
|
tag, field_type, count = struct.unpack(self.tagFormat, self.f.read(8))
|
||||||
|
|
||||||
fieldSize = self.fieldSizes[fieldType]
|
field_size = self.fieldSizes[field_type]
|
||||||
totalSize = fieldSize * count
|
total_size = field_size * count
|
||||||
isLocal = totalSize <= 4
|
is_local = total_size <= 4
|
||||||
if not isLocal:
|
if not is_local:
|
||||||
offset = self.readLong()
|
offset = self.readLong()
|
||||||
offset += self.offsetOfNewPage
|
offset += self.offsetOfNewPage
|
||||||
self.rewriteLastLong(offset)
|
self.rewriteLastLong(offset)
|
||||||
|
|
||||||
if tag in self.Tags:
|
if tag in self.Tags:
|
||||||
curPos = self.f.tell()
|
cur_pos = self.f.tell()
|
||||||
|
|
||||||
if isLocal:
|
if is_local:
|
||||||
self.fixOffsets(
|
self.fixOffsets(
|
||||||
count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)
|
count, isShort=(field_size == 2), isLong=(field_size == 4)
|
||||||
)
|
)
|
||||||
self.f.seek(curPos + 4)
|
self.f.seek(cur_pos + 4)
|
||||||
else:
|
else:
|
||||||
self.f.seek(offset)
|
self.f.seek(offset)
|
||||||
self.fixOffsets(
|
self.fixOffsets(
|
||||||
count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)
|
count, isShort=(field_size == 2), isLong=(field_size == 4)
|
||||||
)
|
)
|
||||||
self.f.seek(curPos)
|
self.f.seek(cur_pos)
|
||||||
|
|
||||||
offset = curPos = None
|
offset = cur_pos = None
|
||||||
|
|
||||||
elif isLocal:
|
elif is_local:
|
||||||
# skip the locally stored value that is not an offset
|
# skip the locally stored value that is not an offset
|
||||||
self.f.seek(4, os.SEEK_CUR)
|
self.f.seek(4, os.SEEK_CUR)
|
||||||
|
|
||||||
|
|
66
src/PIL/_deprecate.py
Normal file
66
src/PIL/_deprecate.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from . import __version__
|
||||||
|
|
||||||
|
|
||||||
|
def deprecate(
|
||||||
|
deprecated: str,
|
||||||
|
when: int | None,
|
||||||
|
replacement: str | None = None,
|
||||||
|
*,
|
||||||
|
action: str | None = None,
|
||||||
|
plural: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Deprecations helper.
|
||||||
|
|
||||||
|
:param deprecated: Name of thing to be deprecated.
|
||||||
|
:param when: Pillow major version to be removed in.
|
||||||
|
:param replacement: Name of replacement.
|
||||||
|
:param action: Instead of "replacement", give a custom call to action
|
||||||
|
e.g. "Upgrade to new thing".
|
||||||
|
:param plural: if the deprecated thing is plural, needing "are" instead of "is".
|
||||||
|
|
||||||
|
Usually of the form:
|
||||||
|
|
||||||
|
"[deprecated] is deprecated and will be removed in Pillow [when] (yyyy-mm-dd).
|
||||||
|
Use [replacement] instead."
|
||||||
|
|
||||||
|
You can leave out the replacement sentence:
|
||||||
|
|
||||||
|
"[deprecated] is deprecated and will be removed in Pillow [when] (yyyy-mm-dd)"
|
||||||
|
|
||||||
|
Or with another call to action:
|
||||||
|
|
||||||
|
"[deprecated] is deprecated and will be removed in Pillow [when] (yyyy-mm-dd).
|
||||||
|
[action]."
|
||||||
|
"""
|
||||||
|
|
||||||
|
is_ = "are" if plural else "is"
|
||||||
|
|
||||||
|
if when is None:
|
||||||
|
removed = "a future version"
|
||||||
|
elif when <= int(__version__.split(".")[0]):
|
||||||
|
raise RuntimeError(f"{deprecated} {is_} deprecated and should be removed.")
|
||||||
|
elif when == 10:
|
||||||
|
removed = "Pillow 10 (2023-07-01)"
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown removal version, update {__name__}?")
|
||||||
|
|
||||||
|
if replacement and action:
|
||||||
|
raise ValueError("Use only one of 'replacement' and 'action'")
|
||||||
|
|
||||||
|
if replacement:
|
||||||
|
action = f". Use {replacement} instead."
|
||||||
|
elif action:
|
||||||
|
action = f". {action.rstrip('.')}."
|
||||||
|
else:
|
||||||
|
action = ""
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
f"{deprecated} {is_} deprecated and will be removed in {removed}{action}",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=3,
|
||||||
|
)
|
|
@ -2,9 +2,10 @@
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
import tkinter
|
import tkinter
|
||||||
import warnings
|
|
||||||
from tkinter import _tkinter as tk
|
from tkinter import _tkinter as tk
|
||||||
|
|
||||||
|
from ._deprecate import deprecate
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if hasattr(sys, "pypy_find_executable"):
|
if hasattr(sys, "pypy_find_executable"):
|
||||||
TKINTER_LIB = tk.tklib_cffi.__file__
|
TKINTER_LIB = tk.tklib_cffi.__file__
|
||||||
|
@ -17,9 +18,6 @@ except AttributeError:
|
||||||
|
|
||||||
tk_version = str(tkinter.TkVersion)
|
tk_version = str(tkinter.TkVersion)
|
||||||
if tk_version == "8.4":
|
if tk_version == "8.4":
|
||||||
warnings.warn(
|
deprecate(
|
||||||
"Support for Tk/Tcl 8.4 is deprecated and will be removed"
|
"Support for Tk/Tcl 8.4", 10, action="Please upgrade to Tk/Tcl 8.5 or newer"
|
||||||
" in Pillow 10 (2023-07-01). Please upgrade to Tk/Tcl 8.5 "
|
|
||||||
"or newer.",
|
|
||||||
DeprecationWarning,
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,16 +2,16 @@ import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def isPath(f):
|
def is_path(f):
|
||||||
return isinstance(f, (bytes, str, Path))
|
return isinstance(f, (bytes, str, Path))
|
||||||
|
|
||||||
|
|
||||||
# Checks if an object is a string, and that it points to a directory.
|
def is_directory(f):
|
||||||
def isDirectory(f):
|
"""Checks if an object is a string, and that it points to a directory."""
|
||||||
return isPath(f) and os.path.isdir(f)
|
return is_path(f) and os.path.isdir(f)
|
||||||
|
|
||||||
|
|
||||||
class deferred_error:
|
class DeferredError:
|
||||||
def __init__(self, ex):
|
def __init__(self, ex):
|
||||||
self.ex = ex
|
self.ex = ex
|
||||||
|
|
||||||
|
|
|
@ -280,9 +280,9 @@ deps = {
|
||||||
"libs": [r"imagequant.lib"],
|
"libs": [r"imagequant.lib"],
|
||||||
},
|
},
|
||||||
"harfbuzz": {
|
"harfbuzz": {
|
||||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/4.2.0.zip",
|
"url": "https://github.com/harfbuzz/harfbuzz/archive/4.2.1.zip",
|
||||||
"filename": "harfbuzz-4.2.0.zip",
|
"filename": "harfbuzz-4.2.1.zip",
|
||||||
"dir": "harfbuzz-4.2.0",
|
"dir": "harfbuzz-4.2.1",
|
||||||
"build": [
|
"build": [
|
||||||
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
|
cmd_cmake("-DHB_HAVE_FREETYPE:BOOL=TRUE"),
|
||||||
cmd_nmake(target="clean"),
|
cmd_nmake(target="clean"),
|
||||||
|
@ -292,9 +292,9 @@ deps = {
|
||||||
"libs": [r"*.lib"],
|
"libs": [r"*.lib"],
|
||||||
},
|
},
|
||||||
"fribidi": {
|
"fribidi": {
|
||||||
"url": "https://github.com/fribidi/fribidi/archive/v1.0.11.zip",
|
"url": "https://github.com/fribidi/fribidi/archive/v1.0.12.zip",
|
||||||
"filename": "fribidi-1.0.11.zip",
|
"filename": "fribidi-1.0.12.zip",
|
||||||
"dir": "fribidi-1.0.11",
|
"dir": "fribidi-1.0.12",
|
||||||
"build": [
|
"build": [
|
||||||
cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"),
|
cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"),
|
||||||
cmd_cmake(),
|
cmd_cmake(),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user