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



readpng2_info_callback()

png_process_data() is, in some sense, the last real libpng function that the main program calls--yet so far we haven't set any transformations and have virtually no information about the PNG image except that its signature is correct. The solution to these little mysteries lies within the first of the callback routines, readpng2_info_callback(). In most respects, it functions as the second half of our libpng initialization routine: it gets the PNG image's header information, including the image dimensions and perhaps the background color; it sets all of the transformations, including gamma correction; and it calls a routine in the main program to initialize the viewing window. In short, it does everything except handle actual pixels.

One important thing it does not do, however, is set up the usual error-handling code via the setjmp() function. The reason for this is simple: libpng requires that control never return to it when an error occurs; ordinarily, it longjumps to a user routine, which then returns an error value to the main program. But in this case it is libpng itself that calls readpng2_info_callback(), so a longjump back to here would make no sense--the only things we could do would be to return to libpng or call exit() without cleaning up, which is a rather brutal method of handling an error. (Well, actually we could do our own longjump back to the main program, but that's effectively what we are already doing. And in the last chapter I noted my dislike of big goto statements.) By not calling setjmp() within the callback, any errors will return to the location of the previous setjmp() call, which was in readpng2_decode_data(). It can then return a proper error value to the main program.

There is a feature in the callback routine that has no analogue in the basic PNG reader, however:

    mainprog_info  *mainprog_ptr;

    mainprog_ptr = (mainprog_info *)png_get_progressive_ptr(png_ptr);

    if (mainprog_ptr == NULL) {
        fprintf(stderr, "readpng2 error:  "
          "main struct not recoverable in info_callback.\n");
        fflush(stderr);
        return;
    }

This is the way we retrieve our image-specific pointer from the bowels of the PNG structs. (If it's invalid, we're in big trouble already, but there's no need to compound the problem by dereferencing a NULL pointer and crashing immediately.) Having done so, we can now stuff the image dimensions into it, where they'll be used by the main program very shortly:

   int  color_type, bit_depth;

   png_get_IHDR(png_ptr, info_ptr, &mainprog_ptr->width,
     &mainprog_ptr->height, &bit_depth, &color_type, NULL, NULL, NULL);

As before, we called a libpng utility routine to retrieve information about the image. There are also so-called easy access functions to retrieve each item separately; the choice of one function call or several is purely a matter of taste.

[!]

CAUTION

This is an appropriate point at which to comment once again on the evils of accessing PNG structures directly, so let us all repeat our favorite mantra: Friends don't let friends access elements of PNG structs directly. Bad, bad, bad!

See Chapter 13 for the detailed explanation, but trust me: it's not good karma.

As soon as we know the bit depth and color type of the image (via the png_get_IHDR() call we just made), we can check for a PNG bKGD chunk and, if it's found, adjust its values in exactly the same way as before:

    if (mainprog_ptr->need_bgcolor &&
        png_get_valid(png_ptr, info_ptr, PNG_INFO_bKGD))
    {
        /* do the same png_get_bKGD() call and scale the RGB values as
         * required; put results in mainprog_ptr->bg_red, bg_green,
         * and bg_blue */
    }

This time, instead of passing the red, green, and blue values back through the arguments to a readpng2 function, we place them into the bg_red, bg_green, and bg_blue elements of our mainprog_info struct.

The next step is to set up the desired libpng transformations; this is completely identical to the code in the first demo program. It is followed by the gamma-correction setup, but here we depart slightly from the previous example:

    if (png_get_gAMA(png_ptr, info_ptr, &gamma))
        png_set_gamma(png_ptr, mainprog_ptr->display_exponent,
          gamma);
    else
        png_set_gamma(png_ptr, mainprog_ptr->display_exponent,
          0.45455);

Because this program is intended to provide an example of how to write a PNG reader for a web browser, we imagine that the files it will be viewing are coming from the Internet--even though the front ends we provide only read from local files, just as in the basic version. Because images from the Internet are more likely to have been either created on PC-like systems or intended for display on PC-like systems, we follow the recommendation of the sRGB proposal (see Chapter 10, "Gamma Correction and Precision Color") and assume that all unlabeled images live in the sRGB color space--which, among other things, means they have a gamma of 1/2.2 or 0.45455, the same as most PCs and workstations. This does mean that unlabeled images created on a Macintosh, SGI, or NeXT workstation and intended for display on one of these systems will appear too dark. But that, of course, is why including a gamma value in the image file is so vitally important.

There is one last ``transformation'' to register after the gamma handling is out of the way; we want libpng to expand interlaced passes for us. This is signaled by calling png_set_interlace_handling(). It returns the number of passes in the image, which we save in case the main program wants to report to the user whether the image is interlaced (seven passes) or not (one pass):

    mainprog_ptr->passes = png_set_interlace_handling(png_ptr);

Then we have libpng update the PNG struct information and return to us the final number of channels in the image and the size of each row:

    png_read_update_info(png_ptr, info_ptr);

    mainprog_ptr->rowbytes = png_get_rowbytes(png_ptr, info_ptr);
    mainprog_ptr->channels = png_get_channels(png_ptr, info_ptr);

The very last thing readpng2_info_callback() does is call its corresponding function in the main program, which allocates the image memory, initializes the windowing system, and creates the display window with the proper dimensions:

    (*mainprog_ptr->mainprog_init)();

    return;

Recall that we saved pointers to three functions in the mainprog_info struct; this calls the first of the three. If we didn't care about separating PNG code from the main program routines, we could use just one routine per callback. But this way is a bit cleaner, and the performance hit is minimal.




Last Update: 2010-Nov-26