mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-10 19:56:47 +03:00
commit
8405412b76
|
@ -240,9 +240,10 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
|
||||
new_ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
for tag, info in core_items.items():
|
||||
assert info.type is not None
|
||||
if info.length == 1:
|
||||
new_ifd[tag] = values[info.type]
|
||||
if info.length == 0:
|
||||
elif not info.length:
|
||||
new_ifd[tag] = tuple(values[info.type] for _ in range(3))
|
||||
else:
|
||||
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))
|
||||
|
|
|
@ -37,6 +37,11 @@ Example: Parse an image
|
|||
Classes
|
||||
-------
|
||||
|
||||
.. autoclass:: PIL.ImageFile._Tile()
|
||||
:member-order: bysource
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: PIL.ImageFile.Parser()
|
||||
:members:
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ class Dib:
|
|||
assert not isinstance(image, str)
|
||||
self.paste(image)
|
||||
|
||||
def expose(self, handle):
|
||||
def expose(self, handle: int | HDC | HWND) -> None:
|
||||
"""
|
||||
Copy the bitmap contents to a device context.
|
||||
|
||||
|
@ -101,19 +101,18 @@ class Dib:
|
|||
if isinstance(handle, HWND):
|
||||
dc = self.image.getdc(handle)
|
||||
try:
|
||||
result = self.image.expose(dc)
|
||||
self.image.expose(dc)
|
||||
finally:
|
||||
self.image.releasedc(handle, dc)
|
||||
else:
|
||||
result = self.image.expose(handle)
|
||||
return result
|
||||
self.image.expose(handle)
|
||||
|
||||
def draw(
|
||||
self,
|
||||
handle,
|
||||
handle: int | HDC | HWND,
|
||||
dst: tuple[int, int, int, int],
|
||||
src: tuple[int, int, int, int] | None = None,
|
||||
):
|
||||
) -> None:
|
||||
"""
|
||||
Same as expose, but allows you to specify where to draw the image, and
|
||||
what part of it to draw.
|
||||
|
@ -128,14 +127,13 @@ class Dib:
|
|||
if isinstance(handle, HWND):
|
||||
dc = self.image.getdc(handle)
|
||||
try:
|
||||
result = self.image.draw(dc, dst, src)
|
||||
self.image.draw(dc, dst, src)
|
||||
finally:
|
||||
self.image.releasedc(handle, dc)
|
||||
else:
|
||||
result = self.image.draw(handle, dst, src)
|
||||
return result
|
||||
self.image.draw(handle, dst, src)
|
||||
|
||||
def query_palette(self, handle):
|
||||
def query_palette(self, handle: int | HDC | HWND) -> int:
|
||||
"""
|
||||
Installs the palette associated with the image in the given device
|
||||
context.
|
||||
|
@ -147,8 +145,8 @@ class Dib:
|
|||
|
||||
:param handle: Device context (HDC), cast to a Python integer, or an
|
||||
HDC or HWND instance.
|
||||
:return: A true value if one or more entries were changed (this
|
||||
indicates that the image should be redrawn).
|
||||
:return: The number of entries that were changed (if one or more entries,
|
||||
this indicates that the image should be redrawn).
|
||||
"""
|
||||
if isinstance(handle, HWND):
|
||||
handle = self.image.getdc(handle)
|
||||
|
@ -210,22 +208,22 @@ class Window:
|
|||
title, self.__dispatcher, width or 0, height or 0
|
||||
)
|
||||
|
||||
def __dispatcher(self, action: str, *args):
|
||||
return getattr(self, f"ui_handle_{action}")(*args)
|
||||
def __dispatcher(self, action: str, *args: int) -> None:
|
||||
getattr(self, f"ui_handle_{action}")(*args)
|
||||
|
||||
def ui_handle_clear(self, dc, x0, y0, x1, y1) -> None:
|
||||
def ui_handle_clear(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
|
||||
pass
|
||||
|
||||
def ui_handle_damage(self, x0, y0, x1, y1) -> None:
|
||||
def ui_handle_damage(self, x0: int, y0: int, x1: int, y1: int) -> None:
|
||||
pass
|
||||
|
||||
def ui_handle_destroy(self) -> None:
|
||||
pass
|
||||
|
||||
def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None:
|
||||
def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
|
||||
pass
|
||||
|
||||
def ui_handle_resize(self, width, height) -> None:
|
||||
def ui_handle_resize(self, width: int, height: int) -> None:
|
||||
pass
|
||||
|
||||
def mainloop(self) -> None:
|
||||
|
@ -235,12 +233,12 @@ class Window:
|
|||
class ImageWindow(Window):
|
||||
"""Create an image window which displays the given image."""
|
||||
|
||||
def __init__(self, image, title: str = "PIL") -> None:
|
||||
def __init__(self, image: Image.Image | Dib, title: str = "PIL") -> None:
|
||||
if not isinstance(image, Dib):
|
||||
image = Dib(image)
|
||||
self.image = image
|
||||
width, height = image.size
|
||||
super().__init__(title, width=width, height=height)
|
||||
|
||||
def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None:
|
||||
def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
|
||||
self.image.draw(dc, (x0, y0, x1, y1))
|
||||
|
|
|
@ -19,6 +19,7 @@ from __future__ import annotations
|
|||
|
||||
import io
|
||||
from functools import cached_property
|
||||
from typing import IO
|
||||
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8
|
||||
|
@ -142,7 +143,9 @@ class PsdImageFile(ImageFile.ImageFile):
|
|||
self._min_frame = 1
|
||||
|
||||
@cached_property
|
||||
def layers(self):
|
||||
def layers(
|
||||
self,
|
||||
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
|
||||
layers = []
|
||||
if self._layers_position is not None:
|
||||
self._fp.seek(self._layers_position)
|
||||
|
@ -181,7 +184,9 @@ class PsdImageFile(ImageFile.ImageFile):
|
|||
return self.frame
|
||||
|
||||
|
||||
def _layerinfo(fp, ct_bytes):
|
||||
def _layerinfo(
|
||||
fp: IO[bytes], ct_bytes: int
|
||||
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
|
||||
# read layerinfo block
|
||||
layers = []
|
||||
|
||||
|
@ -203,7 +208,7 @@ def _layerinfo(fp, ct_bytes):
|
|||
x1 = si32(read(4))
|
||||
|
||||
# image info
|
||||
mode = []
|
||||
bands = []
|
||||
ct_types = i16(read(2))
|
||||
if ct_types > 4:
|
||||
fp.seek(ct_types * 6 + 12, io.SEEK_CUR)
|
||||
|
@ -215,23 +220,23 @@ def _layerinfo(fp, ct_bytes):
|
|||
type = i16(read(2))
|
||||
|
||||
if type == 65535:
|
||||
m = "A"
|
||||
b = "A"
|
||||
else:
|
||||
m = "RGBA"[type]
|
||||
b = "RGBA"[type]
|
||||
|
||||
mode.append(m)
|
||||
bands.append(b)
|
||||
read(4) # size
|
||||
|
||||
# figure out the image mode
|
||||
mode.sort()
|
||||
if mode == ["R"]:
|
||||
bands.sort()
|
||||
if bands == ["R"]:
|
||||
mode = "L"
|
||||
elif mode == ["B", "G", "R"]:
|
||||
elif bands == ["B", "G", "R"]:
|
||||
mode = "RGB"
|
||||
elif mode == ["A", "B", "G", "R"]:
|
||||
elif bands == ["A", "B", "G", "R"]:
|
||||
mode = "RGBA"
|
||||
else:
|
||||
mode = None # unknown
|
||||
mode = "" # unknown
|
||||
|
||||
# skip over blend flags and extra information
|
||||
read(12) # filler
|
||||
|
@ -258,19 +263,22 @@ def _layerinfo(fp, ct_bytes):
|
|||
layers.append((name, mode, (x0, y0, x1, y1)))
|
||||
|
||||
# get tiles
|
||||
layerinfo = []
|
||||
for i, (name, mode, bbox) in enumerate(layers):
|
||||
tile = []
|
||||
for m in mode:
|
||||
t = _maketile(fp, m, bbox, 1)
|
||||
if t:
|
||||
tile.extend(t)
|
||||
layers[i] = name, mode, bbox, tile
|
||||
layerinfo.append((name, mode, bbox, tile))
|
||||
|
||||
return layers
|
||||
return layerinfo
|
||||
|
||||
|
||||
def _maketile(file, mode, bbox, channels):
|
||||
tile = None
|
||||
def _maketile(
|
||||
file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int
|
||||
) -> list[ImageFile._Tile] | None:
|
||||
tiles = None
|
||||
read = file.read
|
||||
|
||||
compression = i16(read(2))
|
||||
|
@ -283,26 +291,26 @@ def _maketile(file, mode, bbox, channels):
|
|||
if compression == 0:
|
||||
#
|
||||
# raw compression
|
||||
tile = []
|
||||
tiles = []
|
||||
for channel in range(channels):
|
||||
layer = mode[channel]
|
||||
if mode == "CMYK":
|
||||
layer += ";I"
|
||||
tile.append(("raw", bbox, offset, layer))
|
||||
tiles.append(ImageFile._Tile("raw", bbox, offset, layer))
|
||||
offset = offset + xsize * ysize
|
||||
|
||||
elif compression == 1:
|
||||
#
|
||||
# packbits compression
|
||||
i = 0
|
||||
tile = []
|
||||
tiles = []
|
||||
bytecount = read(channels * ysize * 2)
|
||||
offset = file.tell()
|
||||
for channel in range(channels):
|
||||
layer = mode[channel]
|
||||
if mode == "CMYK":
|
||||
layer += ";I"
|
||||
tile.append(("packbits", bbox, offset, layer))
|
||||
tiles.append(ImageFile._Tile("packbits", bbox, offset, layer))
|
||||
for y in range(ysize):
|
||||
offset = offset + i16(bytecount, i)
|
||||
i += 2
|
||||
|
@ -312,7 +320,7 @@ def _maketile(file, mode, bbox, channels):
|
|||
if offset & 1:
|
||||
read(1) # padding
|
||||
|
||||
return tile
|
||||
return tiles
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -445,7 +445,7 @@ class IFDRational(Rational):
|
|||
__int__ = _delegate("__int__")
|
||||
|
||||
|
||||
def _register_loader(idx, size):
|
||||
def _register_loader(idx: int, size: int):
|
||||
def decorator(func):
|
||||
from .TiffTags import TYPES
|
||||
|
||||
|
@ -457,7 +457,7 @@ def _register_loader(idx, size):
|
|||
return decorator
|
||||
|
||||
|
||||
def _register_writer(idx):
|
||||
def _register_writer(idx: int):
|
||||
def decorator(func):
|
||||
_write_dispatch[idx] = func # noqa: F821
|
||||
return func
|
||||
|
@ -465,7 +465,7 @@ def _register_writer(idx):
|
|||
return decorator
|
||||
|
||||
|
||||
def _register_basic(idx_fmt_name):
|
||||
def _register_basic(idx_fmt_name: tuple[int, str, str]) -> None:
|
||||
from .TiffTags import TYPES
|
||||
|
||||
idx, fmt, name = idx_fmt_name
|
||||
|
@ -640,7 +640,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
def __contains__(self, tag: object) -> bool:
|
||||
return tag in self._tags_v2 or tag in self._tagdata
|
||||
|
||||
def __setitem__(self, tag, value) -> None:
|
||||
def __setitem__(self, tag: int, value) -> None:
|
||||
self._setitem(tag, value, self.legacy_api)
|
||||
|
||||
def _setitem(self, tag, value, legacy_api) -> None:
|
||||
|
@ -731,10 +731,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
def __iter__(self):
|
||||
return iter(set(self._tagdata) | set(self._tags_v2))
|
||||
|
||||
def _unpack(self, fmt, data):
|
||||
def _unpack(self, fmt: str, data):
|
||||
return struct.unpack(self._endian + fmt, data)
|
||||
|
||||
def _pack(self, fmt, *values):
|
||||
def _pack(self, fmt: str, *values):
|
||||
return struct.pack(self._endian + fmt, *values)
|
||||
|
||||
list(
|
||||
|
@ -755,7 +755,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
)
|
||||
|
||||
@_register_loader(1, 1) # Basic type, except for the legacy API.
|
||||
def load_byte(self, data, legacy_api=True):
|
||||
def load_byte(self, data, legacy_api: bool = True):
|
||||
return data
|
||||
|
||||
@_register_writer(1) # Basic type, except for the legacy API.
|
||||
|
@ -767,7 +767,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
return data
|
||||
|
||||
@_register_loader(2, 1)
|
||||
def load_string(self, data, legacy_api=True):
|
||||
def load_string(self, data: bytes, legacy_api: bool = True) -> str:
|
||||
if data.endswith(b"\0"):
|
||||
data = data[:-1]
|
||||
return data.decode("latin-1", "replace")
|
||||
|
@ -797,7 +797,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
)
|
||||
|
||||
@_register_loader(7, 1)
|
||||
def load_undefined(self, data, legacy_api=True):
|
||||
def load_undefined(self, data, legacy_api: bool = True):
|
||||
return data
|
||||
|
||||
@_register_writer(7)
|
||||
|
@ -809,7 +809,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
return value
|
||||
|
||||
@_register_loader(10, 8)
|
||||
def load_signed_rational(self, data, legacy_api=True):
|
||||
def load_signed_rational(self, data, legacy_api: bool = True):
|
||||
vals = self._unpack(f"{len(data) // 4}l", data)
|
||||
|
||||
def combine(a, b):
|
||||
|
@ -1030,7 +1030,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
|
|||
"""Dictionary of tag types"""
|
||||
|
||||
@classmethod
|
||||
def from_v2(cls, original) -> ImageFileDirectory_v1:
|
||||
def from_v2(cls, original: ImageFileDirectory_v2) -> ImageFileDirectory_v1:
|
||||
"""Returns an
|
||||
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
|
||||
instance with the same data as is contained in the original
|
||||
|
@ -1073,7 +1073,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
|
|||
def __iter__(self):
|
||||
return iter(set(self._tagdata) | set(self._tags_v1))
|
||||
|
||||
def __setitem__(self, tag, value) -> None:
|
||||
def __setitem__(self, tag: int, value) -> None:
|
||||
for legacy_api in (False, True):
|
||||
self._setitem(tag, value, legacy_api)
|
||||
|
||||
|
@ -1212,7 +1212,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
"""Return the current frame number"""
|
||||
return self.__frame
|
||||
|
||||
def get_photoshop_blocks(self):
|
||||
def get_photoshop_blocks(self) -> dict[int, dict[str, bytes]]:
|
||||
"""
|
||||
Returns a dictionary of Photoshop "Image Resource Blocks".
|
||||
The keys are the image resource ID. For more information, see
|
||||
|
@ -1259,7 +1259,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
if ExifTags.Base.Orientation in self.tag_v2:
|
||||
del self.tag_v2[ExifTags.Base.Orientation]
|
||||
|
||||
def _load_libtiff(self):
|
||||
def _load_libtiff(self) -> Image.core.PixelAccess | None:
|
||||
"""Overload method triggered when we detect a compressed tiff
|
||||
Calls out to libtiff"""
|
||||
|
||||
|
|
|
@ -32,17 +32,24 @@ class _TagInfo(NamedTuple):
|
|||
class TagInfo(_TagInfo):
|
||||
__slots__: list[str] = []
|
||||
|
||||
def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None):
|
||||
def __new__(
|
||||
cls,
|
||||
value: int | None = None,
|
||||
name: str = "unknown",
|
||||
type: int | None = None,
|
||||
length: int | None = None,
|
||||
enum: dict[str, int] | None = None,
|
||||
) -> TagInfo:
|
||||
return super().__new__(cls, value, name, type, length, enum or {})
|
||||
|
||||
def cvt_enum(self, value):
|
||||
def cvt_enum(self, value: str) -> int | str:
|
||||
# Using get will call hash(value), which can be expensive
|
||||
# for some types (e.g. Fraction). Since self.enum is rarely
|
||||
# used, it's usually better to test it first.
|
||||
return self.enum.get(value, value) if self.enum else value
|
||||
|
||||
|
||||
def lookup(tag, group=None):
|
||||
def lookup(tag: int, group: int | None = None) -> TagInfo:
|
||||
"""
|
||||
:param tag: Integer tag number
|
||||
:param group: Which :py:data:`~PIL.TiffTags.TAGS_V2_GROUPS` to look in
|
||||
|
@ -89,7 +96,7 @@ DOUBLE = 12
|
|||
IFD = 13
|
||||
LONG8 = 16
|
||||
|
||||
_tags_v2 = {
|
||||
_tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]] = {
|
||||
254: ("NewSubfileType", LONG, 1),
|
||||
255: ("SubfileType", SHORT, 1),
|
||||
256: ("ImageWidth", LONG, 1),
|
||||
|
@ -233,7 +240,7 @@ _tags_v2 = {
|
|||
50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one
|
||||
50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006
|
||||
}
|
||||
TAGS_V2_GROUPS = {
|
||||
_tags_v2_groups = {
|
||||
# ExifIFD
|
||||
34665: {
|
||||
36864: ("ExifVersion", UNDEFINED, 1),
|
||||
|
@ -281,7 +288,7 @@ TAGS_V2_GROUPS = {
|
|||
|
||||
# Legacy Tags structure
|
||||
# these tags aren't included above, but were in the previous versions
|
||||
TAGS = {
|
||||
TAGS: dict[int | tuple[int, int], str] = {
|
||||
347: "JPEGTables",
|
||||
700: "XMP",
|
||||
# Additional Exif Info
|
||||
|
@ -426,9 +433,10 @@ TAGS = {
|
|||
}
|
||||
|
||||
TAGS_V2: dict[int, TagInfo] = {}
|
||||
TAGS_V2_GROUPS: dict[int, dict[int, TagInfo]] = {}
|
||||
|
||||
|
||||
def _populate():
|
||||
def _populate() -> None:
|
||||
for k, v in _tags_v2.items():
|
||||
# Populate legacy structure.
|
||||
TAGS[k] = v[0]
|
||||
|
@ -438,9 +446,8 @@ def _populate():
|
|||
|
||||
TAGS_V2[k] = TagInfo(k, *v)
|
||||
|
||||
for tags in TAGS_V2_GROUPS.values():
|
||||
for k, v in tags.items():
|
||||
tags[k] = TagInfo(k, *v)
|
||||
for group, tags in _tags_v2_groups.items():
|
||||
TAGS_V2_GROUPS[group] = {k: TagInfo(k, *v) for k, v in tags.items()}
|
||||
|
||||
|
||||
_populate()
|
||||
|
|
Loading…
Reference in New Issue
Block a user