Skip to content

JPEG

currentMigrated from docs/images/JPEG.mdx at 37c3317fd058; modified for Encode Wiki · CC BY-SA 4.0

JPEG is the image format everything can open: browsers, phones, print shops, CMS uploads, thirty-year-old software. It is lossy (an 8×8 DCT with quantisation), 8-bit in practice, and has no alpha channel or animation. Newer formats (AVIF, JPEG XL, WebP) compress photos better, but a good JPEG encoder still gets noticeably more out of the old format than the default settings of most apps.

PropertyValue
CompressionLossy (the lossless JPEG mode in libjpeg-turbo 3.x is not usable for delivery)
Bit depth8-bit for anything that has to open everywhere
Alpha / animationNo / no
ProgressiveYes (coarse preview first, refined by later scans)
Royalty-freeYes
  • Photos that must open everywhere: email attachments, uploads to sites that only take JPEG, print, archival access copies.
  • Camera originals you already have: don’t re-encode them. Recompress losslessly (see below) or leave them alone.
  • Not for: screenshots, text, UI and line art (ringing around sharp edges, and PNG or lossless WebP are smaller anyway), anything needing transparency, HDR, or repeated edit-and-save cycles. Every lossy re-save loses quality, so edit from a lossless master.

All three produce ordinary JPEG files that any decoder reads. Their quality numbers are not interchangeable: -quality 80 means something different in each.

EncoderUse it forNotes
libjpeg-turbo cjpegFast, predictable encodingWhat most apps, ImageMagick and libvips link against. Defaults: quality 75, 4:2:0, sequential.
MozJPEG cjpegSmaller web files at the same looklibjpeg-turbo fork with trellis quantisation and optimised progressive scans, both on by default. Slower to encode. It installs its own cjpeg, so use the full path if both are present.
jpegli cjpegliBest quality per byte at high qualityGoogle’s encoder, quality set as a Butteraugli distance. Google reports about 35% smaller files than traditional encoders at high quality. Build from source (no tagged releases); its drop-in libjpeg replacement is also used by other tools.

FFmpeg’s mjpeg encoder (-q:v 2 is the best setting, up to 31 for worst) is fine for grabbing frames but makes larger files than any of the above.

Terminal window
# libjpeg-turbo: progressive 4:2:0, good for web photos
cjpeg -quality 82 -progressive photo.png > photo.jpg
# MozJPEG: progressive and trellis are already on; -sample 2x2 keeps 4:2:0
cjpeg -quality 80 -sample 2x2 photo.png > photo.jpg
# jpegli: distance 1.0 is the "visually lossless" default
cjpegli photo.png photo.jpg -d 1
  • cjpeg reads PPM, BMP, GIF, Targa and (in current builds) PNG; add -outfile out.jpg instead of redirecting if you prefer.
  • For libjpeg-turbo, quality 75 is the default and 85–90 is a common choice for large photos; judge by eye. For jpegli use -d (recommended 0.5–3.0, lower is better) or -q (recommended 68–96).
  • -progressive implies optimised Huffman tables in libjpeg-turbo. Progressive files are usually a few percent smaller than sequential ones and render a preview while loading.
  • Compare candidates with SSIMULACRA2 or Butteraugli and look at them at 100%. jpegli’s distance is a target Butteraugli distance.

JPEG stores colour at lower resolution than brightness. The usual 4:2:0 halves chroma resolution in both directions, which is invisible in most photos and saves a lot of bytes. It smears saturated red/blue edges, coloured text and UI.

  • libjpeg-turbo: -sample 1x1,1x1,1x1 gives 4:4:4. The default is 4:2:0.
  • MozJPEG’s cjpeg picks subsampling from -quality (4:2:0 below 80, 4:2:2 from 80, 4:4:4 from 90), so crossing those thresholds makes files jump in size. Pass -sample to pin it.
  • ImageMagick also switches to 4:4:4 at quality 90 and above. Force it with -sampling-factor 4:2:0 or 4:4:4.
  • jpegli defaults to 4:4:4. Add --chroma_subsampling=420 for smaller files.

Two options, both keep the decoded pixels identical:

Terminal window
# JPEG XL: about 20% smaller on average, and the original JPEG can be rebuilt bit for bit
cjxl photo.jpg photo.jxl
djxl photo.jxl restored.jpg
# JPEG to JPEG: re-do the entropy coding only
jpegtran -copy all -optimize -progressive photo.jpg > smaller.jpg

cjxl transcodes JPEG input losslessly by default. The result needs a JPEG XL-capable viewer (Safari 17+, Chrome 155+, Firefox from 158), but djxl restores the original file, so it is a safe way to shrink an archive. jpegtran is also what you use for lossless rotation and cropping (-rotate 90 -perfect). Its default -copy comments drops EXIF and ICC data, including the orientation tag, so use -copy all when you want them kept.

  • Metadata: stripping EXIF removes the orientation tag, so camera photos can turn sideways; stripping the ICC profile from wide-gamut images shifts colours. Apply the rotation and convert to sRGB before stripping.
  • Generation loss: opening, editing and saving a JPEG repeatedly adds error each time, worst with changed crops or subsampling. Keep a lossless master.
  • Banding: 8-bit output bands in smooth gradients at low quality; add noise or raise quality, or use AVIF/JPEG XL.
  • Arithmetic coding, 12-bit and CMYK JPEG exist but most decoders refuse or mishandle them. Don’t use them for delivery.
  • Quality numbers across tools: don’t copy a quality value between libjpeg-turbo, MozJPEG, jpegli, ImageMagick and FFmpeg and expect the same result.

Understanding these steps helps with most other codecs.

  1. Colour conversion and subsampling: RGB is converted to YCbCr (luma plus two colour-difference channels), and Cb/Cr are usually stored at half resolution.
  2. 8×8 blocks and the DCT: each block is converted to 64 frequency coefficients. Most of the energy lands in a few low-frequency values.
  3. Quantisation: each coefficient is divided by a step size from a quantisation table and rounded. This is the only lossy step, and the quality setting scales the table. Coarser steps give more zeros and smaller files.
  4. Zigzag scan and run-length coding: coefficients are read from low to high frequency so the trailing zeros collapse into short runs.
  5. Huffman coding: the symbols are entropy coded, with shorter codes for common values. Optimised tables, built from the image itself, make the file a little smaller.

Decoding reverses the steps: dequantise, inverse DCT, upsample chroma, convert back to RGB. Progressive JPEG stores the coefficients over several scans instead of block by block.

Every option and default for libjpeg-turbo 3.2.0, MozJPEG 4.1.5 and jpegli (commit 031a0077). Reading option tables explains the Default column.

Switches go before the input filename. Use full switch names in scripts; the parser also accepts abbreviations. cjpeg -help is not a registered option, so it prints usage and exits 1; -version exits 0.

All libjpeg-turbo cjpeg parameters and defaults
Parameter / aliasesType / syntaxDefaultEffect and limits
-qualityinteger list75JPEG quality, 0–100; a comma list sets each quantization table. 100 is still lossy.
-grayscale, -greyscaleflaginput-dependentWrite a one-component grayscale JPEG; chroma is discarded.
-rgbflaginput-dependentStore RGB components directly instead of converting to YCbCr; uses 1x1 sampling.
-optimize, -optimiseflagdisabled for ordinary 8-bit sequential outputBuild image-specific Huffman tables for smaller files, at some cost in speed. Always on for progressive, lossless and 12-bit output.
-progressiveflagdisabledWrite a progressive JPEG using the built-in scan script; -scans overrides the script.
-targaflagdisabled (format detection)Force the Targa reader; other inputs are detected from their first byte.
-precisioninteger8Sample bit depth, 2–16 with lossless support; DCT JPEG allows only 8 or 12.
-losslesspredictor[,point transform]disabled; point transform 0 when selectedPredictive lossless JPEG: predictor 1–7 and optional point transform. Only point transform 0 is bit-exact.
-arithmeticflagdisabled (Huffman coding)Arithmetic entropy coding for smaller files; many decoders cannot read it. Needs a build with arithmetic support.
-dctenumintDCT method: int (accurate), fast or float. Leave at int unless benchmarking.
-iccfile pathunsetEmbed the ICC profile in this file. It does not convert colors.
-noiccflagdisabled (copy input PNG ICC when present)Do not copy the ICC profile from PNG input; an explicit -icc file still applies.
-restartinteger with optional B suffix0Restart interval in MCU rows, or in MCUs with a B suffix; 0 disables it.
-smoothinteger0Smooth dithered input; strength 0–100. Alters pixel values.
-maxmemoryinteger with optional M suffix0 (unlimited unless JPEGMEM overrides)Memory cap in decimal kilobytes, or megabytes with an M suffix. The JPEGMEM environment variable sets the default.
-outfilefile pathstdoutWrite to this file. TWO_FILE_COMMANDLINE builds take a second positional path instead.
-nooverwriteflagdisabledRefuse to overwrite an existing output file.
-memdstflagdisabledCompress to memory and discard the result; for benchmarking.
-reportflagdisabledShow compression progress.
-strictflagdisabledTreat warnings as fatal errors.
-verbose, -debugrepeatable flagtrace level 0Raise trace verbosity; repeat for more output. -debug is identical.
-versionactionnot requestedPrint the version and exit with status 0.
-baselineflagdisabled (16-bit quantizers permitted)Limit quantization tables to 8-bit entries for baseline-compatible output. Does not disable -progressive.
-qtablesfile pathunset (built-in Annex K tables scaled to quality 75)Read up to four 64-entry quantization tables from a file; -quality scaling still applies.
-qslotsinteger list0,1,1 for YCbCr; 0 for grayscale/RGBQuantization table number (0–3) for each component; the last value repeats.
-sampleHxV list2x2,1x1,1x1 for YCbCr; 1x1 for RGB/grayscalePer-component sampling factors, each 1–4. 2x2,1x1,1x1 is 4:2:0; 1x1,1x1,1x1 is 4:4:4.
-scansfile pathunset (single scan unless -progressive)Multi-scan script file listing components and Ss, Se, Ah, Al for each scan.
INPUTpositional file pathstdinOne BMP, GIF, PNG, PPM/PGM or Targa file, depending on build. JPEG input is not accepted.

Available input readers, arithmetic coding, progressive and lossless modes and DCT variants depend on how the library was built.

MozJPEG’s cjpeg has the same basic switches as libjpeg-turbo’s, plus trellis, scan-optimisation and tuning controls, but it lacks -precision, -lossless, -noicc and -nooverwrite. Do not paste switches between the two programs without checking them.

All MozJPEG cjpeg parameters and defaults
Parameter / aliasesType / syntaxDefaultEffect and limits
-qualitynumber list75Quality 0–100; fractions allowed. The last value sets default sampling: 4:2:0 below 80, 4:2:2 from 80, 4:4:4 from 90.
-grayscale, -greyscaleflaginput-dependentWrite a one-component grayscale JPEG; chroma is discarded.
-rgbflaginput-dependentStore RGB components directly instead of converting to YCbCr; uses 1x1 sampling.
-optimize, -optimiseflagenabled in the default maximum-compression profileImage-specific Huffman tables, on by default. -revert turns them off; arithmetic coding disables them.
-progressiveflagenabled in the default maximum-compression profileOptimized progressive scans, on by default. -baseline or -revert switches to sequential.
-baselineflagdisabled (progressive coding active)Sequential output with 8-bit quantizers. Unlike libjpeg-turbo, this also disables progressive coding.
-targaflagdisabled (format detection)Force the Targa reader; other inputs are detected from their first byte.
-revertaction/presetnot requested (maximum-compression profile)Reset to plain libjpeg-turbo-style defaults: sequential, Annex K tables, no trellis. Put it before other switches.
-fastcrushflagdisabled (scan optimization enabled)Skip the progressive scan-script search for faster, slightly larger output.
-dc-scan-optinteger0 (help text says 1)DC scan layout: 0 one scan for all components, 1 one scan per component, 2 optimizer chooses.
-notrellisflagdisabled (trellis enabled)Turn off trellis quantization: faster, larger files. -revert also turns it off.
-trellis-dcflagenabledEnable DC trellis quantization; already on by default. Does not undo -notrellis.
-notrellis-dcflagdisabled (DC trellis enabled)Turn off DC trellis quantization only.
-trellis-dc-ver-weightfloat0Hidden. Weight of the vertical neighbour difference in the DC trellis cost.
-lambda1float14.75Hidden. Trellis rate-distortion log scale 1; the -tune presets overwrite it.
-lambda2float16.5Hidden. Trellis rate-distortion log scale 2; 0 or less selects a block-independent lambda.
-tune-hvs-psnrpreset flagselected (table 3, lambda 14.75 and 16.5)Preset for PSNR-HVS: quant table 3, lambda 14.75 and 16.5, weights on. The default.
-tune-psnrpreset flagnot selectedPreset for plain PSNR: quant table 1, lambda 9 and 0, weights off.
-tune-ssimpreset flagnot selectedPreset for SSIM: quant table 1, lambda 11.5 and 12.75, weights off.
-tune-ms-ssimpreset flagnot selectedPreset for MS-SSIM: quant table 3, lambda 12 and 13, weights on.
-noovershootflagdisabled (overshoot deringing enabled)Turn off overshoot deringing, which reduces ringing around black-on-white edges. -revert also turns it off.
-nojfifflagdisabled (JFIF for ordinary grayscale/YCbCr)Omit the 18-byte JFIF header; browsers still read the file.
-arithmeticflagdisabled (Huffman coding)Arithmetic entropy coding for smaller files; many decoders cannot read it. Needs a build with arithmetic support.
-dctenumintDCT method: int (accurate), fast or float. Leave at int unless benchmarking.
-quant-baselineflagdisabled (quality-75 tables already fit 8-bit entries)Clamp quantizer entries to 8 bits while keeping progressive coding.
-quant-tableinteger3Base quantization table 0–8: 0 Annex K, 1 flat, 2–8 tuned or published tables. Other values are rejected.
-iccfile pathunset (PNG input profile is copied)Embed the ICC profile in this file. A PNG input profile is copied automatically.
-restartinteger with optional B suffix0Restart interval in MCU rows, or MCUs with a B suffix. Progressive output with restarts fails strict decoding in 4.1.5; add -baseline.
-smoothinteger0Smooth dithered input; strength 0–100. Alters pixel values.
-maxmemoryinteger with optional M suffix0 (unlimited unless JPEGMEM overrides)Memory cap in decimal kilobytes, or megabytes with an M suffix. The JPEGMEM environment variable sets the default.
-outfilefile pathstdoutWrite to this file. TWO_FILE_COMMANDLINE builds take a second positional path instead.
-memdstflagdisabledCompress to memory and discard the result; for benchmarking.
-reportflagdisabledShow compression progress.
-strictflagdisabledTreat warnings as fatal errors.
-verbose, -debugrepeatable flagtrace level 0Raise trace verbosity; repeat for more output. -debug is identical.
-versionactionnot requestedPrint the version and exit with status 0.
-qtablesfile pathunset (built-in table family 3 scaled to quality 75)Read up to four 64-entry quantization tables from a file; -quality scaling still applies.
-qslotsinteger list0,1,1 for YCbCr; 0 for grayscale/RGBQuantization table number (0–3) for each component; the last value repeats.
-sampleHxV list2x2,1x1,1x1 at the default quality 75Per-component sampling factors, each 1–4; overrides the quality-based default.
-scansfile pathunset (optimized progressive script)Custom scan script file; replaces the generated progressive script.
INPUTpositional file pathstdinPNG, BMP, GIF, PPM/PGM, Targa or JPEG, depending on build. JPEG input is decoded and re-encoded, not transformed losslessly.

The v4.1.5 help text calls DC scan mode 1 the default, but the source initialises dc_scan_opt_mode to 0, which is what the table lists. An explicit -sample overrides the quality-based subsampling. -revert resets many settings, so put it before other overrides. JPEG input is decoded and re-encoded; use jpegtran for coefficient-domain changes.

Choose one of --distance, --quality or --target_size. --target_size only appears with -v -v -h. The standalone frontend uses double-dash long options and positional input and output files.

All JPEGli cjpegli parameters and defaults
Parameter / aliasesType / syntaxDefaultEffect and limits
INPUTpositional file pathrequiredImage to encode: PNM, PFM, PAM or PGX, plus PNG, GIF and JPEG when built in. A single - reads stdin.
OUTPUTpositional file pathrequired unless —disable_outputOutput JPEG path; a single - writes to stdout.
--disable_outputflagdisabledEncode and discard the result; OUTPUT may be omitted. For benchmarking.
--dec-hints, -xrepeatable key=valueunsetRepeatable key=value input hint: color_space, icc_pathname, exif, xmp or jumbf (file paths), or strip=metadata-name.
--distance, -dfloat1.0Butteraugli distance, 0–25; lower is higher quality and 1.0 is visually lossless. Excludes —quality and —target_size.
--quality, -qintegerunset (distance 1.0, same as quality 90)Quality 1–100, converted to a distance (90 equals 1.0). Excludes —distance and —target_size.
--chroma_subsamplingenumunset (4:4:4 in ordinary non-XYB output)444, 440, 422 or 420. Default is 4:4:4 for non-XYB output; XYB uses library defaults.
--progressive_level, -pinteger20 is sequential; 1 and 2 are progressive with more scans.
--xybflagdisabledEncode in the XYB color space with an embedded ICC profile; needs a color-managed viewer.
--std_quantflagdisabledUse Annex K quantization tables instead of the jpegli tables.
--noadaptive_quantizationflagdisabled (adaptive quantization enabled)Turn off adaptive quantization, which spends bits according to local detail.
--fixed_codeflagdisabled (Huffman optimization enabled)Skip Huffman optimization: faster, larger files. Requires -p 0.
--target_sizeunsigned integer, bytes0 (disabled)Search for output near N bytes, up to 20x slower. Excludes —distance and —quality; shown only with -v -v.
--num_repsunsigned integer1Encode N times for timing; the file is written once.
--quietflagdisabledSuppress status and timing messages.
--verbose, -vrepeatable flagdisabled; help verbosity 0Show advanced options in help; repeat (-v -v) to show all of them.
--help, -hactionnot requestedPrint help and exit with status 0; -v -v -h lists every option.
--parser separatornot suppliedEnd of options; later arguments are filenames, even if they start with -.

Without --quality the distance is 1.0, which is the same as the help text’s default quality of 90. The encoder accepts exactly one frame, so animated input can fail. Check orientation and colour afterwards, because not every metadata field is written.