mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
Added type hints
This commit is contained in:
parent
2ed8502d12
commit
e6e5ef5c5f
|
@ -684,6 +684,13 @@ class TestFileTiff:
|
|||
with Image.open(outfile) as reloaded:
|
||||
assert_image_equal_tofile(reloaded, infile)
|
||||
|
||||
def test_invalid_tiled_dimensions(self) -> None:
|
||||
with open("Tests/images/tiff_tiled_planar_raw.tif", "rb") as fp:
|
||||
data = fp.read()
|
||||
b = BytesIO(data[:144] + b"\x02" + data[145:])
|
||||
with pytest.raises(ValueError):
|
||||
Image.open(b)
|
||||
|
||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||
def test_palette(self, mode: str, tmp_path: Path) -> None:
|
||||
outfile = str(tmp_path / "temp.tif")
|
||||
|
|
|
@ -412,9 +412,8 @@ class TestPyEncoder(CodecsTest):
|
|||
with pytest.raises(NotImplementedError):
|
||||
encoder.encode_to_pyfd()
|
||||
|
||||
fh = BytesIO()
|
||||
with pytest.raises(NotImplementedError):
|
||||
encoder.encode_to_file(fh, 0)
|
||||
encoder.encode_to_file(0, 0)
|
||||
|
||||
def test_zero_height(self) -> None:
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
|
|
|
@ -363,6 +363,7 @@ Classes
|
|||
:show-inheritance:
|
||||
.. autoclass:: PIL.Image.ImagePointHandler
|
||||
.. autoclass:: PIL.Image.ImageTransformHandler
|
||||
.. autoclass:: PIL.Image._E
|
||||
|
||||
Protocols
|
||||
---------
|
||||
|
|
|
@ -319,11 +319,11 @@ class IcoImageFile(ImageFile.ImageFile):
|
|||
self.load()
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
def size(self) -> tuple[int, int]:
|
||||
return self._size
|
||||
|
||||
@size.setter
|
||||
def size(self, value):
|
||||
def size(self, value: tuple[int, int]) -> None:
|
||||
if value not in self.info["sizes"]:
|
||||
msg = "This is not one of the allowed sizes of this image"
|
||||
raise ValueError(msg)
|
||||
|
|
|
@ -504,7 +504,7 @@ class _E:
|
|||
return _E(self.scale / other, self.offset / other)
|
||||
|
||||
|
||||
def _getscaleoffset(expr) -> tuple[float, float]:
|
||||
def _getscaleoffset(expr: Callable[[_E], _E | float]) -> tuple[float, float]:
|
||||
a = expr(_E(1, 0))
|
||||
return (a.scale, a.offset) if isinstance(a, _E) else (0, a)
|
||||
|
||||
|
@ -1894,7 +1894,13 @@ class Image:
|
|||
|
||||
def point(
|
||||
self,
|
||||
lut: Sequence[float] | NumpyArray | Callable[[int], float] | ImagePointHandler,
|
||||
lut: (
|
||||
Sequence[float]
|
||||
| NumpyArray
|
||||
| Callable[[int], float]
|
||||
| Callable[[_E], _E | float]
|
||||
| ImagePointHandler
|
||||
),
|
||||
mode: str | None = None,
|
||||
) -> Image:
|
||||
"""
|
||||
|
@ -1930,10 +1936,10 @@ class Image:
|
|||
# check if the function can be used with point_transform
|
||||
# UNDONE wiredfool -- I think this prevents us from ever doing
|
||||
# a gamma function point transform on > 8bit images.
|
||||
scale, offset = _getscaleoffset(lut)
|
||||
scale, offset = _getscaleoffset(lut) # type: ignore[arg-type]
|
||||
return self._new(self.im.point_transform(scale, offset))
|
||||
# for other modes, convert the function to a table
|
||||
flatLut = [lut(i) for i in range(256)] * self.im.bands
|
||||
flatLut = [lut(i) for i in range(256)] * self.im.bands # type: ignore[arg-type]
|
||||
else:
|
||||
flatLut = lut
|
||||
|
||||
|
@ -2869,11 +2875,11 @@ class Image:
|
|||
self,
|
||||
box: tuple[int, int, int, int],
|
||||
image: Image,
|
||||
method,
|
||||
data,
|
||||
method: Transform,
|
||||
data: Sequence[float],
|
||||
resample: int = Resampling.NEAREST,
|
||||
fill: bool = True,
|
||||
):
|
||||
) -> None:
|
||||
w = box[2] - box[0]
|
||||
h = box[3] - box[1]
|
||||
|
||||
|
@ -4008,15 +4014,19 @@ class Exif(_ExifBase):
|
|||
ifd[tag] = value
|
||||
return b"Exif\x00\x00" + head + ifd.tobytes(offset)
|
||||
|
||||
def get_ifd(self, tag):
|
||||
def get_ifd(self, tag: int) -> dict[int, Any]:
|
||||
if tag not in self._ifds:
|
||||
if tag == ExifTags.IFD.IFD1:
|
||||
if self._info is not None and self._info.next != 0:
|
||||
self._ifds[tag] = self._get_ifd_dict(self._info.next)
|
||||
ifd = self._get_ifd_dict(self._info.next)
|
||||
if ifd is not None:
|
||||
self._ifds[tag] = ifd
|
||||
elif tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]:
|
||||
offset = self._hidden_data.get(tag, self.get(tag))
|
||||
if offset is not None:
|
||||
self._ifds[tag] = self._get_ifd_dict(offset, tag)
|
||||
ifd = self._get_ifd_dict(offset, tag)
|
||||
if ifd is not None:
|
||||
self._ifds[tag] = ifd
|
||||
elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.Makernote]:
|
||||
if ExifTags.IFD.Exif not in self._ifds:
|
||||
self.get_ifd(ExifTags.IFD.Exif)
|
||||
|
@ -4073,7 +4083,9 @@ class Exif(_ExifBase):
|
|||
(offset,) = struct.unpack(">L", data)
|
||||
self.fp.seek(offset)
|
||||
|
||||
camerainfo = {"ModelID": self.fp.read(4)}
|
||||
camerainfo: dict[str, int | bytes] = {
|
||||
"ModelID": self.fp.read(4)
|
||||
}
|
||||
|
||||
self.fp.read(4)
|
||||
# Seconds since 2000
|
||||
|
@ -4089,16 +4101,18 @@ class Exif(_ExifBase):
|
|||
][1]
|
||||
camerainfo["Parallax"] = handler(
|
||||
ImageFileDirectory_v2(), parallax, False
|
||||
)
|
||||
)[0]
|
||||
|
||||
self.fp.read(4)
|
||||
camerainfo["Category"] = self.fp.read(2)
|
||||
|
||||
makernote = {0x1101: dict(self._fixup_dict(camerainfo))}
|
||||
makernote = {0x1101: camerainfo}
|
||||
self._ifds[tag] = makernote
|
||||
else:
|
||||
# Interop
|
||||
self._ifds[tag] = self._get_ifd_dict(tag_data, tag)
|
||||
ifd = self._get_ifd_dict(tag_data, tag)
|
||||
if ifd is not None:
|
||||
self._ifds[tag] = ifd
|
||||
ifd = self._ifds.get(tag, {})
|
||||
if tag == ExifTags.IFD.Exif and self._hidden_data:
|
||||
ifd = {
|
||||
|
|
|
@ -31,6 +31,7 @@ from __future__ import annotations
|
|||
import abc
|
||||
import io
|
||||
import itertools
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
from typing import IO, Any, NamedTuple
|
||||
|
@ -555,7 +556,7 @@ def _encode_tile(
|
|||
fp: IO[bytes],
|
||||
tile: list[_Tile],
|
||||
bufsize: int,
|
||||
fh,
|
||||
fh: int | None,
|
||||
exc: BaseException | None = None,
|
||||
) -> None:
|
||||
for encoder_name, extents, offset, args in tile:
|
||||
|
@ -577,6 +578,7 @@ def _encode_tile(
|
|||
break
|
||||
else:
|
||||
# slight speedup: compress to real file object
|
||||
assert fh is not None
|
||||
errcode = encoder.encode_to_file(fh, bufsize)
|
||||
if errcode < 0:
|
||||
raise _get_oserror(errcode, encoder=True) from exc
|
||||
|
@ -801,7 +803,7 @@ class PyEncoder(PyCodec):
|
|||
self.fd.write(data)
|
||||
return bytes_consumed, errcode
|
||||
|
||||
def encode_to_file(self, fh: IO[bytes], bufsize: int) -> int:
|
||||
def encode_to_file(self, fh: int, bufsize: int) -> int:
|
||||
"""
|
||||
:param fh: File handle.
|
||||
:param bufsize: Buffer size.
|
||||
|
@ -814,5 +816,5 @@ class PyEncoder(PyCodec):
|
|||
while errcode == 0:
|
||||
status, errcode, buf = self.encode(bufsize)
|
||||
if status > 0:
|
||||
fh.write(buf[status:])
|
||||
os.write(fh, buf[status:])
|
||||
return errcode
|
||||
|
|
|
@ -214,7 +214,7 @@ def getiptcinfo(
|
|||
# as 4-byte integers, so we cannot use the get method...)
|
||||
try:
|
||||
data = im.tag_v2[TiffImagePlugin.IPTC_NAA_CHUNK]
|
||||
except (AttributeError, KeyError):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if data is None:
|
||||
|
|
|
@ -624,12 +624,12 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
self._tagdata: dict[int, bytes] = {}
|
||||
self.tagtype = {} # added 2008-06-05 by Florian Hoech
|
||||
self._next = None
|
||||
self._offset = None
|
||||
self._offset: int | None = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(dict(self))
|
||||
|
||||
def named(self):
|
||||
def named(self) -> dict[str, Any]:
|
||||
"""
|
||||
:returns: dict of name|key: value
|
||||
|
||||
|
@ -643,7 +643,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
def __len__(self) -> int:
|
||||
return len(set(self._tagdata) | set(self._tags_v2))
|
||||
|
||||
def __getitem__(self, tag):
|
||||
def __getitem__(self, tag: int) -> Any:
|
||||
if tag not in self._tags_v2: # unpack on the fly
|
||||
data = self._tagdata[tag]
|
||||
typ = self.tagtype[tag]
|
||||
|
@ -855,7 +855,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
raise OSError(msg)
|
||||
return ret
|
||||
|
||||
def load(self, fp):
|
||||
def load(self, fp: IO[bytes]) -> None:
|
||||
self.reset()
|
||||
self._offset = fp.tell()
|
||||
|
||||
|
@ -1098,7 +1098,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
|
|||
for legacy_api in (False, True):
|
||||
self._setitem(tag, value, legacy_api)
|
||||
|
||||
def __getitem__(self, tag):
|
||||
def __getitem__(self, tag: int) -> Any:
|
||||
if tag not in self._tags_v1: # unpack on the fly
|
||||
data = self._tagdata[tag]
|
||||
typ = self.tagtype[tag]
|
||||
|
@ -1124,11 +1124,15 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
format_description = "Adobe TIFF"
|
||||
_close_exclusive_fp_after_loading = False
|
||||
|
||||
def __init__(self, fp=None, filename=None):
|
||||
self.tag_v2 = None
|
||||
def __init__(
|
||||
self,
|
||||
fp: StrOrBytesPath | IO[bytes] | None = None,
|
||||
filename: str | bytes | None = None,
|
||||
) -> None:
|
||||
self.tag_v2: ImageFileDirectory_v2
|
||||
""" Image file directory (tag dictionary) """
|
||||
|
||||
self.tag = None
|
||||
self.tag: ImageFileDirectory_v1
|
||||
""" Legacy tag entries """
|
||||
|
||||
super().__init__(fp, filename)
|
||||
|
@ -1143,9 +1147,6 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
|
||||
self.tag_v2 = ImageFileDirectory_v2(ifh)
|
||||
|
||||
# legacy IFD entries will be filled in later
|
||||
self.ifd: ImageFileDirectory_v1 | None = None
|
||||
|
||||
# setup frame pointers
|
||||
self.__first = self.__next = self.tag_v2.next
|
||||
self.__frame = -1
|
||||
|
@ -1396,8 +1397,11 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING))
|
||||
|
||||
# size
|
||||
xsize = int(self.tag_v2.get(IMAGEWIDTH))
|
||||
ysize = int(self.tag_v2.get(IMAGELENGTH))
|
||||
xsize = self.tag_v2.get(IMAGEWIDTH)
|
||||
ysize = self.tag_v2.get(IMAGELENGTH)
|
||||
if not isinstance(xsize, int) or not isinstance(ysize, int):
|
||||
msg = "Invalid dimensions"
|
||||
raise ValueError(msg)
|
||||
self._size = xsize, ysize
|
||||
|
||||
logger.debug("- size: %s", self.size)
|
||||
|
@ -1545,8 +1549,12 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
else:
|
||||
# tiled image
|
||||
offsets = self.tag_v2[TILEOFFSETS]
|
||||
w = self.tag_v2.get(TILEWIDTH)
|
||||
tilewidth = self.tag_v2.get(TILEWIDTH)
|
||||
h = self.tag_v2.get(TILELENGTH)
|
||||
if not isinstance(tilewidth, int) or not isinstance(h, int):
|
||||
msg = "Invalid tile dimensions"
|
||||
raise ValueError(msg)
|
||||
w = tilewidth
|
||||
|
||||
for offset in offsets:
|
||||
if x + w > xsize:
|
||||
|
@ -1624,7 +1632,7 @@ SAVE_INFO = {
|
|||
}
|
||||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
def _save(im: Image.Image, fp, filename: str | bytes) -> None:
|
||||
try:
|
||||
rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]
|
||||
except KeyError as e:
|
||||
|
@ -1760,10 +1768,11 @@ def _save(im, fp, filename):
|
|||
if im.mode == "1":
|
||||
inverted_im = im.copy()
|
||||
px = inverted_im.load()
|
||||
for y in range(inverted_im.height):
|
||||
for x in range(inverted_im.width):
|
||||
px[x, y] = 0 if px[x, y] == 255 else 255
|
||||
im = inverted_im
|
||||
if px is not None:
|
||||
for y in range(inverted_im.height):
|
||||
for x in range(inverted_im.width):
|
||||
px[x, y] = 0 if px[x, y] == 255 else 255
|
||||
im = inverted_im
|
||||
else:
|
||||
im = ImageOps.invert(im)
|
||||
|
||||
|
@ -1805,11 +1814,11 @@ def _save(im, fp, filename):
|
|||
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)
|
||||
|
||||
if im.mode == "YCbCr":
|
||||
for tag, value in {
|
||||
for tag, default_value in {
|
||||
YCBCRSUBSAMPLING: (1, 1),
|
||||
REFERENCEBLACKWHITE: (0, 255, 128, 255, 128, 255),
|
||||
}.items():
|
||||
ifd.setdefault(tag, value)
|
||||
ifd.setdefault(tag, default_value)
|
||||
|
||||
blocklist = [TILEWIDTH, TILELENGTH, TILEOFFSETS, TILEBYTECOUNTS]
|
||||
if libtiff:
|
||||
|
@ -1852,7 +1861,7 @@ def _save(im, fp, filename):
|
|||
]
|
||||
|
||||
# bits per sample is a single short in the tiff directory, not a list.
|
||||
atts = {BITSPERSAMPLE: bits[0]}
|
||||
atts: dict[int, Any] = {BITSPERSAMPLE: bits[0]}
|
||||
# Merge the ones that we have with (optional) more bits from
|
||||
# the original file, e.g x,y resolution so that we can
|
||||
# save(load('')) == original file.
|
||||
|
@ -1923,13 +1932,15 @@ def _save(im, fp, filename):
|
|||
offset = ifd.save(fp)
|
||||
|
||||
ImageFile._save(
|
||||
im, fp, [("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))]
|
||||
im,
|
||||
fp,
|
||||
[ImageFile._Tile("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))],
|
||||
)
|
||||
|
||||
# -- helper for multi-page save --
|
||||
if "_debug_multipage" in encoderinfo:
|
||||
# just to access o32 and o16 (using correct byte order)
|
||||
im._debug_multipage = ifd
|
||||
setattr(im, "_debug_multipage", ifd)
|
||||
|
||||
|
||||
class AppendingTiffWriter:
|
||||
|
|
Loading…
Reference in New Issue
Block a user