Home / Alt manpages / ppmquant(1)

  • ppmquant(1)
  • User command
  • linux

Reduce a Netpbm image to a controlled colour palette with ppmquant

By the end of this guide, you will have a copy of a Netpbm image with no more than a chosen number of representative colours, plus a command to check the result. The examples use the installed Netpbm 11.5.2 package on Debian and keep the original file untouched.

Allow about 10 minutes if Netpbm is already installed. You need a PPM, PGM or PBM input that you can read, and enough free space for a second image. No command here needs elevated privileges. Do not overwrite the source image until you have inspected the reduced copy.

What ppmquant does now

ppmquant is a compatibility command. In current Netpbm it invokes pnmquant for ordinary colour reduction, or pnmremap when you provide -mapfile. New scripts should call the replacement directly, but old scripts can continue to use the historical name.

This matters because the command accepts PNM input, not just PPM. A PGM or PBM stays that type in the output with this modern implementation. The older, separate ppmquant program converted those inputs to PPM. The local manual describes this change as present since Netpbm 10.19.

Check the installed library version before comparing output with an old machine:

pnmcolormap --version 2>&1 | head -3
dpkg-query -W -f='${Package} ${Version}\n' netpbm

On the machine used for this guide, the result identifies Netpbm 11.5.2 and Debian package version 2:11.05.02-1.1build1. The version command is issued against pnmcolormap because the compatibility wrapper itself does not print a separate version banner.

Checkpoint 1: choose the input and output names

Set an input path that exists and an output path that does not contain valuable data. The shell variables below are placeholders; replace them before running the conversion.

input='source-image.ppm'
output='source-image-64-colours.ppm'
test -r "$input" && test ! -e "$output" && echo 'paths are safe'

Expected output is paths are safe. If the check fails, stop and correct the path. A common distraction is accidentally using the same name for both variables. That would make a redirection truncate the source before the program can read it.

Checkpoint 2: reduce colours without dithering

Pass the desired maximum colour count first, then the optional input file. -nofloyd makes each input pixel choose a replacement based on its own colour. It is the default, but writing it explicitly makes a batch job easier to review.

ppmquant -nofloyd 64 "$input" > "$output"

The command writes the reduced image to standard output. It does not print a success message when it completes normally. The number is a target for the palette, not a promise that every slot will be used: the quantisation process can produce fewer distinct colours.

Inspect the result before using it elsewhere:

pnmfile "$input" "$output"
cmp -s "$input" "$output"; test $? -ne 0 && echo 'output differs from input'

pnmfile should report both files and their dimensions. The output should be readable as a PNM image. The cmp check normally prints output differs from input; its non-zero status is expected here because quantisation changed the bytes. If you need to undo this step, remove only the new output after checking its exact path:

rm -- "$output"

This is the only destructive command in the guide. It removes the generated copy, not the source. Do not run it if output points at an existing file you meant to keep.

Checkpoint 3: decide whether Floyd-Steinberg dithering helps

Flat areas can show bands after a large reduction. -floyd, also spelled -fs, spreads quantisation error into neighbouring pixels so regions retain a closer average colour. It often looks better when reducing to a small palette, but it costs more CPU time and adds a visible dot pattern at close range.

dithered='source-image-64-colours-dithered.ppm'
ppmquant -floyd 64 "$input" > "$dithered"
pnmfile "$dithered"

Compare the two output files at the size at which people will view them. Do not assume the dithered copy is better for line art, diagrams or later pixel-level processing. If reproducible bytes matter, the underlying remap operation has a randomised initial error accumulator by default. The direct pnmremap command supports -norandom for deterministic Floyd-Steinberg runs, as described below.

Checkpoint 4: use a supplied palette when colours must be fixed

Use -mapfile when the output must use colours from another PNM image. The palette image can be a one-row image with one pixel per colour, but its dimensions do not matter. This is different from asking ppmquant to choose a new palette from the input.

palette='approved-palette.pam'
output='source-image-approved-palette.ppm'
test -r "$palette" && ppmquant -mapfile "$palette" -nofloyd "$input" > "$output"
pnmfile "$palette" "$output"

Without dithering, a source colour that is missing from the palette maps to the closest available colour. With -floyd, the programme can preserve average colour across an area instead. The output type and maxval follow the palette image, so check them if another tool expects a particular PPM variant.

For a new script, make the delegation explicit and add deterministic dithering when that is required:

pnmremap -mapfile="$palette" -floyd -norandom "$input" > "$output"

Do not add -norandom to a non-dithered conversion: it has no effect there. Also avoid using a palette from an unrelated colour model. The local pnmremap documentation permits normal PNM depth conversions, but exotic PAM tuple depths can fail.

Common failure modes

  • "cannot open" or an empty result: check the input path and permissions, then test it with test -r. Keep shell quoting around paths containing spaces.
  • The result has fewer colours than requested: this is valid. The palette selection and remapping stages can leave some selected palette entries unused.
  • The result looks speckled: you used Floyd-Steinberg dithering. Try -nofloyd for a cleaner per-pixel result, or view the dithered image at its intended size.
  • A palette conversion fails: verify that both files are PNM images and inspect their depth and maxval with pnmfile. A palette with an incompatible exotic PAM tuple type is not interchangeable with an RGB image.
  • A script behaves differently after an upgrade: record the Netpbm version and call pnmquant or pnmremap directly. The ppmquant name remains for compatibility, not because it is the preferred interface.

Done means

  • The original image still exists and was not redirected over.
  • pnmfile can read the new image and reports the expected dimensions and type.
  • You chose -nofloyd or -floyd deliberately, rather than inheriting a visual surprise.
  • A fixed-palette job uses -mapfile and has been checked for compatible PNM types.
  • Any generated file you do not need can be removed by its exact path.