Home / Alt manpages / pbm(5)

  • pbm(5)
  • File format
  • linux

Build and Inspect Raw PBM Images with Netpbm

PBM looks trivial until you write a parser for it and discover the format hides a couple of traps. You will build a tiny monochrome image, inspect its raw representation byte by byte, and convert it to the more readable Plain PBM form.

The examples use Netpbm 11.5.2, installed here as package version 2:11.05.02-1.1build1. Allow about ten minutes. You need a shell, the netpbm package, and a writable working directory.

This guide only ever touches files in a temporary directory, and none of it needs sudo. Worth knowing up front: PBM is usually an interchange format for piping between Netpbm programs, not something you would choose for a space-efficient image archive.

1. Create a known Plain PBM input

Start with Plain PBM, because its pixels sit right there as ASCII 0 and 1 characters. The image below is eight pixels wide and two pixels high, where a 1 is black and a 0 is white.

demo_dir=$(mktemp -d)
printf '%s\n' \
  'P1' \
  '# two-row test image' \
  '8 2' \
  '1 0 0 0 0 0 0 1' \
  '1 1 1 1 1 1 1 1' > "$demo_dir/example.pbm"
file "$demo_dir/example.pbm"

Expected output identifies an 8 by 2 bitmap and describes it as ASCII text:

/tmp/tmp.XXXX/example.pbm: Netpbm image data, size = 8 x 2, bitmap two-row test image, ASCII text

The comment in that file was optional, but it earns its place: it shows that a PBM comment begins with # and runs to the next carriage return or newline.

The temporary directory name will differ on your machine. Keep demo_dir set in the current shell, since later commands rely on it.

Checkpoint

test -s "$demo_dir/example.pbm" && echo ready should print ready.

2. Read the PBM header correctly

Every PBM image opens with a magic number: Plain PBM uses P1, raw PBM uses P4. After that comes whitespace, the decimal width, more whitespace, the decimal height, one whitespace character, then the raster.

  • Dimensions read as columns then rows. For this file, 8 2 means eight columns and two rows, read left to right then top to bottom. It is not a maximum size or a resize request.
  • Whitespace is generous. PBM accepts spaces, tabs, carriage returns, line feeds, vertical tabs and form feeds anywhere whitespace is expected.
  • Comments can land mid-token. A comment can interrupt what looks like a single field, so a parser needs to follow the actual format rather than assume each header field sits on its own complete line.

3. Convert the image to raw PBM

Netpbm normally generates raw PBM, so use pamtopnm without -plain to turn the test input into that compact form. Write to a new filename so the original stays around for comparison:

$ pamtopnm "$demo_dir/example.pbm" > "$demo_dir/example-raw.pbm"
$ file "$demo_dir/example-raw.pbm"
/tmp/tmp.XXXX/example-raw.pbm: Netpbm image data, size = 8 x 2, rawbits, bitmap

Check the start of the file before you treat it as binary data:

$ head -c 9 "$demo_dir/example-raw.pbm" | od -An -tx1c
  50 34 0a 38 20 32 0a 81 ff
   P  4 \n  8     2 \n 201 377

The first six bytes are the header P4\n8 2\n. The two bytes left over are the two raster rows, each one eight pixels packed into a single byte. The first row is 10000001, so its byte is hex 81. The second row is eight black pixels, so its byte is hex ff.

For widths that are not multiples of eight, each row still occupies enough complete bytes to hold its width. The unused bits at the end of the final byte are padding with no defined pixel meaning: do not count them as extra columns.

4. Convert raw PBM back to readable Plain PBM

Reach for the compatibility command pnmtoplainpnm when you want a text representation for inspection, or a very tolerant interchange format. Current Netpbm keeps it around purely for backwards compatibility, and it just delegates to the common -plain behaviour:

$ pnmtoplainpnm "$demo_dir/example-raw.pbm"
P1
8 2
10000001
11111111

There is exactly one image in Plain PBM, and its raster may contain whitespace between pixels, although Netpbm commonly prints a compact row. Plain PBM has no packed-row padding either, so the two rows above hold exactly 16 pixel values.

Checkpoint

Compare the converted rows against the original input. They should describe the same image; if the dimensions differ, stop and work out which program produced the file before you pass it to anything else.

5. Preview the bitmap in a terminal

pbmtoascii reads a PBM image and prints a rough ASCII graphic. Its default -1x2 mode maps one pixel across to two pixels down, which suits a small test image nicely:

$ pbmtoascii "$demo_dir/example-raw.pbm"
MooooooM

Those character choices belong to pbmtoascii, not the PBM format itself. The useful check is that the output has the shape you expect. Reach for -2x4 on a denser preview of a larger bitmap, accepting that it hides individual pixels.

A preview is not a conversion. It is terminal output, so do not redirect it over a PBM file expecting to get the raster back. Keep the raw file around whenever another Netpbm program needs the actual image.

6. Handle binary output safely

Raw PBM contains arbitrary byte values after its header, so redirect it to a file or pipe it straight to a program that understands PBM. Do not paste the raster into a text editor, run it through a tool that changes line endings, or feed it to a text filter that treats byte values as characters.

Shell redirection with > truncates its destination before the command even starts, so use a new output path while you are testing:

pamtopnm "$demo_dir/example.pbm" > "$demo_dir/example-raw.pbm.new"
if file "$demo_dir/example-raw.pbm.new" | grep -q 'rawbits'; then
    mv -- "$demo_dir/example-raw.pbm.new" "$demo_dir/example-raw.pbm"
else
    printf '%s\n' 'conversion did not produce raw PBM' >&2
    rm -- "$demo_dir/example-raw.pbm.new"
    exit 1
fi

The mv only replaces the old raw file once the check succeeds, so a failed command leaves the original in place. The cleanup rm only ever targets the newly created temporary file; review paths carefully before adapting this pattern to a permanent directory.

Common traps

  • Header says P1 when you expected P4. That is Plain PBM. It is valid, just larger, and holds one image only; reach for a raw-producing Netpbm command when packed rows matter.
  • Preview looks shifted. Check the width first. A wrong width changes where each row ends, and a wrong height changes how many raster rows get read.
  • File contains extra data after the image. Raw PBM permits a sequence of images with no separators. Older programs may only read the first one, so stick to a single image when compatibility with older consumers matters.
  • File is unreadable after editing. Restore the original or regenerate it. Raw PBM stops being a text format the moment the raster begins.

There is no required .pbm filename suffix, though it is the normal convention. The conventional media type is image/x-portable-bitmap, and it is not an IANA-registered PBM type. If a system uses the broader PNM type instead, confirm that choice with the receiving application rather than assuming MIME metadata changes the file.

Done means

  • The test image has a valid header: correct P1 magic number, width, height and raster.
  • The raw copy checks out: begins with P4 and has packed rows at the expected dimensions.
  • Plain conversion reproduces the same pixels without adding columns for row padding.
  • A terminal preview stayed a visual check, never a replacement for the PBM file itself.
  • Existing output was protected by writing to a new temporary name before any replacement.