Open nulano opened 3 weeks ago
zlib-ng v2.2.2 x86_64-compat was not released because of CI test error. It'll be released at next version again. So, downloading pre-built binary is still a valid way.
Anyway, building with CMake is also a fine solution. I'll follow Pillow devs' decision and close my PR #8495 when it's decided.
Here's a chart of the write times for different compression levels and images:
Summary: zlib-ng is much faster.
And file sizes:
Summary: zlib-ng has same file sizes for compression level 0 and bigger files for compression level 1, but if you care about size, any higher compression level gives more or less the same file size (and zlib-ng is faster).
I've now checked that the Windows arm64 wheels pass the test suite on an MacBook Pro M2 Max (tested Python versions: 3.9.10, 3.10.11, 3.11.6, 3.12.0, 3.13.0).
Similar trend for time and identical numbers for size:
Alternative to #8495 .
Changes proposed in this pull request:
Add a flag to
PIL.features
for zlib-ng.python3 -m PIL.report
now looks like this:I took inspiration from https://github.com/python-pillow/Pillow/pull/8495#issuecomment-2436585125 to make a benchmark with multiple compression values. It seems that zlib-ng usually produces a slightly larger file but in much less time during compression.
Script and results (click to expand)
```python import io import os import timeit from PIL import Image PATH = "Tests/images/hopper.png" # or "Tests/images/effect_spread.png" REPEAT = 1000 print(f"{Image.__version__ = }") print(f"{Image.core.zlib_version = }") print(f"Testing {PATH = } with {REPEAT} repetitions") with open(PATH, "rb") as f: data_in = f.read() reader = io.BytesIO(data_in) writer = io.BytesIO() img = Image.open(reader) img.load() def read_png(): reader.seek(0, os.SEEK_SET) return Image.open(reader) def write_png(compress_level): writer.seek(0, os.SEEK_SET) writer.truncate(0) img.save(writer, "PNG", compress_level=compress_level) t = timeit.timeit(read_png, number=REPEAT * 10) print(f"read PNG: time = {t:f} (sec)") for compress_level in range(0, 10): t = timeit.timeit(lambda: write_png(compress_level), number=REPEAT) print( f"write PNG: time = {t:f} (sec); size = {len(writer.getvalue())} bytes; {compress_level = }" ) Image.open(io.BytesIO(writer.getvalue())).show() # Image.__version__ = '11.1.0.dev0' # Image.core.zlib_version = '1.3.1' # Testing PATH = 'Tests/images/hopper.png' with 1000 repetitions # read PNG: time = 0.157310 (sec) # write PNG: time = 0.376219 (sec); size = 49353 bytes; compress_level = 0 # write PNG: time = 0.945004 (sec); size = 32403 bytes; compress_level = 1 # write PNG: time = 1.046024 (sec); size = 32156 bytes; compress_level = 2 # write PNG: time = 1.238888 (sec); size = 31792 bytes; compress_level = 3 # write PNG: time = 1.549707 (sec); size = 30715 bytes; compress_level = 4 # write PNG: time = 1.881609 (sec); size = 30529 bytes; compress_level = 5 # write PNG: time = 2.527365 (sec); size = 30343 bytes; compress_level = 6 # write PNG: time = 3.075476 (sec); size = 30277 bytes; compress_level = 7 # write PNG: time = 5.036960 (sec); size = 30183 bytes; compress_level = 8 # write PNG: time = 6.598194 (sec); size = 30166 bytes; compress_level = 9 # Image.__version__ = '11.1.0.dev0' # Image.core.zlib_version = '1.3.1.zlib-ng' # Testing PATH = 'Tests/images/hopper.png' with 1000 repetitions # read PNG: time = 0.178209 (sec) # write PNG: time = 0.382191 (sec); size = 49353 bytes; compress_level = 0 # write PNG: time = 0.549274 (sec); size = 40354 bytes; compress_level = 1 # write PNG: time = 0.770737 (sec); size = 31458 bytes; compress_level = 2 # write PNG: time = 0.934707 (sec); size = 31096 bytes; compress_level = 3 # write PNG: time = 0.977891 (sec); size = 30836 bytes; compress_level = 4 # write PNG: time = 1.101530 (sec); size = 30771 bytes; compress_level = 5 # write PNG: time = 1.212539 (sec); size = 30649 bytes; compress_level = 6 # write PNG: time = 1.383462 (sec); size = 30217 bytes; compress_level = 7 # write PNG: time = 1.946067 (sec); size = 30175 bytes; compress_level = 8 # write PNG: time = 2.788035 (sec); size = 30166 bytes; compress_level = 9 # Image.__version__ = '11.1.0.dev0' # Image.core.zlib_version = '1.3.1' # Testing PATH = 'Tests/images/effect_spread.png' with 1000 repetitions # read PNG: time = 0.096424 (sec) # write PNG: time = 0.410247 (sec); size = 49353 bytes; compress_level = 0 # write PNG: time = 1.194126 (sec); size = 42199 bytes; compress_level = 1 # write PNG: time = 1.164452 (sec); size = 42096 bytes; compress_level = 2 # write PNG: time = 1.182063 (sec); size = 41977 bytes; compress_level = 3 # write PNG: time = 1.404397 (sec); size = 41011 bytes; compress_level = 4 # write PNG: time = 1.531614 (sec); size = 40969 bytes; compress_level = 5 # write PNG: time = 1.720916 (sec); size = 40903 bytes; compress_level = 6 # write PNG: time = 1.806385 (sec); size = 40896 bytes; compress_level = 7 # write PNG: time = 1.914409 (sec); size = 40889 bytes; compress_level = 8 # write PNG: time = 1.936970 (sec); size = 40889 bytes; compress_level = 9 # Image.__version__ = '11.1.0.dev0' # Image.core.zlib_version = '1.3.1.zlib-ng' # Testing PATH = 'Tests/images/effect_spread.png' with 1000 repetitions # read PNG: time = 0.088179 (sec) # write PNG: time = 0.412940 (sec); size = 49353 bytes; compress_level = 0 # write PNG: time = 0.607201 (sec); size = 48241 bytes; compress_level = 1 # write PNG: time = 0.852157 (sec); size = 41287 bytes; compress_level = 2 # write PNG: time = 1.026876 (sec); size = 41168 bytes; compress_level = 3 # write PNG: time = 1.044624 (sec); size = 41134 bytes; compress_level = 4 # write PNG: time = 1.200349 (sec); size = 41120 bytes; compress_level = 5 # write PNG: time = 1.214951 (sec); size = 41118 bytes; compress_level = 6 # write PNG: time = 0.983906 (sec); size = 40895 bytes; compress_level = 7 # write PNG: time = 0.989961 (sec); size = 40889 bytes; compress_level = 8 # write PNG: time = 1.540352 (sec); size = 40889 bytes; compress_level = 9 ```Looking at the build logs, zlib-ng seems to have been properly detected by webp, libtiff, libpng, and freetype.
TODO: