Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ environment:
- PYTHON: C:/Python310
ARCHITECTURE: x86
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
- PYTHON: C:/Python36-x64
- PYTHON: C:/Python37-x64
ARCHITECTURE: x64
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017

Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ repos:
rev: 911470a610e47d9da5ea938b0887c3df62819b85 # frozen: 21.9b0
hooks:
- id: black
args: ["--target-version", "py36"]
args: ["--target-version", "py37"]
# Only .py files, until https://github.com/psf/black/issues/402 resolved
files: \.py$
types: []
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,5 @@ lint:

.PHONY: lint-fix
lint-fix:
black --target-version py36 .
black --target-version py37 .
isort .
4 changes: 2 additions & 2 deletions Tests/test_file_gif.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ def test_palette_save_P(tmp_path):
# Forcing a non-straight grayscale palette.

im = hopper("P")
palette = bytes([255 - i // 3 for i in range(768)])
palette = bytes(255 - i // 3 for i in range(768))

out = str(tmp_path / "temp.gif")
im.save(out, palette=palette)
Expand Down Expand Up @@ -885,7 +885,7 @@ def test_getdata():
im.putpalette(ImagePalette.ImagePalette("RGB"))
im.info = {"background": 0}

passed_palette = bytes([255 - i // 3 for i in range(768)])
passed_palette = bytes(255 - i // 3 for i in range(768))

GifImagePlugin._FORCE_OPTIMIZE = True
try:
Expand Down
12 changes: 6 additions & 6 deletions Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,26 +85,26 @@ def test_cmyk(self):
f = "Tests/images/pil_sample_cmyk.jpg"
with Image.open(f) as im:
# the source image has red pixels in the upper left corner.
c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))]
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))
assert c == 0.0
assert m > 0.8
assert y > 0.8
assert k == 0.0
# the opposite corner is black
c, m, y, k = [
c, m, y, k = (
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))
]
)
assert k > 0.9
# roundtrip, and check again
im = self.roundtrip(im)
c, m, y, k = [x / 255.0 for x in im.getpixel((0, 0))]
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))
assert c == 0.0
assert m > 0.8
assert y > 0.8
assert k == 0.0
c, m, y, k = [
c, m, y, k = (
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))
]
)
assert k > 0.9

@pytest.mark.parametrize(
Expand Down
4 changes: 1 addition & 3 deletions Tests/test_file_webp.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,7 @@ def test_background_from_gif(self, tmp_path):

with Image.open(out_gif) as reread:
reread_value = reread.convert("RGB").getpixel((1, 1))
difference = sum(
[abs(original_value[i] - reread_value[i]) for i in range(0, 3)]
)
difference = sum(abs(original_value[i] - reread_value[i]) for i in range(0, 3))
assert difference < 5

@skip_unless_feature("webp")
Expand Down
4 changes: 0 additions & 4 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import io
import os
import shutil
import sys
import tempfile

import pytest
Expand Down Expand Up @@ -782,9 +781,6 @@ def test_exif_load_from_fp(self):
34665: 196,
}

@pytest.mark.skipif(
sys.version_info < (3, 7), reason="Python 3.7 or greater required"
)
def test_categories_deprecation(self):
with pytest.warns(DeprecationWarning):
assert hopper().category == 0
Expand Down
8 changes: 4 additions & 4 deletions Tests/test_imagefont.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,7 @@ def test_cbdt(self):
d.text((10, 10), "\U0001f469", font=font, embedded_color=True)

assert_image_similar_tofile(im, "Tests/images/cbdt_notocoloremoji.png", 6.2)
except IOError as e: # pragma: no cover
except OSError as e: # pragma: no cover
assert str(e) in ("unimplemented feature", "unknown file format")
pytest.skip("freetype compiled without libpng or CBDT support")

Expand All @@ -920,7 +920,7 @@ def test_cbdt_mask(self):
assert_image_similar_tofile(
im, "Tests/images/cbdt_notocoloremoji_mask.png", 6.2
)
except IOError as e: # pragma: no cover
except OSError as e: # pragma: no cover
assert str(e) in ("unimplemented feature", "unknown file format")
pytest.skip("freetype compiled without libpng or CBDT support")

Expand All @@ -938,7 +938,7 @@ def test_sbix(self):
d.text((50, 50), "\uE901", font=font, embedded_color=True)

assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1)
except IOError as e: # pragma: no cover
except OSError as e: # pragma: no cover
assert str(e) in ("unimplemented feature", "unknown file format")
pytest.skip("freetype compiled without libpng or SBIX support")

Expand All @@ -956,7 +956,7 @@ def test_sbix_mask(self):
d.text((50, 50), "\uE901", (100, 0, 0), font=font)

assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1)
except IOError as e: # pragma: no cover
except OSError as e: # pragma: no cover
assert str(e) in ("unimplemented feature", "unknown file format")
pytest.skip("freetype compiled without libpng or SBIX support")

Expand Down
2 changes: 1 addition & 1 deletion Tests/test_imagemath.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

def pixel(im):
if hasattr(im, "im"):
return "{} {}".format(im.mode, repr(im.getpixel((0, 0))))
return f"{im.mode} {repr(im.getpixel((0, 0)))}"
else:
if isinstance(im, int):
return int(im) # hack to deal with booleans
Expand Down
76 changes: 39 additions & 37 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ Pillow supports these Python versions.
+----------------------+-----+-----+-----+-----+-----+-----+-----+-----+
| Python |3.10 | 3.9 | 3.8 | 3.7 | 3.6 | 3.5 | 3.4 | 2.7 |
+======================+=====+=====+=====+=====+=====+=====+=====+=====+
| Pillow >= 8.3.2 | Yes | Yes | Yes | Yes | Yes | | | |
| Pillow >= 9.0 | Yes | Yes | Yes | Yes | | | | |
+----------------------+-----+-----+-----+-----+-----+-----+-----+-----+
| Pillow 8.3.2 - 8.4 | Yes | Yes | Yes | Yes | Yes | | | |
+----------------------+-----+-----+-----+-----+-----+-----+-----+-----+
| Pillow 8.0 - 8.3.1 | | Yes | Yes | Yes | Yes | | | |
+----------------------+-----+-----+-----+-----+-----+-----+-----+-----+
Expand Down Expand Up @@ -443,42 +445,42 @@ Continuous Integration Targets

These platforms are built and tested for every change.

+----------------------------------+---------------------------------+---------------------+
| Operating system | Tested Python versions | Tested architecture |
+==================================+=================================+=====================+
| Alpine | 3.9 | x86-64 |
+----------------------------------+---------------------------------+---------------------+
| Amazon Linux 2 | 3.7 | x86-64 |
+----------------------------------+---------------------------------+---------------------+
| Arch | 3.9 | x86-64 |
+----------------------------------+---------------------------------+---------------------+
| CentOS 7 | 3.6 | x86-64 |
+----------------------------------+---------------------------------+---------------------+
| CentOS 8 | 3.6 | x86-64 |
+----------------------------------+---------------------------------+---------------------+
| CentOS Stream 8 | 3.6 | x86-64 |
+----------------------------------+---------------------------------+---------------------+
| Debian 10 Buster | 3.7 | x86 |
+----------------------------------+---------------------------------+---------------------+
| Fedora 34 | 3.9 | x86-64 |
+----------------------------------+---------------------------------+---------------------+
| Fedora 35 | 3.10 | x86-64 |
+----------------------------------+---------------------------------+---------------------+
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 |
+----------------------------------+---------------------------------+---------------------+
| Ubuntu Linux 18.04 LTS (Bionic) | 3.6 | x86-64 |
+----------------------------------+---------------------------------+---------------------+
| Ubuntu Linux 20.04 LTS (Focal) | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 |
| +---------------------------------+---------------------+
| | 3.8 | arm64v8, ppc64le, |
| | | s390x |
+----------------------------------+---------------------------------+---------------------+
| Windows Server 2016 | 3.6 | x86-64 |
+----------------------------------+---------------------------------+---------------------+
| Windows Server 2019 | 3.6, 3.7, 3.8, 3.9, 3.10, PyPy3 | x86, x86-64 |
| +---------------------------------+---------------------+
| | 3.9/MinGW | x86, x86-64 |
+----------------------------------+---------------------------------+---------------------+
+----------------------------------+----------------------------+---------------------+
| Operating system | Tested Python versions | Tested architecture |
+==================================+============================+=====================+
| Alpine | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Amazon Linux 2 | 3.7 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Arch | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| CentOS 7 | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| CentOS 8 | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| CentOS Stream 8 | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Debian 10 Buster | 3.7 | x86 |
+----------------------------------+----------------------------+---------------------+
| Fedora 34 | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 35 | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| macOS 10.15 Catalina | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 18.04 LTS (Bionic) | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 20.04 LTS (Focal) | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86-64 |
| +----------------------------+---------------------+
| | 3.8 | arm64v8, ppc64le, |
| | | s390x |
+----------------------------------+----------------------------+---------------------+
| Windows Server 2016 | 3.7 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Windows Server 2019 | 3.7, 3.8, 3.9, 3.10, PyPy3 | x86, x86-64 |
| +----------------------------+---------------------+
| | 3.9/MinGW | x86, x86-64 |
+----------------------------------+----------------------------+---------------------+


Other Platforms
Expand Down
5 changes: 5 additions & 0 deletions docs/releasenotes/9.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
Backwards Incompatible Changes
==============================

Python 3.6
^^^^^^^^^^

Pillow has dropped support for Python 3.6, which reached end-of-life on 2021-12-23.

PILLOW_VERSION constant
^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
3 changes: 1 addition & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ classifiers =
License :: OSI Approved :: Historical Permission Notice and Disclaimer (HPND)
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Expand All @@ -34,7 +33,7 @@ project_urls =
Twitter=https://twitter.com/PythonPillow

[options]
python_requires = >=3.6
python_requires = >=3.7

[flake8]
extend-ignore = E203
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ def load_prepare(self):
if not self.im and "transparency" in self.info:
self.im = Image.core.fill(self.mode, self.size, self.info["transparency"])

super(GifImageFile, self).load_prepare()
super().load_prepare()

def tell(self):
return self.__frame
Expand Down
42 changes: 15 additions & 27 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,26 +51,18 @@
from ._binary import i32le
from ._util import deferred_error, isPath

if sys.version_info >= (3, 7):

def __getattr__(name):
categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2}
if name in categories:
warnings.warn(
"Image categories are deprecated and will be removed in Pillow 10 "
"(2023-07-01). Use is_animated instead.",
DeprecationWarning,
stacklevel=2,
)
return categories[name]
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


else:
# categories
NORMAL = 0
SEQUENCE = 1
CONTAINER = 2
def __getattr__(name):
categories = {"NORMAL": 0, "SEQUENCE": 1, "CONTAINER": 2}
if name in categories:
warnings.warn(
"Image categories are deprecated and will be removed in Pillow 10 "
"(2023-07-01). Use is_animated instead.",
DeprecationWarning,
stacklevel=2,
)
return categories[name]
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -926,12 +918,8 @@ def convert_transparency(m, v):
transparency = convert_transparency(matrix, transparency)
elif len(mode) == 3:
transparency = tuple(
[
convert_transparency(
matrix[i * 4 : i * 4 + 4], transparency
)
for i in range(0, len(transparency))
]
convert_transparency(matrix[i * 4 : i * 4 + 4], transparency)
for i in range(0, len(transparency))
)
new.info["transparency"] = transparency
return new
Expand Down Expand Up @@ -1934,7 +1922,7 @@ def resize(self, size, resample=None, box=None, reducing_gap=None):
message = f"Unknown resampling filter ({resample})."

filters = [
"{} ({})".format(filter[1], filter[0])
f"{filter[1]} ({filter[0]})"
for filter in (
(NEAREST, "Image.NEAREST"),
(LANCZOS, "Image.LANCZOS"),
Expand Down Expand Up @@ -2529,7 +2517,7 @@ def __transformer(self, box, image, method, data, resample=NEAREST, fill=1):
message = f"Unknown resampling filter ({resample})."

filters = [
"{} ({})".format(filter[1], filter[0])
f"{filter[1]} ({filter[0]})"
for filter in (
(NEAREST, "Image.NEAREST"),
(BILINEAR, "Image.BILINEAR"),
Expand Down
14 changes: 6 additions & 8 deletions src/PIL/ImageDraw.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,11 @@ def coord_at_angle(coord, angle):
angle -= 90
distance = width / 2 - 1
return tuple(
[
p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))
for p, p_d in (
(x, distance * math.cos(math.radians(angle))),
(y, distance * math.sin(math.radians(angle))),
)
]
p + (math.floor(p_d) if p_d > 0 else math.ceil(p_d))
for p, p_d in (
(x, distance * math.cos(math.radians(angle))),
(y, distance * math.sin(math.radians(angle))),
)
)

flipped = (
Expand Down Expand Up @@ -979,6 +977,6 @@ def _color_diff(color1, color2):
Uses 1-norm distance to calculate difference between two values.
"""
if isinstance(color2, tuple):
return sum([abs(color1[i] - color2[i]) for i in range(0, len(color2))])
return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2)))
else:
return abs(color1 - color2)
4 changes: 2 additions & 2 deletions src/PIL/PdfParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ def write_header(self):
self.f.write(b"%PDF-1.4\n")

def write_comment(self, s):
self.f.write(f"% {s}\n".encode("utf-8"))
self.f.write(f"% {s}\n".encode())

def write_catalog(self):
self.del_root()
Expand Down Expand Up @@ -862,7 +862,7 @@ def get_value(cls, data, offset, expect_indirect=None, max_nesting=-1):
if m:
# filter out whitespace
hex_string = bytearray(
[b for b in m.group(1) if b in b"0123456789abcdefABCDEF"]
b for b in m.group(1) if b in b"0123456789abcdefABCDEF"
)
if len(hex_string) % 2 == 1:
# append a 0 if the length is not even - yes, at the end
Expand Down
Loading