JPEG encoding, chroma subsampling, and MozJPEG tuning
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.
Cross-binding JPEG parameter reference
Section titled “Cross-binding JPEG parameter reference”| 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 |
Production JPEG export examples
Section titled “Production JPEG export examples”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) }}import sharp from 'sharp';
// mozjpeg: true enables trellisQuantisation, overshootDeringing, optimiseScans, progressive, and quantisationTable: 3await sharp('input.jpg') .flatten({ background: '#ffffff' }) .jpeg({ quality: 82, progressive: true, chromaSubsampling: '4:2:0', mozjpeg: true, }) .toFile('output.jpg');import pyvips
image = pyvips.Image.new_from_file("input.jpg")if image.hasalpha(): image = image.flatten(background=[255, 255, 255])
image.write_to_file( "output.jpg", Q=82, interlace=True, optimize_coding=True, subsample_mode="auto", trellis_quant=True, overshoot_deringing=True, optimize_scans=True, quant_table=3, keep="icc",)#!/usr/bin/env bashset -euo pipefail
vips jpegsave input.jpg output.jpg \ --Q 82 \ --interlace \ --optimize-coding \ --subsample-mode auto \ --trellis-quant \ --overshoot-deringing \ --optimize-scans \ --quant-table 3 \ --keep iccChroma 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 uses4:2:0chroma subsampling (each2 × 2pixel block shares one chroma sample), halving chroma data; - When
Q >= 90, libvips automatically switches to4: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.