Convert PPM Images to Portable XPM with ppmtoxpm
You will turn a PPM image into an XPM version 3 source file that an XPM library can load, with predictable colour spelling and an optional transparency mask. This guide uses Netpbm 11.5.2, installed from Debian's netpbm 2:11.05.02-1.1build1. Allow about 10 minutes if the input already exists; allow longer if you need to prepare or inspect a mask.
The route
Jump straight to the step you need, or tick off Done means at the end.
Before you start
You need a readable PPM file and the ppmtoxpm command. The output is text, not a rendered preview, and it is written to standard output. You therefore choose the destination explicitly with shell redirection. No step below needs elevated privileges. Keep the input unchanged until you have checked the generated file.
Checkpoint
You are ready when the input path is known and the output path does not name a file you need to preserve.
1. Check the installed converter
Confirm which executable will run and record its Netpbm version. This catches the common distraction of reading documentation for one installation while running another.
$ command -v ppmtoxpm
/usr/bin/ppmtoxpm
$ ppmtoxpm --version
ppmtoxpm: Using libnetpbm from Netpbm Version: Netpbm 11.5.2
The version output also includes build details on this installation. The man page accepts a minimum unique option abbreviation, double hyphens, and whitespace instead of the equals sign. Full option names are clearer in scripts, so the examples use them.
2. Convert the PPM to XPM
Replace the two placeholder paths. A single positional argument supplies the PPM; omitting it would make ppmtoxpm read standard input instead. The command creates or replaces the output file, so do not use a valuable existing path until you have checked it.
$ test -r /path/to/input.ppm
$ test ! -e /path/to/output.xpm
$ ppmtoxpm -name=input /path/to/input.ppm > /path/to/output.xpm
$ head -5 /path/to/output.xpm
/* XPM */
static char *input[] = {
/* width height ncolors chars_per_pixel */
"..."
The exact header name and palette depend on the image and its filename. The man page documents the input filename without its extension as the default identifier prefix, and noname for standard input. This Netpbm 11.5.2 build can preserve a supplied path in that identifier, so give the output a stable prefix when generated files are consumed by source code:
$ ppmtoxpm -name=logo /path/to/input.ppm > /path/to/logo.xpm
$ grep -m1 'static char' /path/to/logo.xpm
static char *logo[] = {
3. Choose portable colour names
Without extra options, the converter tries the system colour dictionary. It uses a named colour when it finds one and hexadecimal RGB text otherwise. That makes compact output, but a consumer may not have the same dictionary available. Use -hexonly when the XPM must describe every colour directly:
$ ppmtoxpm -hexonly -name=logo /path/to/input.ppm > /path/to/logo-hex.xpm
$ grep -E ' c "#' /path/to/logo-hex.xpm | head
"a c #FF0000",
"b c #FFFFFF",
Hexadecimal component width follows the PPM maxval and uses the shortest representation that can represent it. A low maxval can therefore produce one hexadecimal digit per RGB component, which some older readers mishandle. If compatibility with such a reader matters, raise the input depth first with pamdepth 255, then convert the resulting PPM:
$ pamdepth 255 /path/to/input.ppm > /tmp/input-255.ppm
$ ppmtoxpm -hexonly /tmp/input-255.ppm > /path/to/output.xpm
This creates a new temporary input and leaves the original alone. Remove the temporary file after checking the output with rm -- /tmp/input-255.ppm; do not remove a path you did not create.
4. Add transparency with a PGM mask
Transparency is opt-in. Supply a PGM file with exactly the same width and height as the PPM. Pixels whose mask value is at most half white become transparent; without a mask, every output pixel is opaque. ppmcolormask is one way to make a mask from a colour, but inspect its result before using it.
$ ppmtoxpm -hexonly -name=transparent -alphamask=/path/to/mask.pgm \
/path/to/input.ppm > /path/to/transparent.xpm
$ head -5 /path/to/transparent.xpm
/* XPM */
static char *input[] = {
A mismatched mask is an input error, not a reason to force the conversion. Fix the dimensions or generate a new mask. The transparency option also changes the colour-code count calculation, because one code is reserved for transparent pixels.
5. Verify before handing the file on
First check that the file is non-empty and contains XPM markers. These checks do not prove that an application will render it, but they catch a failed command or an accidental empty redirection.
$ test -s /path/to/output.xpm
$ grep -q '^/\* XPM \*/$' /path/to/output.xpm
$ grep -q 'static char' /path/to/output.xpm
$ wc -l -c /path/to/output.xpm
42 987 /path/to/output.xpm
If a downstream X application rejects the file, compare the image dimensions and palette first. Try the -hexonly output to remove colour-dictionary differences. If the problem appears only with transparency, repeat without -alphamask; that isolates the mask from the PPM conversion. If you need to discard an output you created, remove that exact output path only. The original PPM and mask are not changed by ppmtoxpm.
Done means
ppmtoxpm --versionidentified the intended Netpbm installation.- The XPM begins with an XPM marker and contains a generated C-style array.
- You chose the default dictionary behaviour or used
-hexonlydeliberately. - Any mask has the same dimensions as the PPM and was checked before use.
- The original image remains intact and the destination is the file you intended to create.