Grayscale images with Portable Gray Maps

0

I’m working on a project that will generate black and white images of arbitrary sizes, but usually around 510×660. Because the images will be black and white, I initially thought the program could write Portable Bit Map (PBM) images. These are monochrome, black and white images. But writing PBM images requires writing data in exact bytes, and I can’t guarantee that my images will always divide well into 8-bit bytes.

Instead, I turned to Portable Gray Map (PGM) images. PBM and PGM are both part of the Portable aNy Map (PNM) file format “family.” These image files have a header section that defines the image type and dimensions, and a data section with the image data. The relative simplicity of these files makes them easy to generate.

Plain text PGM images

The first use of PGM files was sending small grayscale images over email. This means PGM images can be written as plain text.

To start, write the PGM header. This consists of P2 as the “magic number” to indicate the image file type, plus two values for the width and height of the image, and a third value for the maximum “grayscale” value. Grayscale values start with black as zero; the maximum grayscale value is white. Separate each value in the header with a whitespace character, such as space or newline. For example, to write a 5×9 grayscale image, with 4 gradations from black to white, use 3 as the maximum grayscale value, so the values run from 0 to 3:

P2
5 9 3

The data section consists of numbers, always separated by some kind of whitespace character like a space, tab, or newline. To complete the 5×9 grayscale image, I might write this file to create 4 levels of darkness, from coal black (0) to snow white (3):

P2
5 9 3
0 0 0 0 0
0 3 3 3 0
0 3 3 3 0
0 2 2 2 0
0 2 2 2 0
0 1 1 1 0
0 1 1 1 0
0 0 0 0 0
0 0 0 0 0

This generates a simple 3-level gradient with a black border. The bottom border is thicker than the top border. I’ve converted this PGM image to a more web-friendly PNG format, and “zoomed in” by 800% so you can see the pixels:

gradient from white on top to black on bottom

Binary PGM images

Plain text Portable Gray Map (PGM) files are easy enough for humans to read and write, but my use case requires that a program generate an image on its own. While my program could write a plain text file, it’s a little more efficient for programs to write raw binary data instead. This also generates slightly smaller files.

The basic format for raw PGM files is the same as text PGM files: start with a header section that defines “magic number” for the file type, the width and height, and the maximum grayscale value. While plain text PGM files use P2 for the magic number, raw binary PGM files use P5. The other difference is that instead of writing text values for the data section, raw PGM files use binary data, which we can write using "%c" in a printf statement in a C program.

The maximum grayscale value in a PGM file must be greater than zero and less than 65536. However, the human eye can only perceive about thirty shades of gray, so for this sample program, I’ll create a PGM image with about that many grayscale values:

#include <stdio.h>

int main()
{
    puts("P5 70 70 36");

    for (int y = 0; y < 70; y++) {
        for (int x = 0; x < 70; x++) {
            printf("%c", (x / 10) * (y / 10));
        }
    }

    return 0;
}

The puts statement writes the PGM header: P5 indicates this is a binary PGM image. The next two values provide the width and height: 70 pixels wide and 70 pixels tall. The last value indicates the maximum grayscale value is 36, where black is 0 and white is 36.

The two nested for loops each iterate from 0 to 69. The printf statement writes the gray value; I divide each x and y value by 10 so the values are 0 to 6, then multiply them so the result is 0 to 36. The program writes to standard output, so you need to redirect the output to a file:

$ gcc -Wall -o grays grays.c
$ ./grays > grays.pgm

The Portable Gray Map image renders as a 7×7 grid of squares (each square is 10×10) each with a different gray value. The gradient starts as black in the upper-left corner and ends as white in the lower-right corner:

gradient from black in the upper left to white in the lower right

Learn more

The Portable aNy Map “family” of image formats makes it easy to write images programmatically. Use PGM (Portable Gray Map) when you need to create grayscale images on the fly. To learn more about these files, visit the Netpbm project at SourceForge. The pgm manual page includes a full specification of the PGM file format, including how to add comments to a PGM image.

Leave a Reply