PNM Files: The Portable Bitmap Family (PBM, PGM, PPM, PNM)
The Netpbm family of formats — PBM (Portable BitMap), PGM (Portable GrayMap), PPM (Portable PixMap), and the collective umbrella PNM (Portable aNyMap) — are among the simplest image formats ever designed. Created by Jef Poskanzer in 1988 and developed through the Netpbm toolkit, these formats prioritize simplicity and portability over efficiency. Each format is either plain ASCII text or binary data with a minimal ASCII header, making them trivially parseable in any programming language without libraries. Despite their age and naivety, they remain in active use in scientific computing, image processing research, and Unix tool pipelines.
The Four Formats
PBM — Portable BitMap (1-bit black and white)
PBM stores bilevel (binary) images — every pixel is either black (1) or white (0). Used for scanned text, QR codes, barcodes, and any image that is purely binary.
ASCII format (P1):
P1
# Created by GIMP
4 4
0 0 0 0
0 1 1 0
0 1 1 0
0 0 0 0
Binary format (P4): Same header, pixel data packed 8 pixels per byte.
PGM — Portable GrayMap (8-bit or 16-bit grayscale)
PGM stores grayscale images where each pixel is a value from 0 (black) to MAXVAL (white). MAXVAL is specified in the header and can be up to 65,535 (for 16-bit grayscale).
ASCII format (P2):
P2
# Gradient
5 3
255
0 63 127 191 255
0 63 127 191 255
0 63 127 191 255
Binary format (P5): Same header, raw binary pixel data (1 byte per pixel for MAXVAL ≤ 255, 2 bytes big-endian for MAXVAL > 255).
PPM — Portable PixMap (24-bit RGB)
PPM stores full-color images with three color channels (R, G, B), each from 0 to MAXVAL (typically 255).
ASCII format (P3):
P3
# 2x2 red and blue pixels
2 2
255
255 0 0 0 0 255
255 0 0 0 0 255
Binary format (P6): Same header, raw binary pixel data (3 bytes per pixel for 8-bit color).
PNM — Collective Name
PNM is not a separate format — it is the collective term for the family. A .pnm file can contain PBM, PGM, or PPM data. Most tools that accept .pnm automatically detect which subformat the file contains based on the magic number in the header.
PAM — Portable Arbitrary Map (extension)
PBM/PGM/PPM were extended by PAM (Portable Arbitrary Map, magic number P7) which supports arbitrary numbers of channels, enabling RGBA (4 channels) and other multi-channel images within the same framework.
Magic Numbers
Each format uses a two-character magic number in the header to identify the variant:
| Magic | Format | Variant |
|---|---|---|
| P1 | PBM | ASCII |
| P2 | PGM | ASCII |
| P3 | PPM | ASCII |
| P4 | PBM | Binary |
| P5 | PGM | Binary |
| P6 | PPM | Binary |
| P7 | PAM | Binary |
Why These Formats Still Matter
Zero parser complexity: Reading a binary PPM file requires only:
- Read lines until you find width, height, maxval in the header
- Read
width × height × 3raw bytes for the pixel data
This makes PNM formats perfect for educational use, quick scripts, and systems where adding a library dependency is undesirable.
Pipeline-friendly: Unix pipe workflows can chain tools that output and input PNM:
# Capture screen → convert to grayscale → threshold → count black pixels
scrot - | pngtopnm | ppmtopgm | pgmthresh 128 | pbmminkowski -...
Netpbm toolkit: The Netpbm package includes over 300 programs for converting between image formats and performing basic image operations. On Linux: sudo apt install netpbm.
Reference format in research: Scientific image processing papers often use PNM as a reference format because readers can verify the exact pixel values by reading the ASCII text directly.
No patent encumbrance: PNM is completely free from patents or licensing restrictions. It can be implemented in any language, in any context, without legal concerns.
Converting To and From PNM
PNG to PPM (to get raw pixels for processing):
# Using ImageMagick
convert input.png output.ppm
# Using pngtopnm (Netpbm)
pngtopnm input.png > output.pnm
PPM to PNG (after processing, back to a useful format):
convert output.ppm result.png
# Or:
pnmtopng output.ppm > result.png
JPEG to PGM (grayscale for machine vision):
convert input.jpg -colorspace Gray output.pgm
PPM to JPEG:
convert input.ppm -quality 90 output.jpg
# Or:
ppmtojpeg input.ppm > output.jpg
Any format to PBM (bilevel thresholding):
convert input.png -threshold 50% output.pbm
PNM in Programming
Python (without PIL/Pillow)
Reading a binary PPM file from scratch:
def read_ppm(filename):
with open(filename, 'rb') as f:
assert f.readline().strip() == b'P6'
# Skip comments
line = f.readline()
while line.startswith(b'#'):
line = f.readline()
width, height = map(int, line.split())
maxval = int(f.readline().strip())
data = f.read()
pixels = []
bpp = 1 if maxval <= 255 else 2
for i in range(0, width * height * 3 * bpp, 3 * bpp):
r = int.from_bytes(data[i:i+bpp], 'big')
g = int.from_bytes(data[i+bpp:i+2*bpp], 'big')
b = int.from_bytes(data[i+2*bpp:i+3*bpp], 'big')
pixels.append((r, g, b))
return width, height, maxval, pixels
C (minimal PPM reader)
#include <stdio.h>
#include <stdlib.h>
typedef struct { unsigned char r, g, b; } Pixel;
Pixel* read_ppm(const char *path, int *w, int *h) {
FILE *f = fopen(path, "rb");
int maxval;
fscanf(f, "P6 %d %d %d ", w, h, &maxval);
Pixel *img = malloc(*w * *h * sizeof(Pixel));
fread(img, sizeof(Pixel), *w * *h, f);
fclose(f);
return img;
}
Performance Considerations
PNM binary formats are uncompressed — a 1920×1080 PPM file is exactly 1920 × 1080 × 3 = 6,220,800 bytes (~6 MB). For color images, this is larger than PNG by 2–10×. For workflows where disk space matters, PNG or TIFF are better choices.
The ASCII variants (P1, P2, P3) are even larger and slower to parse. Use binary variants (P4, P5, P6) for any performance-sensitive work.
Tools That Output or Accept PNM
- SANE (Scanner Access Now Easy) — Linux scanner driver, outputs PNM
- dcraw — RAW camera converter, can output PPM
- GraphicsMagick / ImageMagick — full PNM support
- GIMP — reads and writes PNM natively
- OpenCV — reads PNM via
cv2.imread() - Netpbm toolkit — 300+ specialized tools
- ImageJ / Fiji — bioimage analysis tool, reads PNM
Related conversions
Frequent conversions across the catalogue: