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 to ImageFile.__init__() #8336

Merged
merged 2 commits into from
Sep 4, 2024
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
24 changes: 17 additions & 7 deletions Tests/test_imagefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,9 @@ def _open(self) -> None:
self.rawmode = "RGBA"
self._mode = "RGBA"
self._size = (200, 200)
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]
self.tile = [
ImageFile._Tile("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)
]


class CodecsTest:
Expand Down Expand Up @@ -268,7 +270,7 @@ def test_extents_none(self) -> None:
buf = BytesIO(b"\x00" * 255)

im = MockImageFile(buf)
im.tile = [("MOCK", None, 32, None)]
im.tile = [ImageFile._Tile("MOCK", None, 32, None)]

im.load()

Expand All @@ -281,25 +283,33 @@ def test_negsize(self) -> None:
buf = BytesIO(b"\x00" * 255)

im = MockImageFile(buf)
im.tile = [("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)]
im.tile = [ImageFile._Tile("MOCK", (xoff, yoff, -10, yoff + ysize), 32, None)]

with pytest.raises(ValueError):
im.load()

im.tile = [("MOCK", (xoff, yoff, xoff + xsize, -10), 32, None)]
im.tile = [ImageFile._Tile("MOCK", (xoff, yoff, xoff + xsize, -10), 32, None)]
with pytest.raises(ValueError):
im.load()

def test_oversize(self) -> None:
buf = BytesIO(b"\x00" * 255)

im = MockImageFile(buf)
im.tile = [("MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None)]
im.tile = [
ImageFile._Tile(
"MOCK", (xoff, yoff, xoff + xsize + 100, yoff + ysize), 32, None
)
]

with pytest.raises(ValueError):
im.load()

im.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 32, None)]
im.tile = [
ImageFile._Tile(
"MOCK", (xoff, yoff, xoff + xsize, yoff + ysize + 100), 32, None
)
]
with pytest.raises(ValueError):
im.load()

Expand Down Expand Up @@ -336,7 +346,7 @@ def test_extents_none(self) -> None:
buf = BytesIO(b"\x00" * 255)

im = MockImageFile(buf)
im.tile = [("MOCK", None, 32, None)]
im.tile = [ImageFile._Tile("MOCK", None, 32, None)]

fp = BytesIO()
ImageFile._save(im, fp, [ImageFile._Tile("MOCK", None, 0, "RGB")])
Expand Down
4 changes: 3 additions & 1 deletion docs/example/DdsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,9 @@ def _open(self) -> None:
msg = f"Unimplemented pixel format {repr(fourcc)}"
raise NotImplementedError(msg)

self.tile = [(self.decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
self.tile = [
ImageFile._Tile(self.decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))
]

def load_seek(self, pos: int) -> None:
pass
Expand Down
7 changes: 5 additions & 2 deletions src/PIL/BlpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def _open(self) -> None:
raise BLPFormatError(msg)

self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]


class _BLPBaseDecoder(ImageFile.PyDecoder):
Expand Down Expand Up @@ -372,7 +372,10 @@ def _decode_jpeg_stream(self) -> None:
Image._decompression_bomb_check(image.size)
if image.mode == "CMYK":
decoder_name, extents, offset, args = image.tile[0]
image.tile = [(decoder_name, extents, offset, (args[0], "CMYK"))]
assert isinstance(args, tuple)
image.tile = [
ImageFile._Tile(decoder_name, extents, offset, (args[0], "CMYK"))
]
r, g, b = image.convert("RGB").split()
reversed_image = Image.merge("RGB", (b, g, r))
self.set_as_raw(reversed_image.tobytes())
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/BmpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def _bitmap(self, header: int = 0, offset: int = 0) -> None:
args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
args.append(file_info["direction"])
self.tile = [
(
ImageFile._Tile(
decoder_name,
(0, 0, file_info["width"], file_info["height"]),
offset or self.fp.tell(),
Expand Down
4 changes: 2 additions & 2 deletions src/PIL/CurImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#
from __future__ import annotations

from . import BmpImagePlugin, Image
from . import BmpImagePlugin, Image, ImageFile
from ._binary import i16le as i16
from ._binary import i32le as i32

Expand Down Expand Up @@ -64,7 +64,7 @@ def _open(self) -> None:
# patch up the bitmap height
self._size = self.size[0], self.size[1] // 2
d, e, o, a = self.tile[0]
self.tile[0] = d, (0, 0) + self.size, o, a
self.tile[0] = ImageFile._Tile(d, (0, 0) + self.size, o, a)


#
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/DdsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ def _open(self) -> None:
mask_count = 3

masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
self.tile = [("dds_rgb", extents, 0, (bitcount, masks))]
self.tile = [ImageFile._Tile("dds_rgb", extents, 0, (bitcount, masks))]
return
elif pfflags & DDPF.LUMINANCE:
if bitcount == 8:
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/FitsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _open(self) -> None:
raise ValueError(msg)

offset += self.fp.tell() - 80
self.tile = [(decoder_name, (0, 0) + self.size, offset, args)]
self.tile = [ImageFile._Tile(decoder_name, (0, 0) + self.size, offset, args)]

def _get_size(
self, headers: dict[bytes, bytes], prefix: bytes
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/FliImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def _seek(self, frame: int) -> None:
framesize = i32(s)

self.decodermaxblock = framesize
self.tile = [("fli", (0, 0) + self.size, self.__offset, None)]
self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset, None)]

self.__offset += framesize

Expand Down
6 changes: 3 additions & 3 deletions src/PIL/FpxImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def _open_subimage(self, index: int = 1, subimage: int = 0) -> None:

if compression == 0:
self.tile.append(
(
ImageFile._Tile(
"raw",
(x, y, x1, y1),
i32(s, i) + 28,
Expand All @@ -177,7 +177,7 @@ def _open_subimage(self, index: int = 1, subimage: int = 0) -> None:
elif compression == 1:
# FIXME: the fill decoder is not implemented
self.tile.append(
(
ImageFile._Tile(
"fill",
(x, y, x1, y1),
i32(s, i) + 28,
Expand Down Expand Up @@ -205,7 +205,7 @@ def _open_subimage(self, index: int = 1, subimage: int = 0) -> None:
jpegmode = rawmode

self.tile.append(
(
ImageFile._Tile(
"jpeg",
(x, y, x1, y1),
i32(s, i) + 28,
Expand Down
4 changes: 2 additions & 2 deletions src/PIL/FtexImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ def _open(self) -> None:

if format == Format.DXT1:
self._mode = "RGBA"
self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))]
elif format == Format.UNCOMPRESSED:
self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
else:
msg = f"Invalid texture compression format: {repr(format)}"
raise ValueError(msg)
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/GdImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def _open(self) -> None:
)

self.tile = [
(
ImageFile._Tile(
"raw",
(0, 0) + self.size,
7 + true_color_offset + 4 + 256 * 4,
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ def _rgb(color: int) -> tuple[int, int, int]:
elif self.mode not in ("RGB", "RGBA"):
transparency = frame_transparency
self.tile = [
(
ImageFile._Tile(
"gif",
(x0, y0, x1, y1),
self.__offset,
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/IcoImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def frame(self, idx: int) -> Image.Image:
# change tile dimension to only encompass XOR image
im._size = (im.size[0], int(im.size[1] / 2))
d, e, o, a = im.tile[0]
im.tile[0] = d, (0, 0) + im.size, o, a
im.tile[0] = ImageFile._Tile(d, (0, 0) + im.size, o, a)

# figure out where AND mask image starts
if header.bpp == 32:
Expand Down
22 changes: 16 additions & 6 deletions src/PIL/ImImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,11 @@
# use bit decoder (if necessary)
bits = int(self.rawmode[2:])
if bits not in [8, 16, 32]:
self.tile = [("bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1))]
self.tile = [

Check warning on line 256 in src/PIL/ImImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImImagePlugin.py#L256

Added line #L256 was not covered by tests
ImageFile._Tile(
"bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1)
)
]
return
except ValueError:
pass
Expand All @@ -263,13 +267,17 @@
# ever stumbled upon such a file ;-)
size = self.size[0] * self.size[1]
self.tile = [
("raw", (0, 0) + self.size, offs, ("G", 0, -1)),
("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)),
("raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)),
ImageFile._Tile("raw", (0, 0) + self.size, offs, ("G", 0, -1)),
ImageFile._Tile("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)),
ImageFile._Tile(
"raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)
),
]
else:
# LabEye/IFUNC files
self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
self.tile = [
ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))
]

@property
def n_frames(self) -> int:
Expand All @@ -295,7 +303,9 @@

self.fp = self._fp

self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))]
self.tile = [

Check warning on line 306 in src/PIL/ImImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImImagePlugin.py#L306

Added line #L306 was not covered by tests
ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))
]

def tell(self) -> int:
return self.frame
Expand Down
5 changes: 4 additions & 1 deletion src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -3641,7 +3641,10 @@ def merge(mode: str, bands: Sequence[Image]) -> Image:

def register_open(
id: str,
factory: Callable[[IO[bytes], str | bytes], ImageFile.ImageFile],
factory: (
Callable[[IO[bytes], str | bytes], ImageFile.ImageFile]
| type[ImageFile.ImageFile]
),
accept: Callable[[bytes], bool | str] | None = None,
) -> None:
"""
Expand Down
29 changes: 19 additions & 10 deletions src/PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@
import os
import struct
import sys
from typing import IO, Any, NamedTuple
from typing import IO, TYPE_CHECKING, Any, NamedTuple, cast

from . import Image
from ._deprecate import deprecate
from ._util import is_path

if TYPE_CHECKING:
from ._typing import StrOrBytesPath

MAXBLOCK = 65536

SAFEBLOCK = 1024 * 1024
Expand Down Expand Up @@ -107,32 +110,34 @@
class ImageFile(Image.Image):
"""Base class for image file format handlers."""

def __init__(self, fp=None, filename=None):
def __init__(
self, fp: StrOrBytesPath | IO[bytes], filename: str | bytes | None = None
) -> None:
super().__init__()

self._min_frame = 0

self.custom_mimetype = None
self.custom_mimetype: str | None = None

self.tile = None
self.tile: list[_Tile] = []
""" A list of tile descriptors, or ``None`` """

self.readonly = 1 # until we know better

self.decoderconfig = ()
self.decoderconfig: tuple[Any, ...] = ()
self.decodermaxblock = MAXBLOCK

if is_path(fp):
# filename
self.fp = open(fp, "rb")
self.filename = fp
self.filename = os.path.realpath(os.fspath(fp))
self._exclusive_fp = True
else:
# stream
self.fp = fp
self.filename = filename
self.fp = cast(IO[bytes], fp)
self.filename = filename if filename is not None else ""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be self.filename = filename or ""

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not exactly the same. This suggestion would mean that a filename of b"" would be unexpectedly changed to ""

>>> filename = b""
>>> filename if filename is not None else ""
b''
>>> filename = b""
>>> filename or ""
''

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I hadn't thought of that situation.

# can be overridden
self._exclusive_fp = None
self._exclusive_fp = False

try:
try:
Expand All @@ -155,6 +160,9 @@
self.fp.close()
raise

def _open(self) -> None:
pass

Check warning on line 164 in src/PIL/ImageFile.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImageFile.py#L164

Added line #L164 was not covered by tests

def get_format_mimetype(self) -> str | None:
if self.custom_mimetype:
return self.custom_mimetype
Expand All @@ -178,7 +186,7 @@
def load(self) -> Image.core.PixelAccess | None:
"""Load image data based on tile list"""

if self.tile is None:
if not self.tile and self._im is None:
msg = "cannot load this image"
raise OSError(msg)

Expand Down Expand Up @@ -214,6 +222,7 @@
args = (args, 0, 1)
if (
decoder_name == "raw"
and isinstance(args, tuple)
and len(args) >= 3
and args[0] == self.mode
and args[0] in Image._MAPMODES
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/ImtImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def _open(self) -> None:
if s == b"\x0C":
# image data begins
self.tile = [
(
ImageFile._Tile(
"raw",
(0, 0) + self.size,
self.fp.tell() - len(buffer),
Expand Down
4 changes: 3 additions & 1 deletion src/PIL/IptcImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ def _open(self) -> None:

# tile
if tag == (8, 10):
self.tile = [("iptc", (0, 0) + self.size, offset, compression)]
self.tile = [
ImageFile._Tile("iptc", (0, 0) + self.size, offset, compression)
]

def load(self) -> Image.core.PixelAccess | None:
if len(self.tile) != 1 or self.tile[0][0] != "iptc":
Expand Down
Loading
Loading