Skip to content

Commit f19e07b

Browse files
authored
Merge pull request #8234 from radarhere/type_hint
Added type hints
2 parents 2152a17 + 3eeef83 commit f19e07b

File tree

10 files changed

+139
-93
lines changed

10 files changed

+139
-93
lines changed

Tests/test_imagewin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ def test_dib_mode_string(self) -> None:
5757
# Assert
5858
assert dib.size == (128, 128)
5959

60+
with pytest.raises(ValueError):
61+
ImageWin.Dib(mode)
62+
6063
def test_dib_paste(self) -> None:
6164
# Arrange
6265
im = hopper()

src/PIL/EpsImagePlugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def has_ghostscript() -> bool:
6565
return gs_binary is not False
6666

6767

68-
def Ghostscript(tile, size, fp, scale=1, transparency=False):
68+
def Ghostscript(tile, size, fp, scale=1, transparency: bool = False) -> Image.Image:
6969
"""Render an image using Ghostscript"""
7070
global gs_binary
7171
if not has_ghostscript():

src/PIL/IcoImagePlugin.py

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import warnings
2626
from io import BytesIO
2727
from math import ceil, log
28-
from typing import IO
28+
from typing import IO, NamedTuple
2929

3030
from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin
3131
from ._binary import i16le as i16
@@ -119,8 +119,22 @@ def _accept(prefix: bytes) -> bool:
119119
return prefix[:4] == _MAGIC
120120

121121

122+
class IconHeader(NamedTuple):
123+
width: int
124+
height: int
125+
nb_color: int
126+
reserved: int
127+
planes: int
128+
bpp: int
129+
size: int
130+
offset: int
131+
dim: tuple[int, int]
132+
square: int
133+
color_depth: int
134+
135+
122136
class IcoFile:
123-
def __init__(self, buf) -> None:
137+
def __init__(self, buf: IO[bytes]) -> None:
124138
"""
125139
Parse image from file-like object containing ico file data
126140
"""
@@ -141,51 +155,44 @@ def __init__(self, buf) -> None:
141155
for i in range(self.nb_items):
142156
s = buf.read(16)
143157

144-
icon_header = {
145-
"width": s[0],
146-
"height": s[1],
147-
"nb_color": s[2], # No. of colors in image (0 if >=8bpp)
148-
"reserved": s[3],
149-
"planes": i16(s, 4),
150-
"bpp": i16(s, 6),
151-
"size": i32(s, 8),
152-
"offset": i32(s, 12),
153-
}
154-
155158
# See Wikipedia
156-
for j in ("width", "height"):
157-
if not icon_header[j]:
158-
icon_header[j] = 256
159-
160-
# See Wikipedia notes about color depth.
161-
# We need this just to differ images with equal sizes
162-
icon_header["color_depth"] = (
163-
icon_header["bpp"]
164-
or (
165-
icon_header["nb_color"] != 0
166-
and ceil(log(icon_header["nb_color"], 2))
167-
)
168-
or 256
159+
width = s[0] or 256
160+
height = s[1] or 256
161+
162+
# No. of colors in image (0 if >=8bpp)
163+
nb_color = s[2]
164+
bpp = i16(s, 6)
165+
icon_header = IconHeader(
166+
width=width,
167+
height=height,
168+
nb_color=nb_color,
169+
reserved=s[3],
170+
planes=i16(s, 4),
171+
bpp=i16(s, 6),
172+
size=i32(s, 8),
173+
offset=i32(s, 12),
174+
dim=(width, height),
175+
square=width * height,
176+
# See Wikipedia notes about color depth.
177+
# We need this just to differ images with equal sizes
178+
color_depth=bpp or (nb_color != 0 and ceil(log(nb_color, 2))) or 256,
169179
)
170180

171-
icon_header["dim"] = (icon_header["width"], icon_header["height"])
172-
icon_header["square"] = icon_header["width"] * icon_header["height"]
173-
174181
self.entry.append(icon_header)
175182

176-
self.entry = sorted(self.entry, key=lambda x: x["color_depth"])
183+
self.entry = sorted(self.entry, key=lambda x: x.color_depth)
177184
# ICO images are usually squares
178-
self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True)
185+
self.entry = sorted(self.entry, key=lambda x: x.square, reverse=True)
179186

180187
def sizes(self) -> set[tuple[int, int]]:
181188
"""
182-
Get a list of all available icon sizes and color depths.
189+
Get a set of all available icon sizes and color depths.
183190
"""
184-
return {(h["width"], h["height"]) for h in self.entry}
191+
return {(h.width, h.height) for h in self.entry}
185192

186193
def getentryindex(self, size: tuple[int, int], bpp: int | bool = False) -> int:
187194
for i, h in enumerate(self.entry):
188-
if size == h["dim"] and (bpp is False or bpp == h["color_depth"]):
195+
if size == h.dim and (bpp is False or bpp == h.color_depth):
189196
return i
190197
return 0
191198

@@ -202,9 +209,9 @@ def frame(self, idx: int) -> Image.Image:
202209

203210
header = self.entry[idx]
204211

205-
self.buf.seek(header["offset"])
212+
self.buf.seek(header.offset)
206213
data = self.buf.read(8)
207-
self.buf.seek(header["offset"])
214+
self.buf.seek(header.offset)
208215

209216
im: Image.Image
210217
if data[:8] == PngImagePlugin._MAGIC:
@@ -222,8 +229,7 @@ def frame(self, idx: int) -> Image.Image:
222229
im.tile[0] = d, (0, 0) + im.size, o, a
223230

224231
# figure out where AND mask image starts
225-
bpp = header["bpp"]
226-
if 32 == bpp:
232+
if header.bpp == 32:
227233
# 32-bit color depth icon image allows semitransparent areas
228234
# PIL's DIB format ignores transparency bits, recover them.
229235
# The DIB is packed in BGRX byte order where X is the alpha
@@ -253,7 +259,7 @@ def frame(self, idx: int) -> Image.Image:
253259
# padded row size * height / bits per char
254260

255261
total_bytes = int((w * im.size[1]) / 8)
256-
and_mask_offset = header["offset"] + header["size"] - total_bytes
262+
and_mask_offset = header.offset + header.size - total_bytes
257263

258264
self.buf.seek(and_mask_offset)
259265
mask_data = self.buf.read(total_bytes)
@@ -307,7 +313,7 @@ class IcoImageFile(ImageFile.ImageFile):
307313
def _open(self) -> None:
308314
self.ico = IcoFile(self.fp)
309315
self.info["sizes"] = self.ico.sizes()
310-
self.size = self.ico.entry[0]["dim"]
316+
self.size = self.ico.entry[0].dim
311317
self.load()
312318

313319
@property

src/PIL/Image.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3286,7 +3286,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
32863286
return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
32873287

32883288

3289-
def fromqimage(im):
3289+
def fromqimage(im) -> ImageFile.ImageFile:
32903290
"""Creates an image instance from a QImage image"""
32913291
from . import ImageQt
32923292

@@ -3296,7 +3296,7 @@ def fromqimage(im):
32963296
return ImageQt.fromqimage(im)
32973297

32983298

3299-
def fromqpixmap(im):
3299+
def fromqpixmap(im) -> ImageFile.ImageFile:
33003300
"""Creates an image instance from a QPixmap image"""
33013301
from . import ImageQt
33023302

@@ -3867,7 +3867,7 @@ def _fixup_dict(self, src_dict):
38673867
# returns a dict with any single item tuples/lists as individual values
38683868
return {k: self._fixup(v) for k, v in src_dict.items()}
38693869

3870-
def _get_ifd_dict(self, offset, group=None):
3870+
def _get_ifd_dict(self, offset: int, group=None):
38713871
try:
38723872
# an offset pointer to the location of the nested embedded IFD.
38733873
# It should be a long, but may be corrupted.
@@ -3881,7 +3881,7 @@ def _get_ifd_dict(self, offset, group=None):
38813881
info.load(self.fp)
38823882
return self._fixup_dict(info)
38833883

3884-
def _get_head(self):
3884+
def _get_head(self) -> bytes:
38853885
version = b"\x2B" if self.bigtiff else b"\x2A"
38863886
if self.endian == "<":
38873887
head = b"II" + version + b"\x00" + o32le(8)
@@ -4102,16 +4102,16 @@ def __len__(self) -> int:
41024102
keys.update(self._info)
41034103
return len(keys)
41044104

4105-
def __getitem__(self, tag):
4105+
def __getitem__(self, tag: int):
41064106
if self._info is not None and tag not in self._data and tag in self._info:
41074107
self._data[tag] = self._fixup(self._info[tag])
41084108
del self._info[tag]
41094109
return self._data[tag]
41104110

4111-
def __contains__(self, tag) -> bool:
4111+
def __contains__(self, tag: object) -> bool:
41124112
return tag in self._data or (self._info is not None and tag in self._info)
41134113

4114-
def __setitem__(self, tag, value) -> None:
4114+
def __setitem__(self, tag: int, value) -> None:
41154115
if self._info is not None and tag in self._info:
41164116
del self._info[tag]
41174117
self._data[tag] = value

src/PIL/ImageFile.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,9 @@ def _save(im, fp, tile, bufsize: int = 0) -> None:
553553
fp.flush()
554554

555555

556-
def _encode_tile(im, fp, tile: list[_Tile], bufsize: int, fh, exc=None) -> None:
556+
def _encode_tile(
557+
im, fp: IO[bytes], tile: list[_Tile], bufsize: int, fh, exc=None
558+
) -> None:
557559
for encoder_name, extents, offset, args in tile:
558560
if offset > 0:
559561
fp.seek(offset)
@@ -653,7 +655,7 @@ def cleanup(self) -> None:
653655
"""
654656
pass
655657

656-
def setfd(self, fd) -> None:
658+
def setfd(self, fd: IO[bytes]) -> None:
657659
"""
658660
Called from ImageFile to set the Python file-like object
659661

src/PIL/ImageQt.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919

2020
import sys
2121
from io import BytesIO
22-
from typing import Callable
22+
from typing import TYPE_CHECKING, Callable
2323

2424
from . import Image
2525
from ._util import is_path
2626

27+
if TYPE_CHECKING:
28+
from . import ImageFile
29+
2730
qt_version: str | None
2831
qt_versions = [
2932
["6", "PyQt6"],
@@ -90,11 +93,11 @@ def fromqimage(im):
9093
return Image.open(b)
9194

9295

93-
def fromqpixmap(im):
96+
def fromqpixmap(im) -> ImageFile.ImageFile:
9497
return fromqimage(im)
9598

9699

97-
def align8to32(bytes, width, mode):
100+
def align8to32(bytes: bytes, width: int, mode: str) -> bytes:
98101
"""
99102
converts each scanline of data from 8 bit to 32 bit aligned
100103
"""
@@ -172,7 +175,7 @@ def _toqclass_helper(im):
172175
if qt_is_installed:
173176

174177
class ImageQt(QImage):
175-
def __init__(self, im):
178+
def __init__(self, im) -> None:
176179
"""
177180
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
178181
class.

src/PIL/ImageWin.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,14 @@ class Dib:
7070
"""
7171

7272
def __init__(
73-
self, image: Image.Image | str, size: tuple[int, int] | list[int] | None = None
73+
self, image: Image.Image | str, size: tuple[int, int] | None = None
7474
) -> None:
7575
if isinstance(image, str):
7676
mode = image
7777
image = ""
78+
if size is None:
79+
msg = "If first argument is mode, size is required"
80+
raise ValueError(msg)
7881
else:
7982
mode = image.mode
8083
size = image.size
@@ -105,7 +108,12 @@ def expose(self, handle):
105108
result = self.image.expose(handle)
106109
return result
107110

108-
def draw(self, handle, dst, src=None):
111+
def draw(
112+
self,
113+
handle,
114+
dst: tuple[int, int, int, int],
115+
src: tuple[int, int, int, int] | None = None,
116+
):
109117
"""
110118
Same as expose, but allows you to specify where to draw the image, and
111119
what part of it to draw.
@@ -115,7 +123,7 @@ def draw(self, handle, dst, src=None):
115123
the destination have different sizes, the image is resized as
116124
necessary.
117125
"""
118-
if not src:
126+
if src is None:
119127
src = (0, 0) + self.size
120128
if isinstance(handle, HWND):
121129
dc = self.image.getdc(handle)
@@ -202,22 +210,22 @@ def __init__(
202210
title, self.__dispatcher, width or 0, height or 0
203211
)
204212

205-
def __dispatcher(self, action, *args):
213+
def __dispatcher(self, action: str, *args):
206214
return getattr(self, f"ui_handle_{action}")(*args)
207215

208-
def ui_handle_clear(self, dc, x0, y0, x1, y1):
216+
def ui_handle_clear(self, dc, x0, y0, x1, y1) -> None:
209217
pass
210218

211-
def ui_handle_damage(self, x0, y0, x1, y1):
219+
def ui_handle_damage(self, x0, y0, x1, y1) -> None:
212220
pass
213221

214222
def ui_handle_destroy(self) -> None:
215223
pass
216224

217-
def ui_handle_repair(self, dc, x0, y0, x1, y1):
225+
def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None:
218226
pass
219227

220-
def ui_handle_resize(self, width, height):
228+
def ui_handle_resize(self, width, height) -> None:
221229
pass
222230

223231
def mainloop(self) -> None:
@@ -227,12 +235,12 @@ def mainloop(self) -> None:
227235
class ImageWindow(Window):
228236
"""Create an image window which displays the given image."""
229237

230-
def __init__(self, image, title="PIL"):
238+
def __init__(self, image, title: str = "PIL") -> None:
231239
if not isinstance(image, Dib):
232240
image = Dib(image)
233241
self.image = image
234242
width, height = image.size
235243
super().__init__(title, width=width, height=height)
236244

237-
def ui_handle_repair(self, dc, x0, y0, x1, y1):
245+
def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None:
238246
self.image.draw(dc, (x0, y0, x1, y1))

0 commit comments

Comments
 (0)