2020TYPE_CHECKING = False
2121if TYPE_CHECKING :
2222 from collections .abc import Callable
23+ from typing import Any
2324
24- BenchmarkSave = Callable [[Image .Image ], None ]
25-
25+ from _pytest .mark .structures import ParameterSet
2626 from pytest_benchmark .fixture import ( # type: ignore[unused-ignore, import-not-found]
2727 BenchmarkFixture ,
2828 )
2929
30+ BenchmarkSave = Callable [[Image .Image ], None ]
31+
32+
3033if not (find_spec ("pytest_benchmark" ) or find_spec ("pytest_codspeed" )):
3134 pytest .skip ("pytest-benchmark or pytest-codspeed required" , allow_module_level = True )
3235
36+ IMAGES_PATH = pathlib .Path (__file__ ).parent / "images"
37+
3338_save_results = os .environ .get ("PILLOW_BENCHMARK_SAVE_RESULTS_PATH" )
3439SAVE_RESULTS_PATH = pathlib .Path (_save_results ) if _save_results else None
3540
4348# for the benchmark run, so that throughput (Mpx/s) can be recomputed in the future.
4449SIZES = [(1237 , 811 )] # Primes, non-power-of-two, asymmetric, approximately 1024x1024
4550
46- # For benchmarks that act on test fixture files, these are the paths loaded.
47- IMAGES_PATH = pathlib .Path (__file__ ).parent / "images"
48- PATHS = [
49- IMAGES_PATH / "flower2.jpg" ,
50- ]
51-
5251# These are derived from the other configuration, above.
5352RGB_MODES = [mode for mode in MODES if mode .startswith ("RGB" )]
5453ALPHA_MODES = [mode for mode in MODES if mode .endswith ("A" )]
@@ -59,10 +58,6 @@ def _format_size(size: tuple[int, int]) -> str:
5958 return f"{ size [0 ]} x{ size [1 ]} "
6059
6160
62- def _format_path (path : pathlib .Path ) -> str :
63- return path .name
64-
65-
6661@pytest .fixture
6762def bench (
6863 request : pytest .FixtureRequest ,
@@ -567,21 +562,89 @@ def test_draw_lines_blend(
567562
568563
569564@pytest .mark .benchmark (group = "load" )
570- @pytest .mark .parametrize ("path" , PATHS , ids = _format_path )
565+ @pytest .mark .parametrize (
566+ "path" ,
567+ # Commonly used formats and decoder settings.
568+ [
569+ IMAGES_PATH / "avif" / "hopper.avif" , # AVIF, RGB
570+ IMAGES_PATH / "hopper.bmp" , # BMP, RGB
571+ IMAGES_PATH / "hopper.gif" , # GIF, P
572+ IMAGES_PATH / "hopper.jpg" , # JPEG, RGB
573+ IMAGES_PATH / "hopper.png" , # PNG, RGB
574+ IMAGES_PATH / "hopper.tif" , # TIFF, raw
575+ IMAGES_PATH / "hopper.webp" , # WebP, lossy RGB
576+ IMAGES_PATH / "hopper_gray.jpg" , # JPEG, L
577+ IMAGES_PATH / "hopper_lossless.webp" , # WebP, lossless
578+ IMAGES_PATH / "hopper_lzw.tif" , # TIFF, LZW
579+ IMAGES_PATH / "hopper_wal.png" , # PNG, P
580+ IMAGES_PATH / "pil123rgba.png" , # PNG, RGBA
581+ IMAGES_PATH / "pil_sample_cmyk.jpg" , # JPEG, CMYK
582+ IMAGES_PATH / "tiff_adobe_deflate.tif" , # TIFF, deflate
583+ IMAGES_PATH / "transparent.webp" , # WebP, RGBA
584+ IMAGES_PATH / "uncompressed_rgb.dds" , # DDS, uncompressed (see PR #9943)
585+ ],
586+ ids = lambda path : path .name ,
587+ )
571588def test_load (bench : BenchmarkFixture , path : pathlib .Path ) -> None :
572589 def run () -> None :
573590 with Image .open (path ) as im :
574591 im .load ()
575592
593+ try :
594+ run ()
595+ except Exception as e : # e.g. the codec is not available in this build
596+ pytest .skip (f"cannot load { path .name } : { e } " )
576597 bench (run )
577598
578599
600+ def _save_format (
601+ format : str ,
602+ mode : str ,
603+ ** options : Any ,
604+ ) -> ParameterSet :
605+ id = "-" .join ([format , mode , * (f"{ k } ={ v } " for k , v in options .items ())])
606+ return pytest .param (format , mode , options , id = id )
607+
608+
579609@pytest .mark .benchmark (group = "save" )
580- @pytest .mark .parametrize ("path" , PATHS , ids = _format_path )
581- def test_save_jpeg (bench : BenchmarkFixture , path : pathlib .Path ) -> None :
582- with Image .open (path ) as im :
583- im .load ()
584- bench (lambda : im .save (BytesIO (), format = "JPEG" , quality = 85 ))
610+ @pytest .mark .parametrize (
611+ "format, mode, options" ,
612+ [
613+ _save_format ("AVIF" , "RGB" ),
614+ _save_format ("BMP" , "RGB" ),
615+ _save_format ("GIF" , "P" ),
616+ _save_format ("JPEG" , "RGB" ),
617+ _save_format ("JPEG" , "L" ),
618+ _save_format ("PNG" , "L" ),
619+ _save_format ("PNG" , "P" ),
620+ _save_format ("PNG" , "RGB" ),
621+ _save_format ("PNG" , "RGBA" ),
622+ _save_format ("TIFF" , "RGB" ),
623+ _save_format ("TIFF" , "RGB" , compression = "tiff_lzw" ),
624+ _save_format ("TIFF" , "RGB" , compression = "tiff_adobe_deflate" ),
625+ _save_format ("WEBP" , "RGB" ),
626+ _save_format ("WEBP" , "RGBA" , lossless = True ),
627+ ],
628+ )
629+ @pytest .mark .parametrize ("size" , SIZES , ids = _format_size )
630+ def test_save (
631+ bench : BenchmarkFixture ,
632+ format : str ,
633+ mode : str ,
634+ options : dict [str , Any ],
635+ size : tuple [int , int ],
636+ ) -> None :
637+ im = make_pillow_image (mode , size )
638+ bench .extra_info ["label" ] = [format , * (f"{ k } ={ v } " for k , v in options .items ())]
639+
640+ def run () -> None :
641+ im .save (BytesIO (), format , ** options )
642+
643+ try :
644+ run ()
645+ except Exception as e : # e.g. the codec is not available in this build
646+ pytest .skip (f"cannot save { format } : { e } " )
647+ bench (run )
585648
586649
587650@pytest .mark .benchmark (group = "allocate" )
@@ -853,7 +916,7 @@ def test_quantize_grayscale_to_palette(
853916 "source_type" ,
854917 [
855918 "synthetic" ,
856- * ( pytest .param (image , id = f" { image . stem } " ) for image in PATHS ),
919+ pytest .param (IMAGES_PATH / "flower2.jpg" , id = "flower2.jpg" ),
857920 ],
858921)
859922@pytest .mark .parametrize ("palette_type" , ["exact" , "grayscale" , "web" ])
0 commit comments