Merge branch 'main' into type-annotations

This commit is contained in:
Andrew Murray 2024-05-15 21:48:41 +10:00 committed by GitHub
commit 73a3e4938c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 260 additions and 207 deletions

View File

@ -1 +1 @@
cibuildwheel==2.17.0
cibuildwheel==2.18.0

View File

@ -55,6 +55,7 @@ jobs:
packages: >
gcc-g++
ghostscript
git
ImageMagick
jpeg
libfreetype-devel
@ -132,11 +133,12 @@ jobs:
bash.exe .ci/after_success.sh
- name: Upload coverage
uses: codecov/codecov-action@v3.1.5
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
flags: GHA_Cygwin
name: Cygwin Python 3.${{ matrix.python-minor-version }}
token: ${{ secrets.CODECOV_ORG_TOKEN }}
success:
permissions:

View File

@ -100,11 +100,12 @@ jobs:
MATRIX_DOCKER: ${{ matrix.docker }}
- name: Upload coverage
uses: codecov/codecov-action@v3.1.5
uses: codecov/codecov-action@v4
with:
flags: GHA_Docker
name: ${{ matrix.docker }}
gcov: true
token: ${{ secrets.CODECOV_ORG_TOKEN }}
success:
permissions:

View File

@ -85,8 +85,9 @@ jobs:
python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
- name: Upload coverage
uses: codecov/codecov-action@v3.1.5
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
flags: GHA_Windows
name: "MSYS2 MinGW"
token: ${{ secrets.CODECOV_ORG_TOKEN }}

View File

@ -213,11 +213,12 @@ jobs:
shell: pwsh
- name: Upload coverage
uses: codecov/codecov-action@v3.1.5
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
flags: GHA_Windows
name: ${{ runner.os }} Python ${{ matrix.python-version }}
token: ${{ secrets.CODECOV_ORG_TOKEN }}
success:
permissions:

View File

@ -150,11 +150,12 @@ jobs:
.ci/after_success.sh
- name: Upload coverage
uses: codecov/codecov-action@v3.1.5
uses: codecov/codecov-action@v4
with:
flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
gcov: true
token: ${{ secrets.CODECOV_ORG_TOKEN }}
success:
permissions:

View File

@ -1,12 +1,12 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.4
rev: v0.4.3
hooks:
- id: ruff
args: [--exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.3.0
rev: 24.4.2
hooks:
- id: black
@ -29,7 +29,7 @@ repos:
- id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
@ -43,7 +43,7 @@ repos:
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.28.1
rev: 0.28.2
hooks:
- id: check-github-workflows
- id: check-readthedocs
@ -55,7 +55,7 @@ repos:
- id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt
rev: 1.7.0
rev: 1.8.0
hooks:
- id: pyproject-fmt

View File

@ -29,33 +29,6 @@ elif "GITHUB_ACTIONS" in os.environ:
uploader = "github_actions"
modes = (
"1",
"L",
"LA",
"La",
"P",
"PA",
"F",
"I",
"I;16",
"I;16L",
"I;16B",
"I;16N",
"RGB",
"RGBA",
"RGBa",
"RGBX",
"BGR;15",
"BGR;16",
"BGR;24",
"CMYK",
"YCbCr",
"HSV",
"LAB",
)
def upload(a: Image.Image, b: Image.Image) -> str | None:
if uploader == "show":
# local img.show for errors.

View File

@ -336,9 +336,7 @@ def test_readline_psfile(tmp_path: Path) -> None:
strings = ["something", "else", "baz", "bif"]
def _test_readline(t: EpsImagePlugin.PSFile, ending: str) -> None:
ending = "Failure with line ending: %s" % (
"".join("%s" % ord(s) for s in ending)
)
ending = f"Failure with line ending: {''.join(str(ord(s)) for s in ending)}"
assert t.readline().strip("\r\n") == "something", ending
assert t.readline().strip("\r\n") == "else", ending
assert t.readline().strip("\r\n") == "baz", ending

39
Tests/test_file_mpeg.py Normal file
View File

@ -0,0 +1,39 @@
from __future__ import annotations
from io import BytesIO
import pytest
from PIL import Image, MpegImagePlugin
def test_identify() -> None:
# Arrange
b = BytesIO(b"\x00\x00\x01\xb3\x01\x00\x01")
# Act
with Image.open(b) as im:
# Assert
assert im.format == "MPEG"
assert im.mode == "RGB"
assert im.size == (16, 1)
def test_invalid_file() -> None:
# Arrange
invalid_file = "Tests/images/flower.jpg"
# Act / Assert
with pytest.raises(SyntaxError):
MpegImagePlugin.MpegImageFile(invalid_file)
def test_load() -> None:
# Arrange
b = BytesIO(b"\x00\x00\x01\xb3\x01\x00\x01")
with Image.open(b) as im:
# Act / Assert: cannot load
with pytest.raises(OSError):
im.load()

View File

@ -31,7 +31,6 @@ from .helper import (
is_big_endian,
is_win32,
mark_if_feature_version,
modes,
skip_unless_feature,
)
@ -46,7 +45,7 @@ def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image:
class TestImage:
@pytest.mark.parametrize("mode", modes)
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
def test_image_modes_success(self, mode: str) -> None:
helper_image_new(mode, (1, 1))
@ -1027,7 +1026,7 @@ class TestImage:
class TestImageBytes:
@pytest.mark.parametrize("mode", modes)
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
def test_roundtrip_bytes_constructor(self, mode: str) -> None:
im = hopper(mode)
source_bytes = im.tobytes()
@ -1039,7 +1038,7 @@ class TestImageBytes:
reloaded = Image.frombytes(mode, im.size, source_bytes)
assert reloaded.tobytes() == source_bytes
@pytest.mark.parametrize("mode", modes)
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
def test_roundtrip_bytes_method(self, mode: str) -> None:
im = hopper(mode)
source_bytes = im.tobytes()
@ -1048,7 +1047,7 @@ class TestImageBytes:
reloaded.frombytes(source_bytes)
assert reloaded.tobytes() == source_bytes
@pytest.mark.parametrize("mode", modes)
@pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"])
def test_getdata_putdata(self, mode: str) -> None:
if is_big_endian() and mode == "BGR;15":
pytest.xfail("Known failure of BGR;15 on big-endian")

View File

@ -10,7 +10,7 @@ import pytest
from PIL import Image
from .helper import assert_image_equal, hopper, is_win32, modes
from .helper import assert_image_equal, hopper, is_win32
# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
@ -205,12 +205,13 @@ class TestImageGetPixel(AccessTest):
with pytest.raises(error):
im.getpixel((-1, -1))
@pytest.mark.parametrize("mode", modes)
@pytest.mark.parametrize("mode", Image.MODES)
def test_basic(self, mode: str) -> None:
if mode.startswith("BGR;"):
with pytest.warns(DeprecationWarning):
self.check(mode)
else:
self.check(mode)
@pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24"))
def test_deprecated(self, mode: str) -> None:
with pytest.warns(DeprecationWarning):
self.check(mode)
def test_list(self) -> None:
@ -409,13 +410,14 @@ class TestEmbeddable:
from setuptools.command import build_ext
with open("embed_pil.c", "w", encoding="utf-8") as fh:
home = sys.prefix.replace("\\", "\\\\")
fh.write(
"""
f"""
#include "Python.h"
int main(int argc, char* argv[])
{
char *home = "%s";
{{
char *home = "{home}";
wchar_t *whome = Py_DecodeLocale(home, NULL);
Py_SetPythonHome(whome);
@ -430,9 +432,8 @@ int main(int argc, char* argv[])
PyMem_RawFree(whome);
return 0;
}
}}
"""
% sys.prefix.replace("\\", "\\\\")
)
compiler = getattr(build_ext, "new_compiler")()

View File

@ -165,9 +165,9 @@ if __name__ == "__main__":
print("Running selftest:")
status = doctest.testmod(sys.modules[__name__])
if status[0]:
print("*** %s tests of %d failed." % status)
print(f"*** {status[0]} tests of {status[1]} failed.")
exit_status = 1
else:
print("--- %s tests passed." % status[1])
print(f"--- {status[1]} tests passed.")
sys.exit(exit_status)

View File

@ -253,7 +253,7 @@ class BlpImageFile(ImageFile.ImageFile):
format = "BLP"
format_description = "Blizzard Mipmap Format"
def _open(self):
def _open(self) -> None:
self.magic = self.fp.read(4)
self.fp.seek(5, os.SEEK_CUR)
@ -333,7 +333,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
class BLP1Decoder(_BLPBaseDecoder):
def _load(self):
def _load(self) -> None:
if self._blp_compression == Format.JPEG:
self._decode_jpeg_stream()
@ -418,7 +418,7 @@ class BLP2Decoder(_BLPBaseDecoder):
class BLPEncoder(ImageFile.PyEncoder):
_pushes_fd = True
def _write_palette(self):
def _write_palette(self) -> bytes:
data = b""
palette = self.im.getpalette("RGBA", "RGBA")
for i in range(len(palette) // 4):

View File

@ -283,7 +283,7 @@ class BmpImageFile(ImageFile.ImageFile):
)
]
def _open(self):
def _open(self) -> None:
"""Open file, check magic number and read header"""
# read 14 bytes: magic number, filesize, reserved, header final offset
head_data = self.fp.read(14)
@ -376,7 +376,7 @@ class DibImageFile(BmpImageFile):
format = "DIB"
format_description = "Windows Bitmap"
def _open(self):
def _open(self) -> None:
self._bitmap()

View File

@ -37,7 +37,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
format = "BUFR"
format_description = "BUFR"
def _open(self):
def _open(self) -> None:
offset = self.fp.tell()
if not _accept(self.fp.read(4)):

View File

@ -37,7 +37,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
format = "CUR"
format_description = "Windows Cursor"
def _open(self):
def _open(self) -> None:
offset = self.fp.tell()
# check magic

View File

@ -63,7 +63,7 @@ class DcxImageFile(PcxImageFile):
self.is_animated = self.n_frames > 1
self.seek(0)
def seek(self, frame):
def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
self.frame = frame
@ -71,7 +71,7 @@ class DcxImageFile(PcxImageFile):
self.fp.seek(self._offset[frame])
PcxImageFile._open(self)
def tell(self):
def tell(self) -> int:
return self.frame

View File

@ -331,7 +331,7 @@ class DdsImageFile(ImageFile.ImageFile):
format = "DDS"
format_description = "DirectDraw Surface"
def _open(self):
def _open(self) -> None:
if not _accept(self.fp.read(4)):
msg = "not a DDS file"
raise SyntaxError(msg)

View File

@ -178,7 +178,7 @@ class PSFile:
self.char = None
self.fp.seek(offset, whence)
def readline(self):
def readline(self) -> str:
s = [self.char or b""]
self.char = None
@ -212,7 +212,7 @@ class EpsImageFile(ImageFile.ImageFile):
mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
def _open(self):
def _open(self) -> None:
(length, offset) = self._find_offset(self.fp)
# go to offset - start of "%!PS"

View File

@ -123,7 +123,7 @@ class FliImageFile(ImageFile.ImageFile):
palette[i] = (r, g, b)
i += 1
def seek(self, frame):
def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
if frame < self.__frame:
@ -132,7 +132,7 @@ class FliImageFile(ImageFile.ImageFile):
for f in range(self.__frame + 1, frame + 1):
self._seek(f)
def _seek(self, frame):
def _seek(self, frame: int) -> None:
if frame == 0:
self.__frame = -1
self._fp.seek(self.__rewind)
@ -162,7 +162,7 @@ class FliImageFile(ImageFile.ImageFile):
self.__offset += framesize
def tell(self):
def tell(self) -> int:
return self.__frame

View File

@ -237,7 +237,7 @@ class FpxImageFile(ImageFile.ImageFile):
return ImageFile.ImageFile.load(self)
def close(self):
def close(self) -> None:
self.ole.close()
super().close()

View File

@ -71,7 +71,7 @@ class FtexImageFile(ImageFile.ImageFile):
format = "FTEX"
format_description = "Texture File Format (IW2:EOC)"
def _open(self):
def _open(self) -> None:
if not _accept(self.fp.read(4)):
msg = "not an FTEX file"
raise SyntaxError(msg)

View File

@ -41,7 +41,7 @@ class GbrImageFile(ImageFile.ImageFile):
format = "GBR"
format_description = "GIMP brush file"
def _open(self):
def _open(self) -> None:
header_size = i32(self.fp.read(4))
if header_size < 20:
msg = "not a GIMP brush"

View File

@ -76,19 +76,19 @@ class GifImageFile(ImageFile.ImageFile):
global_palette = None
def data(self):
def data(self) -> bytes | None:
s = self.fp.read(1)
if s and s[0]:
return self.fp.read(s[0])
return None
def _is_palette_needed(self, p):
def _is_palette_needed(self, p: bytes) -> bool:
for i in range(0, len(p), 3):
if not (i // 3 == p[i] == p[i + 1] == p[i + 2]):
return True
return False
def _open(self):
def _open(self) -> None:
# Screen
s = self.fp.read(13)
if not _accept(s):
@ -147,7 +147,7 @@ class GifImageFile(ImageFile.ImageFile):
self.seek(current)
return self._is_animated
def seek(self, frame):
def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
if frame < self.__frame:
@ -417,7 +417,7 @@ class GifImageFile(ImageFile.ImageFile):
elif k in self.info:
del self.info[k]
def load_prepare(self):
def load_prepare(self) -> None:
temp_mode = "P" if self._frame_palette else "L"
self._prev_im = None
if self.__frame == 0:
@ -437,7 +437,7 @@ class GifImageFile(ImageFile.ImageFile):
super().load_prepare()
def load_end(self):
def load_end(self) -> None:
if self.__frame == 0:
if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
if self._frame_transparency is not None:
@ -463,7 +463,7 @@ class GifImageFile(ImageFile.ImageFile):
else:
self.im.paste(frame_im, self.dispose_extent)
def tell(self):
def tell(self) -> int:
return self.__frame
@ -474,7 +474,7 @@ class GifImageFile(ImageFile.ImageFile):
RAWMODE = {"1": "L", "L": "L", "P": "P"}
def _normalize_mode(im):
def _normalize_mode(im: Image.Image) -> Image.Image:
"""
Takes an image (or frame), returns an image in a mode that is appropriate
for saving in a Gif.
@ -887,7 +887,7 @@ def _get_optimize(im, info):
return used_palette_colors
def _get_color_table_size(palette_bytes):
def _get_color_table_size(palette_bytes: bytes) -> int:
# calculate the palette size for the header
if not palette_bytes:
return 0
@ -897,7 +897,7 @@ def _get_color_table_size(palette_bytes):
return math.ceil(math.log(len(palette_bytes) // 3, 2)) - 1
def _get_header_palette(palette_bytes):
def _get_header_palette(palette_bytes: bytes) -> bytes:
"""
Returns the palette, null padded to the next power of 2 (*3) bytes
suitable for direct inclusion in the GIF header
@ -915,7 +915,7 @@ def _get_header_palette(palette_bytes):
return palette_bytes
def _get_palette_bytes(im):
def _get_palette_bytes(im: Image.Image) -> bytes:
"""
Gets the palette for inclusion in the gif header

View File

@ -53,5 +53,5 @@ class GimpPaletteFile:
self.palette = b"".join(self.palette)
def getpalette(self):
def getpalette(self) -> tuple[bytes, str]:
return self.palette, self.rawmode

View File

@ -37,7 +37,7 @@ class GribStubImageFile(ImageFile.StubImageFile):
format = "GRIB"
format_description = "GRIB"
def _open(self):
def _open(self) -> None:
offset = self.fp.tell()
if not _accept(self.fp.read(8)):

View File

@ -37,7 +37,7 @@ class HDF5StubImageFile(ImageFile.StubImageFile):
format = "HDF5"
format_description = "HDF5"
def _open(self):
def _open(self) -> None:
offset = self.fp.tell()
if not _accept(self.fp.read(8)):

View File

@ -252,7 +252,7 @@ class IcnsImageFile(ImageFile.ImageFile):
format = "ICNS"
format_description = "Mac OS icns resource"
def _open(self):
def _open(self) -> None:
self.icns = IcnsFile(self.fp)
self._mode = "RGBA"
self.info["sizes"] = self.icns.itersizes()

View File

@ -302,7 +302,7 @@ class IcoImageFile(ImageFile.ImageFile):
format = "ICO"
format_description = "Windows Icon"
def _open(self):
def _open(self) -> None:
self.ico = IcoFile(self.fp)
self.info["sizes"] = self.ico.sizes()
self.size = self.ico.entry[0]["dim"]

View File

@ -119,7 +119,7 @@ class ImImageFile(ImageFile.ImageFile):
format_description = "IFUNC Image Memory"
_close_exclusive_fp_after_loading = False
def _open(self):
def _open(self) -> None:
# Quick rejection: if there's not an LF among the first
# 100 bytes, this is (probably) not a text header.
@ -271,14 +271,14 @@ class ImImageFile(ImageFile.ImageFile):
self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
@property
def n_frames(self):
def n_frames(self) -> int:
return self.info[FRAMES]
@property
def is_animated(self):
def is_animated(self) -> bool:
return self.info[FRAMES] > 1
def seek(self, frame):
def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
@ -296,7 +296,7 @@ class ImImageFile(ImageFile.ImageFile):
self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
def tell(self):
def tell(self) -> int:
return self.frame

View File

@ -249,7 +249,28 @@ def _conv_type_shape(im):
return shape, m.typestr
MODES = ["1", "CMYK", "F", "HSV", "I", "L", "LAB", "P", "RGB", "RGBA", "RGBX", "YCbCr"]
MODES = [
"1",
"CMYK",
"F",
"HSV",
"I",
"I;16",
"I;16B",
"I;16L",
"I;16N",
"L",
"LA",
"La",
"LAB",
"P",
"PA",
"RGB",
"RGBA",
"RGBa",
"RGBX",
"YCbCr",
]
# raw modes that may be memory mapped. NOTE: if you change this, you
# may have to modify the stride calculation in map.c too!
@ -1304,7 +1325,10 @@ class Image:
self.load()
return self._new(self.im.expand(xmargin, ymargin))
def filter(self, filter):
if TYPE_CHECKING:
from . import ImageFilter
def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image:
"""
Filters this image using the given filter. For a list of
available filters, see the :py:mod:`~PIL.ImageFilter` module.
@ -1316,7 +1340,7 @@ class Image:
self.load()
if isinstance(filter, Callable):
if callable(filter):
filter = filter()
if not hasattr(filter, "filter"):
msg = "filter argument should be ImageFilter.Filter instance or class"

View File

@ -34,11 +34,10 @@ from __future__ import annotations
import math
import numbers
import struct
from typing import AnyStr, Sequence, cast
from typing import TYPE_CHECKING, AnyStr, Sequence, cast
from . import Image, ImageColor
from ._typing import Coords
from .ImageFont import FreeTypeFont, ImageFont
"""
A simple 2D drawing interface for PIL images.
@ -93,7 +92,10 @@ class ImageDraw:
self.fontmode = "L" # aliasing is okay for other modes
self.fill = False
def getfont(self) -> FreeTypeFont | ImageFont:
if TYPE_CHECKING:
from . import ImageFont
def getfont(self) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
"""
Get the current default font.
@ -118,7 +120,9 @@ class ImageDraw:
self.font = ImageFont.load_default()
return self.font
def _getfont(self, font_size: float | None) -> FreeTypeFont | ImageFont:
def _getfont(
self, font_size: float | None
) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
if font_size is not None:
from . import ImageFont
@ -473,7 +477,7 @@ class ImageDraw:
xy: tuple[float, float],
text: str,
fill=None,
font: FreeTypeFont | ImageFont | None = None,
font: ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
anchor=None,
spacing=4,
align="left",
@ -680,7 +684,7 @@ class ImageDraw:
def textlength(
self,
text: str,
font: FreeTypeFont | ImageFont | None = None,
font: ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None,
direction=None,
features=None,
language=None,

View File

@ -311,7 +311,7 @@ class ImageFile(Image.Image):
return Image.Image.load(self)
def load_prepare(self):
def load_prepare(self) -> None:
# create image memory if necessary
if not self.im or self.im.mode != self.mode or self.im.size != self.size:
self.im = Image.core.new(self.mode, self.size)
@ -319,7 +319,7 @@ class ImageFile(Image.Image):
if self.mode == "P":
Image.Image.load(self)
def load_end(self):
def load_end(self) -> None:
# may be overridden
pass
@ -390,7 +390,7 @@ class Parser:
offset = 0
finished = 0
def reset(self):
def reset(self) -> None:
"""
(Consumer) Reset the parser. Note that you can only call this
method immediately after you've created a parser; parser
@ -605,7 +605,7 @@ def _safe_read(fp, size):
class PyCodecState:
def __init__(self):
def __init__(self) -> None:
self.xsize = 0
self.ysize = 0
self.xoff = 0
@ -634,7 +634,7 @@ class PyCodec:
"""
self.args = args
def cleanup(self):
def cleanup(self) -> None:
"""
Override to perform codec specific cleanup

View File

@ -16,11 +16,14 @@
#
from __future__ import annotations
import abc
import functools
class Filter:
pass
@abc.abstractmethod
def filter(self, image):
pass
class MultibandFilter(Filter):
@ -541,7 +544,7 @@ class Color3DLUT(MultibandFilter):
_copy_table=False,
)
def __repr__(self):
def __repr__(self) -> str:
r = [
f"{self.__class__.__name__} from {self.table.__class__.__name__}",
"size={:d}x{:d}x{:d}".format(*self.size),

View File

@ -66,7 +66,7 @@ class ImagePalette:
def colors(self, colors):
self._colors = colors
def copy(self):
def copy(self) -> ImagePalette:
new = ImagePalette()
new.mode = self.mode
@ -77,7 +77,7 @@ class ImagePalette:
return new
def getdata(self):
def getdata(self) -> tuple[str, bytes]:
"""
Get palette contents in format suitable for the low-level
``im.putpalette`` primitive.
@ -88,7 +88,7 @@ class ImagePalette:
return self.rawmode, self.palette
return self.mode, self.tobytes()
def tobytes(self):
def tobytes(self) -> bytes:
"""Convert palette to bytes.
.. warning:: This method is experimental.

View File

@ -128,7 +128,7 @@ class PhotoImage:
if image:
self.paste(image)
def __del__(self):
def __del__(self) -> None:
name = self.__photo.name
self.__photo.name = None
try:
@ -136,7 +136,7 @@ class PhotoImage:
except Exception:
pass # ignore internal errors
def __str__(self):
def __str__(self) -> str:
"""
Get the Tkinter photo image identifier. This method is automatically
called by Tkinter whenever a PhotoImage object is passed to a Tkinter
@ -146,7 +146,7 @@ class PhotoImage:
"""
return str(self.__photo)
def width(self):
def width(self) -> int:
"""
Get the width of the image.
@ -154,7 +154,7 @@ class PhotoImage:
"""
return self.__size[0]
def height(self):
def height(self) -> int:
"""
Get the height of the image.
@ -219,7 +219,7 @@ class BitmapImage:
kw["data"] = image.tobitmap()
self.__photo = tkinter.BitmapImage(**kw)
def __del__(self):
def __del__(self) -> None:
name = self.__photo.name
self.__photo.name = None
try:
@ -227,7 +227,7 @@ class BitmapImage:
except Exception:
pass # ignore internal errors
def width(self):
def width(self) -> int:
"""
Get the width of the image.
@ -235,7 +235,7 @@ class BitmapImage:
"""
return self.__size[0]
def height(self):
def height(self) -> int:
"""
Get the height of the image.
@ -243,7 +243,7 @@ class BitmapImage:
"""
return self.__size[1]
def __str__(self):
def __str__(self) -> str:
"""
Get the Tkinter bitmap image identifier. This method is automatically
called by Tkinter whenever a BitmapImage object is passed to a Tkinter

View File

@ -204,7 +204,7 @@ class Window:
def ui_handle_damage(self, x0, y0, x1, y1):
pass
def ui_handle_destroy(self):
def ui_handle_destroy(self) -> None:
pass
def ui_handle_repair(self, dc, x0, y0, x1, y1):
@ -213,7 +213,7 @@ class Window:
def ui_handle_resize(self, width, height):
pass
def mainloop(self):
def mainloop(self) -> None:
Image.core.eventloop()

View File

@ -57,7 +57,7 @@ def dump(c: Sequence[int | bytes]) -> None:
""".. deprecated:: 10.2.0"""
deprecate("IptcImagePlugin.dump", 12)
for i in c:
print("%02x" % _i8(i), end=" ")
print(f"{_i8(i):02x}", end=" ")
print()

View File

@ -63,12 +63,12 @@ class BoxReader:
data = self._read_bytes(size)
return struct.unpack(field_format, data)
def read_boxes(self):
def read_boxes(self) -> BoxReader:
size = self.remaining_in_box
data = self._read_bytes(size)
return BoxReader(io.BytesIO(data), size)
def has_next_box(self):
def has_next_box(self) -> bool:
if self.has_length:
return self.fp.tell() + self.remaining_in_box < self.length
else:
@ -215,7 +215,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
format = "JPEG2000"
format_description = "JPEG 2000 (ISO 15444)"
def _open(self):
def _open(self) -> None:
sig = self.fp.read(4)
if sig == b"\xff\x4f\xff\x51":
self.codec = "j2k"
@ -267,7 +267,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
)
]
def _parse_comment(self):
def _parse_comment(self) -> None:
hdr = self.fp.read(2)
length = _binary.i16be(hdr)
self.fp.seek(length - 2, os.SEEK_CUR)

View File

@ -462,7 +462,7 @@ class JpegImageFile(ImageFile.ImageFile):
box = (0, 0, original_size[0] / scale, original_size[1] / scale)
return self.mode, box
def load_djpeg(self):
def load_djpeg(self) -> None:
# ALTERNATIVE: handle JPEGs via the IJG command line utilities
f, path = tempfile.mkstemp()

View File

@ -38,7 +38,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
format_description = "Microsoft Image Composer"
_close_exclusive_fp_after_loading = False
def _open(self):
def _open(self) -> None:
# read the OLE directory and see if this is a likely
# to be a Microsoft Image Composer file
@ -88,7 +88,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
def tell(self):
return self.frame
def close(self):
def close(self) -> None:
self.__fp.close()
self.ole.close()
super().close()

View File

@ -53,6 +53,10 @@ class BitStream:
return v
def _accept(prefix: bytes) -> bool:
return prefix[:4] == b"\x00\x00\x01\xb3"
##
# Image plugin for MPEG streams. This plugin can identify a stream,
# but it cannot read it.
@ -77,7 +81,7 @@ class MpegImageFile(ImageFile.ImageFile):
# --------------------------------------------------------------------
# Registry stuff
Image.register_open(MpegImageFile.format, MpegImageFile)
Image.register_open(MpegImageFile.format, MpegImageFile, _accept)
Image.register_extensions(MpegImageFile.format, [".mpg", ".mpeg"])

View File

@ -100,7 +100,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
format_description = "MPO (CIPA DC-007)"
_close_exclusive_fp_after_loading = False
def _open(self):
def _open(self) -> None:
self.fp.seek(0) # prep the fp in order to pass the JPEG test
JpegImagePlugin.JpegImageFile._open(self)
self._after_jpeg_open()
@ -127,7 +127,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
def load_seek(self, pos):
self._fp.seek(pos)
def seek(self, frame):
def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
self.fp = self._fp
@ -149,7 +149,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
self.tile = [("jpeg", (0, 0) + self.size, self.offset, self.tile[0][-1])]
self.__frame = frame
def tell(self):
def tell(self) -> int:
return self.__frame
@staticmethod

View File

@ -54,7 +54,7 @@ class PSDraw:
self.fp.write(b"%%EndProlog\n")
self.isofont = {}
def end_document(self):
def end_document(self) -> None:
"""Ends printing. (Write PostScript DSC footer.)"""
self.fp.write(b"%%EndDocument\nrestore showpage\n%%End\n")
if hasattr(self.fp, "flush"):

View File

@ -87,10 +87,10 @@ class IndirectReferenceTuple(NamedTuple):
class IndirectReference(IndirectReferenceTuple):
def __str__(self):
def __str__(self) -> str:
return f"{self.object_id} {self.generation} R"
def __bytes__(self):
def __bytes__(self) -> bytes:
return self.__str__().encode("us-ascii")
def __eq__(self, other):
@ -108,7 +108,7 @@ class IndirectReference(IndirectReferenceTuple):
class IndirectObjectDef(IndirectReference):
def __str__(self):
def __str__(self) -> str:
return f"{self.object_id} {self.generation} obj"
@ -150,7 +150,7 @@ class XrefTable:
def __contains__(self, key):
return key in self.existing_entries or key in self.new_entries
def __len__(self):
def __len__(self) -> int:
return len(
set(self.existing_entries.keys())
| set(self.new_entries.keys())
@ -211,7 +211,7 @@ class PdfName:
else:
self.name = name.encode("us-ascii")
def name_as_str(self):
def name_as_str(self) -> str:
return self.name.decode("us-ascii")
def __eq__(self, other):
@ -222,7 +222,7 @@ class PdfName:
def __hash__(self):
return hash(self.name)
def __repr__(self):
def __repr__(self) -> str:
return f"{self.__class__.__name__}({repr(self.name)})"
@classmethod
@ -231,7 +231,7 @@ class PdfName:
allowed_chars = set(range(33, 127)) - {ord(c) for c in "#%/()<>[]{}"}
def __bytes__(self):
def __bytes__(self) -> bytes:
result = bytearray(b"/")
for b in self.name:
if b in self.allowed_chars:
@ -242,7 +242,7 @@ class PdfName:
class PdfArray(List[Any]):
def __bytes__(self):
def __bytes__(self) -> bytes:
return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]"
@ -286,7 +286,7 @@ class PdfDict(_DictBase):
value = time.gmtime(calendar.timegm(value) + offset)
return value
def __bytes__(self):
def __bytes__(self) -> bytes:
out = bytearray(b"<<")
for key, value in self.items():
if value is None:
@ -304,7 +304,7 @@ class PdfBinary:
def __init__(self, data):
self.data = data
def __bytes__(self):
def __bytes__(self) -> bytes:
return b"<%s>" % b"".join(b"%02X" % b for b in self.data)
@ -409,28 +409,28 @@ class PdfParser:
self.close()
return False # do not suppress exceptions
def start_writing(self):
def start_writing(self) -> None:
self.close_buf()
self.seek_end()
def close_buf(self):
def close_buf(self) -> None:
try:
self.buf.close()
except AttributeError:
pass
self.buf = None
def close(self):
def close(self) -> None:
if self.should_close_buf:
self.close_buf()
if self.f is not None and self.should_close_file:
self.f.close()
self.f = None
def seek_end(self):
def seek_end(self) -> None:
self.f.seek(0, os.SEEK_END)
def write_header(self):
def write_header(self) -> None:
self.f.write(b"%PDF-1.4\n")
def write_comment(self, s):
@ -450,7 +450,7 @@ class PdfParser:
)
return self.root_ref
def rewrite_pages(self):
def rewrite_pages(self) -> None:
pages_tree_nodes_to_delete = []
for i, page_ref in enumerate(self.orig_pages):
page_info = self.cached_objects[page_ref]
@ -529,7 +529,7 @@ class PdfParser:
f.write(b"endobj\n")
return ref
def del_root(self):
def del_root(self) -> None:
if self.root_ref is None:
return
del self.xref_table[self.root_ref.object_id]
@ -547,7 +547,7 @@ class PdfParser:
except ValueError: # cannot mmap an empty file
return b""
def read_pdf_info(self):
def read_pdf_info(self) -> None:
self.file_size_total = len(self.buf)
self.file_size_this = self.file_size_total - self.start_offset
self.read_trailer()
@ -823,11 +823,10 @@ class PdfParser:
m = cls.re_stream_start.match(data, offset)
if m:
try:
stream_len = int(result[b"Length"])
except (TypeError, KeyError, ValueError) as e:
msg = "bad or missing Length in stream dict (%r)" % result.get(
b"Length", None
)
stream_len_str = result.get(b"Length")
stream_len = int(stream_len_str)
except (TypeError, ValueError) as e:
msg = f"bad or missing Length in stream dict ({stream_len_str})"
raise PdfFormatError(msg) from e
stream_data = data[m.end() : m.end() + stream_len]
m = cls.re_stream_end.match(data, m.end() + stream_len)

View File

@ -179,7 +179,7 @@ class ChunkStream:
def __exit__(self, *args):
self.close()
def close(self):
def close(self) -> None:
self.queue = self.fp = None
def push(self, cid, pos, length):
@ -370,14 +370,14 @@ class PngStream(ChunkStream):
)
raise ValueError(msg)
def save_rewind(self):
def save_rewind(self) -> None:
self.rewind_state = {
"info": self.im_info.copy(),
"tile": self.im_tile,
"seq_num": self._seq_num,
}
def rewind(self):
def rewind(self) -> None:
self.im_info = self.rewind_state["info"].copy()
self.im_tile = self.rewind_state["tile"]
self._seq_num = self.rewind_state["seq_num"]
@ -800,7 +800,7 @@ class PngImageFile(ImageFile.ImageFile):
self.fp.close()
self.fp = None
def seek(self, frame):
def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
if frame < self.__frame:
@ -909,10 +909,10 @@ class PngImageFile(ImageFile.ImageFile):
else:
self.dispose = None
def tell(self):
def tell(self) -> int:
return self.__frame
def load_prepare(self):
def load_prepare(self) -> None:
"""internal: prepare to read PNG file"""
if self.info.get("interlace"):
@ -954,7 +954,7 @@ class PngImageFile(ImageFile.ImageFile):
return self.fp.read(read_bytes)
def load_end(self):
def load_end(self) -> None:
"""internal: finished reading image data"""
if self.__idat != 0:
self.fp.read(self.__idat)

View File

@ -57,7 +57,7 @@ class PsdImageFile(ImageFile.ImageFile):
format_description = "Adobe Photoshop"
_close_exclusive_fp_after_loading = False
def _open(self):
def _open(self) -> None:
read = self.fp.read
#
@ -141,23 +141,22 @@ class PsdImageFile(ImageFile.ImageFile):
self.frame = 1
self._min_frame = 1
def seek(self, layer):
def seek(self, layer: int) -> None:
if not self._seek_check(layer):
return
# seek to given layer (1..max)
try:
name, mode, bbox, tile = self.layers[layer - 1]
_, mode, _, tile = self.layers[layer - 1]
self._mode = mode
self.tile = tile
self.frame = layer
self.fp = self._fp
return name, bbox
except IndexError as e:
msg = "no such layer"
raise EOFError(msg) from e
def tell(self):
def tell(self) -> int:
# return layer number (0=image, 1..max=layers)
return self.frame

View File

@ -70,7 +70,7 @@ class PyAccess:
# logger.debug("%s", vals)
self._post_init()
def _post_init(self):
def _post_init(self) -> None:
pass
def __setitem__(self, xy, color):

View File

@ -21,7 +21,7 @@ class QoiImageFile(ImageFile.ImageFile):
format = "QOI"
format_description = "Quite OK Image"
def _open(self):
def _open(self) -> None:
if not _accept(self.fp.read(4)):
msg = "not a QOI file"
raise SyntaxError(msg)

View File

@ -37,6 +37,7 @@ from __future__ import annotations
import os
import struct
import sys
from typing import TYPE_CHECKING
from . import Image, ImageFile
@ -97,7 +98,7 @@ class SpiderImageFile(ImageFile.ImageFile):
format_description = "Spider 2D image"
_close_exclusive_fp_after_loading = False
def _open(self):
def _open(self) -> None:
# check header
n = 27 * 4 # read 27 float values
f = self.fp.read(n)
@ -157,21 +158,21 @@ class SpiderImageFile(ImageFile.ImageFile):
self._fp = self.fp # FIXME: hack
@property
def n_frames(self):
def n_frames(self) -> int:
return self._nimages
@property
def is_animated(self):
def is_animated(self) -> bool:
return self._nimages > 1
# 1st image index is zero (although SPIDER imgnumber starts at 1)
def tell(self):
def tell(self) -> int:
if self.imgnumber < 1:
return 0
else:
return self.imgnumber - 1
def seek(self, frame):
def seek(self, frame: int) -> None:
if self.istack == 0:
msg = "attempt to seek in a non-stack file"
raise EOFError(msg)
@ -191,8 +192,11 @@ class SpiderImageFile(ImageFile.ImageFile):
b = -m * minimum
return self.point(lambda i, m=m, b=b: i * m + b).convert("L")
if TYPE_CHECKING:
from . import ImageTk
# returns a ImageTk.PhotoImage object, after rescaling to 0..255
def tkPhotoImage(self):
def tkPhotoImage(self) -> ImageTk.PhotoImage:
from . import ImageTk
return ImageTk.PhotoImage(self.convert2byte(), palette=256)

View File

@ -381,7 +381,7 @@ class IFDRational(Rational):
f = self._val.limit_denominator(max_denominator)
return f.numerator, f.denominator
def __repr__(self):
def __repr__(self) -> str:
return str(float(self._val))
def __hash__(self):
@ -603,7 +603,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
self._next = None
self._offset = None
def __str__(self):
def __str__(self) -> str:
return str(dict(self))
def named(self):
@ -617,7 +617,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
for code, value in self.items()
}
def __len__(self):
def __len__(self) -> int:
return len(set(self._tagdata) | set(self._tags_v2))
def __getitem__(self, tag):
@ -1041,7 +1041,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
ifd.next = original.next # an indicator for multipage tiffs
return ifd
def to_v2(self):
def to_v2(self) -> ImageFileDirectory_v2:
"""Returns an
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
instance with the same data as is contained in the original
@ -1061,7 +1061,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
def __contains__(self, tag):
return tag in self._tags_v1 or tag in self._tagdata
def __len__(self):
def __len__(self) -> int:
return len(set(self._tagdata) | set(self._tags_v1))
def __iter__(self):
@ -1143,7 +1143,7 @@ class TiffImageFile(ImageFile.ImageFile):
self.seek(current)
return self._n_frames
def seek(self, frame):
def seek(self, frame: int) -> None:
"""Select a given frame as current image"""
if not self._seek_check(frame):
return
@ -1154,7 +1154,7 @@ class TiffImageFile(ImageFile.ImageFile):
Image._decompression_bomb_check(self.size)
self.im = Image.core.new(self.mode, self.size)
def _seek(self, frame):
def _seek(self, frame: int) -> None:
self.fp = self._fp
# reset buffered io handle in case fp
@ -1198,7 +1198,7 @@ class TiffImageFile(ImageFile.ImageFile):
self.__frame = frame
self._setup()
def tell(self):
def tell(self) -> int:
"""Return the current frame number"""
return self.__frame
@ -1237,7 +1237,7 @@ class TiffImageFile(ImageFile.ImageFile):
return self._load_libtiff()
return super().load()
def load_end(self):
def load_end(self) -> None:
# allow closing if we're on the first frame, there's no next
# This is the ImageFile.load path only, libtiff specific below.
if not self.is_animated:
@ -1942,7 +1942,7 @@ class AppendingTiffWriter:
self.beginning = self.f.tell()
self.setup()
def setup(self):
def setup(self) -> None:
# Reset everything.
self.f.seek(self.beginning, os.SEEK_SET)
@ -1967,7 +1967,7 @@ class AppendingTiffWriter:
self.skipIFDs()
self.goToEnd()
def finalize(self):
def finalize(self) -> None:
if self.isFirst:
return
@ -1990,7 +1990,7 @@ class AppendingTiffWriter:
self.f.seek(ifd_offset)
self.fixIFD()
def newFrame(self):
def newFrame(self) -> None:
# Call this to finish a frame.
self.finalize()
self.setup()
@ -2003,7 +2003,7 @@ class AppendingTiffWriter:
self.close()
return False
def tell(self):
def tell(self) -> int:
return self.f.tell() - self.offsetOfNewPage
def seek(self, offset, whence=io.SEEK_SET):
@ -2013,7 +2013,7 @@ class AppendingTiffWriter:
self.f.seek(offset, whence)
return self.tell()
def goToEnd(self):
def goToEnd(self) -> None:
self.f.seek(0, os.SEEK_END)
pos = self.f.tell()
@ -2029,7 +2029,7 @@ class AppendingTiffWriter:
self.shortFmt = f"{self.endian}H"
self.tagFormat = f"{self.endian}HHL"
def skipIFDs(self):
def skipIFDs(self) -> None:
while True:
ifd_offset = self.readLong()
if ifd_offset == 0:
@ -2084,11 +2084,11 @@ class AppendingTiffWriter:
msg = f"wrote only {bytes_written} bytes but wanted 4"
raise RuntimeError(msg)
def close(self):
def close(self) -> None:
self.finalize()
self.f.close()
def fixIFD(self):
def fixIFD(self) -> None:
num_tags = self.readShort()
for i in range(num_tags):

View File

@ -32,7 +32,7 @@ class WalImageFile(ImageFile.ImageFile):
format = "WAL"
format_description = "Quake2 Texture"
def _open(self):
def _open(self) -> None:
self._mode = "P"
# read header fields

View File

@ -43,7 +43,7 @@ class WebPImageFile(ImageFile.ImageFile):
__loaded = 0
__logical_frame = 0
def _open(self):
def _open(self) -> None:
if not _webp.HAVE_WEBPANIM:
# Legacy mode
data, width, height, self._mode, icc_profile, exif = _webp.WebPDecode(
@ -109,7 +109,7 @@ class WebPImageFile(ImageFile.ImageFile):
"""
return self._getxmp(self.info["xmp"]) if "xmp" in self.info else {}
def seek(self, frame):
def seek(self, frame: int) -> None:
if not self._seek_check(frame):
return
@ -144,7 +144,7 @@ class WebPImageFile(ImageFile.ImageFile):
timestamp -= duration
return data, timestamp, duration
def _seek(self, frame):
def _seek(self, frame: int) -> None:
if self.__physical_frame == frame:
return # Nothing to do
if frame < self.__physical_frame:
@ -174,7 +174,7 @@ class WebPImageFile(ImageFile.ImageFile):
def load_seek(self, pos):
pass
def tell(self):
def tell(self) -> int:
if not _webp.HAVE_WEBPANIM:
return super().tell()

View File

@ -79,7 +79,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
format = "WMF"
format_description = "Windows Metafile"
def _open(self):
def _open(self) -> None:
self._inch = None
# check placable header

View File

@ -36,7 +36,7 @@ class XpmImageFile(ImageFile.ImageFile):
format = "XPM"
format_description = "X11 Pixel Map"
def _open(self):
def _open(self) -> None:
if not _accept(self.fp.read(9)):
msg = "not an XPM file"
raise SyntaxError(msg)