How to Compress Images in Python

Learn how to reduce image file size by compressing and resizing the image using Pillow library in Python.
  · 7 min read · Updated jul 2022 · Python for Multimedia

Juggling between coding languages? Let our Code Converter help. Your one-stop solution for language conversion. Start now!

Disclosure: This post may contain affiliate links, meaning when you click the links and make a purchase, we receive a commission.

Image compression is the process of minimizing the size of an image without degrading the image quality. There are a lot of tools online that offer this service; most of them are a great option if you want to minimize your images quickly and reliably. However, in this tutorial, you will learn to reduce image file size in Python using the Pillow library.

You're free how to use the code of this tutorial. For instance, you can make an API around it to reduce image sizes in batches instead of using a third-party API that may cost you money.

I made the code for this tutorial as flexible as possible. You can compress the image and resize it with a scaling factor or exact width and height. You can also specify the quality ratio.

Alright, to get started, let's install Pillow:

$ pip install Pillow

Open up a new Python file and import it:

import os
from PIL import Image

Before we dive into compressing images, let's grab a function from this tutorial to print the file size in a friendly format:

def get_size_format(b, factor=1024, suffix="B"):
    """
    Scale bytes to its proper byte format
    e.g:
        1253656 => '1.20MB'
        1253656678 => '1.17GB'
    """
    for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
        if b < factor:
            return f"{b:.2f}{unit}{suffix}"
        b /= factor
    return f"{b:.2f}Y{suffix}"

Next, let's make our core function for compressing images:

def compress_img(image_name, new_size_ratio=0.9, quality=90, width=None, height=None, to_jpg=True):
    # load the image to memory
    img = Image.open(image_name)
    # print the original image shape
    print("[*] Image shape:", img.size)
    # get the original image size in bytes
    image_size = os.path.getsize(image_name)
    # print the size before compression/resizing
    print("[*] Size before compression:", get_size_format(image_size))
    if new_size_ratio < 1.0:
        # if resizing ratio is below 1.0, then multiply width & height with this ratio to reduce image size
        img = img.resize((int(img.size[0] * new_size_ratio), int(img.size[1] * new_size_ratio)), Image.ANTIALIAS)
        # print new image shape
        print("[+] New Image shape:", img.size)
    elif width and height:
        # if width and height are set, resize with them instead
        img = img.resize((width, height), Image.ANTIALIAS)
        # print new image shape
        print("[+] New Image shape:", img.size)
    # split the filename and extension
    filename, ext = os.path.splitext(image_name)
    # make new filename appending _compressed to the original file name
    if to_jpg:
        # change the extension to JPEG
        new_filename = f"{filename}_compressed.jpg"
    else:
        # retain the same extension of the original image
        new_filename = f"{filename}_compressed{ext}"
    try:
        # save the image with the corresponding quality and optimize set to True
        img.save(new_filename, quality=quality, optimize=True)
    except OSError:
        # convert the image to RGB mode first
        img = img.convert("RGB")
        # save the image with the corresponding quality and optimize set to True
        img.save(new_filename, quality=quality, optimize=True)
    print("[+] New file saved:", new_filename)
    # get the new image size in bytes
    new_image_size = os.path.getsize(new_filename)
    # print the new size in a good format
    print("[+] Size after compression:", get_size_format(new_image_size))
    # calculate the saving bytes
    saving_diff = new_image_size - image_size
    # print the saving percentage
    print(f"[+] Image size change: {saving_diff/image_size*100:.2f}% of the original image size.")

A giant function that does a lot of stuff, let's cover it in more detail:

  • First, we use the Image.open() method to load the image to the memory, we get the size of the image file using os.path.getsize() so we can later compare this size with the new generated file's size.
  • If new_size_ratio is set below 1.0, then resizing is necessary. This number ranges from 0 to 1 and is multiplied by the width and height of the original image to come up with a lower resolution image. This is a suitable parameter if you want to reduce the image size further. You can also set it to 0.95 or 0.9 to reduce the image size with minimal changes to the resolution.
  • If new_size_ratio is 1.0, but width and height are set, then we resize to these new width and height values, make sure they're below the original width and height.
  • If to_jpg is set to True, we change the extension of the original image to be JPEG. This will significantly reduce image size, especially for PNG images. If the conversion raises an OSError, converting the image format to RGB will solve the issue.
  • Finally, we use the save() method to write the optimized image, and we set optimize to True along with the quality passed from the function. We then get the size of the new image and compare it with the size of the original image.

Now that we have our core function, let's use argparse module to integrate it with the command-line arguments:

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="Simple Python script for compressing and resizing images")
    parser.add_argument("image", help="Target image to compress and/or resize")
    parser.add_argument("-j", "--to-jpg", action="store_true", help="Whether to convert the image to the JPEG format")
    parser.add_argument("-q", "--quality", type=int, help="Quality ranging from a minimum of 0 (worst) to a maximum of 95 (best). Default is 90", default=90)
    parser.add_argument("-r", "--resize-ratio", type=float, help="Resizing ratio from 0 to 1, setting to 0.5 will multiply width & height of the image by 0.5. Default is 1.0", default=1.0)
    parser.add_argument("-w", "--width", type=int, help="The new width image, make sure to set it with the `height` parameter")
    parser.add_argument("-hh", "--height", type=int, help="The new height for the image, make sure to set it with the `width` parameter")
    args = parser.parse_args()
    # print the passed arguments
    print("="*50)
    print("[*] Image:", args.image)
    print("[*] To JPEG:", args.to_jpg)
    print("[*] Quality:", args.quality)
    print("[*] Resizing ratio:", args.resize_ratio)
    if args.width and args.height:
        print("[*] Width:", args.width)
        print("[*] Height:", args.height)
    print("="*50)
    # compress the image
    compress_img(args.image, args.resize_ratio, args.quality, args.width, args.height, args.to_jpg)

We make our command-line argument parser in the above code and add the parameters we discussed before.

Let's use our script now. You can get the example image here. First, let's use our script without any parameters:

$ python compress_image.py sample-satellite-images.png

Output:

==================================================
[*] Image: sample-satellite-images.png
[*] To JPEG: False
[*] Quality: 90
[*] Resizing ratio: 1.0
==================================================
[*] Image shape: (953, 496)
[*] Size before compression: 425.65KB
[+] New file saved: sample-satellite-images_compressed.png
[+] Size after compression: 379.25KB
[+] Image size change: -10.90% of the original image size.

The image size is reduced to 379.25KB from 425.65KB, that's about 11% of reduction. Next, let's try to pass -j for converting from PNG to JPEG:

$ python compress_image.py sample-satellite-images.png -j

Output:

==================================================
[*] Image: sample-satellite-images.png
[*] To JPEG: True
[*] Quality: 90
[*] Resizing ratio: 1.0
==================================================
[*] Image shape: (953, 496)
[*] Size before compression: 425.65KB
[+] New file saved: sample-satellite-images_compressed.jpg
[+] Size after compression: 100.07KB
[+] Image size change: -76.49% of the original image size.

Note: You can get the example image here.

That's fantastic, 76.5% of improvement. Let's decrease the quality a bit:

$ python compress_image.py sample-satellite-images.png -j -q 75

Output:

==================================================
[*] Image: sample-satellite-images.png
[*] To JPEG: True
[*] Quality: 75
[*] Resizing ratio: 1.0
==================================================
[*] Image shape: (953, 496)
[*] Size before compression: 425.65KB
[+] New file saved: sample-satellite-images_compressed.jpg
[+] Size after compression: 64.95KB
[+] Image size change: -84.74% of the original image size.

About 85% of reduction without touching the original image resolution. Let's try to multiply the width and height of the image by 0.9:

$ python compress_image.py sample-satellite-images.png -j -q 75 -r 0.9

Output:

==================================================
[*] Image: sample-satellite-images.png
[*] To JPEG: True
[*] Quality: 75
[*] Resizing ratio: 0.9
==================================================
[*] Image shape: (953, 496)
[*] Size before compression: 425.65KB
[+] New Image shape: (857, 446)
[+] New file saved: sample-satellite-images_compressed.jpg
[+] Size after compression: 56.94KB
[+] Image size change: -86.62% of the original image size.

Now setting exact width and height values:

$ python compress_image.py sample-satellite-images.png -j -q 75 -w 800 -hh 400 

Output:

==================================================
[*] Image: sample-satellite-images.png
[*] To JPEG: True
[*] Quality: 75
[*] Resizing ratio: 1.0
[*] Width: 800
[*] Height: 400
==================================================
[*] Image shape: (953, 496)
[*] Size before compression: 425.65KB
[+] New Image shape: (800, 400)
[+] New file saved: sample-satellite-images_compressed.jpg
[+] Size after compression: 49.73KB
[+] Image size change: -88.32% of the original image size.

Conclusion

Awesome! You can try and tweak the parameters to fit your specific needs. I hope this script was handy for you to accelerate the development of your application.

Check the complete code here.

Finally, if you're a beginner and want to learn Python, I suggest you take the Python For Everybody Coursera course, in which you'll learn a lot about Python. You can also check our resources and courses page to see the Python resources I recommend on various topics!

Learn also: How to Compress PDF Files in Python

Happy coding ♥

Finished reading? Keep the learning going with our AI-powered Code Explainer. Try it now!

View Full Code Generate Python Code
Sharing is caring!



Read Also



Comment panel

    Got a coding query or need some guidance before you comment? Check out this Python Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!