Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added type hints #8234

Merged
merged 2 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Tests/test_imagewin.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ def test_dib_mode_string(self) -> None:
# Assert
assert dib.size == (128, 128)

with pytest.raises(ValueError):
ImageWin.Dib(mode)

def test_dib_paste(self) -> None:
# Arrange
im = hopper()
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/EpsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def has_ghostscript() -> bool:
return gs_binary is not False


def Ghostscript(tile, size, fp, scale=1, transparency=False):
def Ghostscript(tile, size, fp, scale=1, transparency: bool = False) -> Image.Image:
"""Render an image using Ghostscript"""
global gs_binary
if not has_ghostscript():
Expand Down
86 changes: 46 additions & 40 deletions src/PIL/IcoImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import warnings
from io import BytesIO
from math import ceil, log
from typing import IO
from typing import IO, NamedTuple

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


class IconHeader(NamedTuple):
width: int
height: int
nb_color: int
reserved: int
planes: int
bpp: int
size: int
offset: int
dim: tuple[int, int]
square: int
color_depth: int


class IcoFile:
def __init__(self, buf) -> None:
def __init__(self, buf: IO[bytes]) -> None:
"""
Parse image from file-like object containing ico file data
"""
Expand All @@ -141,51 +155,44 @@ def __init__(self, buf) -> None:
for i in range(self.nb_items):
s = buf.read(16)

icon_header = {
"width": s[0],
"height": s[1],
"nb_color": s[2], # No. of colors in image (0 if >=8bpp)
"reserved": s[3],
"planes": i16(s, 4),
"bpp": i16(s, 6),
"size": i32(s, 8),
"offset": i32(s, 12),
}

# See Wikipedia
for j in ("width", "height"):
if not icon_header[j]:
icon_header[j] = 256

# See Wikipedia notes about color depth.
# We need this just to differ images with equal sizes
icon_header["color_depth"] = (
icon_header["bpp"]
or (
icon_header["nb_color"] != 0
and ceil(log(icon_header["nb_color"], 2))
)
or 256
width = s[0] or 256
height = s[1] or 256

# No. of colors in image (0 if >=8bpp)
nb_color = s[2]
bpp = i16(s, 6)
icon_header = IconHeader(
width=width,
height=height,
nb_color=nb_color,
reserved=s[3],
planes=i16(s, 4),
bpp=i16(s, 6),
size=i32(s, 8),
offset=i32(s, 12),
dim=(width, height),
square=width * height,
# See Wikipedia notes about color depth.
# We need this just to differ images with equal sizes
color_depth=bpp or (nb_color != 0 and ceil(log(nb_color, 2))) or 256,
)

icon_header["dim"] = (icon_header["width"], icon_header["height"])
icon_header["square"] = icon_header["width"] * icon_header["height"]

self.entry.append(icon_header)

self.entry = sorted(self.entry, key=lambda x: x["color_depth"])
self.entry = sorted(self.entry, key=lambda x: x.color_depth)
# ICO images are usually squares
self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True)
self.entry = sorted(self.entry, key=lambda x: x.square, reverse=True)

def sizes(self) -> set[tuple[int, int]]:
"""
Get a list of all available icon sizes and color depths.
Get a set of all available icon sizes and color depths.
"""
return {(h["width"], h["height"]) for h in self.entry}
return {(h.width, h.height) for h in self.entry}

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

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

header = self.entry[idx]

self.buf.seek(header["offset"])
self.buf.seek(header.offset)
data = self.buf.read(8)
self.buf.seek(header["offset"])
self.buf.seek(header.offset)

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

# figure out where AND mask image starts
bpp = header["bpp"]
if 32 == bpp:
if 32 == header.bpp:
radarhere marked this conversation as resolved.
Show resolved Hide resolved
# 32-bit color depth icon image allows semitransparent areas
# PIL's DIB format ignores transparency bits, recover them.
# The DIB is packed in BGRX byte order where X is the alpha
Expand Down Expand Up @@ -253,7 +259,7 @@ def frame(self, idx: int) -> Image.Image:
# padded row size * height / bits per char

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

self.buf.seek(and_mask_offset)
mask_data = self.buf.read(total_bytes)
Expand Down Expand Up @@ -307,7 +313,7 @@ class IcoImageFile(ImageFile.ImageFile):
def _open(self) -> None:
self.ico = IcoFile(self.fp)
self.info["sizes"] = self.ico.sizes()
self.size = self.ico.entry[0]["dim"]
self.size = self.ico.entry[0].dim
self.load()

@property
Expand Down
14 changes: 7 additions & 7 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -3286,7 +3286,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)


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

Expand All @@ -3296,7 +3296,7 @@ def fromqimage(im):
return ImageQt.fromqimage(im)


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

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

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

def _get_head(self):
def _get_head(self) -> bytes:
version = b"\x2B" if self.bigtiff else b"\x2A"
if self.endian == "<":
head = b"II" + version + b"\x00" + o32le(8)
Expand Down Expand Up @@ -4102,16 +4102,16 @@ def __len__(self) -> int:
keys.update(self._info)
return len(keys)

def __getitem__(self, tag):
def __getitem__(self, tag: int):
if self._info is not None and tag not in self._data and tag in self._info:
self._data[tag] = self._fixup(self._info[tag])
del self._info[tag]
return self._data[tag]

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

def __setitem__(self, tag, value) -> None:
def __setitem__(self, tag: int, value) -> None:
if self._info is not None and tag in self._info:
del self._info[tag]
self._data[tag] = value
Expand Down
6 changes: 4 additions & 2 deletions src/PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,9 @@ def _save(im, fp, tile, bufsize: int = 0) -> None:
fp.flush()


def _encode_tile(im, fp, tile: list[_Tile], bufsize: int, fh, exc=None) -> None:
def _encode_tile(
im, fp: IO[bytes], tile: list[_Tile], bufsize: int, fh, exc=None
) -> None:
for encoder_name, extents, offset, args in tile:
if offset > 0:
fp.seek(offset)
Expand Down Expand Up @@ -653,7 +655,7 @@ def cleanup(self) -> None:
"""
pass

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

Expand Down
11 changes: 7 additions & 4 deletions src/PIL/ImageQt.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@

import sys
from io import BytesIO
from typing import Callable
from typing import TYPE_CHECKING, Callable

from . import Image
from ._util import is_path

if TYPE_CHECKING:
from . import ImageFile

qt_version: str | None
qt_versions = [
["6", "PyQt6"],
Expand Down Expand Up @@ -90,11 +93,11 @@ def fromqimage(im):
return Image.open(b)


def fromqpixmap(im):
def fromqpixmap(im) -> ImageFile.ImageFile:
return fromqimage(im)


def align8to32(bytes, width, mode):
def align8to32(bytes: bytes, width: int, mode: str) -> bytes:
"""
converts each scanline of data from 8 bit to 32 bit aligned
"""
Expand Down Expand Up @@ -172,7 +175,7 @@ def _toqclass_helper(im):
if qt_is_installed:

class ImageQt(QImage):
def __init__(self, im):
def __init__(self, im) -> None:
"""
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
class.
Expand Down
28 changes: 18 additions & 10 deletions src/PIL/ImageWin.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,14 @@
"""

def __init__(
self, image: Image.Image | str, size: tuple[int, int] | list[int] | None = None
self, image: Image.Image | str, size: tuple[int, int] | None = None
) -> None:
if isinstance(image, str):
mode = image
image = ""
if size is None:
msg = "If first argument is mode, size is required"
raise ValueError(msg)
else:
mode = image.mode
size = image.size
Expand Down Expand Up @@ -105,7 +108,12 @@
result = self.image.expose(handle)
return result

def draw(self, handle, dst, src=None):
def draw(
self,
handle,
dst: tuple[int, int, int, int],
src: tuple[int, int, int, int] | None = None,
):
"""
Same as expose, but allows you to specify where to draw the image, and
what part of it to draw.
Expand All @@ -115,7 +123,7 @@
the destination have different sizes, the image is resized as
necessary.
"""
if not src:
if src is None:

Check warning on line 126 in src/PIL/ImageWin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImageWin.py#L126

Added line #L126 was not covered by tests
src = (0, 0) + self.size
if isinstance(handle, HWND):
dc = self.image.getdc(handle)
Expand Down Expand Up @@ -202,22 +210,22 @@
title, self.__dispatcher, width or 0, height or 0
)

def __dispatcher(self, action, *args):
def __dispatcher(self, action: str, *args):
return getattr(self, f"ui_handle_{action}")(*args)

def ui_handle_clear(self, dc, x0, y0, x1, y1):
def ui_handle_clear(self, dc, x0, y0, x1, y1) -> None:
pass

def ui_handle_damage(self, x0, y0, x1, y1):
def ui_handle_damage(self, x0, y0, x1, y1) -> None:
pass

def ui_handle_destroy(self) -> None:
pass

def ui_handle_repair(self, dc, x0, y0, x1, y1):
def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None:
pass

def ui_handle_resize(self, width, height):
def ui_handle_resize(self, width, height) -> None:
pass

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

def __init__(self, image, title="PIL"):
def __init__(self, image, 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):
def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None:
self.image.draw(dc, (x0, y0, x1, y1))
Loading
Loading