There are two parts to JPEG quality. The first is the quality setting which you have already set to the highest possible value.

JPEG also uses chroma subsampling, assuming that color hue changes are less important than lightness changes and some information can be safely thrown away. Unfortunately in demanding applications this isn't always true, and you can most easily notice this on red edges. PIL didn't originally expose a documented setting to control this aspect.

Pascal Beyeler discovered the option which disables chroma subsampling. You can set subsampling=0 when saving an image and the image looks way sharper!

im.save('/path/to/cover-2.jpg', format='JPEG', subsampling=0, quality=100)

The Pillow project took over where PIL left off and made many improvements, including documenting the formerly undocumented subsampling option. It's been enhanced to take either an integer or string argument, but I still recommend 0 as shown above.

https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#jpeg

Note also that the documentation claims quality=95 is the best quality setting and that anything over 95 should be avoided. This may be a change from earlier versions of PIL.

Answer from Mark Ransom on Stack Overflow
🌐
Pillow Documentation
pillow.readthedocs.io › en › stable › handbook › image-file-formats.html
Image file formats - Pillow (PIL Fork) 12.3.0 documentation
It is only possible to save 8-bit AVIF images, and all AVIF images are decoded as 8-bit RGB(A). ... Integer, 0-100, defaults to 75. 0 gives the smallest size and poorest quality, 100 the largest size and best quality.
Discussions

Quality degradation due to PIL.Image.save
@sirfz you have mentioned before that using SetImageFile can be better than SetImage when doing layout analysis. I can fully confirm that. There's a big difference for JPEG files between CLI an... More on github.com
🌐 github.com
7
December 18, 2019
How to adjust the quality of a resized image in Python Imaging Library? - Stack Overflow
But you still haven't begun to ... save the image. Which is to say that in thumbnailing, you aren't dealing with gradations of quality, but with discrete algorithms for resampling. So no, you can't get finer control here. If you save to a format with lossy compression, that's the place to specify levels of quality. ... Don't confuse rescaling and compression. For the best quality you have to use both. See the following code: from PIL import Image ... More on stackoverflow.com
🌐 stackoverflow.com
Trying to compress images with PIL but the file size isn't changing.
The images are probably already optimized and compressed. Why wouldn't they be? You may get a few percent better if you use a program specifically designed to optimize compression, which PIL is not. More on reddit.com
🌐 r/learnpython
8
6
December 15, 2022
Python PIL, preserve quality when resizing and saving - Stack Overflow
The issue seems to be the saving, not the resizing. Even if I start with the Paint-resized image (and therefore do not resize the image in PIL), it still saves it as the crappy quality 96 dpi (16kb) intead of keeping it as it is. More on stackoverflow.com
🌐 stackoverflow.com
🌐
jdhao's digital space
jdhao.github.io › 2019 › 07 › 20 › pil_jpeg_image_quality
JPEG Image Quality in PIL · jdhao's digital space
November 29, 2020 - After looking up the documentation, I find that PIL will save the image with quality factor 75 by default so that the image quality is decreased.
🌐
GeeksforGeeks
geeksforgeeks.org › python › change-image-resolution-using-pillow-in-python
Change image resolution using Pillow in Python - GeeksforGeeks
July 23, 2025 - Open the image using .open( ) method by specifying the image path. The image_file.save() method have a parameter named quality, that specifies the resolution of an image in a 1-100 scale, where 95 is considered as the optimal quality.
🌐
GitHub
github.com › python-pillow › Pillow › discussions › 7896
Feature request: adding quality='keep' to Image.save() with JPEG2000 · python-pillow/Pillow · Discussion #7896
Is your goal to be able to save a JPEG2000 image without "losing quality" aka "modifying the pixels"? I think Pillow should already do that, provided that you haven't set irreversible=True.
Author   python-pillow
🌐
GitHub
github.com › sirfz › tesserocr › issues › 207
Quality degradation due to PIL.Image.save · Issue #207 · sirfz/tesserocr
December 18, 2019 - Quality degradation due to PIL.Image.save#207 · Copy link · bertsky · opened · on Dec 18, 2019 · Issue body actions · @sirfz you have mentioned before that using SetImageFile can be better than SetImage when doing layout analysis. I can fully confirm that.
Author   sirfz
🌐
GeeksforGeeks
geeksforgeeks.org › python › python-pil-image-save-method
Python PIL | Image.save() method - GeeksforGeeks
December 23, 2025 - Explanation: quality=40 tells the JPEG writer to compress the image more, producing a smaller file. Example 3: Saving an Image Using an Explicit Format when using a file object instead of a filename, the format must be explicitly provided.
Find elsewhere
🌐
GitHub
github.com › python-pillow › Pillow › issues › 857
Pillow JpegImagePlugin cannot "keep" quality on save() · Issue #857 · python-pillow/Pillow
August 19, 2014 - In [1]: import PIL In [2]: PIL.PILLOW_VERSION, PIL.VERSION Out[2]: ('2.5.1', '1.1.7') In [3]: from PIL import Image In [4]: ll /tmp/72.jpg -rw-r--r-- 1 ywang wheel 43990 Mar 13 12:37 /tmp/72.jpg In [5]: im = Image.open('/tmp/72.jpg') In [6]: im.layer Out[6]: [('\x01', 1, 1, 0)] In [7]: im Out[7]: <PIL.JpegImagePlugin.JpegImageFile image mode=L size=600x339 at 0x10AE8F1B8> In [8]: im.format Out[8]: 'JPEG' In [9]: im.save('/tmp/72-keep.jpg', 'JPEG', quality='keep') --------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-in
Author   python-pillow
Top answer
1 of 1
3

Quick takeaways from the following explanations...

  1. The quality parameter for PIL.Image.save isn't used when saving PNGs.
  2. JPEG is generationally-lossy so as you keep re-saving images, they will likely degrade in quality because the algorithm will introduce more artifacting (among other things)
  3. PNG is lossless and the file size differences you're seeing are due to PIL stripping metadata when you re-save your image.

Let's look at your PNG file first. PNG is a lossless format - the image data you give it will not suffer generational loss if you were to open it and re-save it as PNG over and over again.

The quality parameter isn't even recognized by the PNG plugin to PIL - if you look at the PngImagePlugin.py/PngStream._save method it is never referenced in there.

What's happening with your specific sample image is that Pillow is dropping some metadata when you re-save it in your code.

On my test system, I have your PNG saved as sample.png, and I did a simple load-and-save with the following code and save it as output.png (inside ipython)

In [1]: from PIL import Image
In [2]: img = Image.open("sample.png")
In [3]: img.save("output.png")

Now let's look at the differences between their metadata with ImageMagick:

#> diff <(magick identify -verbose output.png) <(magick identify -verbose sample.png)

7c7,9
<   Units: Undefined
---
>   Resolution: 94.48x94.48
>   Print size: 10.8383x10.8383
>   Units: PixelsPerCentimeter
74c76,78
<   Orientation: Undefined
---
>   Orientation: TopLeft
>   Profiles:
>     Profile-exif: 5218 bytes
76,77c80,81
<     date:create: 2022-08-12T21:27:13+00:00
<     date:modify: 2022-08-12T21:27:13+00:00
---
>     date:create: 2022-08-12T21:23:42+00:00
>     date:modify: 2022-08-12T21:23:31+00:00
78a83,85
>     exif:ImageDescription: IMGP5493_seamless_2.jpg
>     exif:ImageLength: 1024
>     exif:ImageWidth: 1024
84a92
>     png:pHYs: x_res=9448, y_res=9448, units=1
85a94,95
>     png:text: 1 tEXt/zTXt/iTXt chunks were found
>     png:text-encoded profiles: 1 were found
86a97
>     unknown: nomacs - Image Lounge 3.14
90c101
<   Filesize: 933730B
---
>   Filesize: 939469B
93c104
<   Pixels per second: 42.9936MP
---
>   Pixels per second: 43.7861MP

You can see there are metadata differences - PIL didn't retain some of the information when re-saving the image, especially some exif properties (you can see this PNG was actually converted from a JPG and the EXIF metadata was preserved in the conversion).

However, if you re-save the image with original image's info data...

In [1]: from PIL import Image
In [2]: img = Image.open("sample.png")
In [3]: img.save("output-with-info.png", info=img.info)

You'll see that the two files are exactly the same again:

❯ sha256sum output.png output-with-info.png
37ad78a7b7000c9430f40d63aa2f0afd2b59ffeeb93285b12bbba9c7c3dec4a2  output.png
37ad78a7b7000c9430f40d63aa2f0afd2b59ffeeb93285b12bbba9c7c3dec4a2  output-with-info.png

Maybe Reducing PNG File Size

While lossless, the PNG format does allow for reducing the size of the image by specifying how aggressive the compression is (there are also more advanced things you could do like specifying a compression dictionary).

PIL exposes these options as optimize and compress_level under PNG options.

optimize

    If present and true, instructs the PNG writer to make the
    output file as small as possible. This includes extra 
    processing in order to find optimal encoder settings.

compress_level

    ZLIB compression level, a number between 0 and 9: 1 gives
    best speed, 9 gives best compression, 0 gives no 
    compression at all. Default is 6. When optimize option is 
    True compress_level has no effect (it is set to 9 regardless
    of a value passed).

And seeing it in action...

from PIL import Image

img = Image.open("sample.png")
img.save("optimized.png", optimize=True)

The resulting image I get is about 60K smaller than the original.

❯ ls -lh optimized.png sample.png
-rw-r--r-- 1 wkl staff 843K Aug 12 18:10 optimized.png
-rw-r--r-- 1 wkl staff 918K Aug 12 17:23 sample.png

JPEG File

Now, JPEG is a generationally-lossy image format - as you save it over and over, you will keep losing quality - it doesn't matter if your subsequent generations save it at even higher qualities than the previous ones, you've lost data already from the previous saves.

Note that the likely reason why you saw file sizes balloon if you used quality=100 is because libjpeg/libjpeg-turbo (which are the underlying libraries used by PIL for JPEG) do not do certain things when the quality is set that high, I think it doesn't do quantization which is an important step in determining how many bits are needed to compress.

🌐
Readthedocs
pc-pillow.readthedocs.io › en › latest › Image_class › Image_save.html
5. Image save — PC-Pillow
from PIL import Image with ... image to a jgp before attempting to save it. Use the .save method specifying the quality parameter as quality=95 for best quality....
🌐
Post.Byes
post.bytes.com › home › forum › topic › python
How to save High Quality JPG image by setting Image module from PIL - Post.Byes
I am using Image module from PIL to save created image as JPG format. > Is there any option I could set for function Image.save so that the stored image will have the highest quality. something like im.save(filenam e, quality=90) should do the trick.
Top answer
1 of 10
224

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.

2 of 10
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.

🌐
Appdividend
appdividend.com › pil-image-save
PIL Image.save(): Saving JPEG File in Python
November 20, 2025 - Here is the existing PNG image ... not already in that mode) rgb_image = im.convert('RGB') # Saving an image to file (quality=75 default) rgb_image.save('output.jpg') # Infers JPEG from extension print("Image saved as output.jpg ...