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
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
| 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 |
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.