Skip to content

How to Convert Between JPG and PNG in Python

Learn how to properly convert JPG to PNG and PNG to JPG in Python using Pillow, including handling transparency, optimizing file size, and batch processing.

Web developers frequently need to convert between JPG (small file size, no transparency) and PNG (lossless quality, transparency support). While the conversion seems simple, handling alpha channels correctly is essential to avoid errors and visual artifacts.

This guide covers proper conversion techniques using the Pillow library.

Basic Setup

pip install Pillow
from PIL import Image

JPG to PNG (Simple)

Converting JPG to PNG is straightforward since JPG has no transparency to preserve:

from PIL import Image

def jpg_to_png(source_path, dest_path):
    """Convert JPG to PNG format."""
    img = Image.open(source_path)
    img.save(dest_path, 'PNG')

jpg_to_png("photo.jpg", "photo.png")

With Optimization

from PIL import Image

def jpg_to_png_optimized(source_path, dest_path, compress_level=6):
    """
    Convert JPG to PNG with compression.
    
    Args:
        compress_level: 0-9, higher = smaller file but slower
    """
    img = Image.open(source_path)
    img.save(dest_path, 'PNG', optimize=True, compress_level=compress_level)

jpg_to_png_optimized("photo.jpg", "photo.png")

PNG to JPG (Handling Transparency)

PNG files use RGBA mode (with alpha channel for transparency). JPG only supports RGB, so direct conversion fails:

from PIL import Image

img = Image.open("logo_transparent.png")
img.save("logo.jpg")  # OSError: cannot write mode RGBA as JPEG
from PIL import Image

def png_to_jpg(source_path, dest_path, background_color=(255, 255, 255), quality=95):
    """
    Convert PNG to JPG with proper transparency handling.
    
    Args:
        background_color: RGB tuple for transparent areas (default: white)
        quality: JPG quality 1-100
    """
    img = Image.open(source_path)
    
    if img.mode in ('RGBA', 'LA', 'P'):
        background = Image.new('RGB', img.size, background_color)
        
        if img.mode == 'P':
            img = img.convert('RGBA')
        
        if img.mode == 'RGBA':
            background.paste(img, mask=img.split()[3])
        else:
            background.paste(img, mask=img.split()[1])  # LA mode
        
        background.save(dest_path, 'JPEG', quality=quality)
    else:
        img.convert('RGB').save(dest_path, 'JPEG', quality=quality)

png_to_jpg("logo_transparent.png", "logo.jpg")  # White background
png_to_jpg("icon.png", "icon.jpg", background_color=(0, 0, 0))  # Black background

Simple Conversion (Transparency Becomes Black)

from PIL import Image

def png_to_jpg_simple(source_path, dest_path, quality=95):
    """
    Simple PNG to JPG conversion.
    Warning: Transparent areas become BLACK.
    """
    img = Image.open(source_path)
    rgb_img = img.convert('RGB')
    rgb_img.save(dest_path, 'JPEG', quality=quality)

png_to_jpg_simple("photo.png", "photo.jpg")

Checking Image Mode Before Conversion

from PIL import Image

def analyze_image(path):
    """Check image properties before conversion."""
    img = Image.open(path)
    
    info = {
        'path': path,
        'format': img.format,
        'mode': img.mode,
        'size': img.size,
        'has_transparency': img.mode in ('RGBA', 'LA', 'P'),
    }
    
    if img.mode == 'P':
        info['has_transparency'] = 'transparency' in img.info
    
    return info

print(analyze_image("unknown.png"))

Batch Conversion

Convert All PNGs to JPGs

from PIL import Image
from pathlib import Path

def batch_png_to_jpg(input_dir, output_dir=None, background=(255, 255, 255)):
    """Convert all PNGs in a directory to JPGs."""
    input_path = Path(input_dir)
    output_path = Path(output_dir) if output_dir else input_path
    output_path.mkdir(parents=True, exist_ok=True)
    
    converted = 0
    
    for png_file in input_path.glob("*.png"):
        try:
            img = Image.open(png_file)
            
            if img.mode in ('RGBA', 'LA', 'P'):
                if img.mode == 'P':
                    img = img.convert('RGBA')
                
                bg = Image.new('RGB', img.size, background)
                if img.mode == 'RGBA':
                    bg.paste(img, mask=img.split()[3])
                else:
                    bg.paste(img, mask=img.split()[1])
                img = bg
            else:
                img = img.convert('RGB')
            
            output_file = output_path / f"{png_file.stem}.jpg"
            img.save(output_file, 'JPEG', quality=90)
            converted += 1
            
        except Exception as e:
            print(f"Error converting {png_file.name}: {e}")
    
    print(f"Converted {converted} files")

batch_png_to_jpg("images/", "converted/")

Convert All JPGs to PNGs

from PIL import Image
from pathlib import Path

def batch_jpg_to_png(input_dir, output_dir=None):
    """Convert all JPGs in a directory to PNGs."""
    input_path = Path(input_dir)
    output_path = Path(output_dir) if output_dir else input_path
    output_path.mkdir(parents=True, exist_ok=True)
    
    for jpg_file in input_path.glob("*.jpg"):
        img = Image.open(jpg_file)
        output_file = output_path / f"{jpg_file.stem}.png"
        img.save(output_file, 'PNG', optimize=True)
    
    for jpeg_file in input_path.glob("*.jpeg"):
        img = Image.open(jpeg_file)
        output_file = output_path / f"{jpeg_file.stem}.png"
        img.save(output_file, 'PNG', optimize=True)

batch_jpg_to_png("photos/", "png_output/")

Preserving Metadata

from PIL import Image
from PIL.ExifTags import TAGS

def convert_preserving_metadata(source, dest, target_format):
    """Convert image while preserving EXIF metadata."""
    img = Image.open(source)
    
    exif = img.info.get('exif')
    
    if target_format.upper() == 'JPEG' and img.mode in ('RGBA', 'P'):
        if img.mode == 'P':
            img = img.convert('RGBA')
        background = Image.new('RGB', img.size, (255, 255, 255))
        background.paste(img, mask=img.split()[3])
        img = background
    elif target_format.upper() == 'JPEG':
        img = img.convert('RGB')
    
    if exif:
        img.save(dest, target_format, exif=exif, quality=95)
    else:
        img.save(dest, target_format, quality=95)

convert_preserving_metadata("photo.png", "photo.jpg", "JPEG")

Resizing During Conversion

from PIL import Image

def convert_and_resize(source, dest, max_size=(1920, 1080), background=(255, 255, 255)):
    """Convert format and resize in one operation."""
    img = Image.open(source)
    
    img.thumbnail(max_size, Image.Resampling.LANCZOS)
    
    is_jpg = dest.lower().endswith(('.jpg', '.jpeg'))
    
    if is_jpg:
        if img.mode in ('RGBA', 'LA', 'P'):
            if img.mode == 'P':
                img = img.convert('RGBA')
            bg = Image.new('RGB', img.size, background)
            if img.mode == 'RGBA':
                bg.paste(img, mask=img.split()[3])
            else:
                bg.paste(img, mask=img.split()[1])
            img = bg
        else:
            img = img.convert('RGB')
        img.save(dest, 'JPEG', quality=90)
    else:
        img.save(dest, 'PNG', optimize=True)

convert_and_resize("large_logo.png", "thumbnail.jpg", max_size=(200, 200))

Format Comparison

Feature JPG PNG
Transparency No Yes (alpha channel)
Compression Lossy Lossless
File Size Smaller Larger
Best For Photos, gradients Logos, text, screenshots
Color Depth 24-bit Up to 48-bit

Summary

Conversion Method Key Consideration
JPG → PNG img.save("out.png") Simple, automatic
PNG → JPG Composite onto background Required for transparency
PNG → JPG (simple) .convert("RGB") Transparency becomes black
Navigation