136

I am using PIL to resize the images there by converting larger images to smaller ones. Are there any standard ways to reduce the file size of the image without losing the quality too much? Let's say the original size of the image is 100 kB. I want to get it down to like 5 or 10 kB, especially for PNG and JPEG formats.

wovano
  • 4,543
  • 5
  • 22
  • 49
Yashwanth Kumar
  • 28,931
  • 15
  • 65
  • 69
  • 3
    What do you define as "too much" quality loss? If you want to reduce the filesize by a factor of 10 to 20, the easiest way is to reduce the amount of pixels. Reducing both width and height by 2/3 would give you a picture about 1/9 the size of the original. But that is quite a lot of resolution you loose. – Roland Smith May 15 '12 at 19:42

8 Answers8

214

A built-in parameter for saving JPEGs and PNGs is optimize.

 from PIL import Image

 foo = Image.open('path/to/image.jpg')  # My image is a 200x374 jpeg that is 102kb large
 foo.size  # (200, 374)
 
 # downsize the image with an ANTIALIAS filter (gives the highest quality)
 foo = foo.resize((160,300),Image.ANTIALIAS)
 
 foo.save('path/to/save/image_scaled.jpg', quality=95)  # The saved downsized image size is 24.8kb
 
 foo.save('path/to/save/image_scaled_opt.jpg', optimize=True, quality=95)  # The saved downsized image size is 22.9kb

The optimize flag will do an extra pass on the image to find a way to reduce its size as much as possible. 1.9kb might not seem like much, but over hundreds/thousands of pictures, it can add up.

Now to try and get it down to 5kb to 10 kb, you can change the quality value in the save options. Using a quality of 85 instead of 95 in this case would yield: Unoptimized: 15.1kb Optimized : 14.3kb Using a quality of 75 (default if argument is left out) would yield: Unoptimized: 11.8kb Optimized : 11.2kb

I prefer quality 85 with optimize because the quality isn't affected much, and the file size is much smaller.

turnip
  • 2,246
  • 5
  • 30
  • 58
Ryan G
  • 9,184
  • 4
  • 27
  • 27
  • 11
    ANTIALIAS method name update: As of 2.7.0, all resize methods are ANTIALIAS & the real (new) name for the specific ANTIALIAS filter is LANCZOS. (Tho antialias is currently left for backwards compatibility) https://pillow.readthedocs.io/en/3.0.x/releasenotes/2.7.0.html#antialias-renamed-to-lanczos – hbrannan Feb 22 '19 at 04:57
31

lets say you have a model called Book and on it a field called 'cover_pic', in that case, you can do the following to compress the image:

from PIL import Image
b = Book.objects.get(title='Into the wild')
image = Image.open(b.cover_pic.path)
image.save(b.image.path,quality=20,optimize=True)

hope it helps to anyone stumbling upon it.

nash
  • 331
  • 3
  • 8
7

See the thumbnail function of PIL's Image Module. You can use it to save smaller versions of files as various filetypes and if you're wanting to preserve as much quality as you can, consider using the ANTIALIAS filter when you do.

Other than that, I'm not sure if there's a way to specify a maximum desired size. You could, of course, write a function that might try saving multiple versions of the file at varying qualities until a certain size is met, discarding the rest and giving you the image you wanted.

Matt Parrilla
  • 3,171
  • 6
  • 35
  • 54
Cryptite
  • 1,426
  • 2
  • 28
  • 50
  • 2
    is there a way to reduce the file size by keeping the dimensions constant esp. for png formats. – Yashwanth Kumar May 15 '12 at 19:39
  • 2
    If you're wanting to keep the same dimensions, the only other thing you can try is setting the quality setting when you save the image. Check out [this answer](http://stackoverflow.com/a/1405701/369878) – Cryptite May 15 '12 at 19:42
  • 3
    but the quality attribute makes no difference for png formats.even i change the quality the file size remains same. – Yashwanth Kumar May 15 '12 at 19:47
  • 1
    In that case i'm afraid I don't know. PNG's are traditionally larger in size due to their compression format. Are PNG's a must? If not have you considered trying GIF's? – Cryptite May 15 '12 at 19:48
  • 1
    For PNG, convert the image to use a smaller color palette. Use the "bits" option with a value < 8 when writing the file. – Roland Smith May 15 '12 at 20:03
  • 2
    Run PNGs through the `pngcrush` utility. Although, depending on where you got them, they may have already been crushed, so this won't help. It works great on ones you have created yourself, though. – kindall May 15 '12 at 20:43
6

The main image manager in PIL is PIL's Image module.

from PIL import Image
import math

foo = Image.open("path\\to\\image.jpg")
x, y = foo.size
x2, y2 = math.floor(x-50), math.floor(y-20)
foo = foo.resize((x2,y2),Image.ANTIALIAS)
foo.save("path\\to\\save\\image_scaled.jpg",quality=95)

You can add optimize=True to the arguments of you want to decrease the size even more, but optimize only works for JPEG's and PNG's. For other image extensions, you could decrease the quality of the new saved image. You could change the size of the new image by just deleting a bit of code and defining the image size and you can only figure out how to do this if you look at the code carefully. I defined this size:

x, y = foo.size
x2, y2 = math.floor(x-50), math.floor(y-20)

just to show you what is (almost) normally done with horizontal images. For vertical images you might do:

x, y = foo.size
x2, y2 = math.floor(x-20), math.floor(y-50)

. Remember, you can still delete that bit of code and define a new size.

Song
  • 298
  • 5
  • 20
  • 1
    You don't need `math.floor` if you are subtracting a whole number from a whole number. For example `85-2` is `83`. `math.floor` will simply convert decimal numbers into whole numbers (e.g. `84.912` becomes `84`). If you want to convert a **float** into an **int** simply write something like `x = int(84.912)` or `x = 84.912 // 1` – Toothpick Anemone Jul 30 '22 at 22:34
5

This script will reduce your image's width and height, with saving it's proportion, and reducing size also

there are two options, they are doing the same logic, first one is how i did in django project, second is on pure python

You can change TARGET_WIDTH for your required width

in django models.py after image saved, it will be proccessed again

from PIL import Image

class Theme(models.Model):
    image = models.ImageField(upload_to='theme_image/')
    
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        this_image = Image.open(self.image.path)
        width, height = this_image.size
        TARGET_WIDTH = 500
        coefficient = width / 500
        new_height = height / coefficient
        this_image = this_image.resize((int(TARGET_WIDTH),int(new_height)),Image.ANTIALIAS)
        this_image.save(self.image.path,quality=50)

without django foo.py:

from PIL import Image

this_image = Image.open("path\to\your_image.jpg")
width, height = this_image.size
TARGET_WIDTH = 500
coefficient = width / 500
new_height = height / coefficient
this_image = this_image.resize((int(TARGET_WIDTH),int(new_height)),Image.ANTIALIAS)
this_image.save("path\where\to_save\your_image.jpg",quality=50)
oruchkin
  • 1,145
  • 1
  • 10
  • 21
2

You can resize your image or you can reduce your image quality. A few examples here attached :

Python PIL resize image

from PIL import Image
WIDTH = 1020
HEIGHT = 720
img = Image.open("my_image.jpg")
resized_img = img.resize((WIDTH, HEIGHT))
resized_img.save("resized_image.jpg")

Change image resolution pillow

from PIL import Image
size = 7016, 4961
im = Image.open("my_image.png")
im_resized = im.resize(size, Image.ANTIALIAS)
im_resized.save("image_resized.png", "PNG")

OR you can use

im_resized.save("image_resized.png", quality=95, optimize=True)
Ruhul Amin
  • 821
  • 11
  • 14
1

Resizing an image, storing it as a JPEG and reducing the quality to 95 saves up a lot of bytes on the final output:

image = Image.open("input_file.png")
image = image.resize((WIDTH, HEIGHT))  #smaller width and height than the original

image.save("output_file.jpg", "JPEG", quality=95)

However, let's say you HAVE to bring the image size <= 100 kb, no matter what. In that case, we need to keep decreasing the quality of the image until we get to the right filesize:

minimum_quality = 50      # recommended, but optional (set to 0 if you don't want it)

quality = 95      # initial quality
target = 100000   # 100 kb

while True:
    output_buffer = io.BytesIO()    # import io
    image.save(output_buffer, "JPEG", quality=quality)

    file_size = output_buffer.tell()

    if file_size <= target or quality <= minimum_quality:
        output_buffer.close()
        break
    else:
        quality -= 5

image.save(output_image, "JPEG", quality=quality)

As you can see, we keep storing the image in a temp buffer and reading the size of the buffer to know the file size.

Sumit Wadhwa
  • 2,825
  • 1
  • 20
  • 34
-16

If you hava a fat png (1MB for 400x400 etc.):

__import__("importlib").import_module("PIL.Image").open("out.png").save("out.png")
BaiJiFeiLong
  • 3,716
  • 1
  • 30
  • 28