Skip to main content

How to Convert Between JPG and GIF in Python

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}")

# Usage
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)

# Convert to palette mode with specified colors (max 256 for GIF)
img = img.convert('P', palette=Image.ADAPTIVE, colors=colors)

img.save(output_path, optimize=True)

# Fewer colors = smaller file, lower quality
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)

# Create white background for transparent areas
if img.mode in ('RGBA', 'P'):
# Create new RGB image with background
background = Image.new('RGB', img.size, background_color)

# Convert to RGBA to handle transparency properly
img = img.convert('RGBA')

# Composite the image onto the background
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)

# Usage
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
Common Error

Without .convert('RGB'), you'll get: IOError: cannot write mode P as JPEG. GIFs use palette mode (P) which JPG doesn't support.

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:
# Convert frame to RGB for JPG
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

# Usage
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)
# Convert to consistent format
frames.append(img.convert('RGBA'))

# Save first frame, append the rest
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")

# Usage
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)

# Sort files naturally (frame1, frame2, ..., frame10, frame11)
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")

# Usage
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)

# Maintain aspect ratio
img.thumbnail(max_size, Image.Resampling.LANCZOS)

# Handle format-specific requirements
if output_path.lower().endswith('.jpg'):
img = img.convert('RGB')
img.save(output_path, 'JPEG', quality=90)
else:
img.save(output_path, optimize=True)

# Usage
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

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

Summary

ConversionMethodKey Consideration
JPG → GIFimg.save("out.gif")Simple, automatic
GIF → JPGimg.convert("RGB")Required for transparency/palette
Animated GIF → JPGsimg.seek(n) loopExtract each frame separately
JPGs → Animated GIFsave_all=TrueSet duration and loop count
Best Practice

For GIF to JPG conversion, always use .convert('RGB') to prevent errors. For transparency, composite onto a background color of your choice. When creating animations, use consistent image sizes and the optimize=True flag to reduce file size.