Toolmingo
Guides7 min read

How to Compress an Image (JavaScript, Python, Go, PHP)

Learn how to compress images programmatically — lossy vs lossless, quality settings, format conversion, and WebP — with code examples in JavaScript, Python, Go, and PHP.

How to Compress an Image

Image compression reduces file size so pages load faster, storage costs less, and bandwidth bills shrink. This guide explains how compression works, which format to choose, and how to implement it in JavaScript, Python, Go, and PHP.


Lossy vs Lossless

All image compression falls into one of two categories:

Type How it works File size reduction Quality impact Best for
Lossy Discards data the eye barely notices (colour detail, high-frequency edges) 50–90% smaller Slight; visible at low quality Photos, hero images, thumbnails
Lossless Re-encodes data with no information loss (run-length encoding, deflate) 10–30% smaller None Screenshots, logos, icons, line art

The key parameter for lossy compression is quality — a number from 0 (worst) to 100 (best). A quality of 80 typically cuts JPEG file size in half with no perceptible difference on screen.


Format Quick-Reference

Format Compression Transparency Animation When to use
JPEG Lossy Photos, gradients
PNG Lossless Logos, screenshots, transparent graphics
WebP Both Web — 25–35% smaller than JPEG/PNG at the same quality
AVIF Both Web — best compression ratio, slower encode
GIF Lossless (256 colours) ✓ (1-bit) Simple animations only

Rule of thumb: convert photos to WebP (or JPEG fallback); convert transparent graphics to WebP (or PNG fallback).


JavaScript — Sharp (Node.js)

Sharp is the fastest Node.js image library, built on libvips.

import sharp from "sharp";

// Lossy JPEG at quality 80
await sharp("input.jpg")
  .jpeg({ quality: 80, mozjpeg: true })
  .toFile("output.jpg");

// Lossless PNG optimisation
await sharp("input.png")
  .png({ compressionLevel: 9, palette: true })
  .toFile("output.png");

// Convert to WebP — best bang-for-buck on the web
await sharp("input.jpg")
  .webp({ quality: 82 })
  .toFile("output.webp");

// Resize + compress in one pass (most common pipeline)
await sharp("input.jpg")
  .resize({ width: 1200, withoutEnlargement: true })
  .webp({ quality: 80 })
  .toFile("output.webp");

Browser — Canvas API (client-side):

function compressImage(file, quality = 0.8, maxWidth = 1920) {
  return new Promise((resolve) => {
    const img = new Image();
    img.onload = () => {
      const scale = Math.min(1, maxWidth / img.width);
      const canvas = document.createElement("canvas");
      canvas.width = img.width * scale;
      canvas.height = img.height * scale;
      canvas.getContext("2d").drawImage(img, 0, 0, canvas.width, canvas.height);
      canvas.toBlob(resolve, "image/webp", quality);
    };
    img.src = URL.createObjectURL(file);
  });
}

// Usage
const compressed = await compressImage(inputFile, 0.82);

canvas.toBlob with "image/webp" is supported in all modern browsers. Provide a JPEG fallback for Safari < 14.


Python — Pillow

from PIL import Image
import os

def compress_image(input_path: str, output_path: str, quality: int = 80) -> None:
    img = Image.open(input_path)

    # Convert RGBA → RGB for JPEG (JPEG has no alpha channel)
    if img.mode == "RGBA" and output_path.lower().endswith((".jpg", ".jpeg")):
        background = Image.new("RGB", img.size, (255, 255, 255))
        background.paste(img, mask=img.split()[3])
        img = background

    ext = os.path.splitext(output_path)[1].lower()

    if ext in (".jpg", ".jpeg"):
        img.save(output_path, "JPEG", quality=quality, optimize=True, progressive=True)
    elif ext == ".png":
        img.save(output_path, "PNG", optimize=True)
    elif ext == ".webp":
        img.save(output_path, "WEBP", quality=quality, method=6)
    else:
        img.save(output_path, optimize=True)


# Example: compress with resize
def compress_and_resize(src: str, dst: str, max_width: int = 1200, quality: int = 80) -> None:
    img = Image.open(src)
    if img.width > max_width:
        ratio = max_width / img.width
        img = img.resize((max_width, int(img.height * ratio)), Image.LANCZOS)
    compress_image_obj(img, dst, quality)


def compress_image_obj(img: Image.Image, dst: str, quality: int) -> None:
    ext = dst.rsplit(".", 1)[-1].lower()
    fmt = {"jpg": "JPEG", "jpeg": "JPEG", "png": "PNG", "webp": "WEBP"}.get(ext, "JPEG")
    img.save(dst, fmt, quality=quality, optimize=True)

Go — imaging + jpeg package

Go's standard library has no image compression beyond PNG (deflate). Use the disintegration/imaging package for resizing and jpeg for quality control:

package main

import (
    "image/jpeg"
    "image/png"
    "os"

    "github.com/disintegration/imaging"
    "golang.org/x/image/webp" // decode only; encode via external tool
)

// CompressJPEG saves src as JPEG at the given quality (1–100).
func CompressJPEG(inputPath, outputPath string, quality int) error {
    img, err := imaging.Open(inputPath, imaging.AutoOrientation(true))
    if err != nil {
        return err
    }
    out, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer out.Close()
    return jpeg.Encode(out, img, &jpeg.Options{Quality: quality})
}

// ResizeAndCompress resizes to maxWidth then compresses.
func ResizeAndCompress(inputPath, outputPath string, maxWidth, quality int) error {
    img, err := imaging.Open(inputPath, imaging.AutoOrientation(true))
    if err != nil {
        return err
    }
    if img.Bounds().Dx() > maxWidth {
        img = imaging.Resize(img, maxWidth, 0, imaging.Lanczos)
    }
    out, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer out.Close()
    return jpeg.Encode(out, img, &jpeg.Options{Quality: quality})
}

For WebP output in Go, call cwebp via exec.Command or use the chai2010/webp package.


PHP — GD and Imagick

GD (built into most PHP installs):

function compressImage(
    string $inputPath,
    string $outputPath,
    int $quality = 80
): void {
    $info = getimagesize($inputPath);
    $mime = $info['mime'];

    $src = match ($mime) {
        'image/jpeg' => imagecreatefromjpeg($inputPath),
        'image/png'  => imagecreatefrompng($inputPath),
        'image/webp' => imagecreatefromwebp($inputPath),
        'image/gif'  => imagecreatefromgif($inputPath),
        default      => throw new \InvalidArgumentException("Unsupported format: $mime"),
    };

    $ext = strtolower(pathinfo($outputPath, PATHINFO_EXTENSION));
    match ($ext) {
        'jpg', 'jpeg' => imagejpeg($src, $outputPath, $quality),
        'png'         => imagepng($src, $outputPath, (int) round(9 - $quality / 11)),
        'webp'        => imagewebp($src, $outputPath, $quality),
        'gif'         => imagegif($src, $outputPath),
        default       => throw new \InvalidArgumentException("Unsupported output: $ext"),
    };

    imagedestroy($src);
}

PNG quality in imagepng runs 0–9 (deflate level), not 0–100. The formula round(9 - quality / 11) maps a 0–100 quality input to the right deflate level.

Imagick (better quality, requires the extension):

function compressImageImagick(
    string $inputPath,
    string $outputPath,
    int $quality = 80
): void {
    $imagick = new \Imagick($inputPath);
    $imagick->autoOrient();
    $imagick->setImageCompressionQuality($quality);
    $imagick->stripImage(); // remove EXIF/ICC metadata for smaller file

    $ext = strtolower(pathinfo($outputPath, PATHINFO_EXTENSION));
    if (in_array($ext, ['jpg', 'jpeg'])) {
        $imagick->setImageFormat('jpeg');
    } elseif ($ext === 'webp') {
        $imagick->setImageFormat('webp');
    }

    $imagick->writeImage($outputPath);
    $imagick->destroy();
}

Typical File Size Reductions

Input Method Before After Savings
4 MP photo, JPEG q=95 Re-encode JPEG q=80 3.2 MB 800 KB 75%
4 MP photo, JPEG q=80 Convert to WebP q=80 800 KB 560 KB 30%
PNG screenshot (1920×1080) PNG optimize 1.4 MB 900 KB 36%
PNG screenshot (1920×1080) Convert to WebP 1.4 MB 200 KB 86%
PNG logo with transparency WebP lossless 120 KB 75 KB 37%

6 Common Pitfalls

1. Re-compressing an already-compressed JPEG Each lossy save applies compression on top of prior compression. Artefacts accumulate. Always start from the highest-quality source available; never chain JPEG → JPEG → JPEG.

2. Stripping alpha when converting to JPEG JPEG has no transparency. Converting RGBA PNG directly produces black or corrupted backgrounds. Flatten against a white (or brand-colour) background first.

3. Using quality=100 for "lossless" JPEG JPEG at quality 100 is still lossy — it just retains nearly all data. Use PNG or WebP lossless if you need bit-perfect preservation.

4. Ignoring EXIF orientation Phones embed rotation data in EXIF rather than rotating pixels. Compressing without reading EXIF can produce sideways thumbnails. Call autoOrient (Imagick), AutoOrientation(true) (imaging), or ImageOps.exif_transpose (Pillow) before encoding.

5. Measuring savings on tiny images Format overhead (headers, colour tables, ICC profiles) dominates below ~10 KB. Don't benchmark compression on small images; run it on representative full-size inputs.

6. Compressing images that are already small If an image is already 50 KB, compressing to 40 KB saves 10 KB but adds latency for on-the-fly processing. Set a minimum file size threshold (e.g., skip files < 100 KB).


FAQ

What quality setting should I use for JPEG? 80–85 for most web images. Drop to 70–75 for thumbnails. The difference between 80 and 85 is invisible at typical screen sizes; the file-size difference is 15–20%.

Is WebP always better than JPEG? For photos, WebP is typically 25–35% smaller at equivalent visual quality. The exception: very small images (< 5 KB) where JPEG format overhead is proportionally lower. Support is 97%+ of browsers as of 2025.

How do I compress an image without losing quality? Use lossless compression: PNG with optimize=True, WebP lossless, or AVIF lossless. Savings are smaller (10–30%) but pixel-perfect.

Should I compress images at upload time or at request time? Both: compress at upload to store the smallest master, then serve responsive variants (different sizes/formats) from a CDN at request time using srcset and <picture>.

What's the difference between image compression and image resizing? Resizing changes the pixel dimensions; compression reduces the file size of pixels that remain. Both reduce file size — resizing is often more effective for oversized images (a 4000 × 3000 photo served at 800 × 600 is four times the data you need).

Can I compress images in the browser before upload? Yes — use Canvas API with toBlob (see the JavaScript section). This reduces server bandwidth and upload time. Be aware that toBlob re-encodes from the decoded pixel buffer, so quality settings apply on top of any prior encoding.

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