The PNG Guide is an eBook based on Greg Roelofs' book, originally published by O'Reilly.


Once the display-system exponent is found, it is passed to the readpng code as the first argument to readpng_get_image():

uch *readpng_get_image(double display_exponent, int *pChannels,
                       ulg *pRowbytes)

As with the previous two readpng routines, readpng_get_image() first installs the libpng error-handler code (setjmp()). It then sets up all of the transformations that correspond to the design decisions described earlier, starting with these three:

    if (color_type == PNG_COLOR_TYPE_PALETTE)
    if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))

The astute reader will have noticed something odd in the first block: the same function, png_set_expand(), is called several times, in different contexts but with identical arguments. Indeed, this is perhaps the single most confusing issue in all versions of libpng up through 1.0.3. In the first case, png_set_expand() is used to set a flag that will force palette images to be expanded to 24-bit RGB. In the second case, it indicates that low-bit-depth grayscale images are to be expanded to 8 bits. And in the third case, the function is used to expand any tRNS chunk data into a full alpha channel. Note that the third case can apply to either of the first two, as well. That is, either a palette image or a grayscale image may have a transparency chunk; in each case, png_set_expand() would be called twice in succession, for different purposes (though with the same effect--the function merely sets a flag, independent of context). A less confusing approach would be to create separate functions for each purpose:

    /* These functions are FICTITIOUS!  They DO NOT EXIST in any
     * version of libpng to date (through 1.0.3). */

    if (color_type == PNG_COLOR_TYPE_PALETTE)
    if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))

With luck, these functions will be accepted for libpng version 1.0.4 (and later).

Getting back to the real code, the next pair of transformations involves calls to two new functions, one to reduce images with 16-bit samples (e.g., 48-bit RGB) to 8 bits per sample and one to expand grayscale images to RGB. Fortunately these are appropriately named:

    if (bit_depth == 16)
    if (color_type == PNG_COLOR_TYPE_GRAY ||
        color_type == PNG_COLOR_TYPE_GRAY_ALPHA)

The final transformation sets up the gamma-correction code, but only if the file contains gamma information itself:

    double  gamma;

    if (png_get_gAMA(png_ptr, info_ptr, &gamma))
        png_set_gamma(png_ptr, display_exponent, gamma);

Once again, the declaration of gamma is included here for context; it actually occurs at the beginning of the function. The conditional approach toward gamma correction is on the assumption that guessing incorrectly is more harmful than doing no correction at all; alternatively, the user could be queried for a best-guess value. This approach was chosen because a simple viewer such as we describe here is probably more likely to be used for images created on the local system than for images coming from other systems, for which a web browser might be the usual viewer. An alternate approach, espoused by drafts of the sRGB specification, is to assume that all unlabeled images exist in the sRGB space, which effectively gives them gamma values of 0.45455. On a PC-like system with no lookup table, the two approaches amount to the same thing: multiply the image's gamma of 0.45455 by the display-system exponent of 2.2, and you get an overall exponent of 1.0--i.e., no correction is necessary. But on a Macintosh, SGI, or NeXT system, the sRGB recommendation would result in additional processing that would tend to darken images. This would effectively favor images created on PCs over (unlabeled) images created on the local system. The upshot is that one is making assumptions either way; which approach is more acceptable is likely to be a matter of personal taste. Note that the PNG 1.1 Specification recommends that the viewer ``choose a likely default gamma value, but allow the user to select a new one if the result proves too dark or too light.''

In any case, once we've registered all of our desired transformations, we request that libpng update the information struct appropriately via the png_read_update_info() function. Then we get the values for the number of channels and the size of each row in the image, allocate memory for the main image buffer, and set up an array of pointers:

    png_uint_32  i, rowbytes;
    png_bytep  row_pointers[height];

    png_read_update_info(png_ptr, info_ptr);

    *pRowbytes = rowbytes = png_get_rowbytes(png_ptr, info_ptr);
    *pChannels = (int)png_get_channels(png_ptr, info_ptr);

    if ((image_data = (uch *)malloc(rowbytes*height)) == NULL) {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return NULL;

    for (i = 0;  i < height;  ++i)
        row_pointers[i] = image_data + i*rowbytes;

The only slightly strange feature here is the row_pointers[] array, which is something libpng needs for its processing. In this program, where we have allocated one big block for the image, the array is somewhat unnecessary; libpng could just take a pointer to image_data and calculate the row offsets itself. But the row-pointers approach offers the programmer the freedom to do things like setting up the image for line doubling (by incrementing each row pointer by 2*rowbytes) or even eliminating the image_data array entirely in favor of per-row progressive processing on a single row buffer. Of course, it is also quite a convenient way to deal with reading and displaying the image.

In fact, that was the last of the preprocessing to be done. The next step is to go ahead and read the entire image into the array we just allocated:

    png_read_image(png_ptr, row_pointers);

The readpng routine can return at this point, but we added one final libpng call for completeness. png_read_end() checks the remainder of the image for correctness and optionally reads the contents of any chunks appearing after the IDATs (typically tEXt or tIME) into the indicated information struct. If one has no need for the post-IDAT chunk data, as in our case, the second argument can be NULL:

    png_read_end(png_ptr, NULL);

    return image_data;

Last Update: 2010-Nov-26