Composing the 1D forward transform along rows then columns gives the full block transform. Today you build the separable 2D forward DCT the encoder runs on every block.
Implement the 8-by-8 forward DCT by applying the 1D forward transform along rows and then columns.
Like its inverse, the forward 2D DCT is separable: run the 1D forward transform on each row, then on each column of the result. A flat block of value 64 puts all its energy in the DC coefficient, 8 * 64 = 512, with every AC coefficient zero - the two-dimensional version of the flat-to-DC property, and the mirror image of the decoder’s DC-only block that decoded to a flat 64.
That symmetry is the whole point: this 512 is exactly the coefficient the decoder’s inverse DCT consumed to produce a flat block of 64 back in the inverse-DCT lesson. Forward and inverse are true inverses (up to floating-point rounding), so an encode followed by a decode reconstructs the samples - the loss in JPEG comes not from the transform but from the quantization step that sits between them, which is the next lesson. With the forward transform in hand, every block can be turned into a coefficient grid ready to quantize.
// separable: fdct1D along each row, then fdct1D along each column.func fdct2D(block [64]float64) (coef [64]float64) { }