JPG and PNG are both everywhere, but they serve opposite purposes. JPG trades quality for size using lossy compression. PNG preserves every pixel losslessly. Converting JPG to PNG is straightforward — but there's one thing you must understand before you do it.
When converting JPG to PNG makes sense
| Situation | Convert? | Reason |
|---|---|---|
| Need transparency in the image | ✅ Yes | PNG supports alpha; JPG does not |
| Editing the image further | ✅ Yes | Avoids re-encoding losses each time you save |
| Archiving an original scan | ✅ Yes | No further quality degradation |
| Reducing file size | ❌ No | PNG of a photo is always larger than JPG |
| Recovering lost JPG detail | ❌ No | Lost data cannot be restored |
| Serving on the web | ❌ Usually | Use WebP or keep JPG for photos |
The key insight: converting JPG to PNG is lossless from this point forward, but it does not undo the lossy compression that already happened when the JPG was created. If a photo was saved at low quality, the PNG will faithfully preserve those compression artifacts — it just won't add any new ones.
What changes (and what doesn't)
When you convert JPG to PNG:
- File size increases — a typical 200 KB JPG becomes a 1–3 MB PNG
- No new quality loss — every subsequent edit and re-save will be lossless
- Transparency becomes available — you can add an alpha channel in an editor
- No recovered detail — existing JPEG artifacts (blockiness, colour banding) remain
- Lossless from now on — safe for text overlays, logo compositing, screenshots
JavaScript — browser Canvas API
// Browser: convert a JPG <img> element to PNG and download it
function jpgToPng(imgElement, filename = 'image.png') {
const canvas = document.createElement('canvas');
canvas.width = imgElement.naturalWidth;
canvas.height = imgElement.naturalHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(imgElement, 0, 0);
canvas.toBlob((blob) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}, 'image/png');
}
// Usage
const img = document.getElementById('myImage');
jpgToPng(img, 'converted.png');
canvas.toBlob with 'image/png' always produces lossless output — no quality parameter needed or accepted.
JavaScript — Node.js with Sharp
Sharp is the fastest image library for Node.js and handles the conversion in one line:
import sharp from 'sharp';
// Single file
await sharp('photo.jpg')
.png({ compressionLevel: 6 }) // 0 = fastest, 9 = smallest
.toFile('photo.png');
// Batch: convert all JPGs in a directory
import { readdir } from 'fs/promises';
import path from 'path';
const dir = './images';
const files = await readdir(dir);
for (const file of files) {
if (/\.(jpg|jpeg)$/i.test(file)) {
const input = path.join(dir, file);
const output = path.join(dir, path.basename(file, path.extname(file)) + '.png');
await sharp(input).png({ compressionLevel: 6 }).toFile(output);
console.log(`Converted: ${file} → ${path.basename(output)}`);
}
}
compressionLevel controls the deflate algorithm (0–9). It affects file size and speed, not image quality — PNG is always lossless.
Python — Pillow
from PIL import Image
from pathlib import Path
def jpg_to_png(input_path: str, output_path: str | None = None) -> str:
"""Convert a JPG file to PNG. Returns the output path."""
src = Path(input_path)
dst = Path(output_path) if output_path else src.with_suffix('.png')
with Image.open(src) as img:
# Convert to RGBA only if you need transparency later.
# For a plain copy, RGB is fine and keeps file smaller.
if img.mode not in ('RGB', 'RGBA'):
img = img.convert('RGB')
img.save(dst, format='PNG', optimize=True)
return str(dst)
# Single file
jpg_to_png('photo.jpg', 'photo.png')
# Batch
from glob import glob
for jpg_path in glob('images/*.jpg'):
out = jpg_to_png(jpg_path)
print(f'Converted: {jpg_path} → {out}')
optimize=True runs an extra compression pass to reduce file size without touching quality.
Go
Go's standard library handles both formats natively:
package main
import (
"image"
"image/jpeg" // registers the JPEG decoder
_ "image/jpeg"
"image/png"
"os"
)
func jpgToPng(inputPath, outputPath string) error {
// Open and decode the JPEG
f, err := os.Open(inputPath)
if err != nil {
return err
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
return err
}
// Encode as PNG
out, err := os.Create(outputPath)
if err != nil {
return err
}
defer out.Close()
return png.Encode(out, img)
}
func main() {
if err := jpgToPng("photo.jpg", "photo.png"); err != nil {
panic(err)
}
}
The blank import _ "image/jpeg" registers the JPEG decoder so image.Decode can handle .jpg files.
PHP
<?php
function jpgToPng(string $inputPath, string $outputPath = ''): string {
if ($outputPath === '') {
$outputPath = preg_replace('/\.(jpg|jpeg)$/i', '.png', $inputPath);
}
$img = imagecreatefromjpeg($inputPath);
if ($img === false) {
throw new RuntimeException("Cannot open JPEG: $inputPath");
}
// PNG quality: 0 (no compression, fastest) to 9 (max compression).
// This controls file size only — PNG is always lossless.
imagepng($img, $outputPath, 6);
imagedestroy($img);
return $outputPath;
}
// Single file
$out = jpgToPng('photo.jpg', 'photo.png');
echo "Saved: $out\n";
// Batch
foreach (glob('images/*.jpg') as $jpg) {
$png = jpgToPng($jpg);
echo "Converted: $jpg → $png\n";
}
Quick reference
| Task | JS (Sharp) | Python (Pillow) | Go (stdlib) | PHP (GD) |
|---|---|---|---|---|
| Single file | sharp(in).png().toFile(out) |
Image.open(in).save(out, 'PNG') |
png.Encode(out, img) |
imagepng(imagecreatefromjpeg(in), out) |
| Set compression | .png({compressionLevel:6}) |
save(..., optimize=True) |
not exposed | imagepng(img, out, 6) |
| Add transparency | .flatten({background:'#fff'}) then alpha tools |
.convert('RGBA') |
image.NRGBA{} |
imagealphablending |
| Batch convert | for loop + readdir |
glob('*.jpg') |
filepath.Walk |
glob('*.jpg') |
| CLI | sharp-cli |
mogrify -format png *.jpg |
— | — |
6 common mistakes
1. Expecting the PNG to look sharper than the JPG. The conversion is lossless going forward, but JPEG artifacts already in the file are preserved. If you need sharp text or clean edges, you need the original source file, not a JPG→PNG conversion.
2. Thinking the PNG will be a smaller file. A PNG of a photograph is almost always 3–8× larger than the equivalent JPG. If reducing file size is the goal, PNG is the wrong direction — use WebP or keep the JPG.
3. Converting then converting back. JPG → PNG → JPG applies lossy compression a second time, degrading quality further. Always edit the PNG version and only export to JPG as the final step.
4. Ignoring EXIF orientation.
JPEGs often store rotation in EXIF metadata (smartphones). When decoded without honouring that tag, the image appears sideways in PNG. In Python, call ImageOps.exif_transpose(img) before saving. In Sharp, .rotate() (no argument) applies auto-EXIF rotation.
5. Using imagecreatefromjpeg on a PNG file.
PHP's GD functions are format-specific. Use imagecreatefrompng for PNG inputs and imagecreatefromjpeg for JPEG inputs. Using the wrong one returns false silently.
6. Forgetting to call imagedestroy in PHP.
GD images are not garbage-collected. Always free memory with imagedestroy($img) after saving, especially in batch loops.
FAQ
Does converting JPG to PNG improve quality? No. PNG is lossless from the point of conversion onwards, but it cannot recover detail that JPEG compression already discarded. The result is a lossless copy of an already-lossy image.
Will the PNG file be larger than the JPG? Almost certainly yes, often 3–8× larger for photographs. PNG compression is designed for graphics, not photographic content. If file size matters, keep photos as JPG or convert to WebP instead.
Can I add a transparent background after converting to PNG? Yes. Once you have a PNG, you can use an image editor or a background-removal tool to create an alpha channel. JPG cannot store transparency at all.
How do I convert using the command line?
On macOS/Linux with ImageMagick: convert photo.jpg photo.png. On Windows with ImageMagick or ffmpeg: ffmpeg -i photo.jpg photo.png. Both handle batches with glob patterns.
Is there a lossless way to convert JPG to PNG without decoding and re-encoding?
No — the formats use fundamentally different compression schemes (DCT for JPEG, deflate for PNG). The image must be fully decoded to raw pixels, then re-encoded as PNG. This is why a CLI rename (.jpg → .png) just produces a broken file.
What PNG compression level should I use? Level 6 is the standard default (used by most tools). Levels 7–9 reduce file size by a few percent more but can be significantly slower. Level 0 is fastest but produces the largest file. For web delivery, 6 is the right balance.