@@ -323,6 +323,99 @@ def getmodebands(mode: str) -> int:
323323
324324_initialized = 0
325325
326+ # Mapping from file extension to plugin module name for lazy loading
327+ _EXTENSION_PLUGIN : dict [str , str ] = {
328+ # Common formats (preinit)
329+ ".bmp" : "BmpImagePlugin" ,
330+ ".dib" : "BmpImagePlugin" ,
331+ ".gif" : "GifImagePlugin" ,
332+ ".jfif" : "JpegImagePlugin" ,
333+ ".jpe" : "JpegImagePlugin" ,
334+ ".jpg" : "JpegImagePlugin" ,
335+ ".jpeg" : "JpegImagePlugin" ,
336+ ".pbm" : "PpmImagePlugin" ,
337+ ".pgm" : "PpmImagePlugin" ,
338+ ".pnm" : "PpmImagePlugin" ,
339+ ".ppm" : "PpmImagePlugin" ,
340+ ".pfm" : "PpmImagePlugin" ,
341+ ".png" : "PngImagePlugin" ,
342+ ".apng" : "PngImagePlugin" ,
343+ # Less common formats (init)
344+ ".avif" : "AvifImagePlugin" ,
345+ ".avifs" : "AvifImagePlugin" ,
346+ ".blp" : "BlpImagePlugin" ,
347+ ".bufr" : "BufrStubImagePlugin" ,
348+ ".cur" : "CurImagePlugin" ,
349+ ".dcx" : "DcxImagePlugin" ,
350+ ".dds" : "DdsImagePlugin" ,
351+ ".ps" : "EpsImagePlugin" ,
352+ ".eps" : "EpsImagePlugin" ,
353+ ".fit" : "FitsImagePlugin" ,
354+ ".fits" : "FitsImagePlugin" ,
355+ ".fli" : "FliImagePlugin" ,
356+ ".flc" : "FliImagePlugin" ,
357+ ".fpx" : "FpxImagePlugin" ,
358+ ".ftc" : "FtexImagePlugin" ,
359+ ".ftu" : "FtexImagePlugin" ,
360+ ".gbr" : "GbrImagePlugin" ,
361+ ".grib" : "GribStubImagePlugin" ,
362+ ".h5" : "Hdf5StubImagePlugin" ,
363+ ".hdf" : "Hdf5StubImagePlugin" ,
364+ ".icns" : "IcnsImagePlugin" ,
365+ ".ico" : "IcoImagePlugin" ,
366+ ".im" : "ImImagePlugin" ,
367+ ".iim" : "IptcImagePlugin" ,
368+ ".jp2" : "Jpeg2KImagePlugin" ,
369+ ".j2k" : "Jpeg2KImagePlugin" ,
370+ ".jpc" : "Jpeg2KImagePlugin" ,
371+ ".jpf" : "Jpeg2KImagePlugin" ,
372+ ".jpx" : "Jpeg2KImagePlugin" ,
373+ ".j2c" : "Jpeg2KImagePlugin" ,
374+ ".mic" : "MicImagePlugin" ,
375+ ".mpg" : "MpegImagePlugin" ,
376+ ".mpeg" : "MpegImagePlugin" ,
377+ ".mpo" : "MpoImagePlugin" ,
378+ ".msp" : "MspImagePlugin" ,
379+ ".palm" : "PalmImagePlugin" ,
380+ ".pcd" : "PcdImagePlugin" ,
381+ ".pcx" : "PcxImagePlugin" ,
382+ ".pdf" : "PdfImagePlugin" ,
383+ ".pxr" : "PixarImagePlugin" ,
384+ ".psd" : "PsdImagePlugin" ,
385+ ".qoi" : "QoiImagePlugin" ,
386+ ".bw" : "SgiImagePlugin" ,
387+ ".rgb" : "SgiImagePlugin" ,
388+ ".rgba" : "SgiImagePlugin" ,
389+ ".sgi" : "SgiImagePlugin" ,
390+ ".ras" : "SunImagePlugin" ,
391+ ".tga" : "TgaImagePlugin" ,
392+ ".icb" : "TgaImagePlugin" ,
393+ ".vda" : "TgaImagePlugin" ,
394+ ".vst" : "TgaImagePlugin" ,
395+ ".tif" : "TiffImagePlugin" ,
396+ ".tiff" : "TiffImagePlugin" ,
397+ ".webp" : "WebPImagePlugin" ,
398+ ".wmf" : "WmfImagePlugin" ,
399+ ".emf" : "WmfImagePlugin" ,
400+ ".xbm" : "XbmImagePlugin" ,
401+ ".xpm" : "XpmImagePlugin" ,
402+ }
403+
404+
405+ def _load_plugin_for_extension (ext : str | bytes ) -> bool :
406+ """Load only the plugin needed for a specific file extension."""
407+ if isinstance (ext , bytes ):
408+ ext = ext .decode ()
409+ plugin = _EXTENSION_PLUGIN .get (ext .lower ())
410+ if plugin is None :
411+ return False
412+
413+ try :
414+ __import__ (f"PIL.{ plugin } " , globals (), locals (), [])
415+ return True
416+ except ImportError :
417+ return False
418+
326419
327420def preinit () -> None :
328421 """
@@ -2535,11 +2628,13 @@ def save(
25352628 # only set the name for metadata purposes
25362629 filename = os .fspath (fp .name )
25372630
2538- preinit ()
2539-
25402631 filename_ext = os .path .splitext (filename )[1 ].lower ()
25412632 ext = filename_ext .decode () if isinstance (filename_ext , bytes ) else filename_ext
25422633
2634+ # Try loading only the plugin for this extension first
2635+ if not _load_plugin_for_extension (ext ):
2636+ preinit ()
2637+
25432638 if not format :
25442639 if ext not in EXTENSION :
25452640 init ()
@@ -3524,7 +3619,11 @@ def open(
35243619
35253620 prefix = fp .read (16 )
35263621
3527- preinit ()
3622+ # Try to load just the plugin needed for this file extension
3623+ # before falling back to preinit() which loads common plugins
3624+ ext = os .path .splitext (filename )[1 ] if filename else ""
3625+ if not (ext and _load_plugin_for_extension (ext )):
3626+ preinit ()
35283627
35293628 warning_messages : list [str ] = []
35303629
@@ -3560,14 +3659,19 @@ def _open_core(
35603659 im = _open_core (fp , filename , prefix , formats )
35613660
35623661 if im is None and formats is ID :
3563- checked_formats = ID .copy ()
3564- if init ():
3565- im = _open_core (
3566- fp ,
3567- filename ,
3568- prefix ,
3569- tuple (format for format in formats if format not in checked_formats ),
3570- )
3662+ # Try preinit (few common plugins) then init (all plugins)
3663+ for loader in (preinit , init ):
3664+ checked_formats = ID .copy ()
3665+ loader ()
3666+ if formats != checked_formats :
3667+ im = _open_core (
3668+ fp ,
3669+ filename ,
3670+ prefix ,
3671+ tuple (f for f in formats if f not in checked_formats ),
3672+ )
3673+ if im is not None :
3674+ break
35713675
35723676 if im :
35733677 im ._exclusive_fp = exclusive_fp
0 commit comments