Skip to content

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.

Try these tools

Image Compressor

Related articles

Image Compression Guide 2026Image Compression GuideAi Image Upscaler Guide