Skip to content

JPEG encoding, chroma subsampling, and MozJPEG tuning

Copy page

JPEG remains the most universally compatible lossy format. In production pipelines, tuning Quality alone leaves major gains on the table: chroma subsampling controls whether coloured text edges look blurry, while progressive scans (Interlace) and Huffman/trellis quantization typically cut file size by 5%–15% at the same visual quality.

Setting govips JpegExportParams sharp .jpeg(options) pyvips / vips CLI jpegsave Production guidance
Quality factor Quality (default 80/75) quality (default 80) Q=82 / --Q 82 78–85 for web photography
Chroma subsampling SubsampleMode chromaSubsampling subsample_mode auto (4:2:0) for photos; off (4:4:4) for sharp red/blue text or UI badges
Progressive scan Interlace: true progressive: true interlace=True Recommended for images above 10 KB
Huffman optimization OptimizeCoding: true optimiseCoding: true (default) optimize_coding=True Losslessly shrinks output; keep enabled
Trellis & MozJPEG tuning TrellisQuant, OvershootDeringing, OptimizeScans, QuantTable mozjpeg: true (enables preset bundle) trellis_quant, overshoot_deringing, optimize_scans, quant_table=3 Reduces bitrate when linked against MozJPEG at higher CPU cost
Metadata stripping StripMetadata: true Stripped by default (.keepMetadata() to retain) keep="none" (or strip=True) Removes embedded camera EXIF thumbnails

The snippets below configure Quality = 82, progressive scan, Huffman coding optimization, and MozJPEG quantization options across all four interfaces:

package main
import (
"fmt"
"os"
"github.com/davidbyttow/govips/v2/vips"
)
func main() {
if err := vips.Startup(nil); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer vips.Shutdown()
image, err := vips.NewImageFromFile("input.jpg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer image.Close()
// JPEG does not support alpha: flatten onto white if the input may have transparency
if image.HasAlpha() {
if err := image.Flatten(&vips.Color{R: 255, G: 255, B: 255}); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
params := vips.NewJpegExportParams()
params.Quality = 82
params.Interlace = true
params.OptimizeCoding = true
params.SubsampleMode = vips.VipsForeignSubsampleAuto
params.StripMetadata = true
// Advanced quantization flags (effective when libvips is linked with MozJPEG):
params.TrellisQuant = true
params.OvershootDeringing = true
params.OptimizeScans = true
params.QuantTable = 3
data, _, err := image.ExportJpeg(params)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := os.WriteFile("output.jpg", data, 0o644); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

Chroma subsampling: when to switch from 4:2:0 to 4:4:4

Section titled “Chroma subsampling: when to switch from 4:2:0 to 4:4:4”

Human vision is more sensitive to luminance (Y) detail than chrominance (Cb/Cr). Under default SubsampleMode = Auto:

  • When Q < 90, libvips uses 4:2:0 chroma subsampling (each 2 × 2 pixel block shares one chroma sample), halving chroma data;
  • When Q >= 90, libvips automatically switches to 4:4:4 (no subsampling).

If you export images containing fine red/blue typography, promotional badges, or UI screenshots at Q = 82, disable chroma subsampling explicitly (vips.VipsForeignSubsampleOff / chromaSubsampling: '4:4:4' / subsample_mode="off") to prevent colour bleeding around text edges.