Merge pull request #8268 from radarhere/type_hint

This commit is contained in:
Hugo van Kemenade 2024-07-29 18:44:47 +03:00 committed by GitHub
commit 5833a8b18e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 71 additions and 53 deletions

View File

@ -2052,7 +2052,11 @@ class Image:
msg = "illegal image mode"
raise ValueError(msg)
if isinstance(data, ImagePalette.ImagePalette):
palette = ImagePalette.raw(data.rawmode, data.palette)
if data.rawmode is not None:
palette = ImagePalette.raw(data.rawmode, data.palette)
else:
palette = ImagePalette.ImagePalette(palette=data.palette)
palette.dirty = 1
else:
if not isinstance(data, bytes):
data = bytes(data)

View File

@ -167,7 +167,7 @@ class Draw:
"""
self.render("polygon", xy, *options)
def rectangle(self, xy: Coords, *options) -> None:
def rectangle(self, xy: Coords, *options: Any) -> None:
"""
Draws a rectangle.

View File

@ -269,12 +269,12 @@ class FreeTypeFont:
else:
load_from_bytes(font)
def __getstate__(self):
def __getstate__(self) -> list[Any]:
return [self.path, self.size, self.index, self.encoding, self.layout_engine]
def __setstate__(self, state):
def __setstate__(self, state: list[Any]) -> None:
path, size, index, encoding, layout_engine = state
self.__init__(path, size, index, encoding, layout_engine)
FreeTypeFont.__init__(self, path, size, index, encoding, layout_engine)
def getname(self) -> tuple[str | None, str | None]:
"""

View File

@ -208,7 +208,7 @@ class ImagePalette:
# Internal
def raw(rawmode, data: Sequence[int] | bytes | bytearray) -> ImagePalette:
def raw(rawmode: str, data: Sequence[int] | bytes | bytearray) -> ImagePalette:
palette = ImagePalette()
palette.rawmode = rawmode
palette.palette = data

View File

@ -324,7 +324,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
return self._reduce or super().reduce
@reduce.setter
def reduce(self, value):
def reduce(self, value: int) -> None:
self._reduce = value
def load(self) -> Image.core.PixelAccess | None:

View File

@ -25,7 +25,7 @@ import io
import math
import os
import time
from typing import IO
from typing import IO, Any
from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features
@ -48,7 +48,12 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# (Internal) Image save plugin for the PDF format.
def _write_image(im, filename, existing_pdf, image_refs):
def _write_image(
im: Image.Image,
filename: str | bytes,
existing_pdf: PdfParser.PdfParser,
image_refs: list[PdfParser.IndirectReference],
) -> tuple[PdfParser.IndirectReference, str]:
# FIXME: Should replace ASCIIHexDecode with RunLengthDecode
# (packbits) or LZWDecode (tiff/lzw compression). Note that
# PDF 1.2 also supports Flatedecode (zip compression).
@ -61,10 +66,10 @@ def _write_image(im, filename, existing_pdf, image_refs):
width, height = im.size
dict_obj = {"BitsPerComponent": 8}
dict_obj: dict[str, Any] = {"BitsPerComponent": 8}
if im.mode == "1":
if features.check("libtiff"):
filter = "CCITTFaxDecode"
decode_filter = "CCITTFaxDecode"
dict_obj["BitsPerComponent"] = 1
params = PdfParser.PdfArray(
[
@ -79,22 +84,23 @@ def _write_image(im, filename, existing_pdf, image_refs):
]
)
else:
filter = "DCTDecode"
decode_filter = "DCTDecode"
dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray")
procset = "ImageB" # grayscale
elif im.mode == "L":
filter = "DCTDecode"
decode_filter = "DCTDecode"
# params = f"<< /Predictor 15 /Columns {width-2} >>"
dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray")
procset = "ImageB" # grayscale
elif im.mode == "LA":
filter = "JPXDecode"
decode_filter = "JPXDecode"
# params = f"<< /Predictor 15 /Columns {width-2} >>"
procset = "ImageB" # grayscale
dict_obj["SMaskInData"] = 1
elif im.mode == "P":
filter = "ASCIIHexDecode"
decode_filter = "ASCIIHexDecode"
palette = im.getpalette()
assert palette is not None
dict_obj["ColorSpace"] = [
PdfParser.PdfName("Indexed"),
PdfParser.PdfName("DeviceRGB"),
@ -110,15 +116,15 @@ def _write_image(im, filename, existing_pdf, image_refs):
image_ref = _write_image(smask, filename, existing_pdf, image_refs)[0]
dict_obj["SMask"] = image_ref
elif im.mode == "RGB":
filter = "DCTDecode"
decode_filter = "DCTDecode"
dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceRGB")
procset = "ImageC" # color images
elif im.mode == "RGBA":
filter = "JPXDecode"
decode_filter = "JPXDecode"
procset = "ImageC" # color images
dict_obj["SMaskInData"] = 1
elif im.mode == "CMYK":
filter = "DCTDecode"
decode_filter = "DCTDecode"
dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceCMYK")
procset = "ImageC" # color images
decode = [1, 0, 1, 0, 1, 0, 1, 0]
@ -131,9 +137,9 @@ def _write_image(im, filename, existing_pdf, image_refs):
op = io.BytesIO()
if filter == "ASCIIHexDecode":
if decode_filter == "ASCIIHexDecode":
ImageFile._save(im, op, [("hex", (0, 0) + im.size, 0, im.mode)])
elif filter == "CCITTFaxDecode":
elif decode_filter == "CCITTFaxDecode":
im.save(
op,
"TIFF",
@ -141,21 +147,22 @@ def _write_image(im, filename, existing_pdf, image_refs):
# use a single strip
strip_size=math.ceil(width / 8) * height,
)
elif filter == "DCTDecode":
elif decode_filter == "DCTDecode":
Image.SAVE["JPEG"](im, op, filename)
elif filter == "JPXDecode":
elif decode_filter == "JPXDecode":
del dict_obj["BitsPerComponent"]
Image.SAVE["JPEG2000"](im, op, filename)
else:
msg = f"unsupported PDF filter ({filter})"
msg = f"unsupported PDF filter ({decode_filter})"
raise ValueError(msg)
stream = op.getvalue()
if filter == "CCITTFaxDecode":
filter: PdfParser.PdfArray | PdfParser.PdfName
if decode_filter == "CCITTFaxDecode":
stream = stream[8:]
filter = PdfParser.PdfArray([PdfParser.PdfName(filter)])
filter = PdfParser.PdfArray([PdfParser.PdfName(decode_filter)])
else:
filter = PdfParser.PdfName(filter)
filter = PdfParser.PdfName(decode_filter)
image_ref = image_refs.pop(0)
existing_pdf.write_obj(

View File

@ -47,16 +47,18 @@ import math
import os
import struct
import warnings
from collections.abc import MutableMapping
from collections.abc import Iterator, MutableMapping
from fractions import Fraction
from numbers import Number, Rational
from typing import IO, TYPE_CHECKING, Any, Callable, NoReturn
from typing import IO, TYPE_CHECKING, Any, Callable, NoReturn, cast
from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags
from ._binary import i16be as i16
from ._binary import i32be as i32
from ._binary import o8
from ._deprecate import deprecate
from ._typing import StrOrBytesPath
from ._util import is_path
from .TiffTags import TYPES
logger = logging.getLogger(__name__)
@ -313,7 +315,7 @@ _load_dispatch = {}
_write_dispatch = {}
def _delegate(op):
def _delegate(op: str):
def delegate(self, *args):
return getattr(self._val, op)(*args)
@ -334,7 +336,9 @@ class IFDRational(Rational):
__slots__ = ("_numerator", "_denominator", "_val")
def __init__(self, value, denominator: int = 1) -> None:
def __init__(
self, value: float | Fraction | IFDRational, denominator: int = 1
) -> None:
"""
:param value: either an integer numerator, a
float/rational/other number, or an IFDRational
@ -358,18 +362,20 @@ class IFDRational(Rational):
self._val = float("nan")
elif denominator == 1:
self._val = Fraction(value)
elif int(value) == value:
self._val = Fraction(int(value), denominator)
else:
self._val = Fraction(value, denominator)
self._val = Fraction(value / denominator)
@property
def numerator(self):
return self._numerator
@property
def denominator(self):
def denominator(self) -> int:
return self._denominator
def limit_rational(self, max_denominator):
def limit_rational(self, max_denominator: int) -> tuple[float, int]:
"""
:param max_denominator: Integer, the maximum denominator value
@ -379,6 +385,7 @@ class IFDRational(Rational):
if self.denominator == 0:
return self.numerator, self.denominator
assert isinstance(self._val, Fraction)
f = self._val.limit_denominator(max_denominator)
return f.numerator, f.denominator
@ -396,14 +403,15 @@ class IFDRational(Rational):
val = float(val)
return val == other
def __getstate__(self):
def __getstate__(self) -> list[float | Fraction]:
return [self._val, self._numerator, self._denominator]
def __setstate__(self, state):
def __setstate__(self, state: list[float | Fraction]) -> None:
IFDRational.__init__(self, 0)
_val, _numerator, _denominator = state
self._val = _val
self._numerator = _numerator
assert isinstance(_denominator, int)
self._denominator = _denominator
""" a = ['add','radd', 'sub', 'rsub', 'mul', 'rmul',
@ -730,13 +738,13 @@ class ImageFileDirectory_v2(_IFDv2Base):
self._tags_v1.pop(tag, None)
self._tagdata.pop(tag, None)
def __iter__(self):
def __iter__(self) -> Iterator[int]:
return iter(set(self._tagdata) | set(self._tags_v2))
def _unpack(self, fmt: str, data: bytes):
return struct.unpack(self._endian + fmt, data)
def _pack(self, fmt: str, *values):
def _pack(self, fmt: str, *values) -> bytes:
return struct.pack(self._endian + fmt, *values)
list(
@ -787,7 +795,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
def load_rational(self, data, legacy_api: bool = True):
vals = self._unpack(f"{len(data) // 4}L", data)
def combine(a, b):
def combine(a: int, b: int) -> tuple[int, int] | IFDRational:
return (a, b) if legacy_api else IFDRational(a, b)
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
@ -814,7 +822,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
def load_signed_rational(self, data: bytes, legacy_api: bool = True):
vals = self._unpack(f"{len(data) // 4}l", data)
def combine(a, b):
def combine(a: int, b: int) -> tuple[int, int] | IFDRational:
return (a, b) if legacy_api else IFDRational(a, b)
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
@ -903,11 +911,11 @@ class ImageFileDirectory_v2(_IFDv2Base):
warnings.warn(str(msg))
return
def tobytes(self, offset=0):
def tobytes(self, offset: int = 0) -> bytes:
# FIXME What about tagdata?
result = self._pack("H", len(self._tags_v2))
entries = []
entries: list[tuple[int, int, int, bytes, bytes]] = []
offset = offset + len(result) + len(self._tags_v2) * 12 + 4
stripoffsets = None
@ -916,7 +924,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
for tag, value in sorted(self._tags_v2.items()):
if tag == STRIPOFFSETS:
stripoffsets = len(entries)
typ = self.tagtype.get(tag)
typ = self.tagtype[tag]
logger.debug("Tag %s, Type: %s, Value: %s", tag, typ, repr(value))
is_ifd = typ == TiffTags.LONG and isinstance(value, dict)
if is_ifd:
@ -1072,7 +1080,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
def __len__(self) -> int:
return len(set(self._tagdata) | set(self._tags_v1))
def __iter__(self):
def __iter__(self) -> Iterator[int]:
return iter(set(self._tagdata) | set(self._tags_v1))
def __setitem__(self, tag: int, value) -> None:
@ -1943,17 +1951,18 @@ class AppendingTiffWriter:
521, # JPEGACTables
}
def __init__(self, fn, new: bool = False) -> None:
if hasattr(fn, "read"):
self.f = fn
self.close_fp = False
else:
def __init__(self, fn: StrOrBytesPath | IO[bytes], new: bool = False) -> None:
self.f: IO[bytes]
if is_path(fn):
self.name = fn
self.close_fp = True
try:
self.f = open(fn, "w+b" if new else "r+b")
except OSError:
self.f = open(fn, "w+b")
else:
self.f = cast(IO[bytes], fn)
self.close_fp = False
self.beginning = self.f.tell()
self.setup()
@ -1961,7 +1970,7 @@ class AppendingTiffWriter:
# Reset everything.
self.f.seek(self.beginning, os.SEEK_SET)
self.whereToWriteNewIFDOffset = None
self.whereToWriteNewIFDOffset: int | None = None
self.offsetOfNewPage = 0
self.IIMM = iimm = self.f.read(4)
@ -2000,6 +2009,7 @@ class AppendingTiffWriter:
ifd_offset = self.readLong()
ifd_offset += self.offsetOfNewPage
assert self.whereToWriteNewIFDOffset is not None
self.f.seek(self.whereToWriteNewIFDOffset)
self.writeLong(ifd_offset)
self.f.seek(ifd_offset)
@ -2020,7 +2030,7 @@ class AppendingTiffWriter:
def tell(self) -> int:
return self.f.tell() - self.offsetOfNewPage
def seek(self, offset: int, whence=io.SEEK_SET) -> int:
def seek(self, offset: int, whence: int = io.SEEK_SET) -> int:
if whence == os.SEEK_SET:
offset += self.offsetOfNewPage
@ -2111,7 +2121,6 @@ class AppendingTiffWriter:
field_size = self.fieldSizes[field_type]
total_size = field_size * count
is_local = total_size <= 4
offset: int | None
if not is_local:
offset = self.readLong() + self.offsetOfNewPage
self.rewriteLastLong(offset)
@ -2131,8 +2140,6 @@ class AppendingTiffWriter:
)
self.f.seek(cur_pos)
offset = cur_pos = None
elif is_local:
# skip the locally stored value that is not an offset
self.f.seek(4, os.SEEK_CUR)