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 Pillowfrom PIL import ImageJPG 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 backgroundExtracting 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 |