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

ApproachBest forTradeoff
PillowDefault for any Python project, fast, full controlManual handling for unusual color spaces
Wand / ImageMagickMixed-format pipelines, color profile workNative dep (libmagickwand) on every machine
ChangeThisFile APIEdge runtimes, exotic JPG variants, no bundle hitPer-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.