Here's a function I wrote with PIL. It does some iterative resizing and jpeg compression of an image to then look at the resulting file size and compare it to a target value, guessing the next best width/height combination from the size deviation ratio (basically some sort of a P controller).
It makes use of io.BytesIO which does all the resizing stuff in memory, so there's really only one read and one write access to files on the disk. Also, with this bruteforce approach, you can alter the target file format to let's say PNG, and it would work out of the box.
from PIL import Image
import os
import io
def limit_img_size(img_filename, img_target_filename, target_filesize, tolerance=5):
img = img_orig = Image.open(img_filename)
aspect = img.size[0] / img.size[1]
while True:
with io.BytesIO() as buffer:
img.save(buffer, format="JPEG")
data = buffer.getvalue()
filesize = len(data)
size_deviation = filesize / target_filesize
print("size: {}; factor: {:.3f}".format(filesize, size_deviation))
if size_deviation <= (100 + tolerance) / 100:
# filesize fits
with open(img_target_filename, "wb") as f:
f.write(data)
break
else:
# filesize not good enough => adapt width and height
# use sqrt of deviation since applied both in width and height
new_width = img.size[0] / size_deviation**0.5
new_height = new_width / aspect
# resize from img_orig to not lose quality
img = img_orig.resize((int(new_width), int(new_height)))
limit_img_size(
"test.jpg", # input file
"test_with_limited_size.jpg", # target file
50000, # bytes
tolerance = 5 # percent of what the file may be bigger than target_filesize
)
EDIT:
With "in memory" I meant that when it save
s the img
to buffer
in the loop, it saves it to a BytesIO
object, which is not a file on the disk but in memory. And from that object I can then determine the resulting file size (which is just the length of that data buffer) without actually saving it to a file. In the end maybe that's just how you'd expect it to work, but I've seen too many codes that waste performance on saving files on disk due to a lack of knowledge about Python's io.BytesIO
.
Only the final result will be saved to a file - and that's ofc where you want. Try using an absoulte filename for img_target_filename
.