P1 16 16 # The PPM Image Format # # This file is a valid PBM image, which you can load up with xloadimage(1) or # similar if you like. # # If you have ever attempted to work with image formats without a library # before, odds are good that you have quickly realized that it was an absolute # fool's errand: even formats that are conceptually simple, like the venerable # BMP file format, actually involve maddening amounts of indirection, file # format flexibility, and domain-specific notation. Others, like PNG, # essentially require use of a compression library[1] to emit well-formed images # even though the container file formats for the image data are relatively # simple. Even old file formats like TIFF have a lot of layers of structure to # them that make them difficult to emit even as simple files. # # There is an exception though: the venerable PBM/PGM/PPM/etc ("portable b&w map # / portable greyscale map / portable pixel map") family of image formats, which # are so simple you can actually just emit them from shell scripts or paste them # over IRC. Here's how this family of formats works: they have a header line # ending with a newline, then some metadata lines ending in newlines, then the # image data, which is either ASCII or binary depending on the header line. The # header line is one of: # # "P1", meaning black & white with ASCII image data # "P2", greyscale ASCII image data # "P3", RGB ASCII image data # "P4", black & white binary image data # "P5", greyscale binary image data # "P6", RGB binary image data # # For this example, we've used P1, for B&W ASCII. Regardless of the header, the # next line is the height and width of the image, separated by a space, written # out as decimal numbers in ASCII. For example, in this image, we used 16 16 # above. After that, for greyscale or RGB images, the maximum value for a color # appears, so that decoders can know how many bits deep the image's pixels are. # # And that's it! Now the image data begins. Unusually for an image format, # comments are allowed (beginning with #), and whitespace (including newlines) # are ignored in the data section, so you can format your image data nicely and # wrap it if you need to. In fact, this entire blog post is a comment inside a # PPM data section. # # For example, here's the B&W pixel data needed to draw a '#' comment sign: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 # # And that's it! If we want to add any sort of metadata to the image, we can do # so by adding text comments, or by just adding trailing data after the image # body - PPM decoders generally just ignore such stuff[2]. If you want # compression you can gzip that bad boy yourself, like in the good old Unix # days. # # So overall, the whole format is really, really simple, easy to decode and easy # to encode. For binary data you can literally just do something like: # fprintf(image, "P4\n%d %d\n", width, height); # fwrite(pixels, width * height, 1, image); # with no further encoding needed from a raw in-memory pixel map. Sweet. # # But wait, there's more! # There's a newer, more powerful format called PAM (Portable Arbitrary Map), # which has a similarly easy-to-deal-with but different format. A PAM image # starts with the header "P7", then has one or more key-value pairs each on its # own line, then the line "ENDHDR", then the image data as above. The key-value # pairs look like: # # HEIGHT 123 # WIDTH 456 # MAXVAL 255 # # i.e., representing the exact same values as in the earlier P1-P6 formats but # more explicitly. There are also a couple of new keys that can be expressed: # # DEPTH n: number of channels per pixel (1 for B&W/G, 3 for RGB) # TUPLTYPE: what the image format is # # There are defined TUPLTYPES for the old formats, namely "BLACKANDWHITE", # "GRAYSCALE", and "RGB"; there are also other types that add an alpha channel, # so "GRAYSCALE_ALPHA" has two channels instead of one and "RGB_ALPHA" has four # instead of three. # # The header then ends with an ENDHDR line by itself, and then the image data, # which must be big-endian binary data - no more ASCII image data for us here. # But still, the format's quite easy to generate and parse if you need to. # # That's it for now! Thanks for reading, and stay tuned for next time when we'll # try building a simple image library that works with PPM images :) # # [1]: RFC 2083 defines only one legal compression method: deflate. # [2]: Note that P*M files theoretically can contain multiple images in the # same file.