Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Recipe

The JPEG compression pipeline, assembled

Use when: you need to compress a photo the standard, real-world way -- not a from-scratch technique, but the actual six-step process every JPEG encoder runs

Goal: a compressed byte stream that decodes back into a rebuilt image, lossy at exactly one named step, and small enough to actually save bandwidth

Taught in The JPEG pipeline, assembled

  1. 1

    Apply Chapter 11 §4's own BT.601 conversion. Then subsample C_b and C_r to 4:2:0 (both dimensions halved), while leaving Y at full resolution.

    Example

    An RGB pixel (180,180,60) converts to Y\approx166.32, C_b\approx68.03, C_r\approx137.76 (Chapter 11 §4's own formula). This Y value feeds Step 2's luma block at full resolution. Its chroma neighbors get pooled into a quarter as many stored samples.

    Why: this is the ONLY step in the whole process that exploits Chapter 11's own luma/chroma sensitivity gap. Skip it, and every later step still works, but on three full-resolution channels instead of one full-resolution channel plus two quarter-resolution ones. Most of the bandwidth savings are lost for free.

  2. 2

    Apply Section 4's 2D DCT-II formula to each channel's own 8\times8 tiles (Section 5's own block-based strategy).

    Example

    This chapter's own 4\times4 toy block has F(0,0)=4\times\text{mean}=4\times120=480, Section 4's DC shortcut. It is computed on its own, per block, per channel.

    Why: the DCT is what turns a block's raw pixel values into the energy-compacted form Step 3 can actually exploit. Quantizing raw pixel values directly, skipping the transform, would damage EVERY pixel roughly equally. It would not concentrate the loss where Section 1 said a person will not notice it.

  3. 3

    Divide each block's own coefficients by Section 6's perceptually-weighted matrix and round: F_q(u,v)=\operatorname{round}(F(u,v)/Q(u,v)).

    Example

    Section 6's own worked example: F(0,0)=480 and Q(0,0)=16 give F_q(0,0)=30 exactly. F(1,0)\approx17.844 and Q(1,0)=12 give F_q(1,0)=1, a real 5.844-unit loss.

    Why: this is the process's ONLY lossy step. Every step before and after it can be undone exactly. This is the one place image quality is actually traded for file size.

  4. 4

    Reorder the N\times N grid of F_q values into one 1D sequence, low frequency first, so the high-frequency zeros Step 3 produced cluster at the END of the sequence.

    Example

    This chapter's own smooth block quantizes to F_q=\begin{bmatrix}30&-2&0&0\\1&0&0&0\\0&0&0&0\\0&0&0&0\end{bmatrix}. Zigzag order gives (30,-2,1,0,0,0,0,0,0,0,0,0,0,0,0,0): three real values, then thirteen zeros in a row.

    Why: reading the SAME grid in plain row-major order would scatter those thirteen zeros across the sequence, in short broken-up runs: a 0, then -2, then more 0s. Zigzag order gives one long run instead, which Step 5 can compress in a single symbol.

  5. 5

    Apply Section 2's RLE to the zigzag sequence's own trailing run. (0,13) replaces thirteen separate stored zeros. Real JPEG uses a special end-of-block (EOB) symbol for exactly this "everything from here on is zero" case, instead of a plain RLE count.

    Example

    (30,-2,1,0,0,\dots,0)\to 30,\ -2,\ 1,\ \text{EOB}. Four symbols represent all sixteen original coefficients.

    Why: Section 2's own worked example showed RLE can actually INFLATE data with no repeated runs. Step 4's zigzag scan was chosen on purpose to create exactly the long, repeated run RLE, or an EOB symbol, needs to pay off.

  6. 6

    Huffman-code (Section 3) the resulting stream of symbols. Real encoders often use separate tables tuned to the luma and chroma channels' own statistics, since chroma coefficients, already coarsened by Step 1's subsampling, follow a different pattern than luma's.

    Example

    A stream of mostly 30,\ -2,\ 1,\ \text{EOB}-shaped blocks is exactly the skewed, low-symbol-count data Section 3's own worked example used. That example showed it compressing to 1.875 bits/symbol, instead of a fixed 3.

    Why: this is where Section 1's coding redundancy finally gets squeezed out. Everything before this step reduced HOW MUCH data there is. This step reduces how many bits each remaining symbol actually costs.

Common mistakes

  • Quantizing (Step 3) before transforming (Step 2). There is no coefficient grid to quantize yet; the DCT has to run first.
  • Reading the quantized block in plain row-major order instead of zigzag (Step 4). This buries Step 3's own trailing zeros in the middle of the sequence, instead of collecting them at the end where Step 5 needs them.
  • Using the SAME quantization matrix for luma and chroma. Real JPEG encoders use two separate matrices, since chroma, already lossy from Step 1's subsampling, tolerates a different quantization profile than luma.
  • Treating every step as equally responsible for quality loss. Step 3 is the only lossy step; a quality complaint about a JPEG is always traceable back to that one step's own quantization matrix.