How to reduce a jpeg size to a 'desired size'?
I am still learning Python, so there may be better ways, but here is a function that saves a PIL/Pillow image as a JPEG and allows you to specify a maximum size.
It uses a binary search to minimise the amount of work needed and it encodes into BytesIO
memory buffer to save writing images to disk. If anyone has any suggestions for improvements, please let me know!
#!/usr/local/bin/python3
import io
import math
import sys
import numpy as np
from PIL import Image
def JPEGSaveWithTargetSize(im, filename, target):
"""Save the image as JPEG with the given name at best quality that makes less than "target" bytes"""
# Min and Max quality
Qmin, Qmax = 25, 96
# Highest acceptable quality found
Qacc = -1
while Qmin <= Qmax:
m = math.floor((Qmin + Qmax) / 2)
# Encode into memory and get size
buffer = io.BytesIO()
im.save(buffer, format="JPEG", quality=m)
s = buffer.getbuffer().nbytes
if s <= target:
Qacc = m
Qmin = m + 1
elif s > target:
Qmax = m - 1
# Write to disk at the defined quality
if Qacc > -1:
im.save(filename, format="JPEG", quality=Qacc)
else:
print("ERROR: No acceptble quality factor found", file=sys.stderr)
################################################################################
# main
################################################################################
# Load sample image
im = Image.open('/Users/mark/sample/images/lena.png')
# Save at best quality under 100,000 bytes
JPEGSaveWithTargetSize(im, "result.jpg", 100000)
If I run that as is, with target size of 100,000 bytes, I get:
-rw-r--r--@ 1 mark staff 96835 11 Sep 18:21 result.jpg
If I change the target size to 50,000 bytes, I get:
-rw-r--r--@ 1 mark staff 49532 11 Sep 18:26 result.jpg
Keywords: Python, PIL, Pillow, JPEG, quality, quality setting, max size, maximum size, image, image processing, binary search.
How to reduce the file size on JPEG images in batch (/Mac)?
This is an example from the command line using convert (brew info imagemagick
) converting all *.jpg
images in one directory to .png
:
$ for i in *.jpg; do
convert "$i" "${i%.jpg}.png"
done
To test before (dry-run) you could use echo
instead of the <command>
:
$ for i in *.jpg; do
echo "$i" "${i%.jpg}.png"
done
This will search for files within the directory having the extension .jpg
then execute the command convert
passing as arguments the file name $i
and then using as an output the same file name removing the extension and adding the new one .png
, this is done using:
"${i%.jpg}.png"
The use of double quotes "
is for the case file could contain spaces, check this for more details: shell parameter expansion
For example, to just change the quality of the file you could use:
convert "$i" -quality 80% "${i%.jpg}-new.jpg"
Or if no need to keep the original:
mogrify -quality 80% *.jpg
The main difference is that ‘convert‘ tends to be for working on individual images, whereas ‘mogrify‘ is for batch processing multiple files.
Programmatically Reducing JPEG file size
Another way to reduce image size is to change compression level. You can do that using ImageWriter.
ImageWriter writer = null;
Iterator<ImageWriter> iwi = ImageIO.getImageWritersByFormatName("jpg");
if (!iwi.hasNext())
return;
writer = (ImageWriter) iwi.next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) ;
iwp.setCompressionQuality(compressionQuality);
writer.setOutput(...);
writer.write(null, image, iwp);
Downscaling image file size using imageMagick
You would use -define jpeg:extent=...
.
Here is an example with a large image of random data that would need a very large file size to accurately represent it with any reasonable quality.
convert -size 10000x1000 xc:gray +noise random -define jpeg:extent=2MB out.jpg
Result
-rw-r--r-- 1 mark staff 1844050 15 May 10:44 out.jpg
And check the quality used:
identify -format "%Q" out.jpg
21
Another example:
convert -size 10000x1000 xc:gray +noise random -define jpeg:extent=400kb out.jpg
Result
-rw-r--r-- 1 mark staff 377757 15 May 10:44 out.jpg
And check the quality used:
identify -format "%Q" out.jpg
5
If you want a way to do something similar with Python, I wrote an answer that works pretty well here. It does a binary search for a JPEG quality that satisfies a maximum size requirement.
Related Topics
What Does a . in an Import Statement in Python Mean
Finding Multiple Occurrences of a String Within a String in Python
How to Find All the Subsets of a Set, with Exactly N Elements
Function for Factorial in Python
Why Are Empty Strings Returned in Split() Results
Why Is Parenthesis in Print Voluntary in Python 2.7
Including Non-Python Files with Setup.Py
Insert a Row to Pandas Dataframe
Numpy "Where" with Multiple Conditions
How to Access the Child Classes of an Object in Django Without Knowing the Name of the Child Class
Generating a List of Random Numbers, Summing to 1
Finding Median of List in Python
Running Selenium with Headless Chrome Webdriver
Python: JSON.Loads Returns Items Prefixing with 'U'