Skip to content
Merged
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
126 changes: 115 additions & 11 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,99 @@ def getmodebands(mode: str) -> int:

_initialized = 0

# Mapping from file extension to plugin module name for lazy loading
_EXTENSION_PLUGIN: dict[str, str] = {
# Common formats (preinit)
".bmp": "BmpImagePlugin",
".dib": "BmpImagePlugin",
".gif": "GifImagePlugin",
".jfif": "JpegImagePlugin",
".jpe": "JpegImagePlugin",
".jpg": "JpegImagePlugin",
".jpeg": "JpegImagePlugin",
".pbm": "PpmImagePlugin",
".pgm": "PpmImagePlugin",
".pnm": "PpmImagePlugin",
".ppm": "PpmImagePlugin",
".pfm": "PpmImagePlugin",
".png": "PngImagePlugin",
".apng": "PngImagePlugin",
# Less common formats (init)
".avif": "AvifImagePlugin",
".avifs": "AvifImagePlugin",
".blp": "BlpImagePlugin",
".bufr": "BufrStubImagePlugin",
".cur": "CurImagePlugin",
".dcx": "DcxImagePlugin",
".dds": "DdsImagePlugin",
".ps": "EpsImagePlugin",
".eps": "EpsImagePlugin",
".fit": "FitsImagePlugin",
".fits": "FitsImagePlugin",
".fli": "FliImagePlugin",
".flc": "FliImagePlugin",
".fpx": "FpxImagePlugin",
".ftc": "FtexImagePlugin",
".ftu": "FtexImagePlugin",
".gbr": "GbrImagePlugin",
".grib": "GribStubImagePlugin",
".h5": "Hdf5StubImagePlugin",
".hdf": "Hdf5StubImagePlugin",
".icns": "IcnsImagePlugin",
".ico": "IcoImagePlugin",
".im": "ImImagePlugin",
".iim": "IptcImagePlugin",
".jp2": "Jpeg2KImagePlugin",
".j2k": "Jpeg2KImagePlugin",
".jpc": "Jpeg2KImagePlugin",
".jpf": "Jpeg2KImagePlugin",
".jpx": "Jpeg2KImagePlugin",
".j2c": "Jpeg2KImagePlugin",
".mic": "MicImagePlugin",
".mpg": "MpegImagePlugin",
".mpeg": "MpegImagePlugin",
".mpo": "MpoImagePlugin",
".msp": "MspImagePlugin",
".palm": "PalmImagePlugin",
".pcd": "PcdImagePlugin",
".pcx": "PcxImagePlugin",
".pdf": "PdfImagePlugin",
".pxr": "PixarImagePlugin",
".psd": "PsdImagePlugin",
".qoi": "QoiImagePlugin",
".bw": "SgiImagePlugin",
".rgb": "SgiImagePlugin",
".rgba": "SgiImagePlugin",
".sgi": "SgiImagePlugin",
".ras": "SunImagePlugin",
".tga": "TgaImagePlugin",
".icb": "TgaImagePlugin",
".vda": "TgaImagePlugin",
".vst": "TgaImagePlugin",
".tif": "TiffImagePlugin",
".tiff": "TiffImagePlugin",
".webp": "WebPImagePlugin",
".wmf": "WmfImagePlugin",
".emf": "WmfImagePlugin",
".xbm": "XbmImagePlugin",
".xpm": "XpmImagePlugin",
}


def _load_plugin_for_extension(ext: str | bytes) -> bool:
Comment thread
hugovk marked this conversation as resolved.
Outdated
"""Load only the plugin needed for a specific file extension."""
if isinstance(ext, bytes):
ext = ext.decode()
plugin = _EXTENSION_PLUGIN.get(ext.lower())
Comment thread
hugovk marked this conversation as resolved.
Outdated
if plugin is None:
return False

try:
__import__(f"PIL.{plugin}", globals(), locals(), [])
Comment thread
hugovk marked this conversation as resolved.
Outdated
return True
except ImportError:
return False


def preinit() -> None:
"""
Expand Down Expand Up @@ -2535,11 +2628,13 @@ def save(
# only set the name for metadata purposes
filename = os.fspath(fp.name)

preinit()

filename_ext = os.path.splitext(filename)[1].lower()
ext = filename_ext.decode() if isinstance(filename_ext, bytes) else filename_ext

# Try loading only the plugin for this extension first
if not _load_plugin_for_extension(ext):
Comment thread
hugovk marked this conversation as resolved.
Outdated
preinit()

if not format:
if ext not in EXTENSION:
init()
Expand Down Expand Up @@ -3524,7 +3619,11 @@ def open(

prefix = fp.read(16)

preinit()
# Try to load just the plugin needed for this file extension
# before falling back to preinit() which loads common plugins
Comment thread
hugovk marked this conversation as resolved.
Outdated
ext = os.path.splitext(filename)[1] if filename else ""
if not (ext and _load_plugin_for_extension(ext)):
Comment thread
hugovk marked this conversation as resolved.
Outdated
preinit()

warning_messages: list[str] = []

Expand Down Expand Up @@ -3560,14 +3659,19 @@ def _open_core(
im = _open_core(fp, filename, prefix, formats)

if im is None and formats is ID:
checked_formats = ID.copy()
if init():
im = _open_core(
fp,
filename,
prefix,
tuple(format for format in formats if format not in checked_formats),
)
# 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:
break

if im:
im._exclusive_fp = exclusive_fp
Expand Down