Skip to content

How to Convert Between JPG and GIF in Python

Learn how to convert JPG to GIF and vice versa in Python using Pillow (PIL), including handling transparency, creating animations, and batch processing.

Converting between JPG and GIF formats is a common image processing task. Whether creating static conversions or building animated GIFs from multiple frames, the Pillow (PIL) library provides the tools you need.

This guide covers basic conversion, handling transparency, and creating animations.

Basic Setup

pip install Pillow
from PIL import Image

JPG to GIF (Static Image)

Converting a standard image to GIF format is straightforward:

from PIL import Image

def jpg_to_gif(input_path, output_path):
    """Convert a JPG image to GIF format."""
    img = Image.open(input_path)
    img.save(output_path)
    print(f"Saved: {output_path}")

jpg_to_gif("photo.jpg", "image.gif")

With Quality Options

from PIL import Image

def jpg_to_gif_optimized(input_path, output_path, colors=256):
    """Convert JPG to GIF with color optimization."""
    img = Image.open(input_path)
    
    img = img.convert('P', palette=Image.ADAPTIVE, colors=colors)
    
    img.save(output_path, optimize=True)

jpg_to_gif_optimized("photo.jpg", "small.gif", colors=128)

GIF to JPG (Handling Transparency)

Converting GIF to JPG requires handling two issues that JPG doesn't support: transparency and animation.

from PIL import Image

def gif_to_jpg(gif_path, jpg_path, background_color=(255, 255, 255)):
    """
    Convert GIF to JPG, handling transparency.
    
    Args:
        gif_path: Input GIF file path
        jpg_path: Output JPG file path
        background_color: RGB tuple for transparent areas (default: white)
    """
    img = Image.open(gif_path)
    
    if img.mode in ('RGBA', 'P'):
        background = Image.new('RGB', img.size, background_color)
        
        img = img.convert('RGBA')
        
        background.paste(img, mask=img.split()[3])  # Use alpha channel as mask
        img = background
    else:
        img = img.convert('RGB')
    
    img.save(jpg_path, 'JPEG', quality=95)

gif_to_jpg("logo.gif", "logo.jpg")  # Transparent areas become white
gif_to_jpg("icon.gif", "icon.jpg", background_color=(0, 0, 0))  # Black background

Extracting All Frames from Animated GIF

from PIL import Image
import os

def extract_gif_frames(gif_path, output_dir):
    """Extract all frames from an animated GIF."""
    os.makedirs(output_dir, exist_ok=True)
    
    img = Image.open(gif_path)
    frame_count = 0
    
    try:
        while True:
            frame = img.convert('RGB')
            
            output_path = os.path.join(output_dir, f"frame_{frame_count:04d}.jpg")
            frame.save(output_path, 'JPEG', quality=95)
            
            frame_count += 1
            img.seek(frame_count)  # Move to next frame
            
    except EOFError:
        pass  # No more frames
    
    print(f"Extracted {frame_count} frames to {output_dir}")
    return frame_count

extract_gif_frames("animation.gif", "frames/")

Creating Animated GIF from Multiple Images

from PIL import Image
from pathlib import Path

def create_animated_gif(image_paths, output_path, duration=200, loop=0):
    """
    Create animated GIF from multiple images.
    
    Args:
        image_paths: List of image file paths
        output_path: Output GIF path
        duration: Milliseconds per frame
        loop: Number of loops (0 = infinite)
    """
    if not image_paths:
        raise ValueError("No images provided")
    
    frames = []
    for path in image_paths:
        img = Image.open(path)
        frames.append(img.convert('RGBA'))
    
    frames[0].save(
        output_path,
        save_all=True,
        append_images=frames[1:],
        duration=duration,
        loop=loop,
        optimize=True
    )
    
    print(f"Created {output_path} with {len(frames)} frames")

images = ["frame1.jpg", "frame2.jpg", "frame3.jpg", "frame4.jpg"]
create_animated_gif(images, "animation.gif", duration=150)

From Directory of Images

from PIL import Image
from pathlib import Path
import re

def create_gif_from_directory(input_dir, output_path, pattern="*.jpg", duration=200):
    """Create GIF from all matching images in a directory."""
    input_path = Path(input_dir)
    
    def natural_sort_key(path):
        return [int(c) if c.isdigit() else c for c in re.split(r'(\d+)', path.name)]
    
    image_paths = sorted(input_path.glob(pattern), key=natural_sort_key)
    
    if not image_paths:
        raise ValueError(f"No images matching {pattern} in {input_dir}")
    
    frames = [Image.open(p).convert('RGBA') for p in image_paths]
    
    frames[0].save(
        output_path,
        save_all=True,
        append_images=frames[1:],
        duration=duration,
        loop=0,
        optimize=True
    )
    
    print(f"Created GIF from {len(frames)} images")

create_gif_from_directory("screenshots/", "demo.gif", pattern="screen_*.png")

Resizing During Conversion

from PIL import Image

def convert_and_resize(input_path, output_path, max_size=(800, 600)):
    """Convert image format while resizing."""
    img = Image.open(input_path)
    
    img.thumbnail(max_size, Image.Resampling.LANCZOS)
    
    if output_path.lower().endswith('.jpg'):
        img = img.convert('RGB')
        img.save(output_path, 'JPEG', quality=90)
    else:
        img.save(output_path, optimize=True)

convert_and_resize("large_photo.jpg", "thumbnail.gif", max_size=(200, 200))

Batch Conversion

from PIL import Image
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor

def batch_convert(input_dir, output_dir, input_ext=".gif", output_ext=".jpg"):
    """Convert all images of one format to another."""
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)
    
    def convert_single(file_path):
        try:
            img = Image.open(file_path)
            
            if output_ext.lower() in ['.jpg', '.jpeg']:
                img = img.convert('RGB')
            
            new_name = file_path.stem + output_ext
            img.save(output_path / new_name)
            return file_path.name, True
        except Exception as e:
            return file_path.name, str(e)
    
    files = list(input_path.glob(f"*{input_ext}"))
    
    with ThreadPoolExecutor(max_workers=4) as executor:
        results = list(executor.map(convert_single, files))
    
    success = sum(1 for _, status in results if status is True)
    print(f"Converted {success}/{len(files)} files")
    
    return results

batch_convert("gifs/", "jpgs/", input_ext=".gif", output_ext=".jpg")

Summary

Conversion Method Key Consideration
JPG → GIF img.save("out.gif") Simple, automatic
GIF → JPG img.convert("RGB") Required for transparency/palette
Animated GIF → JPGs img.seek(n) loop Extract each frame separately
JPGs → Animated GIF save_all=True Set duration and loop count
Navigation