Skip to content

Instantly share code, notes, and snippets.

@amw
Last active September 19, 2023 10:48
Show Gist options
  • Save amw/2febf24ebcb3baf409c50decbea71e6e to your computer and use it in GitHub Desktop.
Save amw/2febf24ebcb3baf409c50decbea71e6e to your computer and use it in GitHub Desktop.
Performance comparison of image resizing methods in Pillow and PyVIPS
#!/usr/bin/env python
# Script for comparing Pillow vs PyVIPS used for this issue:
# https://github.com/libvips/pyvips/issues/148
import sys
import timeit
import pyvips
from pathlib import Path
from PIL import Image
OUT = Path(__file__).parent.joinpath('out')
image_path = sys.argv[1]
number, repeat = 10, 3
pyvips.cache_set_max_mem(0)
algorithms = dict(
vips=dict(
fast=lambda im, scale: im.shrink(scale, scale),
adaptive=lambda im, scale: im.resize(1/scale, vscale=1/scale, gap=2.0, kernel='lanczos3'),
slow=lambda im, scale: im.resize(1/scale, vscale=1/scale, gap=0, kernel='lanczos3'),
),
pillow=dict(
fast=lambda im, scale: im.reduce((scale, scale)),
adaptive=lambda im, scale: im.resize(
(round(im.width / scale), round(im.height / scale)),
Image.LANCZOS,
reducing_gap=2.0,
),
slow=lambda im, scale: im.resize(
(round(im.width / scale), round(im.height / scale)),
Image.LANCZOS,
),
)
)
finalize = dict(
vips=lambda im: im.write_to_memory(),
pillow=lambda im: im,
)
saving = dict(
vips=lambda im, path: im.write_to_file(path, compression=1),
pillow=lambda im, path: im.save(path, compress_level=1),
)
images = dict(
vips=pyvips.Image.new_from_file(image_path),
pillow=Image.open(image_path),
)
def get_time(app, method, scale):
callback = algorithms[app][method]
output_path = OUT.joinpath(f'{method}_{scale}x_{app}.png')
image = images[app]
try:
saving[app](callback(image, scale), output_path)
return min(timeit.repeat(
lambda: finalize[app](callback(image, scale)),
repeat=repeat,
number=number
)) * 1_000 / number
except TypeError:
pass
def print_comparison(header, times):
best_time = min(times[k] for k in times if times[k] is not None)
line = f'| {header} | '
for label in times:
if times[label] is not None:
time = times[label]
ratio = time * 100 / best_time
line += f'{label} {time:6.2f}ms ({ratio:4.0f}%) | '
else:
line += f'{label} {"-" * 4}ms {" " * 7} | '
print(line)
for key in images:
assert images['vips'].width == images[key].width
assert images['vips'].height == images[key].height
print(f'\nSize: {images["vips"].width}x{images["vips"].height}\n\n')
print('-' * 69)
for scale in [8, 9.4, 16]:
for method in algorithms['vips']:
times = {app: get_time(app, method, scale) for app in algorithms}
print_comparison(f'{scale:3}x {method:10}', times)
print('-' * 69)
print('\n\n')
print('-' * 91)
for app in algorithms:
for scale in [8, 9.4, 16]:
times = {method: get_time(app, method, scale) for method in algorithms[app]}
print_comparison(f'{app:6} {scale:3}x', times)
print('-' * 91)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment