A.2 Image File Input and Output
Many image file formats have been developed over the years, but for pbrt’s purposes we are mainly interested in those that support imagery represented by floating-point pixel values. In particular, the images generated by pbrt will often have a large dynamic range; such formats are crucial for being able to store the computed radiance values directly. Legacy image file formats, such as those that store 8 bits of data for red, green, and blue components to represent colors in the range , aren’t a good fit for physically based rendering needs.
pbrt supports two floating-point image file formats: OpenEXR and PFM. OpenEXR is a floating-point file format originally designed at Industrial Light and Magic for use in movie productions (Kainz et al. 2004). We chose this format because it has a clean design, is easy to use, and has first-class support for floating-point image data. Libraries that read and write OpenEXR images are freely available, and support for the format is available in many other tools.
PFM is a floating-point format based on an extension to the PPM file format; it is very easily read and written, though it isn’t as widely supported as OpenEXR. Unlike OpenEXR, it doesn’t support compression, so files may be fairly large.
For convenience, pbrt also has support to read and write TGA format files as well as support to read and write PNG images. Neither of these is a high-dynamic-range format like OpenEXR, but both are convenient, especially as input formats for low-dynamic-range texture maps.
The ReadImage() function takes the filename to read from and a pointer to a Point2i that will be initialized with the image resolution. It returns a pointer to the start of a freshly allocated array of RGBSpectrum objects. It will read the given file as an OpenEXR, PFM, PNG, or TGA file, depending on the suffix of the filename.
ReadImage() uses RGBSpectrum for the return values—not Spectrum. The primary client of this function is the image texture mapping code in pbrt, which stores texture maps as RGBSpectrum values, even when pbrt is compiled to do full-spectral rendering, so returning RGBSpectrum values is a natural approach. (We also made this decision under the expectation that the image files being read would be in RGB or another three-channel format, so that returning RGB values wouldn’t discard spectral information; if calling code wants to store full Spectrum values, then it can convert from RGB to the full-spectral representation itself.) If pbrt was extended to support a full-spectral input image format for textures, then a variant of this function that did return Spectrum values would be advisable.
The WriteImage() function takes a filename to be written, a pointer to the beginning of the pixel data, and information about the resolution of the image. The pixel data should be organized as interleaved RGBRGB… values. Like ReadImage(), it uses the suffix of the given filename to determine which image format to use.
With WriteImage(), it’s possible to specify that the pixels being written represent a sub-region of a larger image. Some image formats (e.g., OpenEXR) can record this information in the image file header, which in turn makes it easy to assemble separately rendered subimages into a single image. The totalResolution parameter gives the total resolution of the overall image that the given pixel values are part of, and outputBounds gives the pixel bounding box that the given pixels cover. outputBounds should be within the range and the number of RGB pixel values pointed to by rgb should be equal to outputBounds.Area().
If a non-floating-point image format is being used for output, pixel values are converted to the sRGB representation (Section 10.4.1) and clamped to the range before being written to the file.
We will not show the code that interfaces with the various image-writing libraries or the code that implements file-format-specific I/O. This code can be found in the file core/imageio.cpp and the directory ext/.