Toolmingo
Image formats7 min read

How to Convert PNG to JPG (and When You Should)

Converting PNG to JPG reduces file size dramatically, but transparency becomes a problem. Learn when to convert, how to handle alpha channels, and get code examples in JS, Python, Go, and PHP.

PNG and JPG serve very different purposes. PNG is a lossless format built for graphics, logos, and anything with transparency. JPG is a lossy format built for photographs and complex images where a small quality trade-off yields huge file-size savings. Converting from PNG to JPG makes sense in many situations — but there's one critical pitfall you need to understand first.

When to convert PNG to JPG

Good candidates for conversion:

PNG use case Convert to JPG?
Photograph saved as PNG Yes — JPG compresses photos far better
Screenshot of a webpage Usually yes — mostly opaque content
Marketing banner (no transparency) Yes — big size reduction
Logo with transparent background No — JPG doesn't support transparency
Icon with hard edges No — JPG creates edge artefacts
Image for further editing No — stay lossless until final export

The single rule: if the image has transparency, do not convert to JPG without deciding what to replace the transparent areas with first. JPG has no alpha channel. Every transparent pixel must become a solid colour.

The transparency problem

PNG supports an alpha channel — each pixel has an opacity value from 0 (fully transparent) to 255 (fully opaque). JPG simply cannot store this information. When a naive converter encounters a transparent PNG, it typically fills transparent areas with black, which looks terrible on most backgrounds.

The correct approach:

  1. Create a canvas or image buffer filled with your desired background colour (white is the safest default).
  2. Composite the PNG on top of that background.
  3. Export the result as JPG.

This is called alpha compositing or flattening the alpha channel.

File size comparison

Here's what you can realistically expect when converting photos and graphics:

Image type PNG size JPG at 85% quality Reduction
Photo (1920×1080) 3.2 MB 420 KB ~87%
Screenshot (1440×900) 1.8 MB 290 KB ~84%
Marketing banner (1200×628) 950 KB 140 KB ~85%
Logo (512×512, no transparency) 85 KB 28 KB ~67%

A quality setting of 80–90 is the sweet spot for most web images — nearly indistinguishable from the original at a fraction of the size.


JavaScript (browser + Node.js)

Browser: Canvas API

function pngToJpg(file, quality = 0.85, bgColour = '#ffffff') {
  return new Promise((resolve) => {
    const img = new Image();
    const url = URL.createObjectURL(file);

    img.onload = () => {
      const canvas = document.createElement('canvas');
      canvas.width = img.naturalWidth;
      canvas.height = img.naturalHeight;

      const ctx = canvas.getContext('2d');

      // Fill background first — handles transparency
      ctx.fillStyle = bgColour;
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      // Draw the PNG on top
      ctx.drawImage(img, 0, 0);

      URL.revokeObjectURL(url);

      canvas.toBlob(resolve, 'image/jpeg', quality);
    };

    img.src = url;
  });
}

// Usage
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', async (e) => {
  const blob = await pngToJpg(e.target.files[0], 0.85);
  const link = document.createElement('a');
  link.href = URL.createObjectURL(blob);
  link.download = 'converted.jpg';
  link.click();
});

Node.js: Sharp

const sharp = require('sharp');

async function pngToJpg(inputPath, outputPath, options = {}) {
  const { quality = 85, background = { r: 255, g: 255, b: 255 } } = options;

  await sharp(inputPath)
    .flatten({ background }) // Replace transparent areas with background colour
    .jpeg({ quality, mozjpeg: true }) // mozjpeg: better compression
    .toFile(outputPath);
}

// Convert a single file
await pngToJpg('input.png', 'output.jpg');

// Convert with custom background (e.g. light grey)
await pngToJpg('input.png', 'output.jpg', {
  quality: 90,
  background: { r: 240, g: 240, b: 240 },
});

Python: Pillow

from PIL import Image
import os

def png_to_jpg(input_path: str, output_path: str, quality: int = 85,
               background: tuple = (255, 255, 255)) -> None:
    """
    Convert PNG to JPG with proper alpha handling.

    Args:
        input_path: Path to the source PNG file.
        output_path: Path for the output JPG file.
        quality: JPEG quality 1–95 (85 is a good default).
        background: RGB tuple for the background colour under transparency.
    """
    img = Image.open(input_path)

    # Handle RGBA and P (palette with transparency) modes
    if img.mode in ('RGBA', 'LA', 'P'):
        bg = Image.new('RGB', img.size, background)
        if img.mode == 'P':
            img = img.convert('RGBA')
        if img.mode in ('RGBA', 'LA'):
            bg.paste(img, mask=img.split()[-1])  # Use alpha as mask
        img = bg
    elif img.mode != 'RGB':
        img = img.convert('RGB')

    img.save(output_path, 'JPEG', quality=quality, optimize=True)


# Single file
png_to_jpg('photo.png', 'photo.jpg')

# Batch convert all PNGs in a folder
import glob

for png_file in glob.glob('images/*.png'):
    jpg_file = os.path.splitext(png_file)[0] + '.jpg'
    png_to_jpg(png_file, jpg_file, quality=85)
    print(f'Converted: {png_file} → {jpg_file}')

Go: image/jpeg + image/png

package main

import (
	"image"
	"image/color"
	"image/draw"
	"image/jpeg"
	"image/png"
	"os"
)

// PNGToJPG converts a PNG file to JPG with alpha compositing.
func PNGToJPG(inputPath, outputPath string, quality int, bg color.RGBA) error {
	// Open and decode the PNG
	src, err := os.Open(inputPath)
	if err != nil {
		return err
	}
	defer src.Close()

	srcImg, err := png.Decode(src)
	if err != nil {
		return err
	}

	// Create an opaque RGB canvas filled with the background colour
	bounds := srcImg.Bounds()
	dst := image.NewRGBA(bounds)
	draw.Draw(dst, bounds, &image.Uniform{bg}, image.Point{}, draw.Src)

	// Composite the source image over the background (handles alpha)
	draw.Draw(dst, bounds, srcImg, bounds.Min, draw.Over)

	// Encode as JPEG
	out, err := os.Create(outputPath)
	if err != nil {
		return err
	}
	defer out.Close()

	return jpeg.Encode(out, dst, &jpeg.Options{Quality: quality})
}

func main() {
	white := color.RGBA{R: 255, G: 255, B: 255, A: 255}
	if err := PNGToJPG("input.png", "output.jpg", 85, white); err != nil {
		panic(err)
	}
}

PHP: GD

<?php

function pngToJpg(
    string $inputPath,
    string $outputPath,
    int $quality = 85,
    array $background = [255, 255, 255]
): void {
    $src = imagecreatefrompng($inputPath);
    if (!$src) {
        throw new RuntimeException("Failed to open PNG: $inputPath");
    }

    $width  = imagesx($src);
    $height = imagesy($src);

    // Create a true-colour canvas for the output
    $dst = imagecreatetruecolor($width, $height);

    // Fill with background colour (replaces transparency)
    $bg = imagecolorallocate($dst, ...$background);
    imagefill($dst, 0, 0, $bg);

    // Copy source over background (flattens alpha)
    imagecopy($dst, $src, 0, 0, 0, 0, $width, $height);

    imagejpeg($dst, $outputPath, $quality);

    imagedestroy($src);
    imagedestroy($dst);
}

// Usage
pngToJpg('input.png', 'output.jpg', 85);

// Batch convert
foreach (glob('images/*.png') as $pngFile) {
    $jpgFile = preg_replace('/\.png$/i', '.jpg', $pngFile);
    pngToJpg($pngFile, $jpgFile);
    echo "Converted: $pngFile → $jpgFile\n";
}

Quick reference

Task Tool / function
Browser, in-page canvas.toBlob(cb, 'image/jpeg', quality)
Node.js sharp(input).flatten().jpeg()
Python PillowImage.open() → paste on RGB bg → save('JPEG')
Go image/png decode → draw.Over composite → jpeg.Encode
PHP imagecreatefrompngimagecopyimagejpeg
CLI ffmpeg -i input.png output.jpg or convert input.png output.jpg

Six common mistakes

1. Ignoring transparency Converting a logo with a transparent background to JPG without filling the alpha channel produces a black-background image. Always flatten alpha before encoding as JPG.

2. Re-compressing an already lossy image If you already have a JPG, don't convert it to PNG and back to JPG — you'll stack two rounds of lossy compression. Work from the original source whenever possible.

3. Using quality 100 to "avoid quality loss" JPG at quality 100 is not lossless — it still applies the DCT transform. The file will be enormous (often larger than the original PNG) and still not pixel-perfect. Use 85–90 for general use, 95 for archival.

4. Converting screenshots with text JPG creates block artefacts around hard edges and text. Screenshots and diagrams stay sharper as PNG. Only convert screenshots with photographic content.

5. Not setting optimize=True in Pillow Adding optimize=True to Pillow's save() call enables Huffman table optimisation, typically saving an extra 5–15% without any quality loss.

6. Expecting identical results across tools JPG quality "85" in Pillow, Sharp, and GD all produce different byte sizes because they use different Huffman tables and sub-sampling settings. Sharp with mozjpeg: true usually produces the smallest files.


FAQ

Can I convert JPG back to PNG without quality loss? No. The lossy compression in the original JPG cannot be undone. Converting JPG → PNG creates a lossless PNG, but the JPG artefacts are already baked in. Always keep the original source file.

Will the JPG look different from the PNG? For photographic images at quality 85+, the difference is typically invisible to the naked eye. For graphics with solid colour fills, flat gradients, or text, you may notice subtle block artefacts around edges.

What quality setting should I use? 85–90 for most web images. 75–80 for thumbnails where bandwidth matters more. 95 if the image will be printed or used as a source file.

Does the conversion change the image dimensions? No. Converting PNG to JPG is a re-encoding operation that changes only the compression algorithm, not the pixel dimensions or the image content (aside from minor lossy artefacts).

How do I convert multiple files at once? See the batch examples in Python and PHP above. For command-line bulk conversion, use ImageMagick: mogrify -format jpg -quality 85 *.png.

Is there a way to do it without losing any quality? If transparency isn't an issue, keeping the file as PNG is the lossless option. There's no lossless JPG equivalent — the closest is PNG (lossless), AVIF (can be lossless), or WebP (lossless mode). JPG is inherently lossy.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools