Skip to content
Open
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
9 changes: 9 additions & 0 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@ def test_open_formats(self) -> None:
assert im.mode == "RGB"
assert im.size == (128, 128)

@pytest.mark.parametrize("formats", (("!PNG",), ("PNG", "!PNG"), ("JPEG", "!PNG")))
def test_open_formats_exclude(self, formats: tuple[str]) -> None:
with Image.open("Tests/images/hopper.jpg", formats=formats):
pass

with pytest.raises(UnidentifiedImageError):
with Image.open("Tests/images/hopper.png", formats=formats):
pass

def test_open_verbose_failure(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(Image, "WARN_POSSIBLE_FORMATS", True)

Expand Down
69 changes: 42 additions & 27 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -3598,11 +3598,17 @@ def open(
and be opened in binary mode. The file object will also seek to zero
before reading.
:param mode: The mode. If given, this argument must be "r".
:param formats: A list or tuple of formats to attempt to load the file in.
This can be used to restrict the set of formats checked.
Pass ``None`` to try all supported formats. You can print the set of
available formats by running ``python3 -m PIL`` or using
the :py:func:`PIL.features.pilinfo` function.
:param formats: A list or tuple of formats to attempt to load the file in, for
example, ``("JPEG", "GIF")``. This can be used to restrict the set of formats
checked.

To exclude a format, start the format with "!", for example,
``("!EPS", "!PSD")``.

Pass ``None`` to try all supported formats.

You can print the set of available formats by running ``python3 -m PIL`` or
using the :py:func:`PIL.features.pilinfo` function.
:returns: An :py:class:`~PIL.Image.Image` object.
:exception FileNotFoundError: If the file cannot be found.
:exception PIL.UnidentifiedImageError: If the image cannot be opened and
Expand All @@ -3622,11 +3628,20 @@ def open(
)
raise ValueError(msg)

if formats is None:
formats = ID
elif not isinstance(formats, (list, tuple)):
msg = "formats must be a list or tuple" # type: ignore[unreachable]
raise TypeError(msg)
if formats is not None:
if not isinstance(formats, (list, tuple)):
msg = "formats must be a list or tuple" # type: ignore[unreachable]
raise TypeError(msg)

allowed = set()
excluded = set()
for f in formats:
f = f.upper()
if f.startswith("!"):
excluded.add(f[1:])
else:
allowed.add(f)
allowed -= excluded

exclusive_fp = False
filename: str | bytes = ""
Expand Down Expand Up @@ -3657,12 +3672,15 @@ def _open_core(
fp: IO[bytes],
filename: str | bytes,
prefix: bytes,
formats: list[str] | tuple[str, ...],
check_formats: list[str],
) -> ImageFile.ImageFile | None:
for i in formats:
i = i.upper()
if i not in OPEN:
init()
if formats is not None:
if allowed:
check_formats = [f for f in check_formats if f in allowed]
else:
check_formats = [f for f in check_formats if f not in excluded]

for i in check_formats:
try:
factory, accept = OPEN[i]
result = not accept or accept(prefix)
Expand All @@ -3673,7 +3691,12 @@ def _open_core(
im = factory(fp, filename)
_decompression_bomb_check(im.size)
return im
except (SyntaxError, IndexError, TypeError, struct.error) as e:
except ( # noqa: PERF203
SyntaxError,
IndexError,
TypeError,
struct.error,
) as e:
if WARN_POSSIBLE_FORMATS:
warning_messages.append(i + " opening failed. " + str(e))
except BaseException:
Expand All @@ -3682,21 +3705,13 @@ def _open_core(
raise
return None

im = _open_core(fp, filename, prefix, formats)

if im is None and formats is ID:
if not (im := _open_core(fp, filename, prefix, ID)):
# Try preinit (few common plugins) then init (all plugins)
for loader in (preinit, init):
checked_formats = ID.copy()
loader()
if formats != checked_formats:
im = _open_core(
fp,
filename,
prefix,
tuple(f for f in formats if f not in checked_formats),
)
if im is not None:
if check_formats := [f for f in ID if f not in checked_formats]:
if im := _open_core(fp, filename, prefix, check_formats):
break

if im:
Expand Down