Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions Tests/test_file_spider.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def test_load_image_series() -> None:
img_list = SpiderImagePlugin.loadImageSeries(file_list)

# Assert
assert img_list is not None
assert len(img_list) == 1
assert isinstance(img_list[0], Image.Image)
assert img_list[0].size == (128, 128)
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_imagefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ def __init__(self, mode: str, *args: Any) -> None:

super().__init__(mode, *args)

def decode(self, buffer):
def decode(self, buffer: bytes) -> tuple[int, int]:
# eof
return -1, 0

Expand All @@ -222,7 +222,7 @@ def __init__(self, mode: str, *args: Any) -> None:

super().__init__(mode, *args)

def encode(self, buffer):
def encode(self, bufsize: int) -> tuple[int, int, bytes]:
return 1, 1, b""

def cleanup(self) -> None:
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_imageops_usm.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def test_blur_accuracy(test_images: dict[str, ImageFile.ImageFile]) -> None:
assert i.im.getpixel((x, y))[c] >= 250
# Fuzzy match.

def gp(x, y):
def gp(x: int, y: int) -> tuple[int, ...]:
return i.im.getpixel((x, y))

assert 236 <= gp(7, 4)[0] <= 239
Expand Down
8 changes: 4 additions & 4 deletions Tests/test_imagepalette.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@ def test_file(tmp_path: Path) -> None:

palette.save(f)

p = ImagePalette.load(f)
lut = ImagePalette.load(f)

# load returns raw palette information
assert len(p[0]) == 768
assert p[1] == "RGB"
assert len(lut[0]) == 768
assert lut[1] == "RGB"

p = ImagePalette.raw(p[1], p[0])
p = ImagePalette.raw(lut[1], lut[0])
assert isinstance(p, ImagePalette.ImagePalette)
assert p.palette == palette.tobytes()

Expand Down
2 changes: 2 additions & 0 deletions src/PIL/BlpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ class BLPEncoder(ImageFile.PyEncoder):

def _write_palette(self) -> bytes:
data = b""
assert self.im is not None
palette = self.im.getpalette("RGBA", "RGBA")
for i in range(len(palette) // 4):
r, g, b, a = palette[i * 4 : (i + 1) * 4]
Expand All @@ -444,6 +445,7 @@ def encode(self, bufsize: int) -> tuple[int, int, bytes]:
offset = 20 + 16 * 4 * 2 + len(palette_data)
data = struct.pack("<16I", offset, *((0,) * 15))

assert self.im is not None
w, h = self.im.size
data += struct.pack("<16I", w * h, *((0,) * 15))

Expand Down
7 changes: 6 additions & 1 deletion src/PIL/ImageDraw2.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
"""
from __future__ import annotations

from typing import BinaryIO

from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
from ._typing import StrOrBytesPath


class Pen:
Expand All @@ -45,7 +48,9 @@ def __init__(self, color: str, opacity: int = 255) -> None:
class Font:
"""Stores a TrueType font and color"""

def __init__(self, color, file, size=12):
def __init__(
self, color: str, file: StrOrBytesPath | BinaryIO, size: float = 12
) -> None:
# FIXME: add support for bitmap fonts
self.color = ImageColor.getrgb(color)
self.font = ImageFont.truetype(file, size)
Expand Down
25 changes: 14 additions & 11 deletions src/PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
# Helpers


def _get_oserror(error, *, encoder):
def _get_oserror(error: int, *, encoder: bool) -> OSError:
try:
msg = Image.core.getcodecstatus(error)
except AttributeError:
Expand All @@ -76,7 +76,7 @@ def _get_oserror(error, *, encoder):
return OSError(msg)


def raise_oserror(error):
def raise_oserror(error: int) -> OSError:
deprecate(
"raise_oserror",
12,
Expand Down Expand Up @@ -154,11 +154,12 @@ def __init__(self, fp=None, filename=None):
self.fp.close()
raise

def get_format_mimetype(self):
def get_format_mimetype(self) -> str | None:
if self.custom_mimetype:
return self.custom_mimetype
if self.format is not None:
return Image.MIME.get(self.format.upper())
return None

def __setstate__(self, state):
self.tile = []
Expand Down Expand Up @@ -365,7 +366,7 @@ class StubImageFile(ImageFile):
certain format, but relies on external code to load the file.
"""

def _open(self):
def _open(self) -> None:
msg = "StubImageFile subclass must implement _open"
raise NotImplementedError(msg)

Expand All @@ -381,7 +382,7 @@ def load(self):
self.__dict__ = image.__dict__
return image.load()

def _load(self):
def _load(self) -> StubHandler | None:
"""(Hook) Find actual image loader."""
msg = "StubImageFile subclass must implement _load"
raise NotImplementedError(msg)
Expand Down Expand Up @@ -621,7 +622,7 @@ def __init__(self) -> None:
self.xoff = 0
self.yoff = 0

def extents(self):
def extents(self) -> tuple[int, int, int, int]:
return self.xoff, self.yoff, self.xoff + self.xsize, self.yoff + self.ysize


Expand Down Expand Up @@ -661,7 +662,7 @@ def setfd(self, fd):
"""
self.fd = fd

def setimage(self, im, extents=None):
def setimage(self, im, extents: tuple[int, int, int, int] | None = None) -> None:
"""
Called from ImageFile to set the core output image for the codec

Expand Down Expand Up @@ -710,10 +711,10 @@ class PyDecoder(PyCodec):
_pulls_fd = False

@property
def pulls_fd(self):
def pulls_fd(self) -> bool:
return self._pulls_fd

def decode(self, buffer):
def decode(self, buffer: bytes) -> tuple[int, int]:
"""
Override to perform the decoding process.

Expand All @@ -738,6 +739,7 @@ def set_as_raw(self, data: bytes, rawmode=None) -> None:
if not rawmode:
rawmode = self.mode
d = Image._getdecoder(self.mode, "raw", rawmode)
assert self.im is not None
d.setimage(self.im, self.state.extents())
s = d.decode(data)

Expand All @@ -760,7 +762,7 @@ class PyEncoder(PyCodec):
_pushes_fd = False

@property
def pushes_fd(self):
def pushes_fd(self) -> bool:
return self._pushes_fd

def encode(self, bufsize: int) -> tuple[int, int, bytes]:
Expand All @@ -775,7 +777,7 @@ def encode(self, bufsize: int) -> tuple[int, int, bytes]:
msg = "unavailable in base encoder"
raise NotImplementedError(msg)

def encode_to_pyfd(self):
def encode_to_pyfd(self) -> tuple[int, int]:
"""
If ``pushes_fd`` is ``True``, then this method will be used,
and ``encode()`` will only be called once.
Expand All @@ -787,6 +789,7 @@ def encode_to_pyfd(self):
return 0, -8 # bad configuration
bytes_consumed, errcode, data = self.encode(0)
if data:
assert self.fd is not None
self.fd.write(data)
return bytes_consumed, errcode

Expand Down
48 changes: 29 additions & 19 deletions src/PIL/ImagePalette.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,27 @@ class ImagePalette:
Defaults to an empty palette.
"""

def __init__(self, mode: str = "RGB", palette: Sequence[int] | None = None) -> None:
def __init__(
self,
mode: str = "RGB",
palette: Sequence[int] | bytes | bytearray | None = None,
) -> None:
self.mode = mode
self.rawmode = None # if set, palette contains raw data
self.rawmode: str | None = None # if set, palette contains raw data
self.palette = palette or bytearray()
self.dirty: int | None = None

@property
def palette(self):
def palette(self) -> Sequence[int] | bytes | bytearray:
return self._palette

@palette.setter
def palette(self, palette):
self._colors = None
def palette(self, palette: Sequence[int] | bytes | bytearray) -> None:
self._colors: dict[tuple[int, ...], int] | None = None
self._palette = palette

@property
def colors(self) -> dict[tuple[int, int, int] | tuple[int, int, int, int], int]:
def colors(self) -> dict[tuple[int, ...], int]:
if self._colors is None:
mode_len = len(self.mode)
self._colors = {}
Expand All @@ -66,9 +70,7 @@ def colors(self) -> dict[tuple[int, int, int] | tuple[int, int, int, int], int]:
return self._colors

@colors.setter
def colors(
self, colors: dict[tuple[int, int, int] | tuple[int, int, int, int], int]
) -> None:
def colors(self, colors: dict[tuple[int, ...], int]) -> None:
self._colors = colors

def copy(self) -> ImagePalette:
Expand All @@ -82,7 +84,7 @@ def copy(self) -> ImagePalette:

return new

def getdata(self) -> tuple[str, bytes]:
def getdata(self) -> tuple[str, Sequence[int] | bytes | bytearray]:
"""
Get palette contents in format suitable for the low-level
``im.putpalette`` primitive.
Expand Down Expand Up @@ -137,7 +139,7 @@ def _new_color_index(

def getcolor(
self,
color: tuple[int, int, int] | tuple[int, int, int, int],
color: tuple[int, ...],
image: Image.Image | None = None,
) -> int:
"""Given an rgb tuple, allocate palette entry.
Expand All @@ -162,12 +164,13 @@ def getcolor(
except KeyError as e:
# allocate new color slot
index = self._new_color_index(image, e)
assert isinstance(self._palette, bytearray)
self.colors[color] = index
if index * 3 < len(self.palette):
self._palette = (
self.palette[: index * 3]
self._palette[: index * 3]
+ bytes(color)
+ self.palette[index * 3 + 3 :]
+ self._palette[index * 3 + 3 :]
)
else:
self._palette += bytes(color)
Expand Down Expand Up @@ -204,7 +207,7 @@ def save(self, fp: str | IO[str]) -> None:
# Internal


def raw(rawmode, data) -> ImagePalette:
def raw(rawmode, data: Sequence[int] | bytes | bytearray) -> ImagePalette:
palette = ImagePalette()
palette.rawmode = rawmode
palette.palette = data
Expand All @@ -216,9 +219,9 @@ def raw(rawmode, data) -> ImagePalette:
# Factories


def make_linear_lut(black, white):
def make_linear_lut(black: int, white: float) -> list[int]:
if black == 0:
return [white * i // 255 for i in range(256)]
return [int(white * i // 255) for i in range(256)]

msg = "unavailable when black is non-zero"
raise NotImplementedError(msg) # FIXME
Expand Down Expand Up @@ -251,15 +254,22 @@ def wedge(mode: str = "RGB") -> ImagePalette:
return ImagePalette(mode, [i // len(mode) for i in palette])


def load(filename):
def load(filename: str) -> tuple[bytes, str]:
# FIXME: supports GIMP gradients only

with open(filename, "rb") as fp:
for paletteHandler in [
paletteHandlers: list[
type[
GimpPaletteFile.GimpPaletteFile
| GimpGradientFile.GimpGradientFile
| PaletteFile.PaletteFile
]
] = [
GimpPaletteFile.GimpPaletteFile,
GimpGradientFile.GimpGradientFile,
PaletteFile.PaletteFile,
]:
]
for paletteHandler in paletteHandlers:
try:
fp.seek(0)
lut = paletteHandler(fp).getpalette()
Expand Down
13 changes: 8 additions & 5 deletions src/PIL/ImageWin.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,22 @@ class Dib:
defines the size of the image.
"""

def __init__(self, image, size=None):
if hasattr(image, "mode") and hasattr(image, "size"):
def __init__(
self, image: Image.Image | str, size: tuple[int, int] | list[int] | None = None
) -> None:
if isinstance(image, str):
mode = image
image = ""
else:
mode = image.mode
size = image.size
else:
mode = image
image = None
if mode not in ["1", "L", "P", "RGB"]:
mode = Image.getmodebase(mode)
self.image = Image.core.display(mode, size)
self.mode = mode
self.size = size
if image:
assert not isinstance(image, str)
self.paste(image)

def expose(self, handle):
Expand Down
Loading