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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,33 +29,6 @@ elif "GITHUB_ACTIONS" in os.environ:
uploader = "github_actions" 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: def upload(a: Image.Image, b: Image.Image) -> str | None:
if uploader == "show": if uploader == "show":
# local img.show for errors. # local img.show for errors.

View File

@ -336,9 +336,7 @@ def test_readline_psfile(tmp_path: Path) -> None:
strings = ["something", "else", "baz", "bif"] strings = ["something", "else", "baz", "bif"]
def _test_readline(t: EpsImagePlugin.PSFile, ending: str) -> None: def _test_readline(t: EpsImagePlugin.PSFile, ending: str) -> None:
ending = "Failure with line ending: %s" % ( ending = f"Failure with line ending: {''.join(str(ord(s)) for s in ending)}"
"".join("%s" % ord(s) for s in ending)
)
assert t.readline().strip("\r\n") == "something", ending assert t.readline().strip("\r\n") == "something", ending
assert t.readline().strip("\r\n") == "else", ending assert t.readline().strip("\r\n") == "else", ending
assert t.readline().strip("\r\n") == "baz", 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_big_endian,
is_win32, is_win32,
mark_if_feature_version, mark_if_feature_version,
modes,
skip_unless_feature, skip_unless_feature,
) )
@ -46,7 +45,7 @@ def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image:
class TestImage: 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: def test_image_modes_success(self, mode: str) -> None:
helper_image_new(mode, (1, 1)) helper_image_new(mode, (1, 1))
@ -1027,7 +1026,7 @@ class TestImage:
class TestImageBytes: 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: def test_roundtrip_bytes_constructor(self, mode: str) -> None:
im = hopper(mode) im = hopper(mode)
source_bytes = im.tobytes() source_bytes = im.tobytes()
@ -1039,7 +1038,7 @@ class TestImageBytes:
reloaded = Image.frombytes(mode, im.size, source_bytes) reloaded = Image.frombytes(mode, im.size, source_bytes)
assert reloaded.tobytes() == 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: def test_roundtrip_bytes_method(self, mode: str) -> None:
im = hopper(mode) im = hopper(mode)
source_bytes = im.tobytes() source_bytes = im.tobytes()
@ -1048,7 +1047,7 @@ class TestImageBytes:
reloaded.frombytes(source_bytes) reloaded.frombytes(source_bytes)
assert reloaded.tobytes() == 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: def test_getdata_putdata(self, mode: str) -> None:
if is_big_endian() and mode == "BGR;15": if is_big_endian() and mode == "BGR;15":
pytest.xfail("Known failure of BGR;15 on big-endian") pytest.xfail("Known failure of BGR;15 on big-endian")

View File

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

View File

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

View File

@ -253,7 +253,7 @@ class BlpImageFile(ImageFile.ImageFile):
format = "BLP" format = "BLP"
format_description = "Blizzard Mipmap Format" format_description = "Blizzard Mipmap Format"
def _open(self): def _open(self) -> None:
self.magic = self.fp.read(4) self.magic = self.fp.read(4)
self.fp.seek(5, os.SEEK_CUR) self.fp.seek(5, os.SEEK_CUR)
@ -333,7 +333,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
class BLP1Decoder(_BLPBaseDecoder): class BLP1Decoder(_BLPBaseDecoder):
def _load(self): def _load(self) -> None:
if self._blp_compression == Format.JPEG: if self._blp_compression == Format.JPEG:
self._decode_jpeg_stream() self._decode_jpeg_stream()
@ -418,7 +418,7 @@ class BLP2Decoder(_BLPBaseDecoder):
class BLPEncoder(ImageFile.PyEncoder): class BLPEncoder(ImageFile.PyEncoder):
_pushes_fd = True _pushes_fd = True
def _write_palette(self): def _write_palette(self) -> bytes:
data = b"" data = b""
palette = self.im.getpalette("RGBA", "RGBA") palette = self.im.getpalette("RGBA", "RGBA")
for i in range(len(palette) // 4): 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""" """Open file, check magic number and read header"""
# read 14 bytes: magic number, filesize, reserved, header final offset # read 14 bytes: magic number, filesize, reserved, header final offset
head_data = self.fp.read(14) head_data = self.fp.read(14)
@ -376,7 +376,7 @@ class DibImageFile(BmpImageFile):
format = "DIB" format = "DIB"
format_description = "Windows Bitmap" format_description = "Windows Bitmap"
def _open(self): def _open(self) -> None:
self._bitmap() self._bitmap()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -249,7 +249,28 @@ def _conv_type_shape(im):
return shape, m.typestr 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 # raw modes that may be memory mapped. NOTE: if you change this, you
# may have to modify the stride calculation in map.c too! # may have to modify the stride calculation in map.c too!
@ -1304,7 +1325,10 @@ class Image:
self.load() self.load()
return self._new(self.im.expand(xmargin, ymargin)) 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 Filters this image using the given filter. For a list of
available filters, see the :py:mod:`~PIL.ImageFilter` module. available filters, see the :py:mod:`~PIL.ImageFilter` module.
@ -1316,7 +1340,7 @@ class Image:
self.load() self.load()
if isinstance(filter, Callable): if callable(filter):
filter = filter() filter = filter()
if not hasattr(filter, "filter"): if not hasattr(filter, "filter"):
msg = "filter argument should be ImageFilter.Filter instance or class" msg = "filter argument should be ImageFilter.Filter instance or class"

View File

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

View File

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

View File

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

View File

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

View File

@ -128,7 +128,7 @@ class PhotoImage:
if image: if image:
self.paste(image) self.paste(image)
def __del__(self): def __del__(self) -> None:
name = self.__photo.name name = self.__photo.name
self.__photo.name = None self.__photo.name = None
try: try:
@ -136,7 +136,7 @@ class PhotoImage:
except Exception: except Exception:
pass # ignore internal errors pass # ignore internal errors
def __str__(self): def __str__(self) -> str:
""" """
Get the Tkinter photo image identifier. This method is automatically Get the Tkinter photo image identifier. This method is automatically
called by Tkinter whenever a PhotoImage object is passed to a Tkinter called by Tkinter whenever a PhotoImage object is passed to a Tkinter
@ -146,7 +146,7 @@ class PhotoImage:
""" """
return str(self.__photo) return str(self.__photo)
def width(self): def width(self) -> int:
""" """
Get the width of the image. Get the width of the image.
@ -154,7 +154,7 @@ class PhotoImage:
""" """
return self.__size[0] return self.__size[0]
def height(self): def height(self) -> int:
""" """
Get the height of the image. Get the height of the image.
@ -219,7 +219,7 @@ class BitmapImage:
kw["data"] = image.tobitmap() kw["data"] = image.tobitmap()
self.__photo = tkinter.BitmapImage(**kw) self.__photo = tkinter.BitmapImage(**kw)
def __del__(self): def __del__(self) -> None:
name = self.__photo.name name = self.__photo.name
self.__photo.name = None self.__photo.name = None
try: try:
@ -227,7 +227,7 @@ class BitmapImage:
except Exception: except Exception:
pass # ignore internal errors pass # ignore internal errors
def width(self): def width(self) -> int:
""" """
Get the width of the image. Get the width of the image.
@ -235,7 +235,7 @@ class BitmapImage:
""" """
return self.__size[0] return self.__size[0]
def height(self): def height(self) -> int:
""" """
Get the height of the image. Get the height of the image.
@ -243,7 +243,7 @@ class BitmapImage:
""" """
return self.__size[1] return self.__size[1]
def __str__(self): def __str__(self) -> str:
""" """
Get the Tkinter bitmap image identifier. This method is automatically Get the Tkinter bitmap image identifier. This method is automatically
called by Tkinter whenever a BitmapImage object is passed to a Tkinter 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): def ui_handle_damage(self, x0, y0, x1, y1):
pass pass
def ui_handle_destroy(self): def ui_handle_destroy(self) -> None:
pass pass
def ui_handle_repair(self, dc, x0, y0, x1, y1): def ui_handle_repair(self, dc, x0, y0, x1, y1):
@ -213,7 +213,7 @@ class Window:
def ui_handle_resize(self, width, height): def ui_handle_resize(self, width, height):
pass pass
def mainloop(self): def mainloop(self) -> None:
Image.core.eventloop() Image.core.eventloop()

View File

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

View File

@ -63,12 +63,12 @@ class BoxReader:
data = self._read_bytes(size) data = self._read_bytes(size)
return struct.unpack(field_format, data) return struct.unpack(field_format, data)
def read_boxes(self): def read_boxes(self) -> BoxReader:
size = self.remaining_in_box size = self.remaining_in_box
data = self._read_bytes(size) data = self._read_bytes(size)
return BoxReader(io.BytesIO(data), size) return BoxReader(io.BytesIO(data), size)
def has_next_box(self): def has_next_box(self) -> bool:
if self.has_length: if self.has_length:
return self.fp.tell() + self.remaining_in_box < self.length return self.fp.tell() + self.remaining_in_box < self.length
else: else:
@ -215,7 +215,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
format = "JPEG2000" format = "JPEG2000"
format_description = "JPEG 2000 (ISO 15444)" format_description = "JPEG 2000 (ISO 15444)"
def _open(self): def _open(self) -> None:
sig = self.fp.read(4) sig = self.fp.read(4)
if sig == b"\xff\x4f\xff\x51": if sig == b"\xff\x4f\xff\x51":
self.codec = "j2k" 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) hdr = self.fp.read(2)
length = _binary.i16be(hdr) length = _binary.i16be(hdr)
self.fp.seek(length - 2, os.SEEK_CUR) 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) box = (0, 0, original_size[0] / scale, original_size[1] / scale)
return self.mode, box return self.mode, box
def load_djpeg(self): def load_djpeg(self) -> None:
# ALTERNATIVE: handle JPEGs via the IJG command line utilities # ALTERNATIVE: handle JPEGs via the IJG command line utilities
f, path = tempfile.mkstemp() f, path = tempfile.mkstemp()

View File

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

View File

@ -53,6 +53,10 @@ class BitStream:
return v 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, # Image plugin for MPEG streams. This plugin can identify a stream,
# but it cannot read it. # but it cannot read it.
@ -77,7 +81,7 @@ class MpegImageFile(ImageFile.ImageFile):
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Registry stuff # Registry stuff
Image.register_open(MpegImageFile.format, MpegImageFile) Image.register_open(MpegImageFile.format, MpegImageFile, _accept)
Image.register_extensions(MpegImageFile.format, [".mpg", ".mpeg"]) Image.register_extensions(MpegImageFile.format, [".mpg", ".mpeg"])

View File

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

View File

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

View File

@ -87,10 +87,10 @@ class IndirectReferenceTuple(NamedTuple):
class IndirectReference(IndirectReferenceTuple): class IndirectReference(IndirectReferenceTuple):
def __str__(self): def __str__(self) -> str:
return f"{self.object_id} {self.generation} R" return f"{self.object_id} {self.generation} R"
def __bytes__(self): def __bytes__(self) -> bytes:
return self.__str__().encode("us-ascii") return self.__str__().encode("us-ascii")
def __eq__(self, other): def __eq__(self, other):
@ -108,7 +108,7 @@ class IndirectReference(IndirectReferenceTuple):
class IndirectObjectDef(IndirectReference): class IndirectObjectDef(IndirectReference):
def __str__(self): def __str__(self) -> str:
return f"{self.object_id} {self.generation} obj" return f"{self.object_id} {self.generation} obj"
@ -150,7 +150,7 @@ class XrefTable:
def __contains__(self, key): def __contains__(self, key):
return key in self.existing_entries or key in self.new_entries return key in self.existing_entries or key in self.new_entries
def __len__(self): def __len__(self) -> int:
return len( return len(
set(self.existing_entries.keys()) set(self.existing_entries.keys())
| set(self.new_entries.keys()) | set(self.new_entries.keys())
@ -211,7 +211,7 @@ class PdfName:
else: else:
self.name = name.encode("us-ascii") self.name = name.encode("us-ascii")
def name_as_str(self): def name_as_str(self) -> str:
return self.name.decode("us-ascii") return self.name.decode("us-ascii")
def __eq__(self, other): def __eq__(self, other):
@ -222,7 +222,7 @@ class PdfName:
def __hash__(self): def __hash__(self):
return hash(self.name) return hash(self.name)
def __repr__(self): def __repr__(self) -> str:
return f"{self.__class__.__name__}({repr(self.name)})" return f"{self.__class__.__name__}({repr(self.name)})"
@classmethod @classmethod
@ -231,7 +231,7 @@ class PdfName:
allowed_chars = set(range(33, 127)) - {ord(c) for c in "#%/()<>[]{}"} allowed_chars = set(range(33, 127)) - {ord(c) for c in "#%/()<>[]{}"}
def __bytes__(self): def __bytes__(self) -> bytes:
result = bytearray(b"/") result = bytearray(b"/")
for b in self.name: for b in self.name:
if b in self.allowed_chars: if b in self.allowed_chars:
@ -242,7 +242,7 @@ class PdfName:
class PdfArray(List[Any]): class PdfArray(List[Any]):
def __bytes__(self): def __bytes__(self) -> bytes:
return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]" 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) value = time.gmtime(calendar.timegm(value) + offset)
return value return value
def __bytes__(self): def __bytes__(self) -> bytes:
out = bytearray(b"<<") out = bytearray(b"<<")
for key, value in self.items(): for key, value in self.items():
if value is None: if value is None:
@ -304,7 +304,7 @@ class PdfBinary:
def __init__(self, data): def __init__(self, data):
self.data = data self.data = data
def __bytes__(self): def __bytes__(self) -> bytes:
return b"<%s>" % b"".join(b"%02X" % b for b in self.data) return b"<%s>" % b"".join(b"%02X" % b for b in self.data)
@ -409,28 +409,28 @@ class PdfParser:
self.close() self.close()
return False # do not suppress exceptions return False # do not suppress exceptions
def start_writing(self): def start_writing(self) -> None:
self.close_buf() self.close_buf()
self.seek_end() self.seek_end()
def close_buf(self): def close_buf(self) -> None:
try: try:
self.buf.close() self.buf.close()
except AttributeError: except AttributeError:
pass pass
self.buf = None self.buf = None
def close(self): def close(self) -> None:
if self.should_close_buf: if self.should_close_buf:
self.close_buf() self.close_buf()
if self.f is not None and self.should_close_file: if self.f is not None and self.should_close_file:
self.f.close() self.f.close()
self.f = None self.f = None
def seek_end(self): def seek_end(self) -> None:
self.f.seek(0, os.SEEK_END) self.f.seek(0, os.SEEK_END)
def write_header(self): def write_header(self) -> None:
self.f.write(b"%PDF-1.4\n") self.f.write(b"%PDF-1.4\n")
def write_comment(self, s): def write_comment(self, s):
@ -450,7 +450,7 @@ class PdfParser:
) )
return self.root_ref return self.root_ref
def rewrite_pages(self): def rewrite_pages(self) -> None:
pages_tree_nodes_to_delete = [] pages_tree_nodes_to_delete = []
for i, page_ref in enumerate(self.orig_pages): for i, page_ref in enumerate(self.orig_pages):
page_info = self.cached_objects[page_ref] page_info = self.cached_objects[page_ref]
@ -529,7 +529,7 @@ class PdfParser:
f.write(b"endobj\n") f.write(b"endobj\n")
return ref return ref
def del_root(self): def del_root(self) -> None:
if self.root_ref is None: if self.root_ref is None:
return return
del self.xref_table[self.root_ref.object_id] del self.xref_table[self.root_ref.object_id]
@ -547,7 +547,7 @@ class PdfParser:
except ValueError: # cannot mmap an empty file except ValueError: # cannot mmap an empty file
return b"" return b""
def read_pdf_info(self): def read_pdf_info(self) -> None:
self.file_size_total = len(self.buf) self.file_size_total = len(self.buf)
self.file_size_this = self.file_size_total - self.start_offset self.file_size_this = self.file_size_total - self.start_offset
self.read_trailer() self.read_trailer()
@ -823,11 +823,10 @@ class PdfParser:
m = cls.re_stream_start.match(data, offset) m = cls.re_stream_start.match(data, offset)
if m: if m:
try: try:
stream_len = int(result[b"Length"]) stream_len_str = result.get(b"Length")
except (TypeError, KeyError, ValueError) as e: stream_len = int(stream_len_str)
msg = "bad or missing Length in stream dict (%r)" % result.get( except (TypeError, ValueError) as e:
b"Length", None msg = f"bad or missing Length in stream dict ({stream_len_str})"
)
raise PdfFormatError(msg) from e raise PdfFormatError(msg) from e
stream_data = data[m.end() : m.end() + stream_len] stream_data = data[m.end() : m.end() + stream_len]
m = cls.re_stream_end.match(data, 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): def __exit__(self, *args):
self.close() self.close()
def close(self): def close(self) -> None:
self.queue = self.fp = None self.queue = self.fp = None
def push(self, cid, pos, length): def push(self, cid, pos, length):
@ -370,14 +370,14 @@ class PngStream(ChunkStream):
) )
raise ValueError(msg) raise ValueError(msg)
def save_rewind(self): def save_rewind(self) -> None:
self.rewind_state = { self.rewind_state = {
"info": self.im_info.copy(), "info": self.im_info.copy(),
"tile": self.im_tile, "tile": self.im_tile,
"seq_num": self._seq_num, "seq_num": self._seq_num,
} }
def rewind(self): def rewind(self) -> None:
self.im_info = self.rewind_state["info"].copy() self.im_info = self.rewind_state["info"].copy()
self.im_tile = self.rewind_state["tile"] self.im_tile = self.rewind_state["tile"]
self._seq_num = self.rewind_state["seq_num"] self._seq_num = self.rewind_state["seq_num"]
@ -800,7 +800,7 @@ class PngImageFile(ImageFile.ImageFile):
self.fp.close() self.fp.close()
self.fp = None self.fp = None
def seek(self, frame): def seek(self, frame: int) -> None:
if not self._seek_check(frame): if not self._seek_check(frame):
return return
if frame < self.__frame: if frame < self.__frame:
@ -909,10 +909,10 @@ class PngImageFile(ImageFile.ImageFile):
else: else:
self.dispose = None self.dispose = None
def tell(self): def tell(self) -> int:
return self.__frame return self.__frame
def load_prepare(self): def load_prepare(self) -> None:
"""internal: prepare to read PNG file""" """internal: prepare to read PNG file"""
if self.info.get("interlace"): if self.info.get("interlace"):
@ -954,7 +954,7 @@ class PngImageFile(ImageFile.ImageFile):
return self.fp.read(read_bytes) return self.fp.read(read_bytes)
def load_end(self): def load_end(self) -> None:
"""internal: finished reading image data""" """internal: finished reading image data"""
if self.__idat != 0: if self.__idat != 0:
self.fp.read(self.__idat) self.fp.read(self.__idat)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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