Welcome! Meet our Python Code Assistant, your new coding buddy. Why wait? Start exploring now!
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 to use the code for 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.LANCZOS)
# 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.LANCZOS)
# 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:
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 compare this size later with the newly generated file's size.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.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
.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.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, which is about an 11% 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.
Awesome! You can try to 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.
Learn also: How to Compress PDF Files in Python
Happy coding ♥
Just finished the article? Now, boost your next project with our Python Code Generator. Discover a faster, smarter way to code.
View Full Code Auto-Generate My Code
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!