Python Image Compression: Complete Guide with Code Examples
·
6 min read
·
Imagic AI Team
Learn how to compress images with Python. I've been using these scripts for 5+ years - here's everything you need.
Python Image Compression: Complete Guide with Code Examples
I've automated image compression with Python for 5+ years. Here's my complete guide.
Why Use Python for Image Compression?
After building image processing pipelines for dozens of projects, I can tell you: Python is the best choice for automated image compression.
Why:
- Fast - Process thousands of images
- Flexible - Customize everything
- Free - No expensive software
- Scriptable - Run on schedule or trigger
Prerequisites
Install the libraries:
pip install Pillow python-magic
For WebP/AVIF support:
pip install Pillow-SIMD # Faster Pillow
pip install imageio # AVIF support
Basic Image Compression
Compress JPEG
from PIL import Image
import os
def compress_jpeg(input_path, output_path, quality=85):
"""Compress JPEG with specified quality"""
img = Image.open(input_path)
# Convert RGBA to RGB (JPEG doesn't support alpha)
if img.mode == 'RGBA':
img = img.convert('RGB')
# Save with compression
img.save(
output_path,
'JPEG',
quality=quality,
optimize=True,
progressive=True # Progressive JPEG
)
# Report sizes
original_size = os.path.getsize(input_path)
new_size = os.path.getsize(output_path)
print(f"Compressed: {original_size/1024:.1f}KB → {new_size/1024:.1f}KB")
print(f"Reduction: {(1-new_size/original_size)*100:.1f}%")
# Usage
compress_jpeg('photo.jpg', 'photo_compressed.jpg', quality=80)
Compress PNG
from PIL import Image
import os
def compress_png(input_path, output_path):
"""Compress PNG using optimization"""
img = Image.open(input_path)
# If PNG has transparency, convert to WebP
if img.mode in ('RGBA', 'LA', 'P'):
# Save as WebP for better compression
output_path = output_path.rsplit('.', 1)[0] + '.webp'
img.save(output_path, 'WEBP', quality=85, lossless=False)
else:
# For images without transparency
img.save(output_path, 'PNG', optimize=True)
original_size = os.path.getsize(input_path)
new_size = os.path.getsize(output_path)
print(f"Compressed: {original_size/1024:.1f}KB → {new_size/1024:.1f}KB")
print(f"Reduction: {(1-new_size/original_size)*100:.1f}%")
# Usage
compress_png('image.png', 'image_compressed.png')
Resize and Compress
from PIL import Image
import os
def resize_and_compress(input_path, output_path, max_width=1920, quality=85):
"""Resize image if larger than max_width, then compress"""
img = Image.open(input_path)
# Resize if needed
if img.width > max_width:
ratio = max_width / img.width
new_height = int(img.height * ratio)
img = img.resize((max_width, new_height), Image.Resampling.LANCZOS)
print(f"Resized: {img.width}x{img.height}")
# Convert RGBA to RGB for JPEG
if img.mode == 'RGBA':
img = img.convert('RGB')
# Save compressed
img.save(output_path, 'JPEG', quality=quality, optimize=True)
original_size = os.path.getsize(input_path)
new_size = os.path.getsize(output_path)
print(f"Size: {original_size/1024:.1f}KB → {new_size/1024:.1f}KB")
# Usage
resize_and_compress('large_photo.jpg', 'optimized_photo.jpg')
Batch Processing
from PIL import Image
import os
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
def process_single_image(args):
"""Process one image"""
input_path, output_dir, quality, max_width = args
try:
img = Image.open(input_path)
# Resize if needed
if img.width > max_width:
ratio = max_width / img.width
new_height = int(img.height * ratio)
img = img.resize((max_width, new_height), Image.Resampling.LANCZOS)
# Convert RGBA to RGB
if img.mode == 'RGBA':
img = img.convert('RGB')
# Generate output filename
filename = Path(input_path).name
output_path = os.path.join(output_dir, filename)
# Save compressed
img.save(output_path, 'JPEG', quality=quality, optimize=True)
original_size = os.path.getsize(input_path)
new_size = os.path.getsize(output_path)
reduction = (1-new_size/original_size)*100
return f"✓ {filename}: {original_size/1024:.1f}KB → {new_size/1024:.1f}KB ({reduction:.1f}% reduction)"
except Exception as e:
return f"✗ Error processing {input_path}: {e}"
def batch_compress(input_dir, output_dir, quality=85, max_width=1920, workers=4):
"""Batch process all images in directory"""
os.makedirs(output_dir, exist_ok=True)
# Find all images
extensions = ('.jpg', '.jpeg', '.png', '.webp')
images = [os.path.join(input_dir, f) for f in os.listdir(input_dir)
if f.lower().endswith(extensions)]
print(f"Found {len(images)} images to process")
# Process in parallel
args = [(img, output_dir, quality, max_width) for img in images]
with ThreadPoolExecutor(max_workers=workers) as executor:
results = list(executor.map(process_single_image, args))
# Print results
for result in results:
print(result)
# Summary
total_original = sum(os.path.getsize(img) for img in images)
total_new = sum(os.path.getsize(os.path.join(output_dir, Path(img).name))
for img in images)
print(f"\nTotal: {total_original/1024/1024:.1f}MB → {total_new/1024/1024:.1f}MB")
print(f"Overall reduction: {(1-total_new/total_original)*100:.1f}%")
# Usage
batch_compress(
input_dir='./photos',
output_dir='./optimized',
quality=85,
max_width=1920,
workers=4
)
WebP Conversion
from PIL import Image
import os
def convert_to_webp(input_path, output_path=None, quality=85):
"""Convert image to WebP format"""
img = Image.open(input_path)
# Generate output path if not provided
if output_path is None:
output_path = input_path.rsplit('.', 1)[0] + '.webp'
# Convert RGBA to RGB (WebP lossy supports alpha but RGB is smaller)
if img.mode == 'RGBA':
img = img.convert('RGB')
# Save as WebP
img.save(output_path, 'WEBP', quality=quality)
original_size = os.path.getsize(input_path)
new_size = os.path.getsize(output_path)
print(f"Converted: {original_size/1024:.1f}KB → {new_size/1024:.1f}KB")
print(f"Reduction: {(1-new_size/original_size)*100:.1f}%")
# Usage
convert_to_webp('photo.jpg', quality=80)
Smart Compression
from PIL import Image
import os
def smart_compress(input_path, output_path, target_kb=200, initial_quality=90):
"""Compress to target file size"""
img = Image.open(input_path)
# Convert RGBA to RGB
if img.mode == 'RGBA':
img = img.convert('RGB')
quality = initial_quality
while quality > 20:
img.save(output_path, 'JPEG', quality=quality, optimize=True)
current_size = os.path.getsize(output_path)
if current_size <= target_kb * 1024:
print(f"Success at quality={quality}: {current_size/1024:.1f}KB")
return
quality -= 5
print(f"Warning: Could not reach {target_kb}KB target")
# Usage - compress to under 200KB
smart_compress('photo.jpg', 'photo_200kb.jpg', target_kb=200)
Image Optimization Classes
from PIL import Image
import os
from pathlib import Path
class ImageOptimizer:
def __init__(self, quality=85, max_width=1920):
self.quality = quality
self.max_width = max_width
def process(self, input_path, output_path=None):
"""Process single image"""
img = Image.open(input_path)
# Resize if needed
if img.width > self.max_width:
ratio = self.max_width / img.width
new_height = int(img.height * ratio)
img = img.resize((self.max_width, new_height), Image.Resampling.LANCZOS)
# Convert RGBA to RGB
if img.mode == 'RGBA':
img = img.convert('RGB')
# Generate output path
if output_path is None:
p = Path(input_path)
output_path = str(p.parent / f"{p.stem}_optimized{p.suffix}")
# Save
img.save(output_path, 'JPEG', quality=self.quality, optimize=True)
return output_path
def process_batch(self, input_dir, output_dir):
"""Process all images in directory"""
os.makedirs(output_dir, exist_ok=True)
extensions = ('.jpg', '.jpeg', '.png')
processed = 0
for f in os.listdir(input_dir):
if f.lower().endswith(extensions):
input_path = os.path.join(input_dir, f)
output_path = os.path.join(output_dir, f)
self.process(input_path, output_path)
processed += 1
print(f"Processed {processed} images")
# Usage
optimizer = ImageOptimizer(quality=85, max_width=1920)
optimizer.process_batch('./input', './output')
Common Issues
"Image has wrong mode"
# Fix: Convert to RGB before saving as JPEG
if img.mode == 'RGBA':
img = img.convert('RGB')
"Too large for JPEG"
# Fix: Resize first
if img.width > 4000:
img = img.resize((4000, int(img.height * 4000/img.width)), Image.Resampling.LANCZOS)
"Memory error on large images"
# Fix: Use thumbnail instead of resize (in-place)
img.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)
My Complete Workflow
from PIL import Image
import os
from pathlib import Path
def optimize_image(input_path, max_width=1920, quality=85):
"""My standard optimization workflow"""
img = Image.open(input_path)
# Resize if needed
if img.width > max_width:
ratio = max_width / img.width
new_height = int(img.height * ratio)
img = img.resize((max_width, new_height), Image.Resampling.LANCZOS)
# Handle transparency
if img.mode == 'RGBA':
# Save as PNG if has transparency
if has_transparency(img):
output = input_path.rsplit('.', 1)[0] + '_opt.png'
img.save(output, 'PNG', optimize=True)
else:
img = img.convert('RGB')
output = input_path.rsplit('.', 1)[0] + '_opt.jpg'
img.save(output, 'JPEG', quality=quality, optimize=True)
else:
output = input_path.rsplit('.', 1)[0] + '_opt.jpg'
img.save(output, 'JPEG', quality=quality, optimize=True)
return output
def has_transparency(img):
"""Check if image has transparency"""
if img.mode == 'P':
return img.info.get('transparency', None) is not None
if img.mode == 'RGBA':
extrema = img.getextrema()
return extrema[3][0] < 255
return False
Conclusion
Python is powerful for image compression. Start with the basic scripts and build up to batch processing.
My recommendation: Use Imagic AI for quick tasks. Use Python scripts for automated workflows.
Questions about your specific use case? Ask below.
5+ years automating image processing. Happy to help.