JPG-to-PNG is a one-line conversion in Pillow. The interesting questions are: why are you doing this? PNG is bigger than JPG (typically 3-10x) and won't recover JPG-lost detail. The valid use cases are adding transparency, preparing for further lossless edits, or matching a downstream API that requires PNG.
Method 1: Pillow (the canonical option)
Pillow is the de facto standard for image work in Python. JPG-to-PNG is two lines.
pip install Pillow
from PIL import Image
def jpg_to_png(in_path: str, out_path: str, optimize: bool = True) -> None:
with Image.open(in_path) as img:
# Convert to RGB if image has weird modes (CMYK, palette, etc.)
if img.mode not in ("RGB", "RGBA", "L"):
img = img.convert("RGB")
img.save(out_path, "PNG", optimize=optimize)
jpg_to_png("photo.jpg", "photo.png")
To add transparency (e.g., make white pixels transparent — useful for logos saved as JPG):
from PIL import Image
def jpg_to_png_with_alpha(in_path: str, out_path: str, threshold: int = 240) -> None:
img = Image.open(in_path).convert("RGBA")
pixels = img.load()
width, height = img.size
for y in range(height):
for x in range(width):
r, g, b, _ = pixels[x, y]
if r > threshold and g > threshold and b > threshold:
pixels[x, y] = (r, g, b, 0) # transparent
img.save(out_path, "PNG", optimize=True)
jpg_to_png_with_alpha("logo.jpg", "logo.png")
For huge images, the per-pixel loop is slow. Use NumPy: arr = np.array(img); mask = (arr[..., :3] > 240).all(axis=-1); arr[mask, 3] = 0.
Method 2: Wand (ImageMagick bindings)
If you already use ImageMagick on the command line, Wand exposes the same engine in Python. Useful for mixed-format batch pipelines.
apt install libmagickwand-dev
pip install Wand
from wand.image import Image
def jpg_to_png(in_path: str, out_path: str) -> None:
with Image(filename=in_path) as img:
img.format = "png"
img.compression_quality = 95 # PNG compression level
img.save(filename=out_path)
jpg_to_png("photo.jpg", "photo.png")
Wand is overkill for a simple format conversion. It shines when you need ImageMagick-specific features (color management, advanced compositing, perceptual hashing) alongside the format change.
Method 3: ChangeThisFile API (no library)
If you don't want Pillow in your binary, or you're processing JPGs in unusual color spaces (CMYK, Lab) where you don't trust Pillow's auto-conversion, the API does it. Free tier covers 1,000 conversions/month.
import requests
API_KEY = "ctf_sk_your_key_here"
def jpg_to_png(in_path: str, out_path: str) -> None:
with open(in_path, "rb") as f:
response = requests.post(
"https://changethisfile.com/v1/convert",
headers={"Authorization": f"Bearer {API_KEY}"},
files={"file": f},
data={"source": "jpg", "target": "png"},
timeout=60,
)
response.raise_for_status()
with open(out_path, "wb") as out:
out.write(response.content)
jpg_to_png("photo.jpg", "photo.png")
The API auto-handles CMYK, grayscale, and unusual JPG variants. Output is sRGB PNG with optimal compression.
When to use each
| Approach | Best for | Tradeoff |
|---|---|---|
| Pillow | Default for any Python project, fast, full control | Manual handling for unusual color spaces |
| Wand / ImageMagick | Mixed-format pipelines, color profile work | Native dep (libmagickwand) on every machine |
| ChangeThisFile API | Edge runtimes, exotic JPG variants, no bundle hit | Per-call cost, network call |
Production tips
- Don't expect quality recovery. JPG-to-PNG preserves whatever's in the JPG, including compression artifacts. PNG won't make a low-quality JPG look better — it just stops further degradation.
- Use optimize=True for served images. PNG optimization tries multiple compression filters and picks the smallest. Adds CPU time on save; saves bandwidth on every download.
- For batch jobs, parallelize with concurrent.futures. Pillow releases the GIL during encoding/decoding, so threads work fine.
- Strip EXIF if you don't need it. Default Pillow strips EXIF on save anyway; if you copy via img.save(out, exif=img.info['exif']) you keep it. JPG-side GPS metadata in particular often shouldn't be redistributed.
- Watch for CMYK JPGs. Print-prep JPGs can be CMYK. Pillow's save fails or produces wrong colors. Convert to RGB first: img.convert('RGB').
Pillow is the right answer for nearly every Python JPG-to-PNG case. Wand for mixed pipelines. The API for unusual inputs or zero-dependency requirements. Free tier covers 1,000 conversions/month.