Turn PPM Pixels into Plain Text with Netpbm ppmtoarbtxt
Sometimes an image needs to become plain text, and Netpbm gives you ppmtoarbtxt for exactly that. It converts a PPM into a text stream whose format you define yourself, useful for terminal art, a compact data file, or a scene description for a renderer that only reads text.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about 10 minutes. You need Netpbm, a PPM input image, and a way to create two or three small template files. The examples use Netpbm 11.5.2 from Debian package netpbm 2:11.05.02-1.1build1. The command reads an image and writes to standard output; it needs no elevated privileges.
1. Check the installed command
Confirm the executable and its local version are the ones you intend to test against:
command -v ppmtoarbtxt
ppmtoarbtxt --version
On the version used for these examples, the second command reports Netpbm Version 11.5.2 and a Debian build date. That matters because template behaviour belongs to the installed Netpbm implementation, not to any shell wrapper around it.
What ppmtoarbtxt expects
ppmtoarbtxt takes one body-template filename, optional head and tail templates, and an optional PPM filename. Leave off the image filename and it reads PPM from standard input instead. The generated text always goes to standard output, so redirect it deliberately.
2. Make a small body template
A body template is applied once per pixel, in image order. Text is copied literally except for substitutions written as #(...). This template prints each pixel's luminance as an integer from 0 to 9:
#(ilum %d 0 9)
Save that line as body.txt. The ilum value comes from red, green and blue using the luminance weights the command documents. The last two arguments map black and white, here black becomes 0 and white becomes 9. The shorter #(ilum) form uses the default integer range 0 to 255 instead.
Run it against an existing PPM file:
ppmtoarbtxt body.txt input.ppm > output.txt
Safety boundary
The shell's > overwrites an existing output.txt before the program even starts. Use a new filename whenever the previous output matters. The input is only ever read, so recovery here just means restoring the overwritten output from backup, or regenerating it from the same input and template.
3. Add dimensions with a head template
Body output has no automatic separators or newline. Put the format header in a separate file when the target format needs image dimensions. For a plain PGM-style stream, create head.txt with:
P2
#(width) #(height)
255
The final newline in a template file gets ignored; the other whitespace is significant, so keep the line breaks exactly as shown. Combine the templates like this:
ppmtoarbtxt body.txt -hd head.txt input.ppm > inverted.pgm
To make that output a usable plain grayscale image, the body has to map luminance in the direction the target expects. For inversion, use #(ilum %d 255 0) instead of the 0-to-9 template. Check the result without changing it:
head -n 4 inverted.pgm
You should see P2, the input's width and height, 255, and then the generated pixel values. The body never adds a row break on its own, add a newline or another separator inside the body template whenever the consumer requires one.
4. Use pixel positions and colour channels
Pixel substitutions can include horizontal and vertical position. The first pixel sits at position 0,0. Floating-point channel substitutions run from 0.0 to 1.0 when their reference arguments are 0 and 1:
#(posx),#(posy),#(fred %.1f 0 1),#(fgreen %.1f 0 1),#(fblue %.1f 0 1)
Save it as colour.txt, then run:
ppmtoarbtxt colour.txt input.ppm > pixels.txt
head -n 5 pixels.txt
fred, fgreen and fblue give the red, green and blue channels as floats; ired, igreen and iblue are the integer equivalents, and posx/posy give unsigned integer positions. The command never inserts commas, spaces or newlines on your behalf, put literal separators outside the substitution, as the example does.
5. Add a tail, or pipe the input instead
A tail template behaves like a head template but is appended once, after every body expansion has run. A format that needs a closing marker, for instance, can use a tail.txt containing:
END
Run all three parts explicitly:
ppmtoarbtxt body.txt -hd head.txt -tl tail.txt input.ppm > rendered.txt
Head and tail templates may use width and height. Pixel substitutions such as posx and ilum belong to the body template, the manual says their result is undefined inside a head or tail.
For a pipeline, leave off the input filename entirely:
pnmfile input.ppm
cat input.ppm | ppmtoarbtxt body.txt > output.txt
The first command there is a separate inspection step. If the input is not actually PPM, convert it before calling ppmtoarbtxt, a malformed or wrong-format stream is a likely cause of an early failure.
Common traps and failure checks
- Unusual-looking substitutions get copied literally. But text that sits close to a valid one can make the program fail outright, check spelling and parentheses first.
- Keep format strings simple and fixed, such as
%dor%.1f. They get passed straight to a C formatted-output function, so never put a percent sign or a newline into one, and never build one from untrusted image data. - A missing newline is usually intentional under the template rules. Add one explicitly whenever each pixel or row needs separating.
- Head and tail templates are whole-image templates. Width and height make sense there; per-pixel values do not.
Done means
- Version confirmed:
ppmtoarbtxt --versionidentified the Netpbm build you tested against. - Template verified: your body template contains only checked substitutions and deliberate separators.
- Format complete: the output file begins and ends as the target format requires, with head and tail templates where needed.
- Output inspected: you checked the generated output and did not overwrite anything valuable by accident.