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



readpng2_init()

The serious PNG code once again begins with the main program opening the PNG file, and I emphasize that it is opened in binary mode--hence the ``b'' flag in the second argument to fopen() ("rb"). A real browser would open an HTTP connection to a remote server and request the image instead of opening it as a local file. Rather than immediately jumping into our PNG initialization routine, readpng2_init(), as was the case in the first demo, this version first reads a block of data from the file and checks the first eight bytes for the PNG signature:

    if (!(infile = fopen(filename, "rb")))
        /* report an error and exit */
    } else {
        incount = fread(inbuf, 1, INBUFSIZE, infile);
        if (incount < 8 || !readpng2_check_sig(inbuf, 8)) {
            /* report an error and exit */
        } else {
            rc = readpng2_init(&rpng2_info);

            [etc.]
          }
    }

The readpng2_check_sig() function is nothing more than a wrapper to call png_check_sig(). It would also have been possible to call the libpng routine directly; libpng is unique in that it does not require any special setup or datatypes, and it returns an integer value, which is the default for C functions. But that would violate our separation of libpng and non-libpng code, if only in a tiny way, and it would prevent the compiler from checking the argument and return types against a prototype, in case the libpng function should ever change.

Sharp-eyed readers will have noticed that I call readpng2_init() with a different argument than last time:

int readpng2_init(mainprog_info *mainprog_ptr)

The difference from the first version is that the function now has only one argument, a pointer to an object type called mainprog_info. This is just the per-image struct mentioned earlier. It is defined as follows:

typedef struct _mainprog_info {
    double display_exponent;
    ulg width;
    ulg height;
    void *png_ptr;
    void *info_ptr;
    void (*mainprog_init)(void);
    void (*mainprog_display_row)(ulg row_num);
    void (*mainprog_finish_display)(void);
    uch *image_data;
    uch **row_pointers;
    jmp_buf jmpbuf;
    int passes;
    int rowbytes;
    int channels;
    int need_bgcolor;
    int done;
    uch bg_red;
    uch bg_green;
    uch bg_blue;
} mainprog_info;

I'll explain each member as we need it, but it is clear that many of the variables that were formerly global or passed as arguments to functions now reside in this struct. Note that similar variable types have been grouped, with the smallest ones at the end, so that the larger types will be aligned on even memory boundaries by default, minimizing the amount of padding the compiler has to add to the structure.

readpng2_init() begins by calling libpng to allocate the two PNG structs:

    png_structp  png_ptr;
    png_infop  info_ptr;

    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
      mainprog_ptr, readpng2_error_handler, NULL);
    if (!png_ptr)
        return 4;   /* out of memory */

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        png_destroy_read_struct(&png_ptr, NULL, NULL);
        return 4;   /* out of memory */
    }

I have used a pair of local variables here, png_ptr and info_ptr, for convenience. The mainprog_info struct also includes these variables, but because it's used in the main program, which has no knowledge of libpng datatypes, the struct versions of the two variables are simply declared as pointers to void. To use them directly in readpng2_init(), we would need to typecast them repeatedly, which is annoying and makes the program harder to read and somewhat slower. So I spent a few bytes on the temporary (local) variables to make life easier.

readpng2_error_handler()

In addition to the new local variables, I replaced two of the NULL arguments to png_create_read_struct() with meaningful pointers. This allows us to set up our own error handler and thereby avoid the ugly problem discussed in the previous chapter, where the size of the setjmp() buffer (jmp_buf) could differ between the application and the PNG library. All we've really done is duplicate libpng's error-handling code in the demo program: our mainprog_info struct now includes a jmp_buf to replace the one in the main PNG struct, and we've created a readpng2_error_handler() function that is almost identical to libpng's default error handler. Because the jmp_buf problem doesn't affect libpng's warning handler, we left that alone; thus the fourth argument to png_create_read_struct() is still NULL.

Our version of libpng's error handler looks like this:

static void readpng2_error_handler(png_structp png_ptr,
                                   png_const_charp msg)
{
    mainprog_info  *mainprog_ptr;

    fprintf(stderr, "readpng2 libpng error: %s\n", msg);
    fflush(stderr);

    mainprog_ptr = png_get_error_ptr(png_ptr);
    if (mainprog_ptr == NULL) {
        fprintf(stderr,
          "readpng2 severe error:  jmpbuf not recoverable;
        terminating.\n");
        fflush(stderr);
        exit(99);
    }

    longjmp(mainprog_ptr->jmpbuf, 1);
}

The main difference is that, unlike libpng, we have to retrieve the pointer to our error struct (which happens to be the same as our main struct) as an additional step. And since we know something went wrong (or we wouldn't be executing this code), it is particularly important to make sure the pointer is valid--or at least not NULL. If it is NULL, we're in big trouble: we have no way to retrieve our jmp_buf and therefore no way to return to the main application code and exit somewhat cleanly. In that case, we simply print an error message and give up. Otherwise, we retrieve mainprog_ptr->jmpbuf and longjmp() back to the most recently invoked setjmp(), just as libpng would do.

The next step is to set up one of those setjmp() calls. This differs from the previous version only in that now we're using our own struct's jmpbuf member instead of the one in the main PNG struct:

    if (setjmp(mainprog_ptr->jmpbuf)) {
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return 2;
    }

The second big difference from the basic PNG reader is what comes next:

    png_set_progressive_read_fn(png_ptr, mainprog_ptr,
      readpng2_info_callback, readpng2_row_callback,
      readpng2_end_callback);

Here we get a glimpse of the inversion of the program logic. The original approach was to call libpng and wait for it to return the requested image data, whether header information or actual pixels. That doesn't really work in a progressive program--if you give the library a hunk of data and wait for it to return, you may end up with nothing if the hunk was too small, or you may get the entire image back. More commonly, it is impossible to return a completely sensible result, due to the way compression works. The end of a buffer of compressed data may correspond to the first two bits of the red sample of a single pixel, for example, or it may cut off a piece of a compressed token that is therefore meaningless. Either way, what we really want is a way for the decoding library to provide us with data in a more controlled manner. Callbacks are the answer.

A callback is just what it sounds like: if our main routine calls the library with a chunk of data, the library will call us back when a certain amount has been processed--say, one row of image pixels. The function it calls (back in the main program, presumably) can then handle the decoded data, return, possibly get called again, and so forth. Eventually the library will exhaust the data it was given and return to the original routine. That routine can then read some more data from the network and pass it back to libpng, go and decode part of another image, respond to user input, or do anything else that needs doing.

The progressive handler in libpng is set up to work with three callback functions: one to be called when all of the header information has been read (i.e., everything prior to the first IDAT), one for when each row of the image is decoded (which includes ``short'' rows if the image is interlaced), and one for when the complete PNG stream has been read. These are the last three arguments to png_set_progressive_read_fn(), and our versions are called readpng2_info_callback(), readpng2_row_callback(), and readpng2_end_callback(), respectively. They are all required to have the same two arguments: png_ptr and info_ptr, the pointers to the two standard PNG structs. But in order for the application to associate image-specific data with each callback, libpng makes available a user-specified pointer, embedded somewhere within the PNG structs; it can be retrieved via a libpng function. In our case, we provide a pointer to the mainprog_info struct for the image. This is the second argument to png_set_progressive_read_fn(). (The first argument is just the png_ptr itself.)

As it turns out, the call to png_set_progressive_read_fn() is essentially the whole point of our readpng2 initialization routine. The only remaining detail is to save the two temporary pointers into the mainprog_info struct before returning to the main program:

    mainprog_ptr->png_ptr = png_ptr;
    mainprog_ptr->info_ptr = info_ptr;

    return 0;

These pointers will be used in the readpng2 decoding routine that calls libpng, which in turn sends the pointers back to the callback functions.




Last Update: 2010-Nov-26