# JPEG

Canonical URL: https://encode.wiki/codecs/images/jpeg/
Content version: ca42e61ede0a24f789d7287a0001244ad22f4abf
Status: current
License: CC BY-SA 4.0
Checksum (SHA-256): 48522afc8848520b354b668d57b202159cf2ffc70bc4cb809f365b00d00e8f67

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](/codecs/images/avif/), [JPEG XL](/codecs/images/jxl/), [WebP](/codecs/images/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.

| Property          | Value                                                           |
| ----------------- | --------------------------------------------------------------- |
| Compression       | Lossy (the lossless JPEG mode in libjpeg-turbo 3.x is not usable for delivery) |
| Bit depth         | 8-bit for anything that has to open everywhere                  |
| Alpha / animation | No / no                                                         |
| Progressive       | Yes (coarse preview first, refined by later scans)              |
| Royalty-free      | Yes                                                             |

## When to use it

- **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.

## Pick an encoder

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

| Encoder | Use it for | Notes |
| --- | --- | --- |
| [libjpeg-turbo](https://libjpeg-turbo.org/) `cjpeg` | Fast, predictable encoding | What most apps, ImageMagick and libvips link against. Defaults: quality 75, 4:2:0, sequential. |
| [MozJPEG](https://github.com/mozilla/mozjpeg) `cjpeg` | Smaller web files at the same look | libjpeg-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](https://github.com/google/jpegli) `cjpegli` | Best quality per byte at high quality | Google'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.

## Starting commands

```sh
# 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](/reference/metrics/ssimulacra2/) or [Butteraugli](/reference/metrics/butteraugli/) and look at them at 100%. jpegli's distance is a target Butteraugli distance.

## Chroma subsampling

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.

## Recompress an existing JPEG losslessly

Two options, both keep the decoded pixels identical:

```sh
# 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.

## Pitfalls

- **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.

## How JPEG compresses an image

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](/start-here/terminology/#discrete-cosine-transform-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.

## Full option reference

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

## libjpeg-turbo cjpeg parameters

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.

<details>
<summary>All libjpeg-turbo cjpeg parameters and defaults</summary>

| Parameter / aliases        | Type / syntax                  | Default                                              | Effect and limits                                                                                                          |
| -------------------------- | ------------------------------ | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `-quality`                 | integer list                   | 75                                                   | JPEG quality, 0–100; a comma list sets each quantization table. 100 is still lossy.                                        |
| `-grayscale`, `-greyscale` | flag                           | input-dependent                                      | Write a one-component grayscale JPEG; chroma is discarded.                                                                 |
| `-rgb`                     | flag                           | input-dependent                                      | Store RGB components directly instead of converting to YCbCr; uses 1x1 sampling.                                           |
| `-optimize`, `-optimise` | flag | disabled for ordinary 8-bit sequential output | Build image-specific Huffman tables for smaller files, at some cost in speed. Always on for progressive, lossless and 12-bit output. |
| `-progressive`             | flag                           | disabled                                             | Write a progressive JPEG using the built-in scan script; -scans overrides the script.                                      |
| `-targa`                   | flag                           | disabled (format detection)                          | Force the Targa reader; other inputs are detected from their first byte.                                                   |
| `-precision`               | integer                        | 8                                                    | Sample bit depth, 2–16 with lossless support; DCT JPEG allows only 8 or 12.                                                |
| `-lossless`                | predictor[,point transform]    | disabled; point transform 0 when selected            | Predictive lossless JPEG: predictor 1–7 and optional point transform. Only point transform 0 is bit-exact.                 |
| `-arithmetic`              | flag                           | disabled (Huffman coding)                            | Arithmetic entropy coding for smaller files; many decoders cannot read it. Needs a build with arithmetic support.          |
| `-dct`                     | enum                           | int                                                  | DCT method: int (accurate), fast or float. Leave at int unless benchmarking.                                               |
| `-icc`                     | file path                      | unset                                                | Embed the ICC profile in this file. It does not convert colors.                                                            |
| `-noicc`                   | flag                           | disabled (copy input PNG ICC when present)           | Do not copy the ICC profile from PNG input; an explicit -icc file still applies.                                           |
| `-restart`                 | integer with optional B suffix | 0                                                    | Restart interval in MCU rows, or in MCUs with a B suffix; 0 disables it.                                                   |
| `-smooth`                  | integer                        | 0                                                    | Smooth dithered input; strength 0–100. Alters pixel values.                                                                |
| `-maxmemory`               | integer with optional M suffix | 0 (unlimited unless JPEGMEM overrides)               | Memory cap in decimal kilobytes, or megabytes with an M suffix. The JPEGMEM environment variable sets the default.         |
| `-outfile`                 | file path                      | stdout                                               | Write to this file. TWO_FILE_COMMANDLINE builds take a second positional path instead.                                     |
| `-nooverwrite`             | flag                           | disabled                                             | Refuse to overwrite an existing output file.                                                                               |
| `-memdst`                  | flag                           | disabled                                             | Compress to memory and discard the result; for benchmarking.                                                               |
| `-report`                  | flag                           | disabled                                             | Show compression progress.                                                                                                 |
| `-strict`                  | flag                           | disabled                                             | Treat warnings as fatal errors.                                                                                            |
| `-verbose`, `-debug`       | repeatable flag                | trace level 0                                        | Raise trace verbosity; repeat for more output. -debug is identical.                                                        |
| `-version`                 | action                         | not requested                                        | Print the version and exit with status 0.                                                                                  |
| `-baseline`                | flag                           | disabled (16-bit quantizers permitted)               | Limit quantization tables to 8-bit entries for baseline-compatible output. Does not disable -progressive.                  |
| `-qtables`                 | file path                      | unset (built-in Annex K tables scaled to quality 75) | Read up to four 64-entry quantization tables from a file; -quality scaling still applies.                                  |
| `-qslots`                  | integer list                   | 0,1,1 for YCbCr; 0 for grayscale/RGB                 | Quantization table number (0–3) for each component; the last value repeats.                                                |
| `-sample`                  | HxV list                       | 2x2,1x1,1x1 for YCbCr; 1x1 for RGB/grayscale         | Per-component sampling factors, each 1–4. 2x2,1x1,1x1 is 4:2:0; 1x1,1x1,1x1 is 4:4:4.                                      |
| `-scans`                   | file path                      | unset (single scan unless -progressive)              | Multi-scan script file listing components and Ss, Se, Ah, Al for each scan.                                                |
| `INPUT`                    | positional file path           | stdin                                                | One BMP, GIF, PNG, PPM/PGM or Targa file, depending on build. JPEG input is not accepted.                                  |

</details>

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

## MozJPEG cjpeg parameters

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.

<details>
<summary>All MozJPEG cjpeg parameters and defaults</summary>

| Parameter / aliases        | Type / syntax                  | Default                                                | Effect and limits                                                                                                                      |
| -------------------------- | ------------------------------ | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `-quality`                 | number list                    | 75                                                     | Quality 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`, `-greyscale` | flag                           | input-dependent                                        | Write a one-component grayscale JPEG; chroma is discarded.                                                                             |
| `-rgb`                     | flag                           | input-dependent                                        | Store RGB components directly instead of converting to YCbCr; uses 1x1 sampling.                                                       |
| `-optimize`, `-optimise`   | flag                           | enabled in the default maximum-compression profile     | Image-specific Huffman tables, on by default. -revert turns them off; arithmetic coding disables them.                                 |
| `-progressive`             | flag                           | enabled in the default maximum-compression profile     | Optimized progressive scans, on by default. -baseline or -revert switches to sequential.                                               |
| `-baseline`                | flag                           | disabled (progressive coding active)                   | Sequential output with 8-bit quantizers. Unlike libjpeg-turbo, this also disables progressive coding.                                  |
| `-targa`                   | flag                           | disabled (format detection)                            | Force the Targa reader; other inputs are detected from their first byte.                                                               |
| `-revert`                  | action/preset                  | not requested (maximum-compression profile)            | Reset to plain libjpeg-turbo-style defaults: sequential, Annex K tables, no trellis. Put it before other switches.                     |
| `-fastcrush`               | flag                           | disabled (scan optimization enabled)                   | Skip the progressive scan-script search for faster, slightly larger output.                                                            |
| `-dc-scan-opt`             | integer                        | 0 (help text says 1)                                   | DC scan layout: 0 one scan for all components, 1 one scan per component, 2 optimizer chooses.                                          |
| `-notrellis`               | flag                           | disabled (trellis enabled)                             | Turn off trellis quantization: faster, larger files. -revert also turns it off.                                                        |
| `-trellis-dc`              | flag                           | enabled                                                | Enable DC trellis quantization; already on by default. Does not undo -notrellis.                                                       |
| `-notrellis-dc`            | flag                           | disabled (DC trellis enabled)                          | Turn off DC trellis quantization only.                                                                                                 |
| `-trellis-dc-ver-weight`   | float                          | 0                                                      | Hidden. Weight of the vertical neighbour difference in the DC trellis cost.                                                            |
| `-lambda1`                 | float                          | 14.75                                                  | Hidden. Trellis rate-distortion log scale 1; the -tune presets overwrite it.                                                           |
| `-lambda2`                 | float                          | 16.5                                                   | Hidden. Trellis rate-distortion log scale 2; 0 or less selects a block-independent lambda.                                             |
| `-tune-hvs-psnr`           | preset flag                    | selected (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-psnr`               | preset flag                    | not selected                                           | Preset for plain PSNR: quant table 1, lambda 9 and 0, weights off.                                                                     |
| `-tune-ssim`               | preset flag                    | not selected                                           | Preset for SSIM: quant table 1, lambda 11.5 and 12.75, weights off.                                                                    |
| `-tune-ms-ssim`            | preset flag                    | not selected                                           | Preset for MS-SSIM: quant table 3, lambda 12 and 13, weights on.                                                                       |
| `-noovershoot`             | flag                           | disabled (overshoot deringing enabled)                 | Turn off overshoot deringing, which reduces ringing around black-on-white edges. -revert also turns it off.                            |
| `-nojfif`                  | flag                           | disabled (JFIF for ordinary grayscale/YCbCr)           | Omit the 18-byte JFIF header; browsers still read the file.                                                                            |
| `-arithmetic`              | flag                           | disabled (Huffman coding)                              | Arithmetic entropy coding for smaller files; many decoders cannot read it. Needs a build with arithmetic support.                      |
| `-dct`                     | enum                           | int                                                    | DCT method: int (accurate), fast or float. Leave at int unless benchmarking.                                                           |
| `-quant-baseline`          | flag                           | disabled (quality-75 tables already fit 8-bit entries) | Clamp quantizer entries to 8 bits while keeping progressive coding.                                                                    |
| `-quant-table`             | integer                        | 3                                                      | Base quantization table 0–8: 0 Annex K, 1 flat, 2–8 tuned or published tables. Other values are rejected.                              |
| `-icc`                     | file path                      | unset (PNG input profile is copied)                    | Embed the ICC profile in this file. A PNG input profile is copied automatically.                                                       |
| `-restart`                 | integer with optional B suffix | 0                                                      | Restart interval in MCU rows, or MCUs with a B suffix. Progressive output with restarts fails strict decoding in 4.1.5; add -baseline. |
| `-smooth`                  | integer                        | 0                                                      | Smooth dithered input; strength 0–100. Alters pixel values.                                                                            |
| `-maxmemory`               | integer with optional M suffix | 0 (unlimited unless JPEGMEM overrides)                 | Memory cap in decimal kilobytes, or megabytes with an M suffix. The JPEGMEM environment variable sets the default.                     |
| `-outfile`                 | file path                      | stdout                                                 | Write to this file. TWO_FILE_COMMANDLINE builds take a second positional path instead.                                                 |
| `-memdst`                  | flag                           | disabled                                               | Compress to memory and discard the result; for benchmarking.                                                                           |
| `-report`                  | flag                           | disabled                                               | Show compression progress.                                                                                                             |
| `-strict`                  | flag                           | disabled                                               | Treat warnings as fatal errors.                                                                                                        |
| `-verbose`, `-debug`       | repeatable flag                | trace level 0                                          | Raise trace verbosity; repeat for more output. -debug is identical.                                                                    |
| `-version`                 | action                         | not requested                                          | Print the version and exit with status 0.                                                                                              |
| `-qtables`                 | file path                      | unset (built-in table family 3 scaled to quality 75)   | Read up to four 64-entry quantization tables from a file; -quality scaling still applies.                                              |
| `-qslots`                  | integer list                   | 0,1,1 for YCbCr; 0 for grayscale/RGB                   | Quantization table number (0–3) for each component; the last value repeats.                                                            |
| `-sample`                  | HxV list                       | 2x2,1x1,1x1 at the default quality 75                  | Per-component sampling factors, each 1–4; overrides the quality-based default.                                                         |
| `-scans`                   | file path                      | unset (optimized progressive script)                   | Custom scan script file; replaces the generated progressive script.                                                                    |
| `INPUT`                    | positional file path           | stdin                                                  | PNG, BMP, GIF, PPM/PGM, Targa or JPEG, depending on build. JPEG input is decoded and re-encoded, not transformed losslessly.           |

</details>

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.

## JPEGli cjpegli parameters

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.

<details>
<summary>All JPEGli cjpegli parameters and defaults</summary>

| Parameter / aliases         | Type / syntax           | Default                                  | Effect and limits                                                                                                       |
| --------------------------- | ----------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `INPUT`                     | positional file path    | required                                 | Image to encode: PNM, PFM, PAM or PGX, plus PNG, GIF and JPEG when built in. A single - reads stdin.                    |
| `OUTPUT`                    | positional file path    | required unless --disable_output         | Output JPEG path; a single - writes to stdout.                                                                          |
| `--disable_output`          | flag                    | disabled                                 | Encode and discard the result; OUTPUT may be omitted. For benchmarking.                                                 |
| `--dec-hints`, `-x`         | repeatable key=value    | unset                                    | Repeatable key=value input hint: color_space, icc_pathname, exif, xmp or jumbf (file paths), or strip=metadata-name.    |
| `--distance`, `-d`          | float                   | 1.0                                      | Butteraugli distance, 0–25; lower is higher quality and 1.0 is visually lossless. Excludes --quality and --target_size. |
| `--quality`, `-q`           | integer                 | unset (distance 1.0, same as quality 90) | Quality 1–100, converted to a distance (90 equals 1.0). Excludes --distance and --target_size.                          |
| `--chroma_subsampling`      | enum                    | unset (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`, `-p` | integer                 | 2                                        | 0 is sequential; 1 and 2 are progressive with more scans.                                                               |
| `--xyb`                     | flag                    | disabled                                 | Encode in the XYB color space with an embedded ICC profile; needs a color-managed viewer.                               |
| `--std_quant`               | flag                    | disabled                                 | Use Annex K quantization tables instead of the jpegli tables.                                                           |
| `--noadaptive_quantization` | flag                    | disabled (adaptive quantization enabled) | Turn off adaptive quantization, which spends bits according to local detail.                                            |
| `--fixed_code`              | flag                    | disabled (Huffman optimization enabled)  | Skip Huffman optimization: faster, larger files. Requires -p 0.                                                         |
| `--target_size`             | unsigned integer, bytes | 0 (disabled)                             | Search for output near N bytes, up to 20x slower. Excludes --distance and --quality; shown only with -v -v.             |
| `--num_reps`                | unsigned integer        | 1                                        | Encode N times for timing; the file is written once.                                                                    |
| `--quiet`                   | flag                    | disabled                                 | Suppress status and timing messages.                                                                                    |
| `--verbose`, `-v`           | repeatable flag         | disabled; help verbosity 0               | Show advanced options in help; repeat (-v -v) to show all of them.                                                      |
| `--help`, `-h`              | action                  | not requested                            | Print help and exit with status 0; -v -v -h lists every option.                                                         |
| `--`                        | parser separator        | not supplied                             | End of options; later arguments are filenames, even if they start with -.                                               |

</details>

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.
