Reputation: 369
I wanted to reduce an image to a smaller size for easy sharing and faster uploading.
But I realized if I just reduce the size by its h&w, it doesn't really do the trick because a large image file may have a smaller h&w, and small image file may have a large h&w, so reduce image size by reduce its height & weight may not always shrink the size the way I wanted.
So now I have got the byte size using this:
import os
os.stat('myImage.jpg').st_size
Is it possible to reduce the image size by reducing its byte? And remain its ratio?
Upvotes: 1
Views: 6318
Reputation: 370
it happened that a friend of mine needed to resize/rescale a personal image and it was required that the image to be at most 40KB. I wrote the following code to downscale/rescale the image considering the size on disk. I assume 40KB = 40000Bytes which is not exact but it can come handy
from skimage.io import imread, imshow
from skimage.transform import rescale, resize, downscale_local_mean
from skimage.io import imsave
import matplotlib.pyplot as plt
import os
import numpy as np
img = imread('original.jpg')
target_size = 40000
size = os.path.getsize('original.jpg')
factor = 0.9
while(size>=40000):
image_rescaled = rescale(img, factor, anti_aliasing=False)
imsave('new.jpg', image_rescaled)
print('factor {} image of size {}'.format(factor,size))
factor = factor - 0.05
size = os.path.getsize('new.jpg')
end_size = os.path.getsize('new.jpg')
print(end_size)
Hope it helps!!!! You can follow me on GitHub as LeninGF
Upvotes: 2
Reputation: 2387
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
.
Upvotes: 5