moomoochen
moomoochen

Reputation: 369

Is it possible to resize an image by its bytes rather than width and height?

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

Answers (2)

LeninGF
LeninGF

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

Jeronimo
Jeronimo

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 saves 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

Related Questions