Netpbm File Format

PPM (Portable Pixmap) is a member of the Netpbm family of image formats, alongside PGM (grayscale) and PBM (bilevel). PNM is the umbrella term for the family. These formats were chosen for this series for the same reason Tsoding tends to favour them in from-scratch graphics work: the specification is small enough to hold in your head, there is no compression, no chunked container structure, and no external library is required to read or write one. The full specification is at netpbm.sourceforge.net/doc/ppm.html, with the format family overview at netpbm.sourceforge.net/doc/pnm.html.

A PPM file has two variants, distinguished by a two-byte magic number at the start of the file:

P3, the plain (ASCII) variant. The header and every sample are written as ASCII decimal text, separated by whitespace. This variant is human-readable and easy to debug with a text editor, at the cost of being roughly three to four times larger than the binary variant for the same image.

P6, the raw (binary) variant. The header is still ASCII text, but the pixel data immediately following it is raw bytes with no separators. This is the variant you want once your generator is working, since it is compact and trivial to write with fwrite.

The header, common to both variants, has this exact structure, with fields separated by whitespace (space, tab, carriage return, or line feed):

P6                  <- magic number identifying the format and variant
<width> <height>    <- two ASCII decimal integers
<maxval>             <- ASCII decimal integer, the maximum sample value, almost always 255

A single whitespace character (conventionally a newline) terminates the header, and the raw pixel bytes begin immediately after it. Lines beginning with # before the pixel data are comments and may be skipped by a reader.

For the raster data itself, in P6 with maxval less than 256 (the case you want, maxval = 255), every sample is exactly one byte. The pixels are stored row-major, top row first, left pixel first within each row, and each pixel is three consecutive bytes: red, green, blue, with no padding between pixels or rows. There is no alpha channel in PPM; if you need one, PAM (Portable Arbitrary Map) is the Netpbm sibling format that supports it, but you do not need it for this series.

In P3, the same logical data is written as decimal ASCII numbers, one sample at a time, each separated by at least one whitespace character, conventionally with no line exceeding 70 characters. A 2 by 1 pure red and pure blue image in P3 looks like this in full:

P3
2 1
255
255 0 0
0 0 255

The netpbm specification also notes that Netpbm formally treats sample values as nonlinear, gamma-encoded values roughly following ITU-R BT.709, matching how most real image data is actually stored. This detail becomes important again in stage 11; for now, treat the bytes as plain integers on a 0 to 255 scale.